The Verilog emitter
One structural always block for the whole design, and a signature both backends agree on.
The emitter turns the same Circuit the simulator reads into structural Verilog. The shape is deliberate: one always block, one giant begin/end, continuous assignments for combinational logic and non-blocking assignments for registers.
That shape is what makes the cosimulation check meaningful. If the two backends were structurally different — one procedural, one concurrent — then a disagreement would not tell you which one is wrong. Emitting something a synthesis tool reads the way it reads any other RTL is the whole point.
Step by step
- 01
Build a module from the circuit and the clock
Module::newtakes the name, the built circuit, the clock node id, and the input and output id lists. The clock is passed separately because it is the one signal that is a port but not a stimulus column.use ferrite_lithic_rtl::Module; let module = Module::new( "top", design.build()?, clock_id, design.input_ports().iter().map(|s| s.id()).collect(), design.output_ports().iter().map(|s| s.id()).collect(), ); - 02
One process, in topological order
emitwalks the graph and writes each node once. Combinational nodes become continuous assignments; registers become non-blocking assignments inside the clocked block. Topological order is what makes the generated Verilog readable when a design goes wrong. - 03
A signature both backends can be compared on
signature,inputs,outputsandport_identifiersdefine the interface the cosimulator checks. It is derived from the design rather than written out, which is what stops a test from asserting that the stimulus matched a hand-copied column list. - 04
Names come from PortList, not from string literals
with_port_namesapplies the names a#[derive(PortList)]produced. A port's name in the generated Verilog is therefore the same identifier the testbench used, which is what makes a failing cosim cycle readable.
What bites
The port list is the contract
Order matters: the cosimulator's stimulus columns are built from
inputsin order. Reordering the emitter's list without reordering the plan is a mismatch that shows up as every column comparing the wrong signal.
Notes
- measured
Verilator is looked for, not required
cargo testworks without it and prints what it skipped. CI installs it and then fails if anything skipped — because a green run in which every equivalence test took its skip path is green and meaningless.