Repository navigation
cell M6: the UE's clock fit survives a jump of its DU's TSF (TsfSync step detection) - #535
Conversation
…detection) The UE's TsfSync fit is cumulative, so one jump of the DU's TSF read as skew and biased every translation for a long time (in the phase-planning runs a ~1.3 ms jump ran a UE's skew to thousands of ppm). Opt-in step detection (TsfSync::SetStepDetect): a beacon > threshold off the fit is held out of it; `confirm` in a row agreeing on one offset are a step, re-anchored via the remote axis (a TSF step is an offset change, never the crystals' rate); held beacons that never agree are dropped. The UE enables it (CELL_UE_FIT_STEP_US 200, CELL_UE_FIT_CONFIRM 6 — longer than the DU's own discipline pass, so a jump the DU undoes itself is never followed). CELL_DU_TEST_TSF_STEP (test only): one TSF jump behind the discipline's back. Rig (testrig scripts/m6_fitstep.sh, 3 runs per case, all pass): - DU restart under an attached UE (a ~-31 s jump): followed, UE skew <= 10 ppm, DL 100 % in the DL area; without detection the skew reads ~10^6 ppm and the DL frames scatter. - A 1300 us jump the DU undoes ~500 ms later: not followed (5 beacons held and dropped), 0 uplinks off-slot. With confirm 3 it was followed out and back, 4-5 of 20 uplinks off-slot. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR Summary by QodoKeep the UE clock fit stable across serving DU TSF jumps
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1.
|
…idates the RX clock, as after any write Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Qodo review addressed in 0071ca3: failed read-back after the test TSF step — the RX clock snapshot is now invalidated under |
|
CI green; Qodo's bug is resolved (0071ca3). The remaining item is the high-level alternative 'reset and reacquire the fit after a confirmed step': that discards the crystals' rate the fit already holds and leaves the UE without a clock until 16 new beacons (~1.6 s); a TSF step changes only the offset, so re-anchoring the remote axis keeps the UE scheduled through it (rig: skew <= 10 ppm across a DU restart). Merging with skip-qodo-gate. |
M6 (#260, EPIC #254): a UE re-attaches to its DU after the DU restarts. This closes the open item from #535. ## Why #535 made the UE's clock follow a restarted DU. But the restarted DU's grant maps begin again at a low epoch on a new superframe count, and the UE's `GrantMapReceiver` refused them as old. The UE kept its old map and was never heard in the new grid (0 uplinks on the rig). ## What - **`GrantMapReceiver::offer_after_restart(m, beacon_sf, window = 16)`**: a map at a **strictly** lower epoch than the newest accepted, but due within 16 superframes of the beacon carrying it, starts the receiver over. - A stale map from before the restart is due far from the new count and is still refused. - The same epoch again is the live map repeated, which is not a restart. A first rig run found this case. - Also adds a `newest_epoch()` getter. - **UE:** - the restart path is asked only for `CELL_UE_REATTACH_SF` (200) superframes after the UE's clock fit confirms a step of the DU's TSF (#535); - on a re-attach it resets what belonged to the old DU instance, as a handover switch does: DL HARQ space, MR generation (the DU checks it against its new count), neighbour list, gap parking, and pending handover IEs; - it logs `cell.ue.reattach`. - **Logging fix:** the UE's `cell.dl.rx` `in_slot` now judges against the DL slot of its grant in force, not the position matching its id. Any CU move showed as DL out of slot (0.05), although the frames were right. - **Docs:** `docs/cell-m6.md` gains a "Re-attachment after a DU restart" section and a fix for the doubled DAPS heading left by #535. RACH-joined UEs across a DU restart are listed as open. - **Selftest (cell_m6):** refused when not near the beacon's count, a pre-restart stale map, another cell's map, accepted and then running on, never back to a higher epoch, and the live map repeated. ## Rig (testrig `scripts/m6_fitstep.sh`: DU1 killed and restarted under its UE, 3/3 pass) | KPI | value | |---|---| | re-attachments per run | 1 (old epoch 7–11 → epoch 1) | | uplink heard by the restarted DU, from 10 sf after its service start | 99.8–100 % of superframes | | of them in slot | 99.8 % | | first in-slot uplink after service start | superframe 0 | | UE DL in its granted slot after the re-attach | 100 % | - **Detection off (counterfactual):** no re-attach, and the restarted DU never hears the UE. - **Regression:** same-channel handover 2/2 plus fallback 1/1, measurement-gap handover 1/1, phase planning 1/1, transient TSF jump 3/3. No spurious re-attach in any of them. **Residual:** grant maps carry no tag (they never did), so the epoch rule orders maps; it doesn't authenticate them. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
M6 (#260, EPIC #254): the UE's clock fit now survives a jump of its serving DU's TSF. This closes the open item from #534.
Why
The UE fits the DU's TSF from DU beacons against its own free-running counter (
src/TsfSync.h). The fit is cumulative, so a single jump in the DU's TSF is read as skew and keeps biasing every translation for a long time. In the #534 runs, one ~1.3 ms jump drove a UE's skew estimate to thousands of ppm, and its measurement gaps then missed.What
TsfSync::SetStepDetect(threshold_us, confirm), opt-in and off by default, so other users are unchanged:confirmheld beacons in a row that agree on one offset (within a quarter of the threshold) are treated as a step: the remote axis is re-anchored (a TSF step changes the offset, never the crystals' rate) and the held beacons join the fit;Steps(),LastStepUs(),Rejected();ShiftRemote(handover) also drops beacons held from the previous transmitter.CELL_UE_FIT_STEP_US200 andCELL_UE_FIT_CONFIRM6, and is logged ascell.ue.fit_step. Six beacons outlast the DU's own discipline pass (500 ms), so a jump the DU undoes itself is never followed.CELL_DU_TEST_TSF_STEP=US@SFadds a TSF jump after the discipline's sample, so it stays on air for one pass.tsf_sync):docs/cell-m6.md, a section on the UE's clock fit across a jump.Rig (testrig
scripts/m6_fitstep.sh, 3 runs per case, all pass)With confirm 3 (an earlier iteration), the UE followed the transient out and back, 3 beacons late each way, which put 4–5 of 20 uplinks off-slot. That is why the default is 6.
Regression on the rig:
fit_stepevent). The cause is not established; DU2's discipline was converging from a −90 ppm acquisition estimate in that run.Not in scope, now listed as open in the docs: after a DU restart, the UE's clock follows but its grant state does not. The restarted DU's grant maps restart at a low epoch on another superframe count, so the UE is not heard in the new grid.
🤖 Generated with Claude Code