code wiki / (root) / nishi_synth_gates.nx

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}