code wiki / _hdl_build / nx_gate_energy_test.nx
nx_gate_energy_test.nx
buildroot/runtime/_hdl_build/nx_gate_energy_test.nx
about
nx_gate_energy_test.nx -- the EFFICIENCY leg of the triangle, measured BITS-UP
from the silicon we built, not borrowed from an OS. This is the PORTABLE layer:
it runs identically on ANY hardware because it owes nothing to the host (no
RAPL, no /sys, no privilege) -- the energy is a property of the GATE DESIGN.
THE GOOD TRIANGLE (operator's framing):
SPEED = critical-path gate DEPTH (signals ripple through the carry chain)
SIZE/AREA = gate COUNT (how much silicon the design occupies)
EFFICIENCY = switching ACTIVITY = gate-output TOGGLES per op. Dynamic energy
in real silicon is ~ (toggles * C * V^2): every gate output that
flips charges/discharges a capacitance and burns joules. Counting
toggles is exactly how chip designers estimate dynamic power.
CAPABILITY = 1:1 CORRECTNESS (the sum is right for every vector).
CITATIONS (Cardinal #4 -- real sources): the dynamic-power law P = a*C*V^2*f and
switching-activity-based estimation are textbook -- Weste & Harris, "CMOS VLSI
Design" 4e; Najm, "A Survey of Power Estimation Techniques in VLSI Circuits,"
IEEE Trans. VLSI Systems, 1994. Ripple-carry critical-path ~2W gate delays is
standard (Weste & Harris, adder chapter). The energy/time/memory tradeoff this
triangle measures is empirically grounded in Pereira et al., "Energy Efficiency
across Programming Languages," ACM SLE 2017 (the benchmarks-game-based study).
We build a REAL ripple-carry adder out of PRIMITIVE 1-bit gates (XOR/AND/OR),
simulate it on consecutive input vectors, and count how many gate outputs flip.
Key physical truth this proves: ENERGY IS DATA-DEPENDENT. The same correct adder
burns more on random inputs (carries propagate, ~half the bits flip) than on
small increments (short carries, few flips). That is WHY a garden sensor must
MEASURE its real workload's energy, not assume it -- the companion real-hardware
probe (nx_energy_probe) reads the node's actual power sensor and calibrates THIS
toggle count into joules for whatever silicon it landed on.
Known answer: correctness holds for all vectors AND random-workload toggles >
increment-workload toggles (data-dependent energy). exit 0.
dependencies 1 imports · 0 importers
imports: nx_nxgate_sim.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 37 | const GE_W: i64 = 8 // adder width (small + clear; the structure scales) |
| 38 | const GE_N: i64 = 256 // vectors per workload |
functions
| 44 | func ge_anet(i: i64) -> i64 { return 1 + i } |
| 45 | func ge_bnet(i: i64) -> i64 { return 1 + GE_W + i } |
| 46 | func ge_base() -> i64 { return 1 + 2 * GE_W } |
| 47 | func ge_axb(i: i64) -> i64 { return ge_base() + 5 * i + 0 } |
| 48 | func ge_sum(i: i64) -> i64 { return ge_base() + 5 * i + 1 } |
| 49 | func ge_aab(i: i64) -> i64 { return ge_base() + 5 * i + 2 } |
| 50 | func ge_axbc(i: i64) -> i64 { return ge_base() + 5 * i + 3 } |
| 51 | func ge_cout(i: i64) -> i64 { return ge_base() + 5 * i + 4 } |
| 52 | func ge_cin(i: i64) -> i64 { if i == 0 { return 0 } return ge_cout(i - 1) } |
| 54 | func ge_set(cells: *NxGsimCell, idx: i64, kind: i64, fo: i64, f0: i64, f1: i64, f2: i64, val: i64) -> i64 called by 1: ge_build |
| 67 | func ge_build(cells: *NxGsimCell) -> i64 |
| 83 | func ge_set_inputs(g: *NxGsim, a: i64, b: i64) -> i64 |
| 92 | func ge_read_sum(g: *NxGsim) -> i64 |
| 101 | func ge_depth(g: *NxGsim, cells: *NxGsimCell, ncells: i64) -> i64 |
| 124 | func ge_snapshot(g: *NxGsim, cells: *NxGsimCell, ncells: i64, snap: *i64) -> i64 called by 1: ge_run_workload |
| 130 | func ge_toggles(g: *NxGsim, cells: *NxGsimCell, ncells: i64, prev: *i64) -> i64 called by 1: ge_run_workload |
| 137 | func ge_rng(s: i64) -> i64 { return s * 6364136223846793005 + 1442695040888963407 } called by 1: ge_run_workload |
| 141 | func ge_run_workload(g: *NxGsim, cells: *NxGsimCell, ncells: i64, mode: i64, n: i64, mask: i64, prev: *i64) -> i64 |
| 162 | func _emit(name: *u8, v: i64) -> i64 called by 1: main |
| 172 | func main() -> i64 |