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}