trsim › Manuals › Compiled Backend
trsim Compiled Backend
The Tuwa RTL Simulator, version 1.0
-native turns your design into C and lets a C compiler optimise it. On a large design that is the difference between a simulation you run overnight and one you run in a coffee break.
How it works
An ordinary run interprets the elaborated design: for every event, the engine walks a tree of statements and evaluates them. That is flexible but it pays an interpretation cost on every single operation.
-native instead generates C source for the whole elaborated design — every process, every assignment, the scheduler's fanout tables, the memories — and hands it to your C compiler. The result is a standalone executable that simulates the design and nothing else.
The front end is shared. Parsing, `include handling, parameter override, elaboration and hierarchy flattening are the same code either way, so a design that compiles for one back end compiles for the other.
Using it
Generating the C does not simulate anything:
trsim -top my_tb -time 100000 -native build\my_tb.c rtl\*.v tb\my_tb.v
Then build it. With Microsoft's compiler:
cl /O2 /Fe:my_tb.exe build\my_tb.c trsimrt.c
with gcc or clang:
gcc -O2 -o my_tb build/my_tb.c trsimrt.c
trsimrt.c is the runtime kernel — the event queue, the four-state value helpers, the format engine and the VCD writer. It is the only support file the generated code needs.
Then run it:
my_tb.exe
The program prints exactly what the interpreted run would print. -time is baked into the generated code, so the end time is fixed at generation.
Runtime controls
The generated program reads a few environment variables. These are useful precisely because they need no regeneration — you can get a waveform or a trace out of a binary you already built.
TRS_VCD=<file>
Dump every signal to a VCD, whether or not the design calls $dumpfile.
set TRS_VCD=run.vcd
my_tb.exe
TRS_TRACE=<substring>
Print every change of every signal whose hierarchical name contains the given text, with the time and the new value.
set TRS_TRACE=cpu.alu
my_tb.exe
The match is a plain substring, not a pattern. Signal names carry no brackets, so a filter like mem[ never matches anything — filter on the name and read the index from the output.
TRS_STATS=1
Print a summary when the run ends: events processed, signal updates, and where the time was spent.
TRS_PROGRESS=<n>
Print a line every n simulated time units, so a long run visibly moves.
What it will and will not compile
The compiled path covers a large subset of what the interpreter runs, not all of it. Where it meets something it cannot compile, it says so and stops — it does not generate code that would quietly be wrong:
native backend: unsupported construct 'empty right hand side' at source line 84
native backend: nothing generated - the interpreter still runs this design
correctly, so use it for this case.
The most common thing to hit is a task call. Testbenches that factor their checks into tasks — which is good style, and which four of the six shipped examples do — are interpreter-only for now. The two examples without tasks, 04_fsm and 06_memory, build and run natively as they stand.
A practical arrangement is to keep the design itself free of constructs the compiled path rejects, and accept that a task-heavy testbench runs interpreted.
What it is worth
The compiled backend was built for, and is measured on, a real system-on-chip: 1361 signals and 598 processes, with a CPU, a cache, block RAM and a UART. It runs that design at roughly the speed of a commercial simulator and reproduces its output exactly — the same cache trace, the same bus traffic, the same bytes out of the UART.
For a small testbench the interpreter is quick enough and simpler to use. The compiled path earns its keep when the run is long: a design that takes minutes per simulated microsecond interpreted is a different proposition compiled.
Checking the two agree
Since both back ends share the front end, a difference between them is a bug in one of them and worth reporting. To compare:
trsim -q -top my_tb -time 100000 rtl\*.v tb\my_tb.v > interp.txt
trsim -q -top my_tb -time 100000 -native gen.c rtl\*.v tb\my_tb.v
cl /O2 /Fe:nat.exe gen.c trsimrt.c
nat.exe > native.txt
fc interp.txt native.txt
One caveat when you do: the interpreter and the compiled path can differ in what a signal reads before anything has driven it. The compiled path starts every variable at x, which is what the standard says; the interpreter is less strict about it in a few places. A difference confined to the first sample of an undriven signal is usually this, not a real divergence.