The vcd writer incorrectly treated two scope vectors as the same, whenever
they have the same length of entries and the last item matches.
This is however not always true, for example consider a current_scope of
["top", "something0", "same"]
and a scope of
["top", "something1", "same"]
This commit uses parameter packs to sink `debug_item()` construction
into the `debug_info()`-specific `add()` overload. This makes the stack
space use sub-linear in typical case rather than linear (which is still
the worst case). Oddly, the stack slots that get allocated now are all
for the `0` literal for `lsb_offset`. This could be fixed by allocating
numbers statically but the existing reduction in stack use of ~98% for
a representative example (Minerva SoC) should be enough.
Before this commit, this function would create a temporary `std::string`
per debug item (and scope). After this commit, an additional overload is
used to push that down the call stack. This reduces stack usage by
about 50% more on top of the previous commit.
Before this commit, the creation of (constant) attribute maps caused
`debug_info()` (which is built with `__attribute__((optnone))`) to
consume large amounts of stack space; up to tens of megabytes. This
caused problems particularly on macOS, where the default stack size
is 512 KiB.
After this commit, `std::map` objects are no longer created inline in
the `debug_info()` function, but are compiled to and then expanded from
a string literal in a subroutine call. This reduces stack space usage
by about 50%.
Before this commit, the combination of `_` and `0` format characters
would produce a result like `000000001010_1010`.
After this commit, it would be `0000_0000_1010_1010`.
This has a slight quirk where a format like `{:020_b}` results in
the output `0_0000_0000_1010_1010`, which is one character longer than
requested. Python has the same behavior, and it's not clear what would
be strictly speaking correct, so Python behavior is implemented.
The option is serialized to RTLIL as `_` (to match Python's option with
the same symbol), and sets the `group` flag. This flag inserts an `_`
symbol between each group of 3 digits (for decimal) or four digits (for
binary, hex, and octal).
The option is serialized to RTLIL as `#` (to match Python's and Rust's
option with the same symbol), and sets the `show_base` flag. Because
the flag is called `show_base` and not e.g. `alternate_format` (which
is what Python and Rust call it), in addition to the prefixes `0x`,
`0X`, `0o`, `0b`, the RTLIL option also prints the `0d` prefix.
This format type is used to print an Unicode character (code point) as
its UTF-8 serialization. To this end, two UTF-8 decoders (one for fmt,
one for cxxrtl) are added for rendering. When converted to a Verilog
format specifier, `UNICHAR` degrades to `%c` with the low 7 bits of
the code point, which has equivalent behavior for inputs not exceeding
ASCII. (SystemVerilog leaves source and display encodings completely
undefined.)
Before this commit, the existing alignments were `LEFT` and `RIGHT`,
which added the `padding` character to the right and left just before
finishing formatting. However, if `padding == '0'` and the alignment is
to the right, then the padding character (digit zero) was added after
the sign, if one is present.
After this commit, the special case for `padding == '0'` is removed,
and the new justification `NUMERIC` adds the padding character like
the justification `RIGHT`, except after the sign, if one is present.
(Space, for the `SPACE_MINUS` sign mode, counts as the sign.)
The first two were already supported with the `plus` boolean flag.
The third one is a new specifier, which is allocated the ` ` character.
In addition, `MINUS` is now allocated the `-` character, but old format
where there is no `+`, `-`, or `-` in the respective position is also
accepted for compatibility.
Before this commit, the `STRING` variant inserted a literal string;
the `CHARACTER` variant inserted a string. This commit renames them
to `LITERAL` and `STRING` respectively.
Note that this functionality is only used by diagnostics emitted by
the C++ testbench; diagnostics emitted by the RTL in `eval()` do not
need to be recorded since they will be emitted again during replay.
This commit adds a `debug_scopes` container, which can collect metadata
about scopes in a design. Currently the only scope is that of a module.
A module scope can be represented either by a module and cell pair, or
a `$scopeinfo` cell in a flattened netlist. The metadata produced by
the C++ API is identical between these two cases, so flattening remains
transparent to a netlist with CXXRTL.
The existing `debug_items` method is deprecated. This isn't strictly
necessary, but the user experience is better if the path is provided
as e.g. `"top "` (as some VCD viewers make it awkward to select topmost
anonymous scope), and the upgrade flow encourages that, which should
reduce frustration later.
While the new `debug_items` method could still be broken in the future
as the C++ API permits, this seems unlikely since the debug information
can now capture all common netlist aspects and includes several
extension points (via `debug_item`, `debug_scope` types).
Also, naming of scope paths was normalized to `path` or `top_path`,
as applicable.
Before this commit, `at()` and `operator[]` did the same thing, making
one of them redundant. There was also a somewhat awkward `parts_at()`,
which is more generic than `at()`.
After this commit, `parts_at()` becomes `at()`, and `at()` becomes
`operator[]`. Any quick-and-dirty accesses should use `items["name"]`,
which expects a single-part item. Generic code should instead use
`items.at("name")`, which will return multiple parts. Both will check
for the existence of the name.
This is unlikely to break downstream code since it's likely been using
the shorter `operator[]`. (In any case this API is not stable.)
Because all objects in C++ must have non-zero size, a `value<0>` has
a size of 4 despite consisting of a `uint32_t chunks[0]`. The debug
item assertions were not written expecting that and prevent any debug
items for such values from compiling.
The C API does not define exactly what happens for a zero-width debug
item, but it seems OK to say that they should refer to some unique
pointer that cannot be, in actuality, read or written. This allows
some techniques or optimizations that use `curr` pointers as keys and
assume they correspond 1-to-1 to simulation objects.
This is mostly useful for collecting coverage for the future `$check`
cell, where, depending on the flavor, formatting a message may not be
wanted even for a failed assertion.
The behavior of these format specifiers is highly specific to Verilog
(`$time` and `$realtime` are only defined relative to `$timescale`)
and may not fit other languages well, if at all. If they choose to use
it, it is now clear what they are opting into.
This commit also simplifies the CXXRTL code generation for these format
specifiers.
At the moment the only thing it allows is redirecting `$print` cell
output in a context-dependent manner. In the future, it will allow
customizing handling of `$check` cells (where the default is to abort),
of out-of-range memory accesses, and other runtime conditions with
effects.
This context object also allows a suitably written testbench to add
Verilog-compliant `$time`/`$realtime` handling, albeit it involves
the ceremony of defining a `performer` subclass. Most people will
want something like this to customize `$time`:
int64_t time = 0;
struct : public performer {
int64_t *time_ptr;
int64_t time() const override { return *time_ptr; }
} performer = { &time };
p_top.step(&performer);
This approach to tracking simulation time was a mistake that I did not
catch in review. It has several issues:
1. There is absolutely no requirement to call `step()`, as it is
a convenience function. In particular, `steps` will not be
incremented in submodules if `-noflatten` is used.
2. The semantics of `steps` does not match that of the Verilog `$time`
construct.
3. There is no way to make the semantics of `%t` match that of Verilog.
4. The `module` interface is intentionally very barebones. It is little
more than a container for three method pointers, `reset`, `eval`,
and `commit`. Adding ancillary data to it goes against this.
If similar functionality is introduced again it should probably be
a variable that is global per toplevel design using some object that is
unique for an entire hierarchy of modules, and ideally exposed via
the C API. For now, it is being removed (in this commit) and (in next
commit) the capability is being reintroduced through a context object
that can be specified for `eval()`.
This commit achieves three roughly equally important goals:
1. To bring the rendering code in kernel/fmt.cc and in cxxrtl.h as close
together as possible, with an ideal of only having the bigint library
as the difference between the render functions.
2. To make the treatment of `$time` and `$realtime` in CXXRTL closer to
the Verilog semantics, at least in the formatting code.
3. To change the code generator so that all of the `$print`-to-`string`
conversion code is contained inside of a closure.
There are two reasons to aim for goal (3):
a. Because output redirection through definition of a global ostream
object is neither convenient nor useful for environments where
the output is consumed by other code rather than being printed on
a terminal.
b. Because it may be desirable to, in some cases, ignore the `$print`
cells that are present in the netlist based on a runtime decision.
This is doubly true for an upcoming `$check` cell implementing
assertions, since failing a `$check` would by default cause a crash.
This avoids having to devirtualize them later to get performance back,
and simplifies the code a bit.
The change is prompted by the desire to add a similar observer object
to `eval()`, and a repeated consideration of whether the dispatch on
these should be virtual in first place.
The name `on_commit` was terrible since it would not be called, as one
may conclude from the name, on each `commit()`, but only whenever that
method actually updates a value.
This commit adds a reader/writer implementation for a file format
optimized for fast, single-pass storage and retrieval of design state
changes, as well as a recorder/replayer that integrate with the eval
and commit simulation steps to create replay logs and reproduce them
later.
This feature makes it possible to run a simulation once, recording
the stimulus as well as changes to the registers, and navigate to
a past time point in the simulation later without rerunning it.
Both the changes in inputs (stimulus) and changes in state are saved
so that navigation does not require calling `eval()` or `commit()`;
only a series of memory copy operations.
On a representative example of a SoC netlist, saving the replay log
while simulating it takes 150% of the time it would take to simulate
the same design without logging, which is a much lower overhead than
writing an equivalent full view (including memories) VCD waveform dump.
The replay log is also several times smaller than the VCD dump, and
more space savings are available as low hanging fruit.
Replaying the log has not been optimized and currently takes about
the same time as running the simulation in first place. However, it
is still useful since it provides fast navigation to an arbitrary time
point, something that rerunning the simulation does not allow for.
The current file format should be considered preliminary. It is not
very space-efficient, and my testing shows that a lot of time is spent
in the write() syscall in the kernel. Most likely, compression and/or
writing in another thread could improve performance by 10-20%. This
may be done at a later time.
The commit observer is a structure containing a callback that is invoked
whenever the `commit()` method changes a wire or a memory. This allows
code external to the compiled netlist to react to changes in the design
state in a very efficient way. One example of how this feature can be
used is an efficient implementation of record/replay.
Note that the VCD writer does not benefit from this feature because it
must be able to react to changes in any debug items and not just those
that contain design state.
While the VCD format separates the timescale and the timestep (likely
to allow representing the timestep with a small integer type), time in
CXXRTL is represented using a uniform 96-bit number, which allows for
a ±100 year range at femtosecond resolution.
The implementation uses `value<96>`, which provides fast arithmetic and
comparison operations, as well as conversion to/from a more common
representation of integer seconds plus femtoseconds.