Verifying a capture
Decide whether a power number is a measurement. Every capture writes the numbers and the signals that say how they were obtained; this page lists those signals, the issue codes behind them, and what each failure looks like on the bench.
Prerequisites
Section titled “Prerequisites”A completed power run, whichever instrument produced it.
1. Read the terminal table
Section titled “1. Read the terminal table” Power┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓┃ Metric ┃ Value ┃┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩│ Avg current │ 12.345 mA ││ Avg power │ 22.222 mW ││ Peak current │ 14.567 mA ││ Energy │ 666.630 µJ │└────────────────┴──────────────┘The values are placeholders; the shape is what a run prints. Real figures depend on the board, the model and the clock, and the power example explains the workflow and states whether captured evidence is available.
2. Read the power block
Section titled “2. Read the power block”{ "power": { "measurement_scope": "gpio_gated_clean_window", "avg_current_a": 0.012345, "avg_power_w": 0.022222, "median_current_a": 0.012300, "p95_current_a": 0.012900, "p99_current_a": 0.013100, "energy_per_inference_j": 0.00001305, "inferences_per_joule": 76628.4, "gated_window_count": 1, "gated_window_duration_ratio": 0.998, "gated_vs_whole_current_ok": true, "power_firmware": "dedicated" }}Placeholder values again; the keys are the contract, generated from
run_summary.schema.v1.json on Output files.
| Signal | Healthy | What else it can mean |
|---|---|---|
gated_window_duration_ratio |
0.99 to 1.01 for warm back-to-back captures | far from 1.0 with gated_window_reference_drift present: the firmware’s own clock confirmed the gate and only the profile-phase reference is stale (cold-start HFRC drift, harmless); far from 1.0 without it: the gate did not line up with the inference loop |
gated_vs_whole_current_ok |
true |
false means the gated average was not above the whole-capture average; usually a gate or timing fault, but legitimate for a very light or bursty model whose boot and handshake draw more than the gated steady state; compare the whole_capture_window row in detailed/power_summary.csv |
power.sync.ready_observed |
true with lock-step |
false or absent with lock-step enabled points at wiring or the GO line |
gated_window_duration_suspect |
absent | set when the gate is under the 1 s floor, when the firmware’s window clock disagrees with the gate, when the terminal reported failed or incomplete work, when the duration check fails with no envelope to arbitrate, or when the device-reported timing is corrupt; per-inference metrics are suppressed |
power.power_firmware |
dedicated |
shared carries transport contamination |
measurement_scope |
gpio_gated_clean_window or on_device_gated_inference |
free_form_capture means gating failed and the numbers cover the whole capture; the probe that ran inside the window is a separate field, and a busy_loop window is not an inference measurement |
tops and tops_per_watt appear for any power run whose model can be
analysed for MACs (heliaRT, TFLM and heliaAOT .tflite models; not
ExecuTorch) and only when the window is inference-bracketed.
3. Check the window clocks
Section titled “3. Check the window clocks”The dedicated power firmware times its own window and reports it as
power.terminal.gate_elapsed_us. Nothing else on the host depends on that
clock, so it is the one number that can be wrong while every other check
passes; hpx cross-checks it and four codes come out:
| Code | Mode | Severity | Meaning |
|---|---|---|---|
power.window_clock_frozen |
internal, external | error (internal), warning (external) | the firmware completed its inferences but reported zero elapsed time; fails the run in internal mode because the duration is the denominator for average power and current, degrades it in external mode where the instrument owns the numbers and only elapsed_us is meaningless. Power firmware uses STIMER; check for a stopped or unpopulated crystal |
power.window_observer_mismatch |
external | error; per-inference metrics suppressed; the run is invalid for the power family only, hpx compare still computes cycle, latency, memory and per-layer deltas |
the firmware’s window disagrees with the host-timed gate beyond 1% plus the quantisation allowance; two independent clocks in the same boot cannot drift apart, so the gate did not bracket what the firmware timed |
power.window_clock_mismatch |
internal | warning | the firmware’s window disagrees with inference_count x reference_inference_us by more than 25%, a loose tolerance because the reference comes from a different binary and boot |
power.window_clock_exceeds_host_time |
internal | warning | the reported window is longer than the host wall time that contained it; a blunt backstop that needs roughly 2 to 3x inflation to fire |
power.window_clock_ceiling in summary.json records the envelope
behind the last one on every internal-mode run: elapsed_us,
elapsed_s, host_envelope_s, slack_s and ratio.
The profile binary’s clean window has its own clock checks:
profile.clean_window_stalled(warning): one or more clean-window iterations lost their cycle measurement. On the Cortex-M4F familiesDWT->CYCCNTonly advances while a debugger assertsCDBGPWRUPREQ, and nothing does between the J-Link reset subprocess exiting and the host attach completing, so the window reads short and everything sized from it (clean_infer_avg_us, the latency, a power window) is short by the same factor.stalled_iterscounts deltas of exactly zero;partial_iterscounts deltas below an eighth of the run’s own warm reference;understatement_lower_boundcharges a frozen iteration the full1/totaland a partial0.875/totaland stays a lower bound.profile.clean_window_clock_rate_low(warning): before the window the firmware times a fixednsx_delay_usinterval with DWT, a reference that does not depend on the debug power domain; the warning fires below half the expectedSystemCoreClock x probe_us / 1e6. It exists because the two counts above are DWT-relative and cannot see a uniform slowdown.profile.clean_window_check_inoperative(warning): every warm sample was itself frozen, so no iteration could fall below the floor and zero counts are not evidence of health.
None of them cover a slowdown that begins exactly at window open, ends at
window close and is uniform throughout; a GPIO-gated run catches that
through gated_window_duration_ratio. RTT builds reduce the exposure by
waiting, before the window opens, for the host to drain the RTT up-buffer
(bounded at 1 s by default, HPX_CLEAN_WINDOW_ATTACH_WAIT_MS, reported as
HPX_CLEAN_ATTACH_WAIT_US).
4. Bring-up diagnostics
Section titled “4. Bring-up diagnostics”profiling.clean_window_trace(defaultfalse) makes the firmware emitHPX_CLEAN_ITER=<n>over the transport every clean-window iteration, proving the device is looping for the whole gate. It perturbs the measurement; leave it off for real runs.profiling.clean_window_probe: busy_loop(defaultinfer) replaces the inference loop with a calibrated CPU spin, to separate “the gate semantics are wrong” from “the model’s behaviour is wrong”. The duration reported is always the one STIMER measured, never the nominal target.detailed/power_summary.csv(output.detailed: true) breaks the signals down per gated window with awhole_capture_windowrow.
Expected output
Section titled “Expected output”A run whose power block has gated_window_count: 1, a ratio near 1.0,
ready_observed: true under lock-step, no *_suspect flag and no
power.window_* code in issues.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
joulescope: device not found |
driver not installed, or the device is claimed by another process | on Linux confirm the udev rule and replug; pyjoulescope_driver ships with hpx |
| Current negative or implausibly high | another supply feeds the EVB | disconnect target USB, debug USB power and any coin cell for the capture |
no_gate_rise |
lock-step disabled while state and GO pins are configured: the firmware opens and closes its window before the poller is armed; the run comes back integrity: degraded (no_gate_rise) and looks like a dead wire |
set power.lockstep: true or remove an explicit false; only then check the gate wire, sync_gpio_pin and sync_input_index |
no_gate_fall |
the firmware entered the window but did not close it before the safety bound; not a lock-step problem, the gate was seen rising | raise power.duration_s or look for a hang inside the clean window |
| gate always low or always high | wrong io_voltage, floating Vref on JS220 or JS320, or the wrong INPUTn |
match io_voltage and Vref to the board’s I/O rail; check the index keys against the physical wiring |
ready_observed: false or ratio far from 1.0 |
handshake or gate timing; or a firmware clock fault that only looks similar | compare power.terminal.gate_elapsed_us with power.capture_duration_s: agreement to well under 1% means the wiring is fine and only the reference is off (drift); 0 or several times the gate means the firmware timed with a clock it could not read, a rebuild not a rewire |
| gated average not above whole-capture average | gate or timing; or a genuinely light model | compare the whole_capture_window row in detailed/power_summary.csv |
| TOPS per watt missing | the model cannot be analysed for MACs (ExecuTorch), or the window is not inference-bracketed | expected |
| power differs between transports | power.firmware: shared is set |
use dedicated to remove the capture transport from the power firmware, then verify the window and monitored rail |
What next
Section titled “What next”Analysis and comparison for comparing two power runs and the rules that make them comparable; the issue codes reference for every code above.