code wiki / (root) / nx_nxgate_sim.nx

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}