code wiki / _hdl_build / nx_latency_metric.nx

nx_latency_metric.nx source

↩ module page · 148 lines · 7252 B

1// nx_latency_metric.nx -- the racing-crew's HONEST latency-metric organ. 2// 3// Given a built NxGsim word-level netlist (topologically ordered cells, each a 4// kind with up to 3 fanin nets), compute the CRITICAL PATH = the longest 5// dependency chain of cells from a primary input to an output. Two metrics: 6// 7// (1) UNIT depth -- every non-CONST cell costs 1. This is the cell-DEPTH the 8// functional sim (nx_nxgate_sim) implicitly uses: it assigns depth-1 to 9// every word-level cell, so a 64x64 MUL looks exactly as cheap as a 1-bit 10// AND. That is the HONEST-METRIC TRAP, and the unit metric is the trap. 11// 12// (2) HONEST depth -- every cell costs its per-kind logic depth, weighted by 13// the operand width W (data-driven, NOT hardcoded per call site): 14// CONST / wire = 0 (no logic) 15// AND/OR/XOR/NOT/NAND/... = 1 (one gate level) 16// MUX = 1 (one 2:1 select level) 17// ADD/SUB = ceil(log2 W) (carry-propagate / prefix adder) 18// SHL/SHR/SAR = ceil(log2 W) (barrel shifter, log stages) 19// EQ/NEQ/LT/LTU/GE/GEU = ceil(log2 W) (tree comparator) 20// MUL = 2*ceil(log2 W) (Wallace/Dadda PP-reduction 21// tree + final CPA -- the 22// DEEPEST operator) 23// So the honest metric does NOT treat a multiplier as depth-1: a MUL costs 24// 2*log(W) gate levels, an ADD costs log(W), a boolean costs 1. This is the 25// metric on which any LATENCY claim about a Newton (log(W) multiplies) 26// vs a radix-2 (W subtracts) divider must be stated -- because a multiply 27// being "one cell" in the sim is exactly the self-deception we must avoid. 28// 29// METRIC HONESTY (stated, per the racing doctrine): these weights are a STRUCTURAL 30// gate-LEVEL model (counts of logic levels), not measured silicon nanoseconds and 31// not a placed-and-routed delay. They are a faithful relative ordering of operator 32// depth, sized by W, and they are DATA-DRIVEN here so a future PDK-calibrated table 33// drops in without touching the traversal. The traversal itself is exact: it is the 34// true longest weighted path through the DAG. 35// 36// Reuses nx_nxgate_sim's NxGsim/NxGsimCell structs + the NX_GATE_KIND_* enum 37// (single source of truth -- DRY). Pure read-only analysis: it never mutates the 38// netlist, only walks it. license_tier: ORIGINAL 39 40import "nx_nxgate_sim.nx" 41 42const NX_LAT_OK: i64 = 0 43const NX_LAT_BAD_KIND: i64 = 1 44 45// ceil(log2(w)) for w >= 1. log2_ceil(1)=0, (2)=1, (3)=2, (4)=2, (16)=4, (64)=6. 46// This is the depth of a balanced reduction/prefix tree over w bits -- the honest 47// floor on a carry-propagate adder, a comparator tree, and a barrel shifter. 48func nx_lat_log2_ceil(w: i64) -> i64 { 49 if w <= 1 { return 0 } 50 var p: i64 = 0 51 var v: i64 = 1 52 while v < w { v = v << 1; p = p + 1 } 53 return p 54} 55 56// Per-kind honest logic DEPTH (gate levels), sized by operand width W. 57// Data-driven: the only place a weight lives. A future PDK table replaces this 58// body without touching the critical-path walk. FAIL LOUD on an unknown kind 59// (returns a negative sentinel) so the caller can refuse to fabricate a number. 60func nx_lat_kind_weight(kind: i64, w: i64) -> i64 { 61 // wire-only / no logic 62 if kind == NX_GATE_KIND_CONST { return 0 } 63 // single gate level (boolean + a 2:1 mux) 64 if kind == NX_GATE_KIND_AND { return 1 } 65 if kind == NX_GATE_KIND_OR { return 1 } 66 if kind == NX_GATE_KIND_NOT { return 1 } 67 if kind == NX_GATE_KIND_XOR { return 1 } 68 if kind == NX_GATE_KIND_NAND { return 1 } 69 if kind == NX_GATE_KIND_NOR { return 1 } 70 if kind == NX_GATE_KIND_XNOR { return 1 } 71 if kind == NX_GATE_KIND_MUX { return 1 } 72 // log-depth operators (carry-propagate adder, barrel shifter, tree comparator) 73 let lg: i64 = nx_lat_log2_ceil(w) 74 if kind == NX_GATE_KIND_ADD { return lg } 75 if kind == NX_GATE_KIND_SUB { return lg } 76 if kind == NX_GATE_KIND_SHL { return lg } 77 if kind == NX_GATE_KIND_SHR { return lg } 78 if kind == NX_GATE_KIND_SAR { return lg } 79 if kind == NX_GATE_KIND_EQ { return lg } 80 if kind == NX_GATE_KIND_NEQ { return lg } 81 if kind == NX_GATE_KIND_LT { return lg } 82 if kind == NX_GATE_KIND_LTU { return lg } 83 if kind == NX_GATE_KIND_GE { return lg } 84 if kind == NX_GATE_KIND_GEU { return lg } 85 // the DEEPEST operator: a multiplier = PP-reduction tree (~log W) + final CPA 86 // (~log W). 2*log(W). This is the cell that the unit metric LIES about. 87 if kind == NX_GATE_KIND_MUL { return 2 * lg } 88 return 0 - NX_LAT_BAD_KIND // unknown kind -> LOUD sentinel 89} 90 91// Core critical-path walk, parameterised by a weight mode + width. 92// mode 0 -> UNIT (every non-CONST cell = 1; CONST = 0); w is ignored. 93// mode 1 -> HONEST (per-kind, width-weighted by w). 94// depth[] is a caller-allocated *i64 of size >= g.n_nets; on return depth[net] is 95// the longest weighted path that produces that net (primary inputs = 0). Returns 96// the maximum over all nets = the network's critical-path latency on that metric, 97// or 0 - NX_LAT_BAD_KIND (LOUD) if any cell has an unknown kind. 98// 99// The cells are in topological order (the synth emitters build sub-results before 100// consumers), so a single forward pass is an exact longest-path DAG relaxation: 101// when we reach a cell, every fanin net's depth is already final. 102func nx_lat_critical_path(g: *NxGsim, depth: *i64, mode: i64, w: i64) -> i64 { 103 // primary-input + uninitialised nets start at depth 0 104 var n: i64 = 0 105 while n < g.n_nets { depth[n] = 0; n = n + 1 } 106 107 var best: i64 = 0 108 var i: i64 = 0 109 while i < g.n_cells { 110 let k: i64 = g.cells[i].kind 111 if nx_gsim_kind_supported(k) != 1 { return 0 - NX_LAT_BAD_KIND } 112 113 // longest fanin depth feeding this cell 114 var fan: i64 = 0 115 let f0: i64 = g.cells[i].f0 116 let f1: i64 = g.cells[i].f1 117 let f2: i64 = g.cells[i].f2 118 if f0 >= 0 { if depth[f0] > fan { fan = depth[f0] } } 119 if f1 >= 0 { if depth[f1] > fan { fan = depth[f1] } } 120 if f2 >= 0 { if depth[f2] > fan { fan = depth[f2] } } 121 122 // this cell's own cost on the chosen metric 123 var cost: i64 = 1 124 if mode == 0 { 125 if k == NX_GATE_KIND_CONST { cost = 0 } 126 } 127 if mode != 0 { 128 cost = nx_lat_kind_weight(k, w) 129 if cost < 0 { return 0 - NX_LAT_BAD_KIND } // unknown kind, LOUD 130 } 131 132 let d: i64 = fan + cost 133 depth[g.cells[i].fanout] = d 134 if d > best { best = d } 135 i = i + 1 136 } 137 return best 138} 139 140// Convenience wrappers. The honest model is parameterised by the datapath operand 141// width W (a 64-bit MUL is deeper than a 16-bit MUL), so W is a named argument, not 142// a magic number buried in the walk -- the demo passes it per network it measures. 143func nx_lat_unit(g: *NxGsim, depth: *i64) -> i64 { 144 return nx_lat_critical_path(g, depth, 0, 0) 145} 146func nx_lat_honest(g: *NxGsim, depth: *i64, w: i64) -> i64 { 147 return nx_lat_critical_path(g, depth, 1, w) 148}