In theory this is as simple as capturing all signals and dumping a vcd (or some other format), but is potentially more complex for a few reasons:
Performance for generated simulators is paramount for effectiveness in testing and verification.
Tracing something non-trivial can easily produce hundreds of gigabytes' worth of data (or more!), which in addition to taking up loads of disk space to store the results, can make a simulation prohibitively slow due to IO overhead.
As it is today, the generated simulators have zero dependencies besides basic runtime functionality (eg. allocating a std::Vec for Mem storage). I'd very much like to keep it that way (this makes bootstrapping test projects etc trivial), but we don't necessarily want to also generate a bunch of extra boilerplate to output a certain format when relevant libs are readily available.
I can think of at least twoformats that I would like to dump traces to off of the top of my head, and ideally, users wouldn't be bound by whatever I've chosen to implement.
Module hierarchy should be available to the trace solution, even though the sim graph has been flattened. This is supported by vcd and without it, traces will get unwieldy.
So, this adds some important constraints on such a solution:
Tracing should be optional. Whether or not this means optionally generating tracing code in the first place and/or generating tracing code that can be conditionally enabled/disabled at runtime I'm not entirely sure about yet; that's a decision that has to be considered as well.
Enabling tracing should not create additional library dependencies for the generated code, unless perhaps a user asks for it - for example, if a module accepts a handle to a generic trace object, we can implement that with several backends, or not, if a user wants to add in their own (which I can imagine being very useful). Note that if such a generic approach is implemented, performance should still be paramount, which means we should prefer generics to dynamic dispatch (i.e. a generated module sim is generic over a generic trace type).
There should be at least one default trace implementation provided by kaze that's easy to use. vcd is the obvious choice here.
It should be easy(/automatic?) to get trace output for a failing rust unit test that's based on a generated simulator. This is a very common use case for verification (see these sim tests and even kaze's own sim test for verification!) and ideally a user would be able to pull up a trace dumped from a failing test in order to better understand how it failed.
Module hierarchy should be available to the trace API.
Some loose ends/other ideas that come to mind:
It might make sense to introduce some kind of "compilation profile" that can allow generation to optionally insert/omit extra signals, i.e. debug outputs for a certain module, that otherwise wouldn't be used in a design. Note that this can be done manually today at a higher level when generating modules with simple conditionals, but perhaps language support is something that could make sense.
Note also that basically all of this can be done manually as it is today, either by manually inspecting the module (optionally via some additional debug outputs added by the user to the design to expose internal state/signals) or by using a traditional RTL sim/compiler on verilog output from kaze. While I've already done this a couple times when working on the xenowing console, I don't believe this is a satisfactory solution because it requires an unreasonable amount of extra effort to bootstrap, and there ends up being a lot of duplication of information about which signals are available, which is already known by the kaze compiler.
Is signed-ness something that manifests in traces, and should the compiler actually be made aware of this for this purpose?
In theory this is as simple as capturing all signals and dumping a vcd (or some other format), but is potentially more complex for a few reasons:
std::Vec
for Mem storage). I'd very much like to keep it that way (this makes bootstrapping test projects etc trivial), but we don't necessarily want to also generate a bunch of extra boilerplate to output a certain format when relevant libs are readily available.So, this adds some important constraints on such a solution:
Some loose ends/other ideas that come to mind: