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}