mac: Phase 4 - cap MU-MIMO group size to active TX antenna count

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
This commit is contained in:
2026-04-30 08:27:47 +09:00
parent e1f315bee9
commit f8178cede9
@@ -955,7 +955,23 @@ static void pf_dl(gNB_MAC_INST *mac,
int partner_allocs[MAX_MU_MIMO_PARTNERS] = {-1, -1, -1};
int n_partners = 0;
if (mumimo_enabled() && remainUEs[beam.idx] > 0) {
/* nakaolab Phase 4: cap MU-MIMO group size to the active TX antenna count.
* When the antenna controller mutes some TX chains (e.g., 4->2 or 4->1), the
* effective number of orthogonal codewords in the codebook shrinks. Each
* co-scheduled UE consumes one spatial layer = one TX port. So:
* active_tx = 4 -> up to 4 UEs (3 partners), 4-way MU-MIMO
* active_tx = 2 -> up to 2 UEs (1 partner), 2-way MU-MIMO
* active_tx = 1 -> SU-MIMO only (0 partners) */
int max_partners_now = MAX_MU_MIMO_PARTNERS;
if (mac->antenna_state) {
const uint8_t tx_mask = nr_get_tx_antenna_bitmask(mac->antenna_state);
int active_tx = __builtin_popcount(tx_mask);
if (active_tx < 1) active_tx = 1;
if (active_tx - 1 < max_partners_now)
max_partners_now = active_tx - 1;
}
if (mumimo_enabled() && remainUEs[beam.idx] > 0 && max_partners_now > 0) {
const uint8_t leader_x2 = iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
uint8_t classes_used = 1 << leader_x2;
static int diag_count = 0;
@@ -970,8 +986,9 @@ static void pf_dl(gNB_MAC_INST *mac,
LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d ENTER partner-search after RNTI %04x leader_x2=%d; eligible_others=%d\n",
frame, slot, iterator->UE->rnti, leader_x2, n_cand);
}
/* Find up to MAX_MU_MIMO_PARTNERS partners, one per remaining x2 class. */
while (n_partners < MAX_MU_MIMO_PARTNERS && remainUEs[beam.idx] > 0) {
/* Find up to max_partners_now partners, one per remaining x2 class.
* max_partners_now is capped by active TX antenna count (Phase 4). */
while (n_partners < max_partners_now && remainUEs[beam.idx] > 0) {
NR_UE_info_t *new_partner = NULL;
int new_CCE = -1;
int new_alloc = -1;