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}