trsim Examples
The Tuwa RTL Simulator, version 1.0
Six small designs in the examples\ folder. Each one checks itself, prints PASS or FAIL on its last line, and writes a VCD. Each also carries an expected.txt holding its complete output, so a change in any intermediate value is caught, not just a flipped verdict.
All six produce output identical to ModelSim's for the same file, character for character.
Running them
One example:
cd examples\01_counter
..\..\trsim.exe -top counter_tb -time 5000 counter.v
All six, checked against their recorded output:
cd examples
run_all.ps1
or, from a POSIX shell, ./run_all.sh. Both look for trsim.exe beside the examples folder and then on the PATH; you can also pass its path.
PASS 01_counter
PASS 02_alu
PASS 03_fifo
PASS 04_fsm
PASS 05_uart
PASS 06_memory
6 passed, 0 failed
01_counter — loadable up-counter
counter.v, top counter_tb, 5000 ns
The smallest complete design: one synchronous block with an asynchronous reset, load taking priority over count, and natural wrapping at the top of the range.
Worth reading for the shape of a self-checking testbench — an errors counter, an expect task that reports every mismatch rather than stopping at the first, and a verdict at the end.
The waveform shows the load overriding counting on the very next edge, and the count wrapping through FF back to 00.
02_alu — combinational ALU
alu.v, top alu_tb, 5000 ns
Purely combinational: a case over an opcode, and a carry recovered as the ninth bit of a nine-bit sum rather than reconstructed afterwards.
The testbench works out what it expects independently of the design, so a mistake in the ALU cannot hide behind the same mistake in the check. Twelve directed cases cover the corners, then a 256-vector sweep runs every opcode against a spread of operands.
There is no clock at all. Inputs are driven, one nanosecond is allowed for the logic to settle, and the result is checked.
03_fifo — synchronous FIFO
fifo.v, top fifo_tb, 20000 ns
A memory array with wrapping read and write pointers, and the classic full-versus-empty problem: when the pointers are equal the FIFO is either empty or completely full, and the extra bit on each pointer is what tells the two apart.
The testbench fills it, checks that a push against a full FIFO is refused rather than overwriting, drains it checking first-in-first-out order, checks that a pop against an empty FIFO is refused, and then cycles the pointers right round to prove the wrap works.
Note the style: the clock waits are written out in the main initial block rather than hidden in push and pop tasks, because a task containing a timing control does not suspend its caller.
04_fsm — sequence detector
seq_detect.v, top seq_detect_tb, 5000 ns
The standard three-block state machine: a synchronous block for the state register, a combinational block for the next state, and an output derived from the state alone — which makes it Moore, and is why the pulse appears one cycle after the last bit of the pattern.
It detects 1011 with overlap: the 11 ending one match can begin the next. The test pattern contains three matches, two of which overlap.
This example and 06_memory are the two that also build with -native.
05_uart — UART transmitter
uart_tx.v, top uart_tx_tb, 50000 ns
The most realistic of the six: a baud-rate divider, a shift register, and a frame of start bit, eight data bits least significant first, and a stop bit.
What makes the check meaningful is that the testbench contains an independent receiver which samples in the middle of each bit, the way real hardware does. That confirms the bit timing, not just the bit values — a transmitter with the right bits and the wrong baud divider would fail.
Three bytes are sent, including 0x41 (the letter A) and a value with both nibbles set.
06_memory — RAM with $readmemh
spram.v and spram_init.hex, top spram_tb, 10000 ns
A single-port RAM initialised from a hex file, which is how a boot ROM, a test vector table or a character set gets into a simulation.
It is a write-first RAM: a read in the same cycle as a write to the same address returns the new value, which is what a block RAM in write-first mode does. The testbench verifies all sixteen initial locations against its own copy of the expected contents, then writes, reads back, and confirms a neighbouring location was untouched.
Run this one from its own directory — $readmemh resolves its filename relative to the working directory.
Using them as a starting point
They are MIT licensed like the rest of trsim. Copy one, change the design, keep the testbench shape:
- Count errors rather than stopping at the first.
- Compare with
!==, not!=, so an x is caught instead of comparing equal. - Compute the expected value independently of the design under test.
- Print
PASSorFAILon the last line, and$finish. - Call
$dumpfileand$dumpvarsso a waveform is always there when you want one.
Point 4 is what lets a build script read the exit code, and point 5 costs nothing until you need it.