Reads mac->antenna_state to count active TX ports via
__builtin_popcount(nr_get_tx_antenna_bitmask). Caps the partner-search
while-loop bound to (active_tx - 1), so:
active_tx = 4 -> up to 4-way MU-MIMO (3 partners)
active_tx = 3 -> up to 3-way (2 partners)
active_tx = 2 -> up to 2-way (1 partner)
active_tx = 1 -> SU-MIMO only (0 partners)
This couples antenna muting (the existing PHY-side socket control on
port 9999) to spatial parallelism in the MU-MIMO scheduler. Muting a
chain reduces the codebook's orthogonal codeword count, which is the
right semantic for our antenna-muting research: muting reduces the
number of UEs co-scheduled per slot, not the per-UE rate.
Verified runtime behavior on rfsim (30s windows per state):
TX mask 0x0F: 4-way 952 3-way 484 2-way 333 (full 4-TX)
TX mask 0x03: 4-way 2 3-way 8 2-way 5935 (2-TX -> 2-way)
TX mask 0x01: 4-way 0 3-way 0 2-way 11 (1-TX -> SU-MIMO)
Antenna-control commands tested via existing test interface:
echo "SET TX MASK 0x03" | nc -w 2 localhost 9999
Extends the partner search from 1 partner to up to MAX_MU_MIMO_PARTNERS=3
partners, totalling 4 UEs per slot — one per orthogonal x2 class of the
4-port Type I rank-1 codebook.
Orthogonality test relaxed: any distinct x2 (with same x1) is accepted.
The 4 codewords w_(x1,n) for n=0..3 are mutually orthogonal because
they share spatial vector v_x1 and differ only in co-phasing j^n;
<w_a, w_b> = 0 for any a != b.
Each partner gets a distinct DMRS port:
leader -> port 1000 (CDM group 0, mask 0x1)
partner 0 -> port 1001 (CDM group 0, mask 0x2)
partner 1 -> port 1002 (CDM group 1, mask 0x4)
partner 2 -> port 1003 (CDM group 1, mask 0x8)
numDmrsCdmGrpsNoData=2 reserves both DMRS groups.
Class-uniqueness tracked via classes_used bitmask: each successive
partner must be from an unused x2 class.
Pair log: "[nakaolab MU-MIMO] F.S N-way pair RNTI A (x2=0) + RNTI B
(x2=1) + ... on RBs [..]"
Result on rfsim with 9 UEs cycling 4 classes:
8069 total pairings observed
920 4-way (all 4 classes co-scheduled)
2756 3-way
4404 2-way
Phase 1 (rfsim): per-UE deterministic channel injection.
Each UE container sets OAI_UE_CHANNEL_CLASS=k (k in {0..3}). The rfsim
no-chanmod sample-mixing path applies a 4x4 channel:
H^(k)[r,a] = H_orig[r,a] * w_k[a]
where w_k is the k-th codeword of the Type I 4-port rank-1 codebook
(N1=2, N2=1). Multiplying by w_k rotates the channel's optimal PMI
direction to k while preserving the original symmetric kernel's
full-rank reception (so SSB sync still works on all UE classes).
10 UEs cycle through 4 classes (UE_index mod 4), giving 4 mutually
orthogonal PMI populations.
Phase 3 (scheduler): pf_dl partner search now scans connected_ue_list
(not just UE_sched). After leader UE A is allocated, iterate over all
connected UEs, run update_dlsch_buffer/dlsch_to_schedule per candidate,
and pair the first orthogonal-PMI partner that has data + free CCE/PUCCH.
This bypasses PF time-multiplexing — multiple UEs needn't be in
UE_sched simultaneously for MU-MIMO to fire.
Per-slot pairing tag (mumimo_paired_slot in NR_UE_sched_ctrl_t) prevents
double-allocation: incremented per pf_dl call, leader and partner both
tagged on success; subsequent leader iterations skip already-tagged UEs.
Results on rfsim with 10 UEs cycling 4 classes (10-min run):
17000+ pairings observed
Distribution by (leader_class, partner_class):
0->2: 6261 (XOR=2)
3->1: 4382 (XOR=2)
2->0: 2765 (XOR=2)
1->3: 1920 (XOR=2)
All pairs strictly orthogonal per Type I codebook rank-1 rule
(same x1, x2 differ by 2). 2-way MU-MIMO working end-to-end.
Also includes:
- ri_bitlen=0 fix in evaluate_cqi_report (uninitialized r_index)
- OAI_MU_MIMO=1 env to enable, OAI_MU_MIMO_RELAXED=1 for relaxed test
- Diagnostic logs every 200th call: ENTER + per-skip-reason
Two changes on top of the initial MU-MIMO patch (commit 766d0a4):
1. CSI-parse fix in evaluate_cqi_report path: when maxMIMO_layers=1
(and thus ri_Restriction has only one bit set, ri_bitlen=0),
evaluate_ri_report was being skipped, leaving r_index=-1 which
trips the DevAssert(ri>=0) inside evaluate_cqi_report. Fix: when
ri_bitlen=0, pick the single allowed RI directly from the
restriction bitmap. Affects all three reportQuantity branches
(cri_RI_PMI_CQI, cri_RI_LI_PMI_CQI, cri_RI_CQI).
2. MU-MIMO scheduler: relaxed-orthogonality flag and broader
partner search:
- OAI_MU_MIMO_RELAXED=1 bypasses the strict (same-x1, x2-diff-2)
orthogonality test for testbed validation on rfsim where the
symmetric channel makes all UEs report identical PMI.
- Partner search now scans the full UE_sched (skipping leader and
already-paired entries) instead of only iterator+1; this finds
candidates regardless of PF position.
- Diagnostic LOG_W messages on partner-search entry and per-skip
reason (capped to 20 events to avoid log flood).
Findings on this w17 build with 6 UEs at 5Mbps DL each:
- Patch compiles, deploys, gNB does not crash, scheduler reaches
partner-search block on a fraction of slots.
- However eligible_others=0 in every observed partner-search entry:
PF puts only one UE in UE_sched per pf_dl call almost always,
even at cell saturation. The other attached UEs are skipped
earlier (likely HARQ pids pending feedback). So the partner
search has nothing to pair.
- Architectural conclusion: forcing concurrent multi-UE scheduling
on OAI's PF + finite-HARQ scheduler likely needs scanning
connected_ue_list (not just UE_sched), and may need broader
refactoring to pre-allocate slot resources for MU-MIMO pairs.
Adds opt-in MU-MIMO downlink co-scheduling to the production
proportional-fair scheduler (pf_dl). When OAI_MU_MIMO=1 is set in
the gNB process env, after the leader UE is allocated we look ahead
in the PF-sorted candidate list for an orthogonal-PMI partner. If
found and the partner can secure its own CCE+PUCCH, both UEs share
the same RB allocation with distinct DMRS CDM groups (port 1000 vs
port 1001) and are forced to the same MCS/TBS.
Orthogonality test (4-port Type I rank-1 codebook, N1=2, N2=1):
same x1 (same beam), x2 differing by 2 (co-phasing pairs {0,2} or
{1,3}). Both UEs must report rank-1.
Default OFF — set OAI_MU_MIMO=1 to enable. Builds on the upstream
FAPI/PHY MU-MIMO plumbing (commits 3707d52c95, 498d241d75,
9952996c5d) which carries antenna-port and DMRS-CDM metadata down
to the PHY transmit path.
Brings the O-RAN compliant antenna muting feature (originally introduced
on the 'new' branch) onto the latest upstream 2026.w17 tag.
Files added:
openair1/PHY/NR_TRANSPORT/nr_antenna_state.{c,h}
openair1/PHY/NR_TRANSPORT/nr_antenna_control.{c,h}
Files modified (integration hooks):
CMakeLists.txt, executables/nr-gnb.c
openair1/PHY/INIT/nr_init.c, openair1/PHY/defs_gNB.h
openair1/PHY/MODULATION/{beamforming.c,nr_modulation.h,nr_beamforming.c}
openair1/SCHED_NR/nr_ru_procedures.c
openair2/LAYER2/NR_MAC_gNB/mac_proto.h
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_{primitives,dlsch,uci}.c
Conflicts resolved:
- defs_gNB.h: kept upstream's new headers + appended antenna_state.h
- nr_init.c: dropped reference to upstream-removed nr_srs_info init,
kept nr_init_antenna_state() call
- beamforming.c: upstream moved nr_beam_precoding into new file
nr_beamforming.c; nr_beam_precoding_with_muting added there with
upstream's signature (no slot param)
- nr_modulation.h: aligned _with_muting decl to upstream signature
- nr_ru_procedures.c: muting check applied around upstream's new
fft_shift in nr_feptx, and around per-port memcpy in nr_feptx_prec
- gNB_scheduler_uci.c: SINR-based MCS hint uses get_dl_nrOfLayers(NULL,
sched_ctrl, ...) since nrmac is not in scope at the call site
- mac_proto.h: get_dl_nrOfLayers signature updated to take gNB_MAC_INST*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>