nishi_synth_gates.nx source
↩ module page · 301 lines · 12725 B
1// nishi_synth_gates.nx -- sovereign gate-netlist emitter.
2//
3// Replaces Verilog as the canonical synth output target. Emits a
4// NishiLang-native gate-netlist text format (.nxgate) that downstream
5// nishi-pnr can consume. No Verilog syntax anywhere -- no `module`,
6// no `wire`, no `assign`, no `always_comb`, no `endmodule`.
7//
8// Per the operator's cardinal (2026-05-26): "our own verilog without
9// any of this being something we can be legally hit on" + the
10// [[feedback-open-source-licensed-is-still-third-party]] memory:
11// Verilog 2005 (IEEE 1364) is an external spec; emitting it bakes
12// in a third-party dependency even though the emitter itself is
13// ours. The .nxgate format below is fully sovereign -- the SPEC
14// is here, the EMITTER is here, the CONSUMER (nishi-pnr, future)
15// is also here.
16//
17// The Verilog emitter (nishi_synth.nx + synth_emit_alu.nx) stays
18// as a Wheeler-only test comparator per
19// [[feedback-c-is-wheeler-test-only]] -- run both emitters in
20// parallel during development, diff the resulting netlists for
21// correctness, NEVER ship the Verilog path as canonical.
22//
23// .nxgate v1 format spec:
24//
25// .NXGATE 1 format version
26// .MODULE <name> one per file
27// .PORT IN <name> width=<W> input port; W in 1..512
28// .PORT OUT <name> width=<W> output port
29// .PORT INOUT <name> width=<W> bidirectional port
30// .PORT CLK <name> clock (always 1-bit)
31// .PORT RST <name> reset (always 1-bit)
32// .NET <id> width=<W> internal net; id 0..n_nets-1
33// .CELL <id> kind=<K> fanout=<net> combinational cell
34// fanin=<net>,<net>,...
35// .FLOP <id> kind=DFF fanout=<net> sequential cell (D-flipflop)
36// d=<net> clk=<port> rst=<port>
37// .CONST <id> width=<W> value=<V> compile-time constant
38// .END module end
39//
40// Cell kinds (sealed):
41// AND OR NOT XOR NAND NOR XNOR boolean
42// ADD SUB MUL arithmetic (synth widens fanin)
43// MUX (3 fanin: sel, in0, in1)
44// SHL SHR SAR (2 fanin: value, shamt)
45// EQ NEQ LT LTU GE GEU comparators
46// CONCAT EXT_S EXT_U bit-level reshape
47//
48// Status: SEED. 2026-05-26. Format spec + skeleton emitter; the
49// per-RTL-module gate emitters (one per existing rv64im_min_*.nx)
50// land per the M4 milestone in ZERO_TO_ADVANCED.md.
51
52import "nx_syscalls.nx"
53import "nishi_hdl_primitives.nx"
54import "nishi_synth.nx"
55
56// ===== Gate kinds (sealed enum) =================================================
57//
58// Closed set. nishi-pnr (future) maps each to one or more standard
59// cells per the target PDK. V1 covers the kinds the RV64IM-min
60// modules actually need.
61
62const NX_GATE_KIND_AND: i64 = 0
63const NX_GATE_KIND_OR: i64 = 1
64const NX_GATE_KIND_NOT: i64 = 2
65const NX_GATE_KIND_XOR: i64 = 3
66const NX_GATE_KIND_NAND: i64 = 4
67const NX_GATE_KIND_NOR: i64 = 5
68const NX_GATE_KIND_XNOR: i64 = 6
69const NX_GATE_KIND_ADD: i64 = 7
70const NX_GATE_KIND_SUB: i64 = 8
71const NX_GATE_KIND_MUL: i64 = 9
72const NX_GATE_KIND_MUX: i64 = 10
73const NX_GATE_KIND_SHL: i64 = 11
74const NX_GATE_KIND_SHR: i64 = 12
75const NX_GATE_KIND_SAR: i64 = 13
76const NX_GATE_KIND_EQ: i64 = 14
77const NX_GATE_KIND_NEQ: i64 = 15
78const NX_GATE_KIND_LT: i64 = 16
79const NX_GATE_KIND_LTU: i64 = 17
80const NX_GATE_KIND_GE: i64 = 18
81const NX_GATE_KIND_GEU: i64 = 19
82const NX_GATE_KIND_CONCAT: i64 = 20
83const NX_GATE_KIND_EXT_S: i64 = 21 // sign-extend
84const NX_GATE_KIND_EXT_U: i64 = 22 // zero-extend
85const NX_GATE_KIND_DFF: i64 = 23 // sequential (clocked)
86const NX_GATE_KIND_CONST: i64 = 24
87const NX_GATE_KIND_N: i64 = 25
88
89func nx_gate_kind_is_valid(k: i64) -> i64 {
90 if k < 0 { return 0 }
91 if k >= NX_GATE_KIND_N { return 0 }
92 return 1
93}
94
95// Kind name lookup for .nxgate text emit.
96func nx_gate_kind_name(k: i64) -> *u8 {
97 if k == NX_GATE_KIND_AND { return "AND" as *u8 }
98 if k == NX_GATE_KIND_OR { return "OR" as *u8 }
99 if k == NX_GATE_KIND_NOT { return "NOT" as *u8 }
100 if k == NX_GATE_KIND_XOR { return "XOR" as *u8 }
101 if k == NX_GATE_KIND_NAND { return "NAND" as *u8 }
102 if k == NX_GATE_KIND_NOR { return "NOR" as *u8 }
103 if k == NX_GATE_KIND_XNOR { return "XNOR" as *u8 }
104 if k == NX_GATE_KIND_ADD { return "ADD" as *u8 }
105 if k == NX_GATE_KIND_SUB { return "SUB" as *u8 }
106 if k == NX_GATE_KIND_MUL { return "MUL" as *u8 }
107 if k == NX_GATE_KIND_MUX { return "MUX" as *u8 }
108 if k == NX_GATE_KIND_SHL { return "SHL" as *u8 }
109 if k == NX_GATE_KIND_SHR { return "SHR" as *u8 }
110 if k == NX_GATE_KIND_SAR { return "SAR" as *u8 }
111 if k == NX_GATE_KIND_EQ { return "EQ" as *u8 }
112 if k == NX_GATE_KIND_NEQ { return "NEQ" as *u8 }
113 if k == NX_GATE_KIND_LT { return "LT" as *u8 }
114 if k == NX_GATE_KIND_LTU { return "LTU" as *u8 }
115 if k == NX_GATE_KIND_GE { return "GE" as *u8 }
116 if k == NX_GATE_KIND_GEU { return "GEU" as *u8 }
117 if k == NX_GATE_KIND_CONCAT { return "CONCAT" as *u8 }
118 if k == NX_GATE_KIND_EXT_S { return "EXT_S" as *u8 }
119 if k == NX_GATE_KIND_EXT_U { return "EXT_U" as *u8 }
120 if k == NX_GATE_KIND_DFF { return "DFF" as *u8 }
121 if k == NX_GATE_KIND_CONST { return "CONST" as *u8 }
122 return "UNKNOWN" as *u8
123}
124
125// ===== Port-kind names (different from internal Verilog map) =================================================
126//
127// .nxgate uses our own port-kind tokens (PORT IN/OUT/INOUT/CLK/RST)
128// not Verilog's input/output/inout/wire.
129
130func nx_gate_port_kind_token(kind: i64) -> *u8 {
131 if kind == NX_HDL_KIND_INPUT { return "IN" as *u8 }
132 if kind == NX_HDL_KIND_OUTPUT { return "OUT" as *u8 }
133 if kind == NX_HDL_KIND_INOUT { return "INOUT" as *u8 }
134 if kind == NX_HDL_KIND_CLOCK { return "CLK" as *u8 }
135 if kind == NX_HDL_KIND_RESET { return "RST" as *u8 }
136 return "UNKNOWN" as *u8
137}
138
139// ===== Net naming =================================================
140//
141// .nxgate identifies nets by integer handle (matches NxHdlModule
142// signal indexing). Display as "n<handle>" to keep it scannable.
143
144func nx_gate_emit_net_ref(s: *NxSynthSink, handle: i64) -> i64 {
145 nx_synth_emit_str(s, "n" as *u8)
146 return nx_synth_emit_dec(s, handle)
147}
148
149// ===== Module header + port section =================================================
150//
151// Emits the .NXGATE / .MODULE / .PORT lines for every port-kind
152// signal in the module's signal table.
153
154func nx_gate_emit_header(s: *NxSynthSink, m: *NxHdlModule) -> i64 {
155 nx_synth_emit_str(s, "// Auto-generated by nishi-synth-gates. .nxgate v1 (sovereign).\n" as *u8)
156 nx_synth_emit_str(s, "// Source: nishi-silicon/hdl/ NishiHDL module graph.\n" as *u8)
157 nx_synth_emit_str(s, "// DO NOT EDIT -- regenerated from source.\n" as *u8)
158 nx_synth_emit_str(s, ".NXGATE 1\n" as *u8)
159 nx_synth_emit_str(s, ".MODULE " as *u8)
160 nx_synth_emit_bytes(s, m.name_buf, m.name_len)
161 nx_synth_emit_str(s, "\n" as *u8)
162
163 // Walk signal table for ports.
164 var i: i64 = 0
165 while i < m.n_signals {
166 let k: i64 = m.signals_buf[i * 4 + NX_HDL_SIGNAL_FIELD_KIND]
167 var is_port: i64 = 0
168 if k == NX_HDL_KIND_INPUT { is_port = 1 }
169 if k == NX_HDL_KIND_OUTPUT { is_port = 1 }
170 if k == NX_HDL_KIND_INOUT { is_port = 1 }
171 if k == NX_HDL_KIND_CLOCK { is_port = 1 }
172 if k == NX_HDL_KIND_RESET { is_port = 1 }
173 if is_port == 1 {
174 nx_synth_emit_str(s, ".PORT " as *u8)
175 nx_synth_emit_str(s, nx_gate_port_kind_token(k))
176 nx_synth_emit_str(s, " " as *u8)
177 nx_gate_emit_net_ref(s, i)
178 // CLK/RST are always 1-bit; width is implicit, omit.
179 if k != NX_HDL_KIND_CLOCK { if k != NX_HDL_KIND_RESET {
180 nx_synth_emit_str(s, " width=" as *u8)
181 nx_synth_emit_dec(s, m.signals_buf[i * 4 + NX_HDL_SIGNAL_FIELD_WIDTH])
182 }}
183 nx_synth_emit_str(s, "\n" as *u8)
184 }
185 i = i + 1
186 }
187 return NX_HDL_OK
188}
189
190// ===== Internal nets =================================================
191//
192// Every WIRE / REG declared in the module that ISN'T a port becomes
193// a .NET line.
194
195func nx_gate_emit_nets(s: *NxSynthSink, m: *NxHdlModule) -> i64 {
196 var i: i64 = 0
197 while i < m.n_signals {
198 let k: i64 = m.signals_buf[i * 4 + NX_HDL_SIGNAL_FIELD_KIND]
199 var is_internal: i64 = 0
200 if k == NX_HDL_KIND_WIRE { is_internal = 1 }
201 if k == NX_HDL_KIND_REG { is_internal = 1 }
202 if is_internal == 1 {
203 nx_synth_emit_str(s, ".NET " as *u8)
204 nx_gate_emit_net_ref(s, i)
205 nx_synth_emit_str(s, " width=" as *u8)
206 nx_synth_emit_dec(s, m.signals_buf[i * 4 + NX_HDL_SIGNAL_FIELD_WIDTH])
207 nx_synth_emit_str(s, "\n" as *u8)
208 }
209 i = i + 1
210 }
211 return NX_HDL_OK
212}
213
214// ===== Cell emit =================================================
215//
216// Each cell is a single line:
217// .CELL <id> kind=<KIND> fanout=<net> fanin=<net>,<net>,...
218//
219// Caller-supplied; per-RTL-module emitters (commits to follow) build
220// these via nx_gate_emit_cell.
221
222func nx_gate_emit_cell(s: *NxSynthSink, cell_id: i64, kind: i64,
223 fanout: i64, fanin: *i64, n_fanin: i64) -> i64 {
224 if nx_gate_kind_is_valid(kind) != 1 { return 0 - NX_HDL_BAD_KIND }
225 nx_synth_emit_str(s, ".CELL " as *u8)
226 nx_synth_emit_dec(s, cell_id)
227 nx_synth_emit_str(s, " kind=" as *u8)
228 nx_synth_emit_str(s, nx_gate_kind_name(kind))
229 nx_synth_emit_str(s, " fanout=" as *u8)
230 nx_gate_emit_net_ref(s, fanout)
231 nx_synth_emit_str(s, " fanin=" as *u8)
232 var i: i64 = 0
233 while i < n_fanin {
234 if i > 0 { nx_synth_emit_str(s, "," as *u8) }
235 nx_gate_emit_net_ref(s, fanin[i])
236 i = i + 1
237 }
238 nx_synth_emit_str(s, "\n" as *u8)
239 return NX_HDL_OK
240}
241
242// Flop emit -- sequential cell with explicit clk + rst.
243func nx_gate_emit_flop(s: *NxSynthSink, cell_id: i64, fanout: i64,
244 d: i64, clk: i64, rst: i64) -> i64 {
245 nx_synth_emit_str(s, ".FLOP " as *u8)
246 nx_synth_emit_dec(s, cell_id)
247 nx_synth_emit_str(s, " kind=DFF fanout=" as *u8)
248 nx_gate_emit_net_ref(s, fanout)
249 nx_synth_emit_str(s, " d=" as *u8)
250 nx_gate_emit_net_ref(s, d)
251 nx_synth_emit_str(s, " clk=" as *u8)
252 nx_gate_emit_net_ref(s, clk)
253 nx_synth_emit_str(s, " rst=" as *u8)
254 nx_gate_emit_net_ref(s, rst)
255 nx_synth_emit_str(s, "\n" as *u8)
256 return NX_HDL_OK
257}
258
259// Constant emit.
260func nx_gate_emit_const(s: *NxSynthSink, const_id: i64, width: i64, value: i64) -> i64 {
261 nx_synth_emit_str(s, ".CONST " as *u8)
262 nx_synth_emit_dec(s, const_id)
263 nx_synth_emit_str(s, " width=" as *u8)
264 nx_synth_emit_dec(s, width)
265 nx_synth_emit_str(s, " value=" as *u8)
266 nx_synth_emit_dec(s, value)
267 nx_synth_emit_str(s, "\n" as *u8)
268 return NX_HDL_OK
269}
270
271// ===== Module end =================================================
272func nx_gate_emit_end(s: *NxSynthSink) -> i64 {
273 nx_synth_emit_str(s, ".END\n" as *u8)
274 return NX_HDL_OK
275}
276
277// ===== Top-level emit (skeleton) =================================================
278//
279// Emits header + port + net sections. Per-RTL-module cell-emit
280// callbacks plug into the gap below; future commits add one
281// callback per existing rv64im_min_*.nx module.
282
283func nx_gate_emit_module_skeleton(s: *NxSynthSink, m: *NxHdlModule) -> i64 {
284 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND }
285 nx_gate_emit_header(s, m)
286 nx_gate_emit_nets(s, m)
287 // Logic-injection point: per-module emitters call nx_gate_emit_cell /
288 // nx_gate_emit_flop / nx_gate_emit_const between the header and
289 // the .END marker.
290 nx_synth_emit_str(s, "// ---- BEGIN logic injection ----\n" as *u8)
291 nx_synth_emit_str(s, "// TODO_SILICON: per-module gate emitter inserts here.\n" as *u8)
292 nx_synth_emit_str(s, "// nx_rv64im_alu -> case-statement -> mux tree -> ADD/SUB/etc cells\n" as *u8)
293 nx_synth_emit_str(s, "// nx_rv64im_decoder -> opcode-decode LUT -> per-kind one-hot\n" as *u8)
294 nx_synth_emit_str(s, "// nx_rv64im_regfile -> dual-read mux + write DFF bank\n" as *u8)
295 nx_synth_emit_str(s, "// nx_rv64im_csr -> 9-slot decode -> DFF bank\n" as *u8)
296 nx_synth_emit_str(s, "// nx_clint -> MTIME up-counter (DFF + ADD) + comparator\n" as *u8)
297 nx_synth_emit_str(s, "// nx_uart -> TX shift register + baud counter\n" as *u8)
298 nx_synth_emit_str(s, "// ---- END logic injection ----\n" as *u8)
299 nx_gate_emit_end(s)
300 return NX_HDL_OK
301}