code wiki / _hdl_build / nx_recip_synth.nx

nx_recip_synth.nx source

↩ module page · 166 lines · 9043 B

1// nx_recip_synth.nx -- the MULTIPLICATIVE (Newton-Raphson reciprocal) divider 2// instantiated as an NxGsim GATE-NETWORK (the gate-network instantiation of the 3// algorithm proven scalar in nx_alu_divider_newton.nx, maxcorr=1). 4// 5// This is the "first provable rung" (W=16): the SAME Newton math as the scalar 6// proof, emitted as a pure combinational word-level netlist using ONLY the 7// already-proven cell builders div_const / div_op2 / div_mux (nx_alu_divider.nx) 8// over the gate-sim's MUL/SHL/SHR/SUB/ADD/LTU/XOR/AND/OR/MUX/CONST cells. 9// 10// WHY W=16 is the smallest GATE-NETWORK-provable variant: 11// At W=16 every intermediate fits i64 with a PLAIN depth-1 MUL cell, so NO 12// nx_mul_wide (128-bit organ) is needed -- verified bit-widths (D<=2^16): 13// dn <= 2^16 (17b) dn*x = dx <= 2^33 (34b) 14// 2F^2-dx = t <= 2^33 (34b) x*t <= 2^49 (49b) 15// N*x <= 2^33 (34b) 16// all <= 49 bits, so a single MUL cell (low 64 bits, no wrap) is EXACT here. 17// The new code is therefore just the gate-emitter wrapper around 100%-proven 18// math + proven cells -- no new unproven gate organ is exercised. The 19// nx_mul_wide-dependent rungs (W>=24, x*t=73b) are the next milestone. 20// 21// data-dependent normalization (msb_pos / barrel-shift) is BUILT IN GATES here: 22// - msb_synth : priority encoder over a runtime net (cascade of LTU/MUX), 23// returns p with 2^p <= D < 2^(p+1) for D in [1, 2^16). 24// - barrel SHL/SHR by a *net* count uses the sim's SHL/SHR cells directly 25// (the cell takes b = the runtime shift-count net), so normalization 26// Dn = D<<s and extraction (N*x)>>(17+p) are pure gates. 27// 28// The correction tail is UNROLLED to a fixed KCORR (data-INDEPENDENT, so the 29// latency stays O(iters)+O(1)); the scalar proof showed maxcorr=1, so KCORR=2 30// is a safe constant. license_tier: ORIGINAL 31// 32// HONEST-METRIC NOTE (do not fool ourselves): nx_nxgate_sim assigns depth-1 to 33// every word-level cell, so a MUL cell looks as cheap as an AND. This proves 34// FUNCTIONAL 1:1 correctness of the gate-network, and the iteration COUNT 35// (3 vs W=16 restoring stages) is a real cell-count/sequential-step win -- but 36// NOT a true critical-path latency exceed until a real gate-delay model exists. 37 38import "nx_mul_wide.nx" 39 40const RS_W: i64 = 16 41const RS_F: i64 = 65536 // 2^16 fixed-point scale (Q16) 42const RS_FF2: i64 = 8589934592 // 2 * F * F = 2^33 43const RS_ITERS: i64 = 3 // matches the proven scalar NW_ITERS 44const RS_KCORR: i64 = 2 // unrolled correction stages (proven maxcorr=1) 45 46// ----- msb_synth: priority encoder as gates ---------------------------------- 47// returns a NET whose value is p with 2^p <= D < 2^(p+1), for D in [1, 2^RS_W). 48// Built as a cascade: p = sum over k in 1..W-1 of (D >= 2^k ? 1 : 0) 49// ge_k = !(D <u 2^k) = (D <u 2^k) XOR 1 50// p = p + ge_k 51// For D=0 this returns 0 (matches the scalar msb_pos(0)=0 fall-through; the 52// caller's correction tail + the test's D>=1 keep this path unexercised, but it 53// is defined, not undefined). Topological order is preserved (each cell's fanins 54// are already-built nets). 55func msb_synth(g: *NxGsim, nd: i64) -> i64 { 56 let c1: i64 = div_const(g, 1) 57 var p: i64 = div_const(g, 0) 58 var k: i64 = 1 59 while k < RS_W { 60 let pow: i64 = div_const(g, 1 << k) // 2^k 61 let lt: i64 = div_op2(g, NX_GATE_KIND_LTU, nd, pow) // D <u 2^k 62 let ge: i64 = div_op2(g, NX_GATE_KIND_XOR, lt, c1) // D >= 2^k ? 1 : 0 63 p = div_op2(g, NX_GATE_KIND_ADD, p, ge) // accumulate 64 k = k + 1 65 } 66 return p 67} 68 69// ----- recip_const_div17: exact integer floor(num/17) as a gate-network ------- 70// 17 is a compile-time constant, so this is a fixed restoring divide-by-17 71// network (no divider-in-the-divider). num <= 48*65536 = 3145728 < 2^22, so 22 72// MSB-first restoring stages give the exact floor. Pure SHL/SHR/AND/OR/LTU/XOR/ 73// SUB/MUX/CONST -- the same proven primitives as nx_alu_divider's restoring core. 74func recip_const_div17(g: *NxGsim, num: i64) -> i64 { 75 let c0: i64 = div_const(g, 0) 76 let c1: i64 = div_const(g, 1) 77 let c17: i64 = div_const(g, 17) 78 var rem: i64 = c0 79 var quo: i64 = c0 80 var i: i64 = 22 - 1 81 while i >= 0 { 82 let ci: i64 = div_const(g, i) 83 let sh: i64 = div_op2(g, NX_GATE_KIND_SHL, rem, c1) // rem << 1 84 let absh: i64 = div_op2(g, NX_GATE_KIND_SHR, num, ci) // num >> i 85 let abit: i64 = div_op2(g, NX_GATE_KIND_AND, absh, c1) // (num>>i)&1 86 let remin: i64 = div_op2(g, NX_GATE_KIND_OR, sh, abit) // (rem<<1)|bit 87 let lt: i64 = div_op2(g, NX_GATE_KIND_LTU, remin, c17) // remin <u 17 88 let ge: i64 = div_op2(g, NX_GATE_KIND_XOR, lt, c1) // !lt 89 let sub: i64 = div_op2(g, NX_GATE_KIND_SUB, remin, c17) // remin-17 90 let remn: i64 = div_mux(g, ge, sub, remin) 91 let qbit: i64 = div_op2(g, NX_GATE_KIND_SHL, ge, ci) // ge<<i 92 let quon: i64 = div_op2(g, NX_GATE_KIND_OR, quo, qbit) 93 rem = remn 94 quo = quon 95 i = i - 1 96 } 97 return quo 98} 99 100// ----- nx_recip_div_synth: Newton reciprocal divider as a gate-network -------- 101// Builds the network for nets na (dividend) / nb (divisor) at W=16. 102// Writes the remainder net id to rem_out[0]; returns the quotient net id. 103// Mirrors nw_div() in nx_alu_divider_newton.nx exactly, op for op. 104func nx_recip_div_synth(g: *NxGsim, na: i64, nb: i64, rem_out: *i64) -> i64 { 105 let c1: i64 = div_const(g, 1) 106 let cF48: i64 = div_const(g, 48 * RS_F) // 48F (compile-folded const) 107 let c32: i64 = div_const(g, 32) 108 let c17: i64 = div_const(g, 17) 109 let cFF2: i64 = div_const(g, RS_FF2) // 2F^2 = 2^33 110 let cWm1: i64 = div_const(g, RS_W - 1) // W-1 = 15 111 let cWp1: i64 = div_const(g, RS_W + 1) // W+1 = 17 112 113 // --- normalize: p=msb(D), s=(W-1)-p, dn=D<<s (dn in [F/2,F)) --- 114 let p: i64 = msb_synth(g, nb) 115 let s: i64 = div_op2(g, NX_GATE_KIND_SUB, cWm1, p) // s = 15 - p 116 let dn: i64 = div_op2(g, NX_GATE_KIND_SHL, nb, s) // Dn = D << s 117 118 // --- minimax linear seed x0 = (48F - 32*Dn)/17 (Q16) --- 119 // The scalar uses /17, a CONSTANT divide. There is no DIV cell kind by design, 120 // and 1/17 ~ 3855/2^16 truncation perturbs the 4-bit seed enough that the W=16 121 // Newton+KCORR budget can break on some divisors -- so we synthesize the EXACT 122 // integer floor(num/17) as a tiny restoring divide-by-constant network 123 // (recip_const_div17, defined above; 17 is a compile-time constant => fixed gates). 124 let t32: i64 = div_op2(g, NX_GATE_KIND_MUL, c32, dn) // 32*Dn (<= 2^21) 125 let num: i64 = div_op2(g, NX_GATE_KIND_SUB, cF48, t32) // 48F - 32Dn 126 var x: i64 = recip_const_div17(g, num) // EXACT floor(num/17) 127 128 // --- Newton: x = (x*(2F^2 - Dn*x)) >> 32, RS_ITERS times --- 129 var it: i64 = 0 130 while it < RS_ITERS { 131 let dx: i64 = div_op2(g, NX_GATE_KIND_MUL, dn, x) // Dn*x (<= 2^33) 132 let t: i64 = div_op2(g, NX_GATE_KIND_SUB, cFF2, dx) // 2F^2 - Dn*x 133 let xt: i64 = div_op2(g, NX_GATE_KIND_MUL, x, t) // x*t (<= 2^49) 134 x = div_op2(g, NX_GATE_KIND_SHR, xt, c32) // >> 32 135 it = it + 1 136 } 137 138 // --- quotient extraction: q = (N*x) >> (17 + p) --- 139 let nx: i64 = div_op2(g, NX_GATE_KIND_MUL, na, x) // N*x (<= 2^33) 140 let amt: i64 = div_op2(g, NX_GATE_KIND_ADD, cWp1, p) // 17 + p 141 var q: i64 = div_op2(g, NX_GATE_KIND_SHR, nx, amt) // >> (17+p) 142 143 // --- UNROLLED O(1) correction (KCORR fixed stages; proven maxcorr=1) --- 144 // each stage: if (q+1)*D <= N -> q+1 ; else if q*D > N -> q-1 ; else q 145 var kc: i64 = 0 146 while kc < RS_KCORR { 147 let qp1: i64 = div_op2(g, NX_GATE_KIND_ADD, q, c1) // q+1 148 let qp1D: i64 = div_op2(g, NX_GATE_KIND_MUL, qp1, nb) // (q+1)*D 149 // (q+1)*D <= N <=> !(N <u (q+1)*D) 150 let nlt: i64 = div_op2(g, NX_GATE_KIND_LTU, na, qp1D) // N <u (q+1)*D 151 let le: i64 = div_op2(g, NX_GATE_KIND_XOR, nlt, c1) // (q+1)*D <= N ? 1 : 0 152 let qD: i64 = div_op2(g, NX_GATE_KIND_MUL, q, nb) // q*D 153 let high: i64 = div_op2(g, NX_GATE_KIND_LTU, na, qD) // q*D > N (N <u q*D) 154 let qup: i64 = div_op2(g, NX_GATE_KIND_ADD, q, le) // q + le 155 let qdn: i64 = div_op2(g, NX_GATE_KIND_SUB, q, high) // q - high 156 // le and high are mutually exclusive, so order is safe: 157 q = div_mux(g, le, qup, div_mux(g, high, qdn, q)) 158 kc = kc + 1 159 } 160 161 // --- remainder: r = N - q*D --- 162 let qDf: i64 = div_op2(g, NX_GATE_KIND_MUL, q, nb) 163 let r: i64 = div_op2(g, NX_GATE_KIND_SUB, na, qDf) 164 rem_out[0] = r 165 return q 166}