Skip to content

EPIC: 5G-NR-style scheduled RAN on devourer (multi-user + multi-cell) #254

Open

Description

@josephnef

Umbrella / tracking issue for building a 5G-NR-style scheduled Radio Access Network on devourer: time-synced AP cells serving multiple scheduled UEs (robots/drones), with per-UE timing advance, coordinated scheduling, and make-before-break handover — riding the bench-proven per-link primitives and modelled on 5G NR design decisions (not LTE).

docs/multi-ap-cellular.md promotes the architecture; this EPIC turns it into a step-by-step build. The per-milestone write-ups are docs/scheduled-mac.md (M0) and docs/cell-m1.md … docs/cell-m6.md. Rig orchestration and results are in the testrig repo (scripts/m*_*.sh, results/).

Status (2026-11-12)

Built and run on the testrig: three Topton J6412 boxes, each with two RTL8852CE (Kestrel) over PCIe/vfio, plus an N300 hub that is PTP grandmaster and CU host (a star of direct i226 links, no switch in the timing path). On the rig:

  • three PTP-locked cells, on three channels, with UEs scheduled in fixed slots;
  • HARQ and link adaptation;
  • RACH join and departure;
  • CU-coordinated schedules and phase planning;
  • measurement gaps;
  • conditional handover, and DAPS on one channel and across channels;
  • per-cell keys;
  • UL and DL sequence handling across handovers.

What remains:

The primitives (this is the multi-user/multi-cell layer)

Bench-proven and library-stable:

5G-NR mapping

5G NR Our realization
gNB (cell) a devourer AP node
CU / DU split DU = AP node (src/cell/ MAC + examples/cell/): per-cell scheduling, HARQ, TA. CU = tools/ran-controller/: multi-cell coordination, mobility, admission
SSB the hardware beacon (StartBeacon)
DCI / grant (PDCCH) beacon-carried grant map {ue, dl_slot, ul_slot, rate, pwr, ta}, with an epoch and its effective superframe (src/cell/GrantMap.h, updated with UpdateBeaconPayload)
Timing advance a per-UE TA loop (TaLoop). The UE's uplink is its hardware beacon, steered through its TSF on Kestrel; PinBeaconTbtt can't hold an offset on TSF-locked parts
Numerology / mini-slots flexible slot lengths + low-latency mini-slots
Bandwidth Part (BWP) narrowband 5/10 MHz via FastSetBandwidth
Dynamic TDD per-cell flexible DL/UL split
QoS flows / 5QI per-UE 5QI class → HARQ budget, DL discard timer, link adaptation, admission priority (src/cell/CellScheduler.h; the fixed grid gives each UE one slot pair, so not slot allocation)
2-step RACH fast contention join (src/cell/Rach.h)
F1 interface DU↔CU UDP control link (src/cell/F1.h, keyed)
Xn interface direct DU↔DU coordination (inter-cell, M5/M6)
TSN / 802.1AS PTP backhaul: each DU holds its beacon grid to its PHC (src/cell/TsfDiscipline.h service mode)
Measurement gaps idle and suspend gaps for inter-frequency measurement (src/cell/MeasGap.h), with phase planning by the CU (src/cell/PhasePlan.h)
CHO an A3 condition at the CU on filtered measurements (offset, hysteresis, time-to-trigger), two-phase command, TSF continuity, conditional fallback (src/cell/Handover.h, HoCommand.h)
DAPS handover UL duplication with CU dedup, DL duplication to the target. Across channels: sequential UE visits to the target's channel, which answers with DL
PDCP SN / status report UL SeqDedup; DL DlStreamRx; the UE's DL status report in its beacon (UeDlStatus), so a target sends only what the UE lacks
Security context (802.11r / NR key derivation) per-cell, area and UE keys from one master; the CU wraps the target cell's key per handover (src/cell/Keys.h)

Architecture boundary (enforced in every milestone)

  • src/cell/ — the per-cell (DU) scheduled-MAC library (reusable):

    • SlotGrid, GrantMap, UeRegistry, UeRxAttribution, Harq, LinkAdapt, Rach (M1–M3);
    • TsfDiscipline, SyncHealth (M4);
    • F1, Coord, PhasePlan (M5);
    • Handover, HoCommand, MeasGap, Keys (M6).

    It adds nothing to the radio core unless a bench test proves userspace can't. (CellScheduler: 5QI classes; TimingAdvance: the per-UE TA loop, held per UE in UeRegistry's UeContext.)

  • examples/cell/ — a reference gNB-DU + UE node.

  • tools/ran-controller/ — the CU / backhaul app (multi-cell scheduler, coordination, CHO/DAPS). Distributed software over the F1 control link (with direct Xn DU↔DU coordination where lower latency helps); never touches the radio. (Note: this is an NR-inspired centralized facility scheduler, not a faithful 3GPP CU — it owns scheduling, not PDCP/RRC.)

Rule: logic that would still make sense with a different radio underneath is NOT in the radio core.

Milestones (coarse; each demoable, implemented step by step)

Phase 0 — feasibility contracts (measure, don't assume):

Phase A — single gNB-DU (one cell, N UEs):

Phase B — multi-cell RAN (CU + mobility):

Dependency chain: M0 → M1; M1 → M2, M3, M4; M4 → M5 → M6 → #563 (test realism).

Cross-cutting deliverables (from the architecture review)

  • Slot-budget accounting — a valid slot = PSDU airtime + PHY preamble + SIFS/ACK + retry allowance + A-MPDU duration + radio turnaround + retune/settling + timing guard. Payload size alone is not a budget. Done in src/cell/SlotGrid.h (A-MPDU airtime not modelled); a DL frame that would start past its slot's guard is not aired.
  • A versioned on-air protocol envelope — protocol version, cell+UE identity, superframe/slot number, schedule epoch, packet/HARQ sequence, integrity + replay protection. Done: kCellProtoVersion (v5), cell-key tags on every cell frame, area tags for neighbours, replay floors. Integrity is on only with a key configured.
  • Fault detection — watchdogs for lost PTP sync, stalled beacon engine, missed schedule epochs, CU/backhaul outage, excessive TX-queue delay, over-budget retunes. Partly done:
    • loss of PTP sync (SyncHealth, auto-suspend), CU outage (DUs go autonomous), stale or late schedules (acks), and late DL frames are handled;
    • a stalled beacon engine is detected only indirectly, through the UEs' uplinks, and there is no general over-budget-retune alarm.
  • KPI-based validation — report distributions not averages: deadline-miss ratio, slot-arrival phase, collision rate, handover interruption, DAPS duplicate gain, per-UE fairness, clock holdover, control-plane update latency. Mostly done in the docs/cell-m*.md tables; still missing: per-UE fairness, a stated DAPS duplicate gain and clock holdover.

Out of scope / relationships

Standing risk register (status 2026-11-12)

  • Deterministic downlink (M0/M1, headline): retired. Kestrel's DL departure was measured in M0, and in M1 the DL air start was p50/p99 31/41 µs after its slot start. A rare multi-ms send_packet tail remains, so a RACH slot is kept away from the next beacon.
  • No atomic beacon update (M0/M1): retired. UpdateBeaconPayload exists on every family and is frame-atomic on air (not atomic against the TBTT; it airs on the next or next-but-one TBTT). The grant map rides the beacon with an epoch and its effective superframe.
  • Unicast ACK per node, not multi-MACID (M0/M2): retired. One responder per node; the 8852CE responder measured 1036/1036. Kestrel has no per-frame TxReport, so HARQ runs on explicit UE feedback.
  • GetRxQuality is device-wide: addressed differently. UeRxAttribution exists, but link adaptation uses the SNR the UE reports for the downlink.
  • send_packet UL has no sub-slot air-departure control: followed. The UL is the UE's hardware beacon, steered through its TSF: 1.4–1.8 µs rms.
  • ~ms FastRetune: measured, 23.6 ms on the 8852CE. Different-channel DAPS is sequential (visits), with measurement gaps budgeted at 26 + 2 ms.
  • Timing advance corrects clock/actuation phase, not propagation: as designed.
  • PCIe vs USB sync tiers: the tight tier is done, with loss-of-sync auto-suspend; the coarse tier is unmeasured (Coarse-tier (USB) DU on a PTP node — the M4/M5 items split out of the PCIe tier #565).
  • A3/CHO tuning: hysteresis and time-to-trigger on filtered quality. In the selftest the filtered A3 made one handover per crossing (raw RSSI: 30.6); on the rig, 0 in the hover case. Fading is still untested (M6 test realism — a moving-UE handover bench (conducted path-loss matrix, then an over-the-air walk) #563).
  • Half-duplex UE turnaround: budgeted in the grid (turnaround_us).

Architecture review folded in: this epic and its milestones were revised per an external review — M0 added, terminology corrected (F1/Xn), the multi-MACID framing corrected, M1 numerology restricted to fixed, and cross-cutting deliverables added. Refreshed 2026-11-12 against what was built.

Activity

  1. josephnef commented on Aug 4, 2026

    @josephnef
    CollaboratorAuthor

    Test-rig hardware spike filed: #381 — ARM cell-node feasibility for warehouse scale-out (PTP quality on ARM MACs, vfio/SMMU gate for the PCIe transport, and the unmeasured PinBeaconTbtt-over-USB discipline number that decides whether coarse-tier cells need PCIe at all). Doesn't gate M0–M6; informs the fleet platform choice for Phase B deployment.

  2. josephnef commented on Sep 28, 2026

    @josephnef
    CollaboratorAuthor

    A concrete external customer for Phase A/B has turned up, with the traffic direction reversed from the warehouse case: a film-set multi-camera preview link — 2–25 cameras (HDMI/SDI in, hardware H.265) streaming 1080p60 into one video village at ~100 ms, 1–1.5 km. Almost all traffic is uplink (camera → tent). Conceptual write-up on branch docs/film-set-preview-link: docs/film-set-preview-link.md.

    How it lands on the milestones:

    • M0 already sizes it. Submit→air guard 1–3 ms p99.9 → ~5 ms slots. One 20 MHz channel carries 1–2 cameras at a kilometre-ish MCS, so a per-channel cell is 2–4 UEs with a 10–20 ms superframe — inside the budget. 25 UEs on one channel would be a 125 ms superframe, which is the independent confirmation that the product is a 10–25-channel plan with one ground radio per channel, not one big cell.
    • M1 is exactly the per-channel cell: 1 ground radio + 1–2 camera UEs, TDMA'd uplink where CSMA between two mutually hidden cameras would not be collision-free.
    • M4 + M5 are the ground station: 10–25 co-located cells on one TSF/PTP timebase, the CU owning channel plan, admission and per-camera rate/power. This is the layer that turns 25 wfb-ng-style links into a product. F1/Xn here is literally the tent's Ethernet.
    • M6 has a literal customer: a Steadicam operator walking out of one sector antenna's lobe into another.
    • SPIKE: ARM cell-node feasibility for warehouse-scale RAN (PTP quality, vfio/SMMU, USB-tier PinBeaconTbtt discipline) #381 — the camera box is the ARM cell node, and this case needs only the coarse tier (ms guards, frequency separation), not the µs PCIe tier. The USB-tier PinBeaconTbtt discipline number is the one that decides whether the camera box can carry a cell's timebase itself.

    Two things the roadmap does not cover that this customer needs, filed separately: a multi-receiver / multi-channel ground-station story (nothing measured beyond 2 TX → 1 RX today), and the HDMI/SDI ingest on the encoder side (ecosystem-wide gap, not a devourer item). Range at 1 km is unmeasured everywhere in the project and is the dominant risk; the write-up says so in the same sentence as every number.

  3. 23 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions