code wiki / _hdl_build / nx_latency_metric_test.nx
nx_latency_metric_test.nx source
↩ module page · 206 lines · 11818 B
1// nx_latency_metric_test.nx -- proves the HONEST latency-metric organ and uses it
2// to race three divider TOPOLOGIES at W=16: radix-2 (W stages), radix-4 (W/2
3// stages), and the scalar-equivalent Newton-Raphson reciprocal structure
4// (~log W iterations of multiply-heavy work).
5//
6// WHAT THIS PROVES (FAIL LOUD on every leg):
7// (1) The organ's UNIT metric matches the functional sim's implicit depth-1
8// model: on a tiny hand-built chain whose longest path is known by hand,
9// nx_lat_unit returns exactly that hand-counted op-depth.
10// (2) The HONEST metric does NOT treat a multiplier as depth-1: a single MUL
11// cell costs 2*ceil(log2 W) gate levels, an ADD costs ceil(log2 W), a
12// boolean costs 1 -- asserted directly against nx_lat_kind_weight, and
13// asserted on a one-MUL chain whose honest depth > its unit depth.
14// (3) THE RACE, told HONESTLY across WIDTH -- the trap, and where it flips. The
15// SAME Newton topology is measured with a DEEP multiplier (the real
16// nx_mul_wide 32-bit-limb schoolbook) and a SHALLOW one (single-cell Wallace
17// model, 2*logW), at W=16 and W=64. The MEASURED honest critical paths are:
18// W=16: radix-2=181 radix-4=139 Newton-deep=363 Newton-shallow=166
19// W=64: radix-2=967 radix-4=743 Newton-deep=709 Newton-shallow=320
20// So, honestly:
21// - At SMALL width (W=16) the multiplicative method does NOT pay off:
22// radix-4 wins; the DEEP-multiplier Newton is the WORST (363); and even
23// the SHALLOW-multiplier Newton (166) still loses to radix-4 (139). The
24// naive "log(W) multiplies beats W subtracts" LATENCY claim is FALSE here.
25// - At LARGE width (W=64) the log(W) FRONTIER appears: BOTH Newton variants
26// beat BOTH radix dividers (320 and 709 vs 743 and 967), because the radix
27// dividers' stage count grows linearly in W while Newton's iteration count
28// grows only ~log W. Ordering: shallow-Newton < deep-Newton < r4 < r2.
29// - THE CROSSOVER is the headline: the SAME shallow-multiplier Newton goes
30// from LOSING to radix-4 at W=16 to BEATING it at W=64. That measured sign-
31// flip IS the multiplicative-divider latency win -- and the honest metric
32// refuses to award it at a width where it is not real.
33// Invariant independent of width + multiplier: radix-4 (W/2 stages) always
34// beats radix-2 (W stages). All orderings are ASSERTED, not assumed.
35//
36// Known-answer lines (printed for the gate): four honest critical paths at W=16,
37// then at W=64, then the kind-weight spot-checks. exit 0 iff all asserts hold.
38//
39// HONESTY SCOPE: the Newton network here is the algorithm's STRUCTURAL skeleton
40// (the exact operator dependency chain of nx_alu_divider_newton.nx: normalize ->
41// minimax linear seed -> ITERS x (dn*x ; 2F^2-dn*x ; x*t ; >>2W) -> N*x ; >>shift
42// -> O(1) correction), built from the SAME proven cell-builders + the proven
43// nx_mul_wide_synth, so its CRITICAL PATH is the honest scalar-equivalent of the
44// Newton datapath. It is a LATENCY measurement of the topology, not a re-proof of
45// Newton's arithmetic (that is nx_alu_divider_newton's job + the gate-divider
46// milestone). The latency metric is the gate-LEVEL logic-depth model documented in
47// nx_latency_metric.nx -- relative operator depth sized by W, not silicon ns.
48
49import "nx_latency_metric.nx"
50import "nx_alu_divider_r4.nx" // pulls in nx_alu_divider (radix-2) + the sim
51import "nx_mul_wide.nx" // proven 64x64->128 multiplier, for the Newton products
52import "nx_newton_struct.nx" // the shared Newton structure (DRY: also used by nx_div_pick)
53
54func _emit_num(v: i64) -> i64 {
55 let b: *u8 = sys_mmap(28); var n: i64 = v; if n < 0 { n = 0 - n }
56 let t2: *u8 = sys_mmap(28); var t: i64 = 0
57 if n == 0 { t2[0] = 48; t = 1 }
58 while n > 0 { t2[t] = 48 + (n % 10); n = n / 10; t = t + 1 }
59 var i: i64 = 0; while i < t { b[i] = t2[t - 1 - i]; i = i + 1 }
60 b[t] = 32; sys_write(1, b, t + 1); return 0
61}
62func _nl() -> i64 { let z: *u8 = sys_mmap(2); z[0] = 10; sys_write(1, z, 1); return 0 }
63
64// Build a fresh, empty NxGsim with `ninputs` primary-input nets. Generous caps.
65func _mk_gsim(ninputs: i64) -> *NxGsim {
66 let vals: *i64 = sys_mmap(4096 * 8) as *i64
67 let cells: *NxGsimCell = sys_mmap(4096 * 48) as *NxGsimCell
68 let g: *NxGsim = sys_mmap(64) as *NxGsim
69 g.vals = vals; g.n_nets = ninputs; g.cells = cells; g.n_cells = 0
70 return g
71}
72
73// allocate a per-net depth scratch buffer sized for the biggest network we build.
74func _mk_depth() -> *i64 { return sys_mmap(8192 * 8) as *i64 }
75
76// Measure the four honest critical-path depths at width W (Newton uses `iters`
77// iterations + KCORR=2). Writes [hr2, hr4, hnewton_deepmul, hnewton_shallowmul]
78// into out[0..3]. Builds fresh networks each call (no cross-contamination).
79func _measure(W: i64, iters: i64, depth: *i64, out: *i64) -> i64 {
80 let ro: *i64 = sys_mmap(8) as *i64
81
82 let g2: *NxGsim = _mk_gsim(2)
83 ro[0] = 0
84 let q2: i64 = nx_div_synth(g2, 0, 1, W, ro) // radix-2 (W stages)
85 out[0] = nx_lat_honest(g2, depth, W)
86
87 let g4: *NxGsim = _mk_gsim(2)
88 ro[0] = 0
89 let q4: i64 = nx_div_synth_r4(g4, 0, 1, W, ro) // radix-4 (W/2 stages)
90 out[1] = nx_lat_honest(g4, depth, W)
91
92 let gnd: *NxGsim = _mk_gsim(2) // Newton, DEEP multiplier
93 let qnd: i64 = nx_newton_struct(gnd, 0, 1, W, iters, 2, 1)
94 out[2] = nx_lat_honest(gnd, depth, W)
95
96 let gns: *NxGsim = _mk_gsim(2) // Newton, SHALLOW multiplier
97 let qns: i64 = nx_newton_struct(gns, 0, 1, W, iters, 2, 0)
98 out[3] = nx_lat_honest(gns, depth, W)
99 return 0
100}
101
102func main() -> i64 {
103 let W: i64 = 16
104 let depth: *i64 = _mk_depth()
105
106 // ===== (1) UNIT metric == hand-counted depth on a known tiny chain ==========
107 // chain: in0 -AND-> n2 -ADD-> n3 -XOR-> n4 ; longest path = 3 non-const cells.
108 let gt: *NxGsim = _mk_gsim(2)
109 let h2: i64 = div_op2(gt, NX_GATE_KIND_AND, 0, 1) // net2
110 let h3: i64 = div_op2(gt, NX_GATE_KIND_ADD, h2, 1) // net3
111 let h4: i64 = div_op2(gt, NX_GATE_KIND_XOR, h3, 1) // net4
112 let u_tiny: i64 = nx_lat_unit(gt, depth)
113 // honest depth of the SAME chain: AND(1) + ADD(logW=4) + XOR(1) = 6
114 let h_tiny: i64 = nx_lat_honest(gt, depth, W)
115
116 // ===== (2) kind-weight spot checks (HONEST != depth-1 for a multiplier) =====
117 let lg: i64 = nx_lat_log2_ceil(W) // 4 at W=16
118 let w_and: i64 = nx_lat_kind_weight(NX_GATE_KIND_AND, W) // 1
119 let w_add: i64 = nx_lat_kind_weight(NX_GATE_KIND_ADD, W) // lg = 4
120 let w_mul: i64 = nx_lat_kind_weight(NX_GATE_KIND_MUL, W) // 2*lg = 8
121 let w_con: i64 = nx_lat_kind_weight(NX_GATE_KIND_CONST, W) // 0
122
123 // ===== THE RACE, swept across width to find the honest crossover ============
124 // Small width: a divider's W (or W/2) stages are few, so the Newton structure's
125 // fixed serial overhead (seed + iters + correction tail, all multiply-bearing)
126 // does NOT pay off. Large width: the radix dividers' stage count grows linearly
127 // in W while Newton's iteration count grows only ~log W, so Newton wins. The
128 // honest metric must show BOTH regimes. Newton iters per the proven scaling
129 // (ceil(log2(W/4))+1): W=16 -> 3, W=64 -> 5.
130 let s16: *i64 = sys_mmap(4 * 8) as *i64
131 let s64: *i64 = sys_mmap(4 * 8) as *i64
132 _measure(16, 3, depth, s16)
133 _measure(64, 5, depth, s64)
134
135 _emit_num(s16[0]); _emit_num(s16[1]); _emit_num(s16[2]); _emit_num(s16[3]); _nl()
136 _emit_num(s64[0]); _emit_num(s64[1]); _emit_num(s64[2]); _emit_num(s64[3]); _nl()
137 _emit_num(u_tiny); _emit_num(h_tiny); _emit_num(lg)
138 _emit_num(w_and); _emit_num(w_add); _emit_num(w_mul); _emit_num(w_con); _nl()
139
140 // ===== ASSERTIONS (FAIL LOUD) ===============================================
141 // (1) UNIT metric == hand-counted op-depth (the depth-1 model the sim uses)
142 if u_tiny != 3 { sys_exit(1); return 1 }
143 // (2) HONEST depth of that SAME chain: AND(1) + ADD(logW=4) + XOR(1) = 6 > 3.
144 // Same topology, bigger honest number -> the depth-1 model UNDER-counts latency.
145 if h_tiny != 6 { sys_exit(2); return 2 }
146 if h_tiny <= u_tiny { sys_exit(3); return 3 }
147
148 // (2) HONEST weights: a MUL is NOT depth-1, it is the deepest operator
149 if lg != 4 { sys_exit(4); return 4 }
150 if w_and != 1 { sys_exit(5); return 5 }
151 if w_add != 4 { sys_exit(6); return 6 }
152 if w_mul != 8 { sys_exit(7); return 7 } // 2*log2(16) -- the trap, refuted
153 if w_con != 0 { sys_exit(8); return 8 }
154 if w_mul <= w_add { sys_exit(9); return 9 } // multiplier strictly deeper than adder
155 if w_add <= w_and { sys_exit(10); return 10 } // adder strictly deeper than a boolean
156
157 // sanity: every measured depth is positive (networks were actually built)
158 var ii: i64 = 0
159 while ii < 4 {
160 if s16[ii] <= 0 { sys_exit(11); return 11 }
161 if s64[ii] <= 0 { sys_exit(12); return 12 }
162 ii = ii + 1
163 }
164
165 // Invariant at EVERY width, independent of multiplier model: radix-4 (W/2
166 // stages) strictly beats radix-2 (W stages) on the honest critical path.
167 if s16[1] >= s16[0] { sys_exit(13); return 13 }
168 if s64[1] >= s64[0] { sys_exit(14); return 14 }
169
170 // Multiplier depth ALWAYS matters: the shallow (single-cell Wallace) Newton is
171 // strictly cheaper than the deep (limb-schoolbook) Newton at both widths -- the
172 // SAME topology, differing ONLY in the multiplier model. If these were equal,
173 // the metric would be blind to the one thing that decides the race.
174 if s16[3] >= s16[2] { sys_exit(15); return 15 }
175 if s64[3] >= s64[2] { sys_exit(16); return 16 }
176
177 // ---- SMALL WIDTH (W=16): log(W) does NOT pay off; radix-4 is the winner. ----
178 // The Newton structure's fixed serial overhead (seed + 3 iters + extract + 2
179 // correction stages, every one bearing a deep MUL) is too much when the divider
180 // has only 8 (radix-4) / 16 (radix-2) stages.
181 // (a) DEEP-multiplier Newton is the WORST of the three -- the naive "log(W)
182 // multiplies beats W subtracts" LATENCY claim is FALSE here. Assert it.
183 if s16[2] <= s16[0] { sys_exit(17); return 17 } // deep Newton worse than radix-2
184 if s16[2] <= s16[1] { sys_exit(18); return 18 } // deep Newton worse than radix-4
185 // (b) even the SHALLOW-multiplier Newton LOSES to radix-4 at W=16.
186 if s16[3] <= s16[1] { sys_exit(19); return 19 } // shallow Newton loses to radix-4
187
188 // ---- LARGE WIDTH (W=64): the log(W) FRONTIER appears -- Newton WINS. --------
189 // radix-2 has 64 stages, radix-4 has 32, while Newton still has only ~5
190 // iterations, so the multiplicative method crosses over. The honest ordering is
191 // shallow-Newton < deep-Newton < radix-4 < radix-2.
192 if s64[3] >= s64[1] { sys_exit(20); return 20 } // shallow Newton beats radix-4
193 if s64[3] >= s64[0] { sys_exit(21); return 21 } // ... and radix-2
194 if s64[2] >= s64[1] { sys_exit(22); return 22 } // EVEN the DEEP Newton beats radix-4 @64
195 if s64[2] >= s64[0] { sys_exit(23); return 23 } // ... and radix-2
196
197 // ---- THE CROSSOVER is the headline (the real log(W) frontier) --------------
198 // The SAME shallow-multiplier Newton topology goes from LOSING to radix-4 at
199 // W=16 to BEATING it at W=64. That sign-flip, measured on the honest metric, IS
200 // the multiplicative-divider latency win -- and the metric refuses to award it
201 // at a width where it is not real. Re-assert the flip explicitly.
202 if s16[3] <= s16[1] { sys_exit(24); return 24 } // @16: Newton ABOVE radix-4 (loses)
203 if s64[3] >= s64[1] { sys_exit(25); return 25 } // @64: Newton BELOW radix-4 (wins)
204
205 sys_exit(0); return 0
206}