nx_nxgate_sim.nx source
↩ module page · 155 lines · 7030 B
1// nx_nxgate_sim.nx -- gate-level FUNCTIONAL simulator: the VERIFIER organ's
2// hardware oracle (closes SIL-3, "no gate-level functional verification").
3//
4// The .nxgate netlist (nishi_synth_gates.nx) is word-level cells: each cell is
5// a kind (ADD/SUB/MUX/EQ/AND/...) with a fanout net + fanin nets. This module
6// evaluates such a netlist over input vectors so we can assert it computes 1:1
7// what the behavioral RTL (rv64im_min_alu.nx) computes. WHY this matters: the
8// ALU gate emitter currently emits an ADD cell for DIV/REM (the SIL-1 stub), so
9// a faithful functional sim will DIVERGE from behavioral DIV -- i.e. it CATCHES
10// the silent gate-emit miscompile that today's structural-only smokes miss.
11//
12// V1: combinational, word-level, 64-bit (i64) values; cells assumed in
13// topological order (the synth emitter builds sub-results before consumers).
14// LOUD on any unsupported/sequential kind -- never silently miscomputes
15// (Cardinal: build intelligence, fail loud). Reuses the real NX_GATE_KIND_*
16// enum from nishi_synth_gates.nx (single source of truth -- DRY).
17//
18// 5W1H feedback hook: nx_gsim_diff returns the mismatch count; a future variant
19// records WHICH vector + WHICH net diverged + the expected-vs-got, the actionable
20// diagnostic the invention loop's Generator consumes.
21
22import "nishi_synth_gates.nx"
23
24struct NxGsimCell {
25 kind: i64
26 fanout: i64 // net id this cell drives
27 f0: i64 // fanin net ids; -1 if unused
28 f1: i64
29 f2: i64
30 val: i64 // CONST cell value (ignored by non-CONST cells)
31}
32
33struct NxGsim {
34 vals: *i64 // per-net current value (caller-allocated, size >= n_nets)
35 n_nets: i64
36 cells: *NxGsimCell // caller-allocated, topologically ordered
37 n_cells: i64
38}
39
40const NX_GSIM_OK: i64 = 0
41const NX_GSIM_BAD_KIND: i64 = 1
42
43// Combinational kinds this V1 evaluates. Sequential (DFF) + not-yet-implemented
44// kinds are deliberately UNSUPPORTED so nx_gsim_run fails loud instead of
45// silently returning a wrong value.
46func nx_gsim_kind_supported(k: i64) -> i64 {
47 if k == NX_GATE_KIND_AND { return 1 }
48 if k == NX_GATE_KIND_OR { return 1 }
49 if k == NX_GATE_KIND_NOT { return 1 }
50 if k == NX_GATE_KIND_XOR { return 1 }
51 if k == NX_GATE_KIND_NAND { return 1 }
52 if k == NX_GATE_KIND_NOR { return 1 }
53 if k == NX_GATE_KIND_XNOR { return 1 }
54 if k == NX_GATE_KIND_ADD { return 1 }
55 if k == NX_GATE_KIND_SUB { return 1 }
56 if k == NX_GATE_KIND_MUL { return 1 }
57 if k == NX_GATE_KIND_MUX { return 1 }
58 if k == NX_GATE_KIND_SHL { return 1 }
59 if k == NX_GATE_KIND_SHR { return 1 }
60 if k == NX_GATE_KIND_SAR { return 1 }
61 if k == NX_GATE_KIND_EQ { return 1 }
62 if k == NX_GATE_KIND_NEQ { return 1 }
63 if k == NX_GATE_KIND_LT { return 1 }
64 if k == NX_GATE_KIND_LTU { return 1 }
65 if k == NX_GATE_KIND_CONST { return 1 }
66 if k == NX_GATE_KIND_DFF { return 1 } // sequential (held in combinational pass)
67 return 0
68}
69
70// Evaluate one cell. a/b/c are the resolved fanin net values.
71// MUX convention (MATCHES the .nxgate emitter nx_alu_emit_mux_step): fanin =
72// [sel, this, prev]; out = sel!=0 ? this : prev. CONST is handled in nx_gsim_run
73// (its value comes from the cell, not a fanin), so it is not in this switch.
74func nx_gsim_eval_cell(kind: i64, a: i64, b: i64, c: i64) -> i64 {
75 let ones: i64 = 0 - 1
76 if kind == NX_GATE_KIND_AND { return a & b }
77 if kind == NX_GATE_KIND_OR { return a | b }
78 if kind == NX_GATE_KIND_NOT { return a ^ ones }
79 if kind == NX_GATE_KIND_XOR { return a ^ b }
80 if kind == NX_GATE_KIND_NAND { return (a & b) ^ ones }
81 if kind == NX_GATE_KIND_NOR { return (a | b) ^ ones }
82 if kind == NX_GATE_KIND_XNOR { return (a ^ b) ^ ones }
83 if kind == NX_GATE_KIND_ADD { return a + b }
84 if kind == NX_GATE_KIND_SUB { return a - b }
85 if kind == NX_GATE_KIND_MUL { return a * b }
86 if kind == NX_GATE_KIND_MUX { if a != 0 { return b } return c }
87 if kind == NX_GATE_KIND_SHL { return a << b }
88 if kind == NX_GATE_KIND_SHR { // LOGICAL right shift (zero-fill)
89 if b == 0 { return a }
90 if b >= 64 { return 0 }
91 return (a >> b) & ((1 << (64 - b)) - 1)
92 }
93 if kind == NX_GATE_KIND_SAR { return a >> b } // arithmetic right shift: NishiLang >> sign-extends
94 if kind == NX_GATE_KIND_EQ { if a == b { return 1 } return 0 }
95 if kind == NX_GATE_KIND_NEQ { if a != b { return 1 } return 0 }
96 if kind == NX_GATE_KIND_LT { if a < b { return 1 } return 0 }
97 if kind == NX_GATE_KIND_LTU {
98 // unsigned a < b: MSB-set values are LARGE. same sign-region => signed compare;
99 // signs differ => a<u b iff a's MSB is 0 (a small unsigned, b large unsigned).
100 if a >= 0 { if b >= 0 { if a < b { return 1 } return 0 } return 1 }
101 if b >= 0 { return 0 }
102 if a < b { return 1 } return 0
103 }
104 return 0
105}
106
107// Evaluate the whole combinational netlist. Primary-input nets must be pre-set
108// in g.vals. Returns NX_GSIM_OK, or 0 - NX_GSIM_BAD_KIND (LOUD) if any cell has
109// an unsupported kind.
110func nx_gsim_run(g: *NxGsim) -> i64 {
111 var i: i64 = 0
112 while i < g.n_cells {
113 let k: i64 = g.cells[i].kind
114 if nx_gsim_kind_supported(k) != 1 { return 0 - NX_GSIM_BAD_KIND }
115 if k == NX_GATE_KIND_CONST { g.vals[g.cells[i].fanout] = g.cells[i].val }
116 // DFF: held in the combinational pass -- its fanout (Q) keeps the stored
117 // state (set by init or the previous tick's latch); nx_gsim_tick latches D->Q.
118 if k != NX_GATE_KIND_CONST { if k != NX_GATE_KIND_DFF {
119 let f0: i64 = g.cells[i].f0
120 let f1: i64 = g.cells[i].f1
121 let f2: i64 = g.cells[i].f2
122 var a: i64 = 0
123 var b: i64 = 0
124 var c: i64 = 0
125 if f0 >= 0 { a = g.vals[f0] }
126 if f1 >= 0 { b = g.vals[f1] }
127 if f2 >= 0 { c = g.vals[f2] }
128 g.vals[g.cells[i].fanout] = nx_gsim_eval_cell(k, a, b, c)
129 }}
130 i = i + 1
131 }
132 return NX_GSIM_OK
133}
134
135// One CLOCKED tick: combinational eval (DFFs hold current state), then latch each
136// DFF's D-input (f0) into its Q-output (fanout) SIMULTANEOUSLY (the clock edge).
137// A DFF cell is {kind=DFF, fanout=Q, f0=D}. The caller inits each Q net before the
138// first tick. dtmp is caller scratch (>= number of DFF cells). This makes the sim
139// SEQUENTIAL -- registers, PC, the whole CPU state -- so a gate-level CPU can run.
140func nx_gsim_tick(g: *NxGsim, dtmp: *i64) -> i64 {
141 if nx_gsim_run(g) != NX_GSIM_OK { return 0 - NX_GSIM_BAD_KIND }
142 var i: i64 = 0
143 var nd: i64 = 0
144 while i < g.n_cells {
145 if g.cells[i].kind == NX_GATE_KIND_DFF { dtmp[nd] = g.vals[g.cells[i].f0]; nd = nd + 1 }
146 i = i + 1
147 }
148 i = 0
149 nd = 0
150 while i < g.n_cells {
151 if g.cells[i].kind == NX_GATE_KIND_DFF { g.vals[g.cells[i].fanout] = dtmp[nd]; nd = nd + 1 }
152 i = i + 1
153 }
154 return NX_GSIM_OK
155}