From c5a3ef8b60d1613d74b9b8a50663c1e9d6adc482 Mon Sep 17 00:00:00 2001 From: makigo Date: Wed, 29 Apr 2026 15:09:54 +0900 Subject: [PATCH] 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. --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 50 +++++++++++++++++-- .../LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c | 42 ++++++++++++++-- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 03acd3aa7f..5cdc1e4d4f 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -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; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c index 32337fd8eb..a823c0c461 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c @@ -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;