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}