ferrite-lithic811 tests · 21 designs

ferrite-lithic-rtlphase A252 tests

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

  1. 01

    Build a module from the circuit and the clock

    Module::new takes 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(),
    );
  2. 02

    One process, in topological order

    emit walks 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.

  3. 03

    A signature both backends can be compared on

    signature, inputs, outputs and port_identifiers define 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.

  4. 04

    Names come from PortList, not from string literals

    with_port_names applies 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 inputs in 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 test works 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.