code wiki / _hdl_build / synth_emit_regfile_gates.nx
synth_emit_regfile_gates.nx source
↩ module page · 192 lines · 8182 B
1// synth_emit_regfile_gates.nx -- per-module register-file emitter for .nxgate.
2//
3// SECOND per-RTL-module emitter in the SOVEREIGN canonical chain
4// (after synth_emit_alu_gates.nx). Emits the .nxgate gate netlist
5// for rv64im_min_regfile.nx: a 31-deep D-flip-flop bank (x1..x31;
6// x0 is hardwired zero) with one write port and two combinational
7// read ports.
8//
9// This is the FIRST emitter to exercise the SEQUENTIAL (.FLOP) half
10// of the .nxgate format -- synth_emit_alu_gates.nx was purely
11// combinational (no clocked storage). It also exercises AND-gating
12// (write-enable) which the ALU cascade never used.
13//
14// Gate structure (matches every in-order RV64 core: SiFive E2 .. Rocket):
15// x0: constant 0 (no flop; reads return 0, writes drop)
16// x1..x31 each: q_i = DFF(d_i, clk, rst) ; rst -> 0
17// d_i = MUX(we_i, q_i, rd_value) ; hold unless written
18// we_i = AND(write_en, (rd_idx == i))
19// rs1_data = 32:1 read cascade: (rs1_idx == i) ? q_i : prev (seed = 0)
20// rs2_data = 32:1 read cascade: (rs2_idx == i) ? q_i : prev (seed = 0)
21//
22// The per-index 5-bit constant is SHARED across the write match and
23// both read matches (one .CONST per register, not three).
24//
25// Net-linkage conventions (TIGHTER than synth_emit_alu_gates.nx; see
26// the RECONCILE block at the end of this file):
27// 1. Every internal net gets an explicit `.NET <id> width=<W>` line
28// so the netlist is width-complete. The ALU seed referenced
29// cell-output nets that were never declared with a width.
30// 2. `.CONST <netid> width value` uses the NET handle as its id, so
31// the constant net is self-consistently linked to its consumers
32// (the ALU seed used a running cell id that no fanin referenced).
33// 3. MUX fanin order follows the .nxgate spec verbatim:
34// (sel, in0, in1) where in0 is the sel==0 value, in1 the sel==1
35// value. (out = sel ? in1 : in0.)
36//
37// Status: SEED. 2026-05-28. Covers all 31 writable regs + x0-zero +
38// dual read. Verified structurally by synth_emit_regfile_gates_smoke.nx
39// (port/flop/cell/const/net counts + format markers + all-OK verdicts).
40// Functional gate-sim equivalence vs the behavioural regfile awaits
41// nishi-pnr's gate simulator (M5).
42//
43// License-honest: storage + access semantics per the RISC-V
44// Unprivileged ISA (x0 hardwired-zero); no external code.
45
46import "nx_syscalls.nx"
47import "nishi_hdl_primitives.nx"
48import "nishi_synth.nx"
49import "nishi_synth_gates.nx"
50import "rv64im_min_regfile.nx"
51
52// ===== Net helpers (width-complete) =================================================
53
54// Allocate an internal net + emit its `.NET <id> width=<W>` line.
55func nx_rf_net(s: *NxSynthSink, m: *NxHdlModule, width: i64) -> i64 {
56 let n: i64 = nx_hdl_wire(m, width)
57 if n < 0 { return n }
58 nx_synth_emit_str(s, ".NET " as *u8)
59 nx_gate_emit_net_ref(s, n)
60 nx_synth_emit_str(s, " width=" as *u8)
61 nx_synth_emit_dec(s, width)
62 nx_synth_emit_str(s, "\n" as *u8)
63 return n
64}
65
66// Allocate a constant net + emit `.CONST <netid> width=<W> value=<V>`.
67// The net handle IS the .CONST id (convention 2 above).
68func nx_rf_const(s: *NxSynthSink, m: *NxHdlModule, width: i64, value: i64) -> i64 {
69 let n: i64 = nx_hdl_wire(m, width)
70 if n < 0 { return n }
71 nx_gate_emit_const(s, n, width, value)
72 return n
73}
74
75// 2-input combinational cell: out = KIND(a, b). Returns the out net.
76func nx_rf_bin(s: *NxSynthSink, m: *NxHdlModule, kind: i64,
77 a: i64, b: i64, width: i64, cell_id: i64) -> i64 {
78 let out: i64 = nx_rf_net(s, m, width)
79 if out < 0 { return out }
80 let fin: *i64 = (sys_mmap(16)) as *i64
81 fin[0] = a
82 fin[1] = b
83 nx_gate_emit_cell(s, cell_id, kind, out, fin, 2)
84 return out
85}
86
87// 3-input MUX: out = sel ? in1 : in0. Fanin order per .nxgate spec
88// (sel, in0, in1). Returns the 64-bit out net.
89func nx_rf_mux(s: *NxSynthSink, m: *NxHdlModule,
90 sel: i64, in0: i64, in1: i64, cell_id: i64) -> i64 {
91 let out: i64 = nx_rf_net(s, m, NX_RV64IM_RF_WIDTH)
92 if out < 0 { return out }
93 let fin: *i64 = (sys_mmap(32)) as *i64
94 fin[0] = sel
95 fin[1] = in0
96 fin[2] = in1
97 nx_gate_emit_cell(s, cell_id, NX_GATE_KIND_MUX, out, fin, 3)
98 return out
99}
100
101// ===== Top-level emit =================================================
102//
103// Caller passes an NxHdlModule already populated by nx_rv64im_rf_build
104// (ports declared) + a sink. Emits the full .nxgate netlist.
105
106func nx_emit_regfile_gates(s: *NxSynthSink, m: *NxHdlModule,
107 ports: *NxRv64imRegfilePorts) -> i64 {
108 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND }
109
110 nx_gate_emit_header(s, m)
111 nx_gate_emit_nets(s, m) // .NET for any build-time WIRE/REG (regfile declares none)
112
113 // Per-register workspace (slot 0 unused; x0 is constant zero).
114 let q_net: *i64 = (sys_mmap(8 * NX_RV64IM_RF_N_REGS)) as *i64
115 let idx_const: *i64 = (sys_mmap(8 * NX_RV64IM_RF_N_REGS)) as *i64
116 var cell_seq: i64 = 0
117
118 // ----- Phase 1: per-reg shared index const + write DFF -----
119 var i: i64 = 1
120 while i < NX_RV64IM_RF_N_REGS {
121 // 5-bit constant carrying the register index i (shared by all
122 // three comparators for this register).
123 idx_const[i] = nx_rf_const(s, m, NX_RV64IM_RF_IDX_BITS, i)
124
125 // q_i: stored value net (declared now; the flop below drives it).
126 q_net[i] = nx_rf_net(s, m, NX_RV64IM_RF_WIDTH)
127
128 // we_i = write_en AND (rd_idx == i)
129 let rd_match: i64 = nx_rf_bin(s, m, NX_GATE_KIND_EQ, ports.rd_idx, idx_const[i], 1, cell_seq)
130 cell_seq = cell_seq + 1
131 let we: i64 = nx_rf_bin(s, m, NX_GATE_KIND_AND, ports.write_en, rd_match, 1, cell_seq)
132 cell_seq = cell_seq + 1
133
134 // d_i = we ? rd_value : q_i (hold unless written this cycle)
135 let d: i64 = nx_rf_mux(s, m, we, q_net[i], ports.rd_value, cell_seq)
136 cell_seq = cell_seq + 1
137
138 // q_i = DFF(d_i) on clk, reset -> 0
139 nx_gate_emit_flop(s, cell_seq, q_net[i], d, ports.clk, ports.reset)
140 cell_seq = cell_seq + 1
141
142 i = i + 1
143 }
144
145 // ----- Phase 2a: rs1 read cascade (x0 reads 0) -----
146 var prev1: i64 = nx_rf_const(s, m, NX_RV64IM_RF_WIDTH, 0)
147 i = 1
148 while i < NX_RV64IM_RF_N_REGS {
149 let m1: i64 = nx_rf_bin(s, m, NX_GATE_KIND_EQ, ports.rs1_idx, idx_const[i], 1, cell_seq)
150 cell_seq = cell_seq + 1
151 prev1 = nx_rf_mux(s, m, m1, prev1, q_net[i], cell_seq)
152 cell_seq = cell_seq + 1
153 i = i + 1
154 }
155 nx_hdl_connect(m, ports.rs1_data, prev1)
156 nx_synth_emit_str(s, "// rs1_data <- " as *u8)
157 nx_gate_emit_net_ref(s, prev1)
158 nx_synth_emit_str(s, "\n" as *u8)
159
160 // ----- Phase 2b: rs2 read cascade (x0 reads 0) -----
161 var prev2: i64 = nx_rf_const(s, m, NX_RV64IM_RF_WIDTH, 0)
162 i = 1
163 while i < NX_RV64IM_RF_N_REGS {
164 let m2: i64 = nx_rf_bin(s, m, NX_GATE_KIND_EQ, ports.rs2_idx, idx_const[i], 1, cell_seq)
165 cell_seq = cell_seq + 1
166 prev2 = nx_rf_mux(s, m, m2, prev2, q_net[i], cell_seq)
167 cell_seq = cell_seq + 1
168 i = i + 1
169 }
170 nx_hdl_connect(m, ports.rs2_data, prev2)
171 nx_synth_emit_str(s, "// rs2_data <- " as *u8)
172 nx_gate_emit_net_ref(s, prev2)
173 nx_synth_emit_str(s, "\n" as *u8)
174
175 nx_gate_emit_end(s)
176 return NX_HDL_OK
177}
178
179// ===== RECONCILE (sovereignty follow-up) =================================================
180//
181// synth_emit_alu_gates.nx (the first emitter) diverges from the
182// .nxgate spec in two ways this file corrects:
183// A. it omits `.NET` width declarations for cell-output nets, and
184// B. its `.CONST` id is a running cell counter that no fanin
185// references (the match EQ fans in a separately-allocated wire
186// whose value is never set).
187// C. its MUX fanin is (sel, in1, in0), reversed vs the spec's
188// (sel, in0, in1).
189// When nishi-pnr (the .nxgate consumer, M5) lands and pins the
190// contract, retrofit synth_emit_alu_gates.nx to whichever single
191// convention nishi-pnr accepts so both emitters agree. Tracked as a
192// one-commit cleanup; additive, no behaviour change to this file.