mac: MU-MIMO diagnostic instrumentation + ri_bitlen=0 fix

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.
This commit is contained in:
2026-04-29 15:09:54 +09:00
parent 766d0a4bd3
commit c5a3ef8b60
2 changed files with 85 additions and 7 deletions
@@ -627,6 +627,18 @@ static bool pmi_is_orthogonal(const NR_UE_info_t *a, const NR_UE_info_t *b)
/* Both UEs must report rank-1; other ranks need codebook-specific orthogonality. */
if (ra->ri != 0 || rb->ri != 0)
return false;
/* OAI rfsimulator with a symmetric channel produces identical PMIs across UEs,
* so the strict-orthogonality test (same x1, x2 differ by 2) never fires.
* OAI_MU_MIMO_RELAXED=1 accepts any rank-1 pair so we can exercise the
* co-scheduling path; PHY throughput will not actually improve without true
* channel diversity, but the scheduler correctness can be validated. */
static int relaxed = -1;
if (relaxed < 0) {
const char *e = getenv("OAI_MU_MIMO_RELAXED");
relaxed = (e && atoi(e) > 0) ? 1 : 0;
}
if (relaxed)
return true;
if (ra->pmi_x1 != rb->pmi_x1)
return false;
/* x2 in {0,1,2,3}; orthogonal pairs are (0,2) and (1,3). */
@@ -767,6 +779,12 @@ static void pf_dl(gNB_MAC_INST *mac,
qsort(UE_sched, numUE, sizeof(UEsched_t), comparator);
UEsched_t *iterator = UE_sched;
/* nakaolab: diagnostic — log numUE > 1 to understand MU-MIMO opportunity */
if (mumimo_enabled() && numUE >= 2) {
static int dump_count = 0;
if (dump_count++ < 20)
LOG_W(NR_MAC, "[nakaolab MU-MIMO] %4d.%2d numUE=%d (PF candidates)\n", frame, slot, numUE);
}
const int min_rbSize = 5;
@@ -933,13 +951,35 @@ static void pf_dl(gNB_MAC_INST *mac,
if (mumimo_enabled() && remainUEs[beam.idx] > 0) {
/* For MU-MIMO, the leader UE's DMRS group must be 2 so the second group is
* available for the partner. Patch the just-built sched_pdsch in place. */
for (UEsched_t *cand = iterator + 1; cand->UE != NULL; cand++) {
if (cand->mumimo_paired)
static int diag_count = 0;
bool log_diag = (diag_count++ < 20);
if (log_diag) {
int n_cand = 0;
for (UEsched_t *c = UE_sched; c->UE != NULL; c++)
if (c != iterator && !c->mumimo_paired) n_cand++;
LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d ENTER partner-search after RNTI %04x; eligible_others=%d\n",
frame, slot, iterator->UE->rnti, n_cand);
}
/* Scan ALL entries in UE_sched (not just iterator+1) — partner can be at any
* position. The leader's PF order doesn't constrain orthogonality. */
for (UEsched_t *cand = UE_sched; cand->UE != NULL; cand++) {
if (cand == iterator)
continue;
if (cand->UE->UE_sched_ctrl.available_dl_harq.head < 0)
if (cand->mumimo_paired) {
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: already paired\n", frame, slot, cand->UE->rnti);
continue;
if (!pmi_is_orthogonal(iterator->UE, cand->UE))
}
if (cand->UE->UE_sched_ctrl.available_dl_harq.head < 0) {
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: no HARQ\n", frame, slot, cand->UE->rnti);
continue;
}
if (!pmi_is_orthogonal(iterator->UE, cand->UE)) {
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: PMI not orthogonal (leader ri=%d, cand ri=%d)\n",
frame, slot, cand->UE->rnti,
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.ri,
cand->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.ri);
continue;
}
/* Candidate's beam must match ours so the precoding is on the same TRX
* resources. (Different beams would also work but our config has a
* single beam pool.) */
@@ -947,6 +987,7 @@ static void pf_dl(gNB_MAC_INST *mac,
cand->UE->UE_beam_index,
slots_per_frame);
if (pbeam.idx != beam.idx) {
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: beam idx %d != leader %d\n", frame, slot, cand->UE->rnti, pbeam.idx, beam.idx);
reset_beam_status(&mac->beam_info, frame, slot, cand->UE->UE_beam_index,
slots_per_frame, pbeam.new_beam);
continue;
@@ -960,6 +1001,7 @@ static void pf_dl(gNB_MAC_INST *mac,
&cand->UE->UE_sched_ctrl.sched_pdcch,
cand->UE->UE_sched_ctrl.pdcch_cl_adjust);
if (partner_CCE < 0) {
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: no free CCE\n", frame, slot, cand->UE->rnti);
reset_beam_status(&mac->beam_info, frame, slot, cand->UE->UE_beam_index,
slots_per_frame, pbeam.new_beam);
continue;
+39 -3
View File
@@ -763,8 +763,20 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
cumul_bits += cri_bitlen;
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
if (ri_bitlen)
if (ri_bitlen) {
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
} else {
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
* Pick that RI directly — without this, r_index stays -1 and trips
* the DevAssert(ri>=0) in evaluate_cqi_report. */
for (int i = 0; i < 8; i++) {
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
r_index = i;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
break;
}
}
}
cumul_bits += ri_bitlen;
if (r_index != -1)
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
@@ -777,8 +789,20 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
cumul_bits += cri_bitlen;
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
if (ri_bitlen)
if (ri_bitlen) {
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
} else {
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
* Pick that RI directly — without this, r_index stays -1 and trips
* the DevAssert(ri>=0) in evaluate_cqi_report. */
for (int i = 0; i < 8; i++) {
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
r_index = i;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
break;
}
}
}
cumul_bits += ri_bitlen;
if (r_index != -1)
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
@@ -794,8 +818,20 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
cumul_bits += cri_bitlen;
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
if (ri_bitlen)
if (ri_bitlen) {
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
} else {
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
* Pick that RI directly — without this, r_index stays -1 and trips
* the DevAssert(ri>=0) in evaluate_cqi_report. */
for (int i = 0; i < 8; i++) {
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
r_index = i;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
break;
}
}
}
cumul_bits += ri_bitlen;
li_bitlen = evaluate_li_report(payload, csi_report, cumul_bits, r_index, sched_ctrl);
cumul_bits += li_bitlen;