feat(cosim): reg_init register value-injection ($deposit at t0) (#108)#110
Conversation
58e4f1c to
566e00a
Compare
|
Field-testing this on a Hazard3 DTM+DM netlist surfaced a The bug
Concretely it hit Minimal synthetic repro (Apache-2.0, ~30 lines, no external deps)module reginit_constbit (input wire clk, en, input wire [2:0] din,
input wire [3:0] mask, output wire [3:0] dout);
reg [3:0] r;
always @(posedge clk) if (en) r <= {din, 1'b0}; // r[0] folds to constant
assign dout = r & mask; // comb use keeps `r` named
endmoduleSynth to AIGPDK → Root cause + suggested fix (parity, not a skip-patch)
So rather than a local "skip constant bits" patch,
That gives parity by construction: anything Explicitly out of scopeRegisters the optimizer dissolved entirely (resource-shared/merged so the (Aside: the field test also showed reactive cosim |
Add a `reg_init` array to the testbench JSON that deposits a definite value into chosen registers at tick 0 with $deposit semantics: the seed clears the power-up X-mask, then the design's own logic drives the register normally. NOT force — applied once, so a CDC crossing register carries real protocol data after t0 instead of being pinned to the seed. This is the register sibling of sram_init and the fix path for the X-poisoned unreset CDC launch registers in #102: depositing on the launch flops lets X-aware cosim of debug-loaded firmware proceed. Composes with the x-assert detection work (#106) — inject at the source, assert at the sink. Implementation: - RegInitEntry { name, value, width } in TestbenchConfig (cosim-only; the sim path takes initial state from the VCD). - resolve_to_input_state_pos in trace_signals: resolves a register name to its DFF Q slot in input_map (keyed by the bare aigpin, iv >> 1), scanning all pins on the net so the driver pin is found regardless of order. Shared candidate->netid preamble factored into resolve_net_id. - Deposit writes the value and clears the X-mask in BOTH state slots so it survives state_prep's per-edge output->input copy (a DFF Q is sequential state, not re-driven by the BitOp schedule). Also fix a pre-existing panic: cosim on a SRAM-less design built a nil MTLBuffer (new_buffer(0)) whose .contents() is null; size the SRAM data buffer to max(1) word, matching the X-mask shadow buffer. Regression (tests/xprop_cosim, CI): cosim A/B on the same design — without reg_init the unreset counter stays X; with a reg_init deposit on `unreset_count` q_unreset reads known 0/1. New check.py `reg-init` mode. Co-developed-by: Claude Code v2.1.162 (claude-opus-4-8)
566e00a to
1123030
Compare
Summary
Implements #108 —
reg_initregister value-injection for cosim — and along the way fixes a pre-existing panic on SRAM-less cosim designs.A new
reg_initarray in the testbench JSON deposits a definite value into chosen registers at tick 0 with$depositsemantics: the seed clears the power-up X-mask, then the design's own logic drives the register normally. It is notforce— applied once, so a CDC crossing register carries real protocol data after t0 instead of being pinned to the seed (which would write zeros across the handshake).This is the register sibling of
sram_initand the fix path the maintainer root-caused in #102: depositing definite values on the unreset Hazard3 CDC launch registers (hazard3_apb_async_bridge.src_paddr_pwdata_pwrite/dst_prdata_pslverr) clears the conservative-X poison so X-aware cosim of debug-loaded firmware can proceed. It composes with the x-assert detection work (#106) — inject at the source, assert!$isunknownat the sink.Implementation
RegInitEntry { name, value, width }inTestbenchConfig(cosim-only — thesimpath takes initial state from the VCD).resolve_to_input_state_pos(trace_signals.rs): resolves a register name to its DFF Q slot ininput_map. Two subtleties that made bus bits and output-aliased registers resolve:input_mapis keyed by the bare aigpin (dff.qfromadd_aigpin), whereaspin2aigpin_ivis iv-encoded (aigpin << 1 | invert) — recover the bare aigpin with>> 1.resolve_net_id(also used byresolve_to_state_pos).state_prep's per-edge output→input copy (a DFF Q is sequential state, not re-driven by the BitOp schedule like primary inputs are).Bonus fix — cosim on SRAM-less designs
cosim on a design with zero SRAM panicked:
new_buffer(0)returns a nilMTLBufferwhose.contents()is null (foreign-types asserts non-null). The SRAM data buffer is now sized tomax(1)word, matching the X-mask shadow buffer. This was never caught because thexprop_demoCI guard only exercised thesimpath; this PR adds the first cosim coverage of a zero-SRAM design.Testing
tests/xprop_cosim/): cosim A/B on the same SRAM-less design — withoutreg_initthe unreset counter stays X (q_unreset = x); with areg_initdeposit onunreset_count,q_unresetreads known0/1. Newcheck.pyreg-initmode (the inverse assertion ofxpropmode).q_unresettoggle1,0,1,0…from the seed — known, and not pinned (proving$deposit, notforce).trace_signalsunit tests pass; clean Metal build;/simplifyrun over the diff.Notes / decisions
width1 (or omitted) → bare name;width > 1→name[0]..name[width-1]resolved per-bit (synthesis scatters bus bits to non-contiguous state positions).0(documented) — exactly the "clear a wide unreset register to a definite 0" case the cosim --xprop: JTAG-DM firmware load writes no SRAM (debug-load path reads X) — not a read-mask bug #102 launch registers need.Closes #108.