code wiki / _hdl_build / nx_alu_divider_newton.nx

nx_alu_divider_newton.nx source

↩ module page · 160 lines · 6472 B

1// nx_alu_divider_newton.nx -- MULTIPLICATIVE (Newton-Raphson reciprocal) divider: 2// the ~log(W) latency FRONTIER the radix-2/radix-4 dividers are LAPPED by (~5x). 3// 4// q = floor(N/D) via a fixed-point reciprocal R ~ F/D (F = 2^k) refined by 5// Newton's iteration x_{i+1} = x_i*(2F - D*x_i) / F , which DOUBLES the correct 6// bits per step (quadratic convergence) -> ceil(log2 W) iterations, vs W (radix-2) 7// or W/2 (radix-4) compare-subtract stages. Each iteration is 2 multiplies, so 8// full-width (64b) it composes the TRIANGULATED nx_mul_wide (each product needs 9// >64 bits -- which is exactly why the wide multiplier was the prerequisite). 10// 11// This module PROVES the math + the log(W) iteration count at a provable width 12// (W=16, products fit i64), TRIANGULATED by three independent methods that must 13// agree: (A) Newton, (B) an independent restoring shift-subtract divider, (C) the 14// i64 oracle `/`. It also measures the post-Newton correction so the latency is 15// honestly O(iterations)+O(1), not a hidden long loop. license_tier: ORIGINAL 16 17import "nx_syscalls.nx" 18const NW_MAGIC_8191: i64 = 8191 19const NW_MAGIC_2463534242: i64 = 2463534242 20const NW_MAGIC_200000: i64 = 200000 21const NW_MAGIC_65535: i64 = 65535 22const NW_MAGIC_32768: i64 = 32768 23const NW_MAGIC_12345: i64 = 12345 24 25const NW_W: i64 = 16 26const NW_F: i64 = 65536 // 2^16 fixed-point scale (Q16) 27const NW_FF: i64 = 4294967296 // F*F = 2^32 28const NW_ITERS: i64 = 3 // linear seed (~4 bits) doubled 3x -> ~32 bits >> 16 29const NW_LCG_A: i64 = 6364136223846793005 30const NW_LCG_C: i64 = 1442695040888963407 31 32func _emit_num(v: i64) -> i64 { 33 let b: *u8 = sys_mmap(28); var n: i64 = v; if n < 0 { n = 0 - n } 34 let t2: *u8 = sys_mmap(28); var t: i64 = 0 35 if n == 0 { t2[0] = 48; t = 1 } 36 while n > 0 { t2[t] = 48 + (n % 10); n = n / 10; t = t + 1 } 37 var i: i64 = 0; while i < t { b[i] = t2[t - 1 - i]; i = i + 1 } 38 b[t] = 32; sys_write(1, b, t + 1); return 0 39} 40func _nl() -> i64 { let z: *u8 = sys_mmap(2); z[0] = 10; sys_write(1, z, 1); return 0 } 41 42// MSB position p such that 2^p <= d < 2^(p+1) (d >= 1) 43func msb_pos(d: i64) -> i64 { 44 var p: i64 = 0 45 while (1 << (p + 1)) <= d { p = p + 1 } 46 return p 47} 48 49// LEG A -- Newton reciprocal divide. Returns floor(N/D); writes #correction 50// steps to corr_out[0] so the caller can prove the tail is O(1). 51// 52// Normalize D into [F/2, F) (divisor d in [0.5,1)); seed the reciprocal with the 53// minimax linear approximation x0 = (48F - 32*Dn)/17 (|rel err| <= 1/17 ~ 4 bits) 54// so the quadratic e_{i+1}=e_i^2 needs only ceil(log2(W/4)) iterations -- the 55// log(W) frontier -- instead of ~W for a 1-bit seed. All Q16; products held in 56// F^2 scale (each < 2^50, fits i64 at W=16; the 64b gate version uses nx_mul_wide). 57func nw_div(N: i64, D: i64, corr_out: *i64) -> i64 { 58 let p: i64 = msb_pos(D) 59 let s: i64 = 15 - p // normalize: MSB of Dn at bit 15 60 let dn: i64 = D << s // Dn in [2^15, 2^16) = [F/2, F) 61 var x: i64 = (48 * NW_F - 32 * dn) / 17 // minimax linear seed (Q16), reciprocal of dn 62 var it: i64 = 0 63 while it < NW_ITERS { 64 let dx: i64 = dn * x // F^2 scale, <= 2^33 65 let t: i64 = 2 * NW_FF - dx // 2F^2 - Dn*x, <= 2^33 66 x = (x * t) >> 32 // x in (F,2F]; x*t <= 2^50, fits i64 67 it = it + 1 68 } 69 // 1/D = 2^s * x / F^2 -> q = (N*x) >> (32 - s) = (N*x) >> (17+p) 70 var q: i64 = (N * x) >> (17 + p) // N*x <= 2^33, fits i64 71 var corr: i64 = 0 72 while (q + 1) * D <= N { q = q + 1; corr = corr + 1 } // bump up if low 73 while q * D > N { q = q - 1; corr = corr + 1 } // trim down if high 74 corr_out[0] = corr 75 return q 76} 77 78// LEG B -- independent restoring (shift-subtract) divider, W=16. 79func nw_restoring(N: i64, D: i64) -> i64 { 80 var q: i64 = 0 81 var r: i64 = 0 82 var i: i64 = NW_W - 1 83 while i >= 0 { 84 r = (r << 1) | ((N >> i) & 1) 85 if r >= D { r = r - D; q = q | (1 << i) } 86 i = i - 1 87 } 88 return q 89} 90 91func main() -> i64 { 92 var total: i64 = 0 93 var ok: i64 = 0 94 var maxcorr: i64 = 0 95 let co: *i64 = sys_mmap(8) as *i64 96 97 // dense exhaustive: D in [1,256], N in [0,8191] -- all 3 legs must agree 98 var D: i64 = 1 99 while D <= 256 { 100 var N: i64 = 0 101 while N <= NW_MAGIC_8191 { 102 co[0] = 0 103 let a: i64 = nw_div(N, D, co) // LEG A 104 let b: i64 = nw_restoring(N, D) // LEG B 105 let c: i64 = N / D // LEG C (oracle) 106 total = total + 1 107 if a == b { if a == c { ok = ok + 1 } } 108 if co[0] > maxcorr { maxcorr = co[0] } 109 N = N + 1 110 } 111 D = D + 1 112 } 113 114 // random full-range W=16: N,D in [1,65535] 115 var seed: i64 = NW_MAGIC_2463534242 116 var nr: i64 = 0 117 while nr < NW_MAGIC_200000 { 118 seed = seed * NW_LCG_A + NW_LCG_C; var N: i64 = seed & NW_MAGIC_65535 119 seed = seed * NW_LCG_A + NW_LCG_C; var D: i64 = (seed & NW_MAGIC_65535) 120 if D == 0 { D = 1 } 121 co[0] = 0 122 let a: i64 = nw_div(N, D, co) 123 let b: i64 = nw_restoring(N, D) 124 let c: i64 = N / D 125 total = total + 1 126 if a == b { if a == c { ok = ok + 1 } } 127 if co[0] > maxcorr { maxcorr = co[0] } 128 nr = nr + 1 129 } 130 131 // edges 132 let en: *i64 = sys_mmap(8 * 8) as *i64 133 let ed: *i64 = sys_mmap(8 * 8) as *i64 134 en[0]=NW_MAGIC_65535; ed[0]=1 135 en[1]=NW_MAGIC_65535; ed[1]=NW_MAGIC_65535 136 en[2]=NW_MAGIC_65535; ed[2]=NW_MAGIC_32768 137 en[3]=0; ed[3]=NW_MAGIC_65535 138 en[4]=1; ed[4]=1 139 en[5]=NW_MAGIC_65535; ed[5]=2 140 en[6]=NW_MAGIC_12345; ed[6]=255 141 en[7]=NW_MAGIC_65535; ed[7]=257 142 var e: i64 = 0 143 while e < 8 { 144 co[0] = 0 145 let a: i64 = nw_div(en[e], ed[e], co) 146 let b: i64 = nw_restoring(en[e], ed[e]) 147 let c: i64 = en[e] / ed[e] 148 total = total + 1 149 if a == b { if a == c { ok = ok + 1 } } 150 if co[0] > maxcorr { maxcorr = co[0] } 151 e = e + 1 152 } 153 154 _emit_num(ok); _emit_num(total); _emit_num(NW_ITERS); _emit_num(maxcorr); _nl() 155 // FAIL LOUD: all 3 legs agree everywhere, AND the correction tail is O(1) 156 // (small constant) so latency is honestly ~log(W) iterations + a constant. 157 if ok != total { sys_exit(1); return 1 } 158 if maxcorr > 4 { sys_exit(2); return 2 } // tail must stay a small constant 159 sys_exit(0); return 0 160}