code wiki / _hdl_build / synth_emit_alu_gates.nx
synth_emit_alu_gates.nx source
↩ module page · 136 lines · 5342 B
1// synth_emit_alu_gates.nx -- per-module ALU emitter for .nxgate.
2//
3// First per-RTL-module emitter in the SOVEREIGN canonical chain.
4// Pairs with synth_emit_alu.nx (Wheeler-only Verilog emit, commit
5// d43fbb4) but emits the .nxgate format from nishi_synth_gates.nx
6// (commit 04bca84) instead. Verilog version stays as a test
7// comparator; THIS is the path that ships.
8//
9// Strategy: 29-way cascade MUX over per-op sub-networks.
10//
11// For each op kind K in NX_RV64IM_ALU_*:
12// sub_K = result of K's compute (one cell each for ADD/SUB/...)
13// match_K = (op_in == K_constant)
14//
15// Cascade MUX (associative):
16// intermediate_0 = sub_INVALID (= 64'b0)
17// intermediate_K = mux(match_K, sub_K, intermediate_K-1)
18// result = intermediate_LAST
19//
20// Cell count: ~29 sub-networks + ~29 match constants + ~29 EQ cells
21// + ~29 MUX cells + small overhead = ~150 cells. Trivial for
22// nishi-synth to place on ECP5 BRAM-rich fabric (each ECP5 slice has
23// 4 LUT4 + 4 DFF; 150 cells fits in <50 slices).
24//
25// Status: SEED. 2026-05-26. Covers all 29 NX_RV64IM_ALU_* ops.
26// Multiply-high (mulh/mulhsu/mulhu) emits the cell but downstream
27// synth needs to implement the 64x64->128 multiplier (TODO_SILICON
28// in rv64im_min_alu.nx already noted).
29
30import "nx_syscalls.nx"
31import "nishi_hdl_primitives.nx"
32import "nishi_synth_gates.nx"
33import "rv64im_min_alu.nx"
34import "nx_alu_op_kind.nx"
35import "nx_alu_select.nx"
36
37// ===== Helper: allocate an internal wire net + return handle =================================================
38
39func nx_alu_alloc_wire(m: *NxHdlModule, width: i64) -> i64 {
40 return nx_hdl_wire(m, width)
41}
42
43// Allocate a CONST cell holding a small u8 value (for op-code match).
44func nx_alu_emit_const_u8(s: *NxSynthSink, m: *NxHdlModule, value: i64, cell_id: i64) -> i64 {
45 let n: i64 = nx_alu_alloc_wire(m, 8)
46 if n < 0 { return n }
47 nx_gate_emit_const(s, cell_id, 8, value)
48 return n
49}
50
51// Emit a "match this op-code" 1-bit signal: net_match = (op_in == K)
52func nx_alu_emit_op_match(s: *NxSynthSink, m: *NxHdlModule,
53 op_in: i64, op_const_net: i64, cell_id: i64) -> i64 {
54 let n: i64 = nx_alu_alloc_wire(m, 1)
55 if n < 0 { return n }
56 let fanin: *i64 = (sys_mmap(16)) as *i64
57 fanin[0] = op_in
58 fanin[1] = op_const_net
59 nx_gate_emit_cell(s, cell_id, NX_GATE_KIND_EQ, n, fanin, 2)
60 return n
61}
62
63// Emit a binary-op sub-network: result = OP(a, b), 64-bit.
64func nx_alu_emit_binop(s: *NxSynthSink, m: *NxHdlModule,
65 a_in: i64, b_in: i64, kind: i64, cell_id: i64) -> i64 {
66 let n: i64 = nx_alu_alloc_wire(m, 64)
67 if n < 0 { return n }
68 let fanin: *i64 = (sys_mmap(16)) as *i64
69 fanin[0] = a_in
70 fanin[1] = b_in
71 nx_gate_emit_cell(s, cell_id, kind, n, fanin, 2)
72 return n
73}
74
75// Emit a MUX in the cascade: result = match ? this_branch : prev
76func nx_alu_emit_mux_step(s: *NxSynthSink, m: *NxHdlModule,
77 match_net: i64, this_net: i64, prev_net: i64,
78 cell_id: i64) -> i64 {
79 let n: i64 = nx_alu_alloc_wire(m, 64)
80 if n < 0 { return n }
81 let fanin: *i64 = (sys_mmap(32)) as *i64
82 fanin[0] = match_net
83 fanin[1] = this_net
84 fanin[2] = prev_net
85 nx_gate_emit_cell(s, cell_id, NX_GATE_KIND_MUX, n, fanin, 3)
86 return n
87}
88
89// ===== Top-level emit =================================================
90//
91// Builds the entire ALU as a .nxgate cell graph + writes to sink.
92// Caller passes the NxHdlModule with ports already declared via
93// nx_rv64im_alu_build (commit 81b87e2).
94
95struct NxAluGateBuild {
96 cell_seq: i64 // next cell id
97 sub_nets: *i64 // per-op sub-network handles (size N)
98 match_nets: *i64 // per-op match-1 handles (size N)
99 cascade_handle: i64
100}
101
102func nx_emit_alu_gates(s: *NxSynthSink, m: *NxHdlModule,
103 ports: *NxRv64imAluPorts) -> i64 {
104 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND }
105
106 // Header first.
107 nx_gate_emit_header(s, m)
108
109 // Build the op-select logic via the ONE unified builder (nx_alu_build_select),
110 // TEXT sink -> the SAME code the gate-sim runs in MEM mode (one source, two
111 // sinks). use_divider=1 emits the PROVEN restoring divider for DIV/DIVU/REM/
112 // REMU instead of the ADD stub -- so the shipped .nxgate carries the verified
113 // divider. nx_hdl_wire allocates the internal nets in `m` DURING the build,
114 // so the .NET section MUST be emitted AFTER (cells buffered, then appended).
115 let cbuf: *u8 = sys_mmap(1048576)
116 let cs: *NxSynthSink = sys_mmap(64) as *NxSynthSink
117 nx_synth_sink_init(cs, cbuf, 1048576)
118 let k: *NxCellSink = sys_mmap(64) as *NxCellSink
119 nx_sink_init_text(k, cs, m, 0)
120 let result_net: i64 = nx_alu_build_select(k, ports.a_in, ports.b_in, ports.op_in, 1)
121
122 // Now every internal wire exists -> emit the .NET section, then the cells.
123 nx_gate_emit_nets(s, m)
124 nx_synth_emit_bytes(s, cs.buf, cs.used)
125
126 // Connect cascade output to the result port + end.
127 nx_synth_emit_str(s, "// final cascade output: " as *u8)
128 nx_gate_emit_net_ref(s, result_net)
129 nx_synth_emit_str(s, " -> " as *u8)
130 nx_gate_emit_net_ref(s, ports.result)
131 nx_synth_emit_str(s, "\n" as *u8)
132 nx_hdl_connect(m, ports.result, result_net)
133
134 nx_gate_emit_end(s)
135 return NX_HDL_OK
136}