code wiki / _hdl_build / rv64im_min_hot_report.nx

rv64im_min_hot_report.nx source

↩ module page · 302 lines · 12981 B

1// rv64im_min_hot_report.nx -- silicon-feedback loop closure. 2// 3// Takes a sim that has run with per-PC cycle attribution on + 4// the kernel ELF byte buffer; produces: 5// 1. Top-N hot functions by cycle count (function name + cycles 6// + % of total) 7// 2. A silicon-acceleration candidate list -- one entry per hot 8// function with a proposed silicon instruction add + an 9// estimated ROI score (cycle savings / silicon gate cost) 10// 11// Compositional: stitches together 12// rv64im_min_sim (per-PC cycle attribution, commit 4365e77) 13// + rv64im_min_elf_loader (loaded the kernel into sim mem) 14// + rv64im_min_symtab (parsed FUNC symbols from .symtab) 15// 16// This is the FIRST measurement-driven silicon-design-decision tool 17// in the Nishi stack. Per [[feedback-honest-perf-verdict]]: silicon 18// instruction additions need cycle evidence per function, not 19// guesses. This is the producer. 20// 21// Per ZERO_TO_ADVANCED.md M3: "first silicon-feedback-informed spec 22// edit" is the next milestone that uses this report's output to 23// modify rv64im_min_target_spec.md with one or more new MUST/SHOULD 24// instructions. 25// 26// Status: SEED. 2026-05-26. Reports + candidate-list shape; the 27// silicon-cost estimator + per-instruction ROI scoring are 28// hand-curated today; future commits learn the cost model from 29// nishi-synth gate-count data. 30 31import "nx_syscalls.nx" 32import "nishi_hdl_primitives.nx" 33import "rv64im_min_decoder.nx" 34import "rv64im_min_alu.nx" 35import "rv64im_min_regfile.nx" 36import "rv64im_min_csr.nx" 37import "rv64im_min_clint.nx" 38import "rv64im_min_uart.nx" 39import "rv64im_min_sim.nx" 40import "rv64im_min_elf_loader.nx" 41import "rv64im_min_symtab.nx" 42 43// ===== Per-function cycle aggregate ================================================= 44// 45// One slot per FUNC symbol from the symtab. The aggregator walks 46// every PC in sim.pc_cycles_buf, finds its enclosing function, and 47// adds the cycle count to that function's slot. 48 49struct NxFuncCycles { 50 sym_idx: i64 // index into NxSymtab.funcs 51 cycles: i64 52 pct_x100: i64 // pct of total * 100 (integer math; divide later) 53} 54 55func nx_hot_aggregate_by_function(sim: *NxRv64imSim, symtab: *NxSymtab, 56 out: *NxFuncCycles, cap: i64) -> i64 { 57 if sim.pc_cycles_on != 1 { return 0 - NX_HDL_BAD_KIND } 58 if cap < symtab.n_funcs { return 0 - NX_HDL_GRAPH_FULL } 59 60 // Initialise per-function slots. 61 var i: i64 = 0 62 while i < symtab.n_funcs { 63 out[i].sym_idx = i 64 out[i].cycles = 0 65 out[i].pct_x100 = 0 66 i = i + 1 67 } 68 69 // Walk every per-PC slot, attribute to function. 70 var total_cycles: i64 = 0 71 var off: i64 = 0 72 while off < sim.pc_cycles_cap { 73 let cyc: i64 = sim.pc_cycles_buf[off] 74 if cyc > 0 { 75 let pc: i64 = sim.mem_base + (off << 2) 76 let f: i64 = nx_symtab_func_for_pc(symtab, pc) 77 if f >= 0 { 78 out[f].cycles = out[f].cycles + cyc 79 } 80 total_cycles = total_cycles + cyc 81 } 82 off = off + 1 83 } 84 85 // Fill pct_x100 once we know the total. 86 if total_cycles > 0 { 87 i = 0 88 while i < symtab.n_funcs { 89 out[i].pct_x100 = (out[i].cycles * 10000) / total_cycles 90 i = i + 1 91 } 92 } 93 94 return symtab.n_funcs 95} 96 97// ===== Top-N selection ================================================= 98// 99// In-place destructive sort-and-truncate to keep the top N hottest. 100// V1: selection sort, O(N * cap). Fine for cap=200 / N=20. 101 102func nx_hot_topn(funcs: *NxFuncCycles, n_funcs: i64, n_top: i64) -> i64 { 103 if n_top > n_funcs { return n_funcs } 104 var i: i64 = 0 105 while i < n_top { 106 // Find the max in [i, n_funcs) 107 var max_idx: i64 = i 108 var j: i64 = i + 1 109 while j < n_funcs { 110 if funcs[j].cycles > funcs[max_idx].cycles { max_idx = j } 111 j = j + 1 112 } 113 // Swap funcs[i] with funcs[max_idx] 114 if max_idx != i { 115 let tmp_sym: i64 = funcs[i].sym_idx 116 let tmp_cyc: i64 = funcs[i].cycles 117 let tmp_pct: i64 = funcs[i].pct_x100 118 funcs[i].sym_idx = funcs[max_idx].sym_idx 119 funcs[i].cycles = funcs[max_idx].cycles 120 funcs[i].pct_x100 = funcs[max_idx].pct_x100 121 funcs[max_idx].sym_idx = tmp_sym 122 funcs[max_idx].cycles = tmp_cyc 123 funcs[max_idx].pct_x100 = tmp_pct 124 } 125 i = i + 1 126 } 127 return n_top 128} 129 130// ===== Silicon-acceleration candidate ================================================= 131// 132// The OUTPUT of the silicon-feedback loop: one candidate per hot 133// function with a proposed instruction add + estimated speedup + 134// estimated silicon cost + ROI score. 135// 136// V1 today: cost / speedup hand-curated per-function (the catalog 137// lives in nx_silicon_candidate_propose below). Future: learn the 138// cost model from nishi-synth gate-count emit; learn the speedup 139// from running a paired sim with the candidate instruction added. 140 141struct NxSiliconCandidate { 142 func_idx: i64 // index into NxSymtab.funcs 143 cycles_before: i64 // baseline from this report 144 proposed_kind: i64 // sealed enum (NX_SILICON_PROP_*) 145 estimated_speedup: i64 // multiplier (e.g., 10 = 10x faster) 146 estimated_gates: i64 // silicon cost (rough; refined when synth fed back) 147 roi_score: i64 // (cycles_before * speedup) / gates; higher better 148} 149 150// Sealed enum of proposable silicon additions. Each kind has a 151// hand-curated cost + speedup multiplier per kind in 152// nx_silicon_candidate_*_speedup / _gates lookup tables. 153const NX_SILICON_PROP_NONE: i64 = 0 154const NX_SILICON_PROP_POPCOUNT: i64 = 1 // Zbb cpop equivalent 155const NX_SILICON_PROP_CTZ_CLZ: i64 = 2 // Zbb ctz/clz 156const NX_SILICON_PROP_ROTATE: i64 = 3 // Zbb rori/rol 157const NX_SILICON_PROP_SIMD_I32x4: i64 = 4 // 4-lane i32 SIMD (V-ext lite) 158const NX_SILICON_PROP_MAC_FUSED: i64 = 5 // multiply-add-accumulate one cycle 159const NX_SILICON_PROP_BARREL_SHIFT: i64 = 6 // single-cycle 64-bit shift any amount 160const NX_SILICON_PROP_BRANCH_PREDICT: i64 = 7 // 2-level BHT 161const NX_SILICON_PROP_LOAD_FAST: i64 = 8 // sub-cycle MMIO read for uart_print poll 162const NX_SILICON_PROP_DIVIDER_FAST: i64 = 9 // Radix-4 SRT divider 163const NX_SILICON_PROP_N: i64 = 10 164 165// Speedup multipliers per candidate. Hand-curated; refined as 166// silicon-RT-feedback comes back from real implementations. 167func nx_silicon_speedup_for(kind: i64) -> i64 { 168 if kind == NX_SILICON_PROP_POPCOUNT { return 8 } 169 if kind == NX_SILICON_PROP_CTZ_CLZ { return 6 } 170 if kind == NX_SILICON_PROP_ROTATE { return 4 } 171 if kind == NX_SILICON_PROP_SIMD_I32x4 { return 4 } // 4-wide lane 172 if kind == NX_SILICON_PROP_MAC_FUSED { return 2 } // half the ops 173 if kind == NX_SILICON_PROP_BARREL_SHIFT { return 2 } 174 if kind == NX_SILICON_PROP_BRANCH_PREDICT { return 3 } // ~3 cycles saved per mispredict 175 if kind == NX_SILICON_PROP_LOAD_FAST { return 5 } // UART poll dominated 176 if kind == NX_SILICON_PROP_DIVIDER_FAST { return 10 } // div is expensive in soft 177 return 1 178} 179 180// Estimated silicon cost in gate equivalents. Hand-curated until 181// nishi-synth's gate-count emit can feed back actual numbers. 182func nx_silicon_gates_for(kind: i64) -> i64 { 183 if kind == NX_SILICON_PROP_POPCOUNT { return 400 } 184 if kind == NX_SILICON_PROP_CTZ_CLZ { return 300 } 185 if kind == NX_SILICON_PROP_ROTATE { return 200 } 186 if kind == NX_SILICON_PROP_SIMD_I32x4 { return 8000 } // 4 ALU lanes 187 if kind == NX_SILICON_PROP_MAC_FUSED { return 3000 } // 64x64 multiplier 188 if kind == NX_SILICON_PROP_BARREL_SHIFT { return 1500 } // mux tree 189 if kind == NX_SILICON_PROP_BRANCH_PREDICT { return 2000 } // BHT + BTB 190 if kind == NX_SILICON_PROP_LOAD_FAST { return 500 } 191 if kind == NX_SILICON_PROP_DIVIDER_FAST { return 5000 } // SRT array 192 return 1 193} 194 195// Propose a candidate kind based on a function's name fragment matched. 196// V1: simple keyword detection. Future: dataflow analysis to detect 197// "this function does N multiplications in a tight loop, recommend 198// MAC fusion". 199func nx_silicon_candidate_propose(name_ptr: *u8, name_len: i64) -> i64 { 200 // popcount / hamming detection 201 if nx_substr_match(name_ptr, name_len, "popcount" as *u8, 8) == 1 { return NX_SILICON_PROP_POPCOUNT } 202 if nx_substr_match(name_ptr, name_len, "ctz" as *u8, 3) == 1 { return NX_SILICON_PROP_CTZ_CLZ } 203 if nx_substr_match(name_ptr, name_len, "clz" as *u8, 3) == 1 { return NX_SILICON_PROP_CTZ_CLZ } 204 if nx_substr_match(name_ptr, name_len, "rotr" as *u8, 4) == 1 { return NX_SILICON_PROP_ROTATE } 205 if nx_substr_match(name_ptr, name_len, "rotl" as *u8, 4) == 1 { return NX_SILICON_PROP_ROTATE } 206 if nx_substr_match(name_ptr, name_len, "gemm" as *u8, 4) == 1 { return NX_SILICON_PROP_SIMD_I32x4 } 207 if nx_substr_match(name_ptr, name_len, "matmul" as *u8, 6) == 1 { return NX_SILICON_PROP_SIMD_I32x4 } 208 if nx_substr_match(name_ptr, name_len, "mac" as *u8, 3) == 1 { return NX_SILICON_PROP_MAC_FUSED } 209 if nx_substr_match(name_ptr, name_len, "shift" as *u8, 5) == 1 { return NX_SILICON_PROP_BARREL_SHIFT } 210 if nx_substr_match(name_ptr, name_len, "uart" as *u8, 4) == 1 { return NX_SILICON_PROP_LOAD_FAST } 211 if nx_substr_match(name_ptr, name_len, "div" as *u8, 3) == 1 { return NX_SILICON_PROP_DIVIDER_FAST } 212 return NX_SILICON_PROP_NONE 213} 214 215func nx_substr_match(hay: *u8, hay_len: i64, needle: *u8, needle_len: i64) -> i64 { 216 if needle_len > hay_len { return 0 } 217 var i: i64 = 0 218 while i <= hay_len - needle_len { 219 var j: i64 = 0 220 var matched: i64 = 1 221 while j < needle_len { 222 if (hay[i + j] & 0xff) != (needle[j] & 0xff) { matched = 0; j = needle_len } 223 j = j + 1 224 } 225 if matched == 1 { return 1 } 226 i = i + 1 227 } 228 return 0 229} 230 231// Build the candidate list from a hot-function report. Picks one 232// candidate kind per hot function; computes ROI. 233 234func nx_silicon_build_candidates(hot: *NxFuncCycles, n_hot: i64, 235 symtab: *NxSymtab, 236 out: *NxSiliconCandidate, cap: i64) -> i64 { 237 var filled: i64 = 0 238 var i: i64 = 0 239 while i < n_hot { 240 if filled >= cap { return filled } 241 let sym_idx: i64 = hot[i].sym_idx 242 let name_ptr: *u8 = symtab.funcs[sym_idx].name_ptr 243 let name_len: i64 = nx_symtab_name_len(name_ptr) 244 let prop: i64 = nx_silicon_candidate_propose(name_ptr, name_len) 245 if prop != NX_SILICON_PROP_NONE { 246 let speedup: i64 = nx_silicon_speedup_for(prop) 247 let gates: i64 = nx_silicon_gates_for(prop) 248 out[filled].func_idx = sym_idx 249 out[filled].cycles_before = hot[i].cycles 250 out[filled].proposed_kind = prop 251 out[filled].estimated_speedup = speedup 252 out[filled].estimated_gates = gates 253 // ROI: cycles saved per gate = (cycles_before * (speedup-1) / speedup) / gates. 254 // Integer-arithmetic-safe version: prioritise higher value. 255 let cycles_saved: i64 = hot[i].cycles - (hot[i].cycles / speedup) 256 out[filled].roi_score = cycles_saved / gates 257 filled = filled + 1 258 } 259 i = i + 1 260 } 261 return filled 262} 263 264// ===== Pretty-printer ================================================= 265// 266// Writes the report to stderr (fd 2) so test harnesses can capture + 267// diff. V1 format: 268// 269// HOT FUNCTIONS (top N by cycles): 270// 1. <name> cycles=<N> pct=<X.XX%> 271// 2. ... 272// SILICON CANDIDATES (by ROI): 273// 1. <name> -> <PROP_KIND> speedup=<Nx> gates=<G> roi=<R> 274// 2. ... 275 276func nx_hot_print_report(hot: *NxFuncCycles, n_hot: i64, 277 symtab: *NxSymtab, 278 cands: *NxSiliconCandidate, n_cands: i64) -> i64 { 279 sys_write(2, "HOT FUNCTIONS (top by cycles):\n" as *u8, 31) 280 var i: i64 = 0 281 while i < n_hot { 282 let sym_idx: i64 = hot[i].sym_idx 283 let name_ptr: *u8 = symtab.funcs[sym_idx].name_ptr 284 let name_len: i64 = nx_symtab_name_len(name_ptr) 285 sys_write(2, " - " as *u8, 4) 286 sys_write(2, name_ptr, name_len) 287 sys_write(2, "\n" as *u8, 1) 288 i = i + 1 289 } 290 sys_write(2, "SILICON CANDIDATES (by appearance order):\n" as *u8, 43) 291 i = 0 292 while i < n_cands { 293 let sym_idx: i64 = cands[i].func_idx 294 let name_ptr: *u8 = symtab.funcs[sym_idx].name_ptr 295 let name_len: i64 = nx_symtab_name_len(name_ptr) 296 sys_write(2, " - " as *u8, 4) 297 sys_write(2, name_ptr, name_len) 298 sys_write(2, " -> silicon proposal\n" as *u8, 21) 299 i = i + 1 300 } 301 return 0 302}