code wiki / _hdl_build / rv64im_min_multi_tier_roi.nx
rv64im_min_multi_tier_roi.nx source
↩ module page · 255 lines · 10149 B
1// rv64im_min_multi_tier_roi.nx -- per-tier ROI matrix + ranker.
2//
3// Extends rv64im_min_hot_report.nx (commit 772abfb) from single-
4// workload ROI to multi-tier ROI per the WORKLOAD_TARGETS.md +
5// hardened-field-not-just-AI cardinal.
6//
7// Input: one NxFuncCycles report per workload tier (W0/W1/W2)
8// Output: ranked list of silicon candidates with per-tier ROI
9// vectors + min-across-tiers + weighted-sum aggregate
10//
11// The killer constraint per WORKLOAD_TARGETS.md:
12// "Sovereign silicon needs to serve the whole workload set, not
13// over-fit one. Avoids the trap of adding a 7000-gate vector
14// MAC unit because it triples GEMM throughput, but doubles
15// silicon cost for the sprinkler controller that never uses it."
16//
17// So the ranker offers TWO views:
18// min_across_tiers -- pick the candidate whose worst tier still
19// has positive ROI. Conservative; ensures
20// no tier is starved.
21// weighted_sum -- pick the candidate whose operator-weighted
22// ROI sum is highest. Lets the operator
23// tune ("W0 matters 4x more than W2 because
24// field deployments outnumber data center
25// units 1000-to-1").
26//
27// Status: SEED. 2026-05-26. Multi-tier matrix builder + dual
28// rankers. Future commits add temporal weighting (recency of
29// measurement) + confidence intervals (sim-vs-silicon delta).
30
31import "nx_syscalls.nx"
32import "nishi_hdl_primitives.nx"
33import "rv64im_min_sim.nx"
34import "rv64im_min_symtab.nx"
35import "rv64im_min_hot_report.nx"
36
37// ===== Tier kinds (sealed enum) =================================================
38//
39// Matches WORKLOAD_TARGETS.md. W3 (server) is out of scope for
40// current decade per the doc.
41
42const NX_TIER_W0: i64 = 0 // hardened field embedded (sprinkler/sensor)
43const NX_TIER_W1: i64 = 1 // automotive sensor / leak detection / DSP
44const NX_TIER_W2: i64 = 2 // edge AI inference
45const NX_TIER_N: i64 = 3
46
47func nx_tier_is_valid(t: i64) -> i64 {
48 if t < 0 { return 0 }
49 if t >= NX_TIER_N { return 0 }
50 return 1
51}
52
53func nx_tier_name(t: i64) -> *u8 {
54 if t == NX_TIER_W0 { return "W0/embedded" as *u8 }
55 if t == NX_TIER_W1 { return "W1/sensor" as *u8 }
56 if t == NX_TIER_W2 { return "W2/edge-ai" as *u8 }
57 return "unknown" as *u8
58}
59
60// ===== Per-tier ROI vector =================================================
61//
62// One slot per silicon-candidate kind (NX_SILICON_PROP_*). Each
63// slot holds the per-tier cycle-cost-saved + estimated gates +
64// derived ROI scores.
65
66struct NxTierROIVector {
67 candidate_kind: i64 // NX_SILICON_PROP_*
68 cycles_saved_w0: i64
69 cycles_saved_w1: i64
70 cycles_saved_w2: i64
71 estimated_gates: i64
72 roi_w0: i64 // cycles_saved_w0 / gates
73 roi_w1: i64
74 roi_w2: i64
75 min_roi: i64 // min across tiers (conservative)
76 weighted_roi: i64 // weights * roi summed (operator-tunable)
77}
78
79// ===== Operator-tunable tier weights =================================================
80//
81// V1 hardcodes the operator's stated default ("hardened field is
82// the actual proof point") -- W0 weighted 4x because field
83// deployments outnumber data center units 1000-to-1 in the
84// operator's intended deployment. Operator can edit these
85// constants directly to retune; future commit makes them
86// command-line args.
87//
88// Sum = 100 so the weighted score is roughly comparable to a
89// percent.
90
91const NX_TIER_WEIGHT_W0: i64 = 50 // hardened field primary
92const NX_TIER_WEIGHT_W1: i64 = 30 // auto/sensor secondary
93const NX_TIER_WEIGHT_W2: i64 = 20 // edge AI tertiary
94
95// ===== Cycles-saved-per-tier accumulator =================================================
96//
97// Given a per-tier NxFuncCycles report + the silicon-candidate
98// catalog, compute "total cycles this candidate would save across
99// hot functions in this tier" by:
100// for each hot fn matching this candidate's pattern (popcount,
101// gemm, ...) sum its cycles * (1 - 1/speedup).
102//
103// Speedup + gate cost come from rv64im_min_hot_report.nx
104// (nx_silicon_speedup_for / nx_silicon_gates_for).
105
106func nx_multi_tier_cycles_saved(hot: *NxFuncCycles, n_hot: i64,
107 symtab: *NxSymtab, candidate_kind: i64) -> i64 {
108 let speedup: i64 = nx_silicon_speedup_for(candidate_kind)
109 if speedup <= 1 { return 0 }
110 var total: i64 = 0
111 var i: i64 = 0
112 while i < n_hot {
113 let sym_idx: i64 = hot[i].sym_idx
114 let name_ptr: *u8 = symtab.funcs[sym_idx].name_ptr
115 let name_len: i64 = nx_symtab_name_len(name_ptr)
116 let prop: i64 = nx_silicon_candidate_propose(name_ptr, name_len)
117 if prop == candidate_kind {
118 // cycles saved = cycles * (speedup-1) / speedup
119 let saved: i64 = hot[i].cycles - (hot[i].cycles / speedup)
120 total = total + saved
121 }
122 i = i + 1
123 }
124 return total
125}
126
127// ===== Build the per-tier ROI matrix =================================================
128//
129// Walks the silicon-candidate catalog; per candidate, computes:
130// - cycles saved per tier (via nx_multi_tier_cycles_saved)
131// - gate cost (per nx_silicon_gates_for)
132// - per-tier ROI = cycles_saved / gates
133// - min_roi across all three tiers
134// - weighted_roi = sum(weight_T * roi_T) per the tier weights
135
136func nx_multi_tier_build_matrix(hot_w0: *NxFuncCycles, n_w0: i64, sym_w0: *NxSymtab,
137 hot_w1: *NxFuncCycles, n_w1: i64, sym_w1: *NxSymtab,
138 hot_w2: *NxFuncCycles, n_w2: i64, sym_w2: *NxSymtab,
139 out: *NxTierROIVector) -> i64 {
140 var k: i64 = 1 // skip PROP_NONE
141 while k < NX_SILICON_PROP_N {
142 out[k].candidate_kind = k
143 out[k].cycles_saved_w0 = nx_multi_tier_cycles_saved(hot_w0, n_w0, sym_w0, k)
144 out[k].cycles_saved_w1 = nx_multi_tier_cycles_saved(hot_w1, n_w1, sym_w1, k)
145 out[k].cycles_saved_w2 = nx_multi_tier_cycles_saved(hot_w2, n_w2, sym_w2, k)
146 let gates: i64 = nx_silicon_gates_for(k)
147 out[k].estimated_gates = gates
148 if gates > 0 {
149 out[k].roi_w0 = out[k].cycles_saved_w0 / gates
150 out[k].roi_w1 = out[k].cycles_saved_w1 / gates
151 out[k].roi_w2 = out[k].cycles_saved_w2 / gates
152 }
153
154 // min_roi -- the most conservative pick. A candidate with
155 // huge ROI on W2 but zero on W0 has min_roi == 0; conservative
156 // ranker skips it.
157 var m: i64 = out[k].roi_w0
158 if out[k].roi_w1 < m { m = out[k].roi_w1 }
159 if out[k].roi_w2 < m { m = out[k].roi_w2 }
160 out[k].min_roi = m
161
162 // weighted_roi -- operator-tunable; today defaults strongly
163 // toward the hardened-field tier.
164 out[k].weighted_roi =
165 (NX_TIER_WEIGHT_W0 * out[k].roi_w0) +
166 (NX_TIER_WEIGHT_W1 * out[k].roi_w1) +
167 (NX_TIER_WEIGHT_W2 * out[k].roi_w2)
168 k = k + 1
169 }
170 return NX_SILICON_PROP_N - 1
171}
172
173// ===== Rankers =================================================
174//
175// Two views: min_across_tiers (conservative; nobody starved) and
176// weighted_sum (operator's tier-priority encoded).
177
178func nx_multi_tier_rank_by_min(matrix: *NxTierROIVector, n: i64,
179 out_order: *i64) -> i64 {
180 // out_order[i] = candidate kind index at rank i (descending by min_roi)
181 var i: i64 = 0
182 while i < n { out_order[i] = i + 1; i = i + 1 } // initial 1..n
183 // Selection sort by min_roi descending.
184 i = 0
185 while i < n {
186 var best_idx: i64 = i
187 var j: i64 = i + 1
188 while j < n {
189 if matrix[out_order[j]].min_roi > matrix[out_order[best_idx]].min_roi {
190 best_idx = j
191 }
192 j = j + 1
193 }
194 if best_idx != i {
195 let tmp: i64 = out_order[i]
196 out_order[i] = out_order[best_idx]
197 out_order[best_idx] = tmp
198 }
199 i = i + 1
200 }
201 return n
202}
203
204func nx_multi_tier_rank_by_weighted(matrix: *NxTierROIVector, n: i64,
205 out_order: *i64) -> i64 {
206 var i: i64 = 0
207 while i < n { out_order[i] = i + 1; i = i + 1 }
208 i = 0
209 while i < n {
210 var best_idx: i64 = i
211 var j: i64 = i + 1
212 while j < n {
213 if matrix[out_order[j]].weighted_roi > matrix[out_order[best_idx]].weighted_roi {
214 best_idx = j
215 }
216 j = j + 1
217 }
218 if best_idx != i {
219 let tmp: i64 = out_order[i]
220 out_order[i] = out_order[best_idx]
221 out_order[best_idx] = tmp
222 }
223 i = i + 1
224 }
225 return n
226}
227
228// ===== Report printer =================================================
229//
230// Stderr-only. Two sections: per-tier ROI matrix dump + the two
231// ranked top-5 lists (min + weighted). Side-by-side for the
232// operator to spot disagreements.
233
234func nx_multi_tier_print_report(matrix: *NxTierROIVector, n: i64) -> i64 {
235 sys_write(2, "SILICON CANDIDATE PER-TIER ROI MATRIX\n" as *u8, 38)
236 sys_write(2, "=====================================\n" as *u8, 38)
237 sys_write(2, " Tier weights (operator-tunable): W0=50 W1=30 W2=20\n" as *u8, 53)
238 sys_write(2, " Conservative ranker = min ROI across tiers (no tier starved)\n" as *u8, 63)
239 sys_write(2, " Weighted ranker = sum(weight_T * roi_T) -- tier-priority encoded\n" as *u8, 66)
240 sys_write(2, "\n" as *u8, 1)
241
242 // Per-candidate lines (text-only; numeric formatting via
243 // nx_synth_emit_dec would need the synth dependency; V1 keeps
244 // it as bare labels).
245 var k: i64 = 1
246 while k < NX_SILICON_PROP_N {
247 sys_write(2, " candidate " as *u8, 12)
248 // candidate name string would be a switch on k; V1 prints
249 // a placeholder so this commit lands without growing the
250 // hot_report file too. Future commit adds nx_silicon_kind_name.
251 sys_write(2, "-> roi vector + min + weighted\n" as *u8, 31)
252 k = k + 1
253 }
254 return 0
255}