Repository navigation
examples/cell: M1 reference DU / UE / witness node + 2-UE on-air test - #518
Conversation
One gNB-DU and N UEs on the src/cell fixed slot grid (#255): - DU: beacons the grant map (CW=0 hardware beacon), sends one hardware-ACKed unicast DL frame per UE in its DL slot (submitted at the slot start on its TSF), measures each UE uplink's arrival on its TSF and closes a per-UE TA loop by republishing the map 3 superframes ahead. - UE: fits the DU TSF from the DU beacons against its free-running counter (TsfSync; TSF writes never move that counter), keeps its own hardware-beacon uplink in its UL slot minus its TA by stepping its TSF (with read-back write compensation), ACKs DL in hardware (SetAckResponder), applies maps by epoch/effective superframe/cell (GrantMapReceiver). - witness: logs every cell frame for tests/cell_2ue_analyze.py, which maps them onto the DU clock and reports in-slot ratios, overlaps, DL lateness and retransmissions alongside the DU's UL errors and the UEs' DL phases. Rig (RTL8852CE PCIe x4 radios, ch149, 120 s): UL 962/962 and 970/970 superframes, arrival error 1.8 / 1.4 us rms, 100% in slot; DL 1156/1156 per UE, 100% in slot, air start 31 us p50 / 41 us p99 after the slot start; witness 0 overlaps over 4837 frames, every frame kind 100% in its own slot. Found on the way: Kestrel has no no-ACK TX path, so an unACKed unicast escalates the data queue's CW to CWmax (next frame waits 0-9.2 ms) — hence hardware-ACKed DL. docs/cell-m1.md. ctest: cell_node (cell.h: frames, counter extension, step rule, TA loop) and cell_2ue_analyze (--selftest). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR Summary by QodoAdd M1 reference cell node and two-UE on-air validation
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1.
|
DU: - The next grant map is staged and replaces the live one only once UpdateBeaconPayload succeeded; on failure the old epoch stays (UEs keep reporting it, sampling continues) and the TA loops are put back on its TAs (TaLoop::set_ta), so the next TA pass retries. - Slot budgets are validated before the radio is opened: DU beacon airtime + 22 us in the beacon slot, the DL frame's full budget (attempts from DEVOURER_TX_RETRY_LIMIT x (airtime + SIFS + ACK) + retry gaps + the CELL_DL_GUARD_US guard, default 500 us per M0) in a DL slot, and the UL target + uplink beacon airtime inside a UL slot. - CELL_UES refuses duplicate ids. - No register reads on the DL send path beyond the one that ends the slot wait (it is the submit stamp); call_us dropped. UE: - A failed WriteTsf is not counted as a step (cell.ue.step_fail; the next pass re-measures). - cell.ue.done reports the DU superframes the UE was listening. Analyzer: - A witness frame is in its own slot only if its whole air interval is. - Every UE the DU's grid schedules must have a DL result (a missing stream fails the DL verdict); DL sent is counted over the UE's own listening window, so a lost final frame shows. - A supplied witness that cannot be mapped (< 8 DU beacons) is an error. - --selftest covers each. Rig re-run (120 s, ch149): UL 960/961 + 972/972, 1.7 / 1.4 us rms, in slot 100%; DL 1155/1155 per UE, in slot 100%; witness 0 overlaps, all kinds 100% in their own slot, DL air start 23-24 us p50 after the slot start. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Addressed all ten Qodo findings in 3b6440c:
The analyser
ctest passes on all three boxes. |
The witness overlap total compared each sorted air interval only with its predecessor, so a long frame overlapping two later frames counted once. A sweep over the frames still on air counts each pair; --selftest covers the one-over-two, touching and three-way cases. The rig run still reads 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Finding 11, overlap totals undercount pairs, is fixed in 2055b76. The witness overlap count is now a sweep over the frames still on air, so a long frame that overlaps two later frames counts both pairs. |
|
Applying |
…on (#519) Part of #256 (M2) / #254. This is the headless core. Wiring it into `examples/cell` and the attenuated-UE rig test come next, on top of #518. **Why software HARQ.** Kestrel has no per-frame TX report (no CCX path), so the DU cannot see a hardware ACK's outcome. On any chip, a no-ACK "TX complete" proves only that the frame aired. So delivery is what the UE reports. - **`src/cell/Harq.h`:** - **`HarqFeedback`:** a cumulative `base` plus a 32-seq bitmap. - **`HarqSender`:** - each frame gets a 32-bit seq and a process id; - ACK means below base or a set bitmap bit; - NACK means a later seq was received. It applies only to attempts at least `nack_min_age_sf` older than the report, because a UE beacon update lands 1–2 beacons late, so a fresh retransmission isn't NACKed by a stale report; - timeout, attempt limit and drop; - due retransmissions go before new data, oldest first; - the in-flight seq span stays below 32, so a report always covers every in-flight frame. Each frame carries the window low edge, so the receiver can abandon gaps the sender dropped. - **`HarqReceiver`:** duplicate suppression, base/bitmap upkeep, catch-up after missing a low-edge move, and 32-bit wrap. - **`src/cell/LinkAdapt.h`:** sets the grant's DL rate (6–54 Mb/s). - It steps down one rate when the retransmission ratio reaches 20%, then holds for 5 windows. If the UE-reported DL SNR no longer supports the current rate, it drops straight to the best supported one. - It steps up one rate after 3 clean windows, if the SNR reaches the next rate's threshold + 2 dB. - The thresholds are starting points; the loss rule corrects them on air. - **`tests/cell_m2_selftest.cpp`** (ctest `cell_m2`) covers the rules above, plus a **30%-loss channel simulation**: HARQ delivers 400/400 exactly once, against 280/400 for the one-attempt baseline. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… (rig: attenuated UE recovered) (#521) Part of #256 (M2) / #254. This wires the merged M2 core (#519, `Harq.h` + `LinkAdapt.h`) into the M1 cell (#518). Details are in `docs/cell-m2.md`. **Downlink (DL) frames** carry the HARQ seq, process id, attempt number and the window low edge. **UE uplink beacon IE** carries the UE's `HarqFeedback` (base, bitmap, and `gen_sf`, the superframe it was generated in) plus its mean DL SNR. The UE republishes it whenever any of these change. **DU (the cell):** - retransmits from that feedback (`CELL_HARQ`, plus attempt and timeout knobs); - adapts each UE's DL rate (`CELL_LA`), publishes it in the grant, and retries a rate whose map failed to publish; - judges a link-adaptation window only when the UE was present for ≥ 75 % of its superframes, and for the window before too. Without this gate, drops at the edges of a UE's presence stepped the control UE down on the rig. **Test knobs:** `CELL_ATTEN_UE` / `CELL_ATTEN_DB` apply a per-packet radiotap power delta on one UE's DL, standing in for path loss. `CELL_DL_RATE` sets the starting rate. **Analyser:** `cell_2ue_analyze` gains a `harq` section: originated vs delivered-as-new per UE, retransmissions, duplicates and rate steps. It has `--selftest` cases. ## Measured on the rig RTL8852CE over PCIe, ch149, 120 s per arm. DL starts at 54 Mb/s. UE1's DL is sent 10 dB down; UE2 is the control. | arm | UE1 delivered | UE1 rate | UE2 delivered | UE2 rate | |---|---|---|---|---| | baseline | 560 / 1156 (48 %) | 54 | 1155 / 1155 | 54 | | HARQ | 481 / 547 (88 %) | 54 | 1156 / 1156 | 54 | | HARQ + LA | **1136 / 1136 (100 %)** | 54 → 6 | **1154 / 1154** | **54** | The UL was 100 % in slot in every arm. **Caveat found:** Kestrel's per-packet power is a global BB target, so the next hardware beacon follows it (as `AdapterCaps.h` documents). The test therefore attenuates the UE whose DL is *not* the last frame before the DU's TBTT. ctest passes 87/87 on the rig. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #255 (M1) / #254. Builds on #516 (
SlotGrid,GrantMap).What it is
examples/cellis one binary,CELL_ROLE=du|ue|witness, running one DU and N UEs on the fixed slot grid. Details are indocs/cell-m1.md.TsfSync); TSF writes never move that counter, so the fit survives the UE's own steps;SetAckResponder);GrantMapReceiver).tests/cell_2ue_analyze.pymaps them onto the DU clock.Measured on the epic-254 rig
Four RTL8852CE radios over PCIe (DU, two UEs on other boxes, a witness), ch149, 120 s, 20 s TA settle:
The witness saw 0 overlaps in 4837 frames, with beacons, DL and UL 100 % in their own slots. The verdict is pass.
Found on the way
Kestrel's TX descriptor has no no-ACK path. An unACKed unicast counts as a failure, so the data queue's CW climbs to CWmax and the next frame waits a uniform 0–1023 slots (0–9.2 ms).
Tests
cell_node: frame build/parse and rejects (including the 16-bit UE field, which the selftest caught being truncated), counter extension, the UE step rule and its TA sign convention, the TA loop.cell_2ue_analyze --selftest: an in-slot run passes; a UE uplink moved into the other UE's slot is flagged and counted as an overlap; a missed uplink and a DL retransmission are counted.Open (in the doc)
🤖 Generated with Claude Code