Skip to content

cell M6: the UE's timing advance in the handover command (F1 v17) - #573

Merged
josephnef merged 2 commits into
masterfrom
feat/m6-ho-ta
Nov 13, 2026
Merged

josephnef merged 2 commits into
masterfrom
feat/m6-ho-ta

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Optional M6 follow-up on #566: carry the UE's timing advance in the handover command.

What changes

  • F1 v17: UeMeas.ta_us, each DU's own UEs' TA (refreshed on the TA cadence), and HoCommand.ta_us.
  • CU (tools/ran-controller): puts the source DU's reported TA in the command (RAN_HO_TA, on by default; logged in ran.ho_cmd).
  • DU (examples/cell): the target starts the arriving UE's TA loop and its first grant at the carried value (cell.ho.ta_carried).
  • cell_m6 selftest: the v17 handover and report round-trips.
  • docs/cell-m6.md: new section "The timing advance in the handover command".

Rig (testrig scripts/m6_ho_ta.sh, UE1 cell 1 → 2 forced after the source's TA converged, 3 interleaved A/B rounds, the target's in-slot uplinks)

TA carried (µs) arrival error, first 40 (median, µs) uplinks until within 50 µs
RAN_HO_TA=1 −426, −426, −425 −2, −1, −1 0, 0, 0
RAN_HO_TA=0 0 −427, −428.5, −425.5 98, 84, 79
  • Slot hits are equal (38–39/40) on this layout: 427 µs still lands in the UL slot. The gain is guard margin from the first frame.
  • An A3 firing ~4 s after attach carries a half-converged TA (one loop step). The value is applied exactly, but it is only as good as the source's loop.

ctest: 95/95 pass.

🤖 Generated with Claude Code

Each DU reports its own UEs' TA (UeMeas.ta_us); the CU carries the
source's value in HoCommand.ta_us (RAN_HO_TA, default on); the target
starts the arriving UE's TA loop and first grant at it
(cell.ho.ta_carried). Rig (testrig m6_ho_ta.sh, 3 A/B rounds): carried,
the target's first uplinks land within 1-2 us; not, ~427 us off and
79-98 uplinks to within 50 us. Slot hits equal on this layout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Carry source UE timing advance through F1 handover

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Carry each source UE’s timing advance in F1 v17 reports and handover commands.
• Initialize the target’s TA loop and first grant from that value to reduce arrival error.
• Add a default-on CU toggle, protocol round-trip tests, and measured handover results.
Diagram

sequenceDiagram
    participant UE
    participant Source as Source DU
    participant CU as RAN controller
    participant Target as Target DU
    UE->>Source: Uplink arrivals
    Source->>Source: Update TA loop
    Source->>CU: F1 Report with ta_us
    CU->>Target: F1 HoCommand with ta_us
    Target->>Target: Seed TA loop
    Target->>UE: First grant with carried TA
Loading
High-Level Assessment

Piggybacking the source’s existing TA estimate on F1 reporting and handover is appropriate: it avoids a new UE exchange or waiting for the target’s TA loop to converge. Starting at zero remains available through RAN_HO_TA=0; early handovers can still carry an unconverged source estimate.

Files changed (6) +96 / -12

Enhancement (4) +44 / -6
main.cppReport source TA and seed target handover timing +21/-2

Report source TA and seed target handover timing

• Publishes each own UE’s TA on the TA cadence for inclusion in F1 reports. Stores TA from an incoming handover command, applies it to the arriving UE’s TA loop and first grant, and emits a carry event for nonzero values.

examples/cell/main.cpp

F1.hAdd TA fields to F1 v17 messages +8/-2

Add TA fields to F1 v17 messages

• Bumps the F1 version to 17 and adds signed microsecond TA to own-UE report measurements and handover command encoding and decoding.

src/cell/F1.h

HoCommand.hRepresent source timing advance in handover commands +5/-0

Represent source timing advance in handover commands

• Adds a zero-defaulted, signed microsecond TA field for initializing the target’s timing state.

src/cell/HoCommand.h

main.cppCarry reported source TA into handover commands +10/-2

Carry reported source TA into handover commands

• Adds default-on RAN_HO_TA configuration, copies the matching UE’s TA from the source report into the command when enabled, and includes the selected value in ran.ho_cmd logs.

tools/ran-controller/main.cpp

Tests (1) +13 / -2
cell_m6_selftest.cppTest F1 v17 TA round-trips +13/-2

Test F1 v17 TA round-trips

• Checks positive and negative TA values in report measurements and a negative value in a handover command. Adjusts a malformed home-identity test offset for the command’s additional two bytes.

tests/cell_m6_selftest.cpp

Documentation (1) +39 / -4
cell-m6.mdDocument carried TA and handover measurements +39/-4

Document carried TA and handover measurements

• Explains the F1 v17 TA path and revises the earlier description of target TA starting at zero. Records three enabled/disabled rig comparisons, including arrival error, convergence time, unchanged slot hits on this layout, and the limitation of early handovers.

docs/cell-m6.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Failed beacons send uncommitted timing ✓ Resolved
Description
ta_report is filled from the pending TA loop before the updated grant map is published, and it is
not restored when publication fails or a pending map change is discarded. A subsequent report can
therefore give the controller a TA that the source UE was never granted, which the next handover
applies at the target.

2. Rapid handovers lose the carried timing ✓ Resolved
Description
The target installs cmd.ta_us in its grant and TA loop, but ta_report is populated only at the
next enabled TA cadence. If that UE moves again before the cadence—or CELL_TA=0 on the
intermediate cell—its report leaves ta_us at zero and the next target starts without the advance
it was using.

3. New UEs inherit another UE's timing 🐞 Bug ≡ Correctness
Description
ta_report retains entries until the next TA cadence, while report construction joins them to
measurements using only the reusable numeric UE ID. If one UE is released and another receives its
ID before that cadence, the new UE's fresh measurement can carry the former UE's TA into a handover.
Code

examples/cell/main.cpp[R1891-1893]

+            for (auto &q : r.meas) /* each own UE's TA now (carried on a handover, v17) */
+              if (auto t = ta_report.find(q.ue); t != ta_report.end())
+                q.ta_us = t->second;
Evidence
The registry frees an ID and resets its context when a different UE is admitted, but neither action
removes the new report cache entry. Until its next cadence replacement, report construction can
attach the old entry to a fresh measurement under the reused ID, and the controller carries it.

examples/cell/main.cpp[2853-2857]
examples/cell/main.cpp[2729-2741]
examples/cell/main.cpp[3102-3114]
src/cell/UeRegistry.h[119-161]
examples/cell/main.cpp[1714-1719]
examples/cell/main.cpp[1890-1893]
tools/ran-controller/main.cpp[1116-1120]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The report cache can associate a released UE's TA with a different UE that reuses its ID.
## Fix Focus Areas
- examples/cell/main.cpp[1891-1893]
- examples/cell/main.cpp[2853-2857]
- examples/cell/main.cpp[3102-3114]
## Recommended Fix
Invalidate the cached TA when an ID is released and initialize it from the new UE's committed grant when that ID is admitted. Ensure report construction cannot pair a measurement with a cache entry from a previous holder of the ID.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

…ish, arrival, ID reuse)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@josephnef

josephnef commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

All three findings were valid, and they share one cause: ta_report was filled from the pending TA loop on the TA cadence. Fixed in head a76b48a4: the report now takes each UE's TA from the grant map when that map goes on air (map = next), so:

  1. A failed or deferred publish never reaches the report. The cache is rebuilt only on a committed map.
  2. A carried arrival is reported as soon as its grant is on air, independent of the TA cadence or CELL_TA=0.
  3. A released ID drops out with its grant, and a re-used ID gets its new holder's grant TA.

Rig re-check (testrig m6_ho_ta.sh --rounds 1): carried −423 µs, the target's first uplinks within 2 µs, 0 uplinks to settle; without the TA, 75. ctest: 95/95 pass.

@josephnef

Copy link
Copy Markdown
Collaborator Author

Finding 3 (ID reuse) is resolved in a76b48a4. Its text still describes the cadence cache ("retains entries until the next TA cadence"), which no longer exists. ta_report is now rebuilt in full from the committed map at map = next:

  • A release at a commit (to_release, main.cpp:3106) follows the map that dropped that ID's grant, so the cache has no entry for it.
  • The other two release_id paths are a staged-only cancel (2129) and an arrival expired before its publish (2682). Both apply to IDs never granted in a map on air, so they never had an entry.
  • A re-used ID gets an entry only when the map with its new holder's grant commits, and that entry is the new holder's TA.

No report can pair a measurement with a previous holder's TA.

@josephnef josephnef added the skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision) label Nov 13, 2026
@josephnef
josephnef merged commit daf5299 into master Nov 13, 2026
57 of 59 checks passed
@josephnef
josephnef deleted the feat/m6-ho-ta branch November 13, 2026 17:07
@josephnef

Copy link
Copy Markdown
Collaborator Author

Correction to my earlier comment: the one-round "rig re-check" I gave for the review fix did not run the fix. The J6412s still had the cell binary built before it. The testrig scripts rebuild only the CU, and the boxes had not been redeployed. That run said nothing about the fix.

Re-checked on the merged code (daf52996, deployed to all three boxes, m6_ho_ta.sh --rounds 2):

  • TA carried: −426 and −425 µs. The target's first uplinks land within 2.5 and 2.0 µs (median), with 0 uplinks to settle.
  • TA not carried: the target starts −426 µs off, and takes 96 and 68 uplinks to settle.

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

Labels

skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant