ferrite-lithic811 tests · 21 designs

ferrite-lithic-simphase A252 tests

The cycle simulator

Combinational within a cycle, sequential at the edge, and the edge is a real boundary.

The simulator evaluates the graph in topological order. Combinational nodes settle first, then registers sample their inputs and take their new values at the clock edge. Everything else — memories, handshakes, the lot — falls out of that one rule.

The rule matters because it is the rule real hardware uses. A design that works in a simulator which re-evaluates everything in arbitrary order is a design whose behaviour you have not actually checked.

Step by step

  1. 01

    Compile once, then settle

    Sim::new compiles the circuit into an evaluation program. comb settles the combinational cone; step does the whole cycle and returns the snapshots either side of the edge.

    use ferrite_lithic_sim::Sim;
    
    let mut sim = Sim::new(&design)?;
    sim.initialize_to(&state, ferrite_lithic_bits::Bits::zeros(8)?)?;
    
    let (before, after) = sim.step()?;
  2. 02

    Three explicit points around the edge

    before_clock_edge, at_clock_edge and after_clock_edge are separate methods because separate methods are what let a testbench place stimulus on the right side of the edge. The alternative — a step that takes an input — is what caused the Handle::set bug recorded in DETAILS.md: it silently acquired an extra clock edge.

    sim.before_clock_edge()?;   // sample here
    sim.set_input(&d, &value)?;
    sim.at_clock_edge()?;          // registers take their new values
    sim.after_clock_edge()?;
  3. 03

    Registers update last, and visibly

    reg_updates and mem_updates expose what the edge actually did. A testbench that wants to assert on the transition rather than on the value needs exactly this, and it is the reason the corpus can check a one-symbol-per-cycle decoder rather than only its final answer.

    let updates = sim.reg_updates()?;
    for update in updates {
        println!("{}: {:?} -> {:?}", update.signal, update.before, update.after);
    }
  4. 04

    Errors rather than silent zero

    An undriven wire, a width mismatch and a combinational loop are all errors. A simulator that substitutes zero for a missing driver produces plausible waveforms for a broken design, which is the failure mode this project has the least tolerance for.

What bites

  • initialise-to-value refuses memories

    Not a limitation left unstated: it is a deliberate refusal, because there is no initialised-memory node in the IR to initialise. Adding one is the change that would unlock it.

  • Widths are not coerced

    A Bits handed to set_input must match the port's width or the call errors. Silent zero-extension here would be a bug that only appears on one backend.