Skip to content

M0: measured feasibility contracts — DL guard, dynamic beacon grants, ACK/TxReport matrix, per-UE RX attribution (closes #261)#286

Merged
josephnef merged 1 commit into
masterfrom
m0-feasibility-contracts
Jul 12, 2026
Merged

M0: measured feasibility contracts — DL guard, dynamic beacon grants, ACK/TxReport matrix, per-UE RX attribution (closes #261)#286
josephnef merged 1 commit into
masterfrom
m0-feasibility-contracts

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Part of the 5G-NR RAN EPIC #254. Closes #261. Unblocks M1 (#255).

M0 turns the epic's four unmeasured scheduled-MAC assumptions into measured contracts — a docs note with bench distributions and an explicit go/no-go per contract, plus the two small radio-core primitives the measurements proved userspace can't do.

Contract 1 — Deterministic DL departure (docs/dl-departure.md)

  • TD tag v2 (examples/tdma/tdma.h, version-gated, v1 stays parseable): adds host_ns — steady_clock stamped immediately before send_packet — so the witness fit measures the host-submit→air quantity a slot scheduler actually controls.
  • tests/txegress_analyze.py: tail percentiles (guard is a tail contract, not RMS) + machine txeg.verdict line; tests/dl_departure_tx.cpp (any USB adapter) + updated tests/pcie_txegress_tx.cpp; tests/dl_departure_matrix.sh sweeps J1/J2/J3 USB + 8821CE PCIe (remote vfio bind/run/restore) against a fixed witness.
  • Measured: transport floor 11–26 µs on every transport (PCIe 11, J2 14, J3 16, J1 22) — transport is not the bottleneck. The p99.9 tail is 1–3 ms channel deferral on all transports. Go/no-go: fine sub-ms DL slots refuted → M1 uses a submission-ahead scheduler with the measured guard.

Contract 2 — Dynamic grant delivery (docs/beacon-grant.md)

  • New IRtlDevice::UpdateBeaconPayload — in-place content swap for an active beacon (interval/TBTT/port identity untouched), riding the existing steer re-download per generation (~20 lines each).
  • New IRtlDevice::StopBeacon — the chip beacons autonomously after process death; a killed probe's stale beacon contaminated the next test's witness (bench-bitten), so any beaconing session needs a stop primitive. M1's DU needs it anyway.
  • Probe beacons a CRC16-versioned vendor IE (one frame proves a torn swap); analyzer reconstructs the TBTT grid from witness tsfl and separates update-caused skips from background witness loss (--selftest covered).
  • Measured GO on all three generations: 30/30 updates aired per gen, zero excess skips, zero torn frames, zero version regressions; update→air latency TBTT-quantized (p50 40–72 ms, p99 ≤ 174 ms @ 100 TU) → grants effective 1–2 beacon intervals after the scheduler decides. Static-grant fallback not needed.

Contract 3 — Unicast ACK + TxReport capability matrix (docs/ack-txreport.md)

  • tests/ack_txreport_matrix.sh: per TX generation, phases on / retarget (responder re-armed to a different unicast MAC) / off against a SetAckResponder peer; tests/ack_txreport_analyze.py computes ACK rate, retry distribution and report coverage (tx.stats.submitted denominator + HalMAC SW_DEFINE tag-gap counting).
  • TX sessions run DEVOURER_TX_WITH_RX=thread — measured: J1/J2 TX-only sessions get zero CCX reports (C2H needs the RX loop; only J3 drains it off its coex runtime). txdemo now emits a final tx.stats tally so short runs report their true sent count.
  • Measured: per-frame no-ACK detection is GO on all gens (OFF pins retries at 12, state=1 — exactly what M2's retransmission logic needs). J1/J3 TX perfect (100% ACK, retries ~0.2, arbitrary-MAC retarget works, coverage 96–100%). J2 8812BU TX marginal (ACK closure 12–91% run-to-run, ~24 ms/frame pace in this shape) — recorded open, prefer J1/J3/8821CE for the DU role. Responder ranking on this bench: 8814AU (retries ~0.1) ≫ 8812AU (97% @ ~7 retries) > 8821AU (never closes the loop).

Contract 4 — Per-UE RX attribution (docs/ue-rx-attribution.md)

  • src/cell/UeRxAttribution.h — the epic's first src/cell/ file, seed of M1's UeRegistry: TA-keyed windowed accumulator with RxQualityAccumulator folding conventions, drain-snapshot delta semantics, bounded table with eviction accounting; pure caller-side logic, GetRxQuality stays device-wide.
  • Headless selftest in the ctest aggregate (ue_rx_attribution_derive) + tests/ue_rx_probe.cpp (per-UE ue.rx JSONL) + on-air two-TX check.
  • Measured: two transmitters at a 4× cadence split attributed separately with a frame-count ratio of exactly 4.0 and distinct per-UE RSSI.

Validation

  • ctest: 20/20 (new: TD v2 round-trip cases, ue_rx_attribution_derive; Python analyzers each carry a --selftest).
  • Hardware: tests/dl_departure_matrix.sh (4 transports incl. PCIe on the remote rig), tests/beacon_update_check.sh (3 generations), tests/ack_txreport_matrix.sh (9 cells), tests/ue_rx_attribution_check.sh — all runs behind the docs tables.

🤖 Generated with Claude Code

…ants, ACK/TxReport matrix, per-UE RX attribution (#261)

Contract 1 — DL departure (docs/dl-departure.md): TD tag v2 adds host_ns
(steady_clock at the send_packet call) so the witness fit measures the
host-submit->air quantity directly; txegress_analyze.py gains tail
percentiles + a machine txeg.verdict; dl_departure_matrix.sh sweeps TX over
J1/J2/J3 USB + 8821CE PCIe against a fixed witness. Measured: transport
floor 11-26 us on every transport, but the p99.9 tail is 1-3 ms channel
deferral everywhere -> fine sub-ms DL slots refuted; M1 schedules
submission-ahead with the measured guard.

Contract 2 — dynamic grant delivery (docs/beacon-grant.md): new
IRtlDevice::UpdateBeaconPayload (in-place content swap for an active
beacon, riding the steer re-download; ~20 lines/gen) and StopBeacon (the
chip beacons autonomously after process death — bench-bitten). Measured GO
on all three generations: 30/30 updates aired per gen, zero excess skips,
zero torn frames (CRC-versioned vendor IE probe), update->air latency
TBTT-quantized (p50 40-72 ms at 100 TU). Static-grant fallback not needed.

Contract 3 — unicast ACK + TxReport matrix (docs/ack-txreport.md):
ack_txreport_matrix.sh sweeps on/retarget/off phases per TX generation
against a SetAckResponder peer, with report coverage via tx.stats +
SW_DEFINE tag gaps. J1/J3 TX perfect (100% ACK, retries ~0.2, arbitrary-MAC
retarget, no-ACK visible per frame); J2 8812BU TX marginal (12-91%
run-to-run) — recorded open. Responders: 8814AU best, 8812AU degraded,
8821AU never closes the loop. txdemo now emits a final tx.stats tally.

Contract 4 — per-UE RX attribution (docs/ue-rx-attribution.md):
src/cell/UeRxAttribution.h — TA-keyed windowed accumulator (drain-snapshot,
RxQualityAccumulator conventions, bounded with eviction accounting), the
seed of M1's UeRegistry; headless selftest in ctest + on-air two-TX check
(frame ratio 4.0 exactly matching the 4x cadence split).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit a7fae8b into master Jul 12, 2026
15 checks passed
@josephnef josephnef deleted the m0-feasibility-contracts branch July 12, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M0 — Feasibility contracts (measure before building the cell)

1 participant