mac+rfsim: working MU-MIMO DL co-scheduling with 4-class channel diversity
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
This commit is contained in:
@@ -779,6 +779,11 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
|
|
||||||
qsort(UE_sched, numUE, sizeof(UEsched_t), comparator);
|
qsort(UE_sched, numUE, sizeof(UEsched_t), comparator);
|
||||||
UEsched_t *iterator = UE_sched;
|
UEsched_t *iterator = UE_sched;
|
||||||
|
/* nakaolab: tag to mark UEs already paired in this pf_dl call. Increments each
|
||||||
|
* call so a UE's stored tag != current tag means "available for pairing". */
|
||||||
|
static uint64_t mumimo_pf_call_id = 0;
|
||||||
|
if (mumimo_enabled())
|
||||||
|
mumimo_pf_call_id++;
|
||||||
/* nakaolab: diagnostic — log numUE > 1 to understand MU-MIMO opportunity */
|
/* nakaolab: diagnostic — log numUE > 1 to understand MU-MIMO opportunity */
|
||||||
if (mumimo_enabled() && numUE >= 2) {
|
if (mumimo_enabled() && numUE >= 2) {
|
||||||
static int dump_count = 0;
|
static int dump_count = 0;
|
||||||
@@ -791,8 +796,10 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
/* Loop UE_sched to find max coeff and allocate transmission */
|
/* Loop UE_sched to find max coeff and allocate transmission */
|
||||||
while (iterator->UE != NULL) {
|
while (iterator->UE != NULL) {
|
||||||
|
|
||||||
/* nakaolab: skip UEs already allocated as a MU-MIMO partner of an earlier UE */
|
/* nakaolab: skip UEs already allocated as a MU-MIMO partner of an earlier UE
|
||||||
if (iterator->mumimo_paired) {
|
* in this same pf_dl call (mumimo_pf_call_id matches). */
|
||||||
|
if (mumimo_enabled() &&
|
||||||
|
iterator->UE->UE_sched_ctrl.mumimo_paired_slot == (int64_t)mumimo_pf_call_id) {
|
||||||
iterator++;
|
iterator++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -942,9 +949,8 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
/* nakaolab MU-MIMO: try to find an orthogonal-PMI partner UE that can ride on
|
/* nakaolab MU-MIMO: try to find an orthogonal-PMI partner UE that can ride on
|
||||||
* the same RB allocation as this UE. The partner is given a separate DCI with
|
* the same RB allocation as this UE. The partner is given a separate DCI with
|
||||||
* its own CCE, and a different DMRS CDM group so the receiver can separate
|
* its own CCE, and a different DMRS CDM group so the receiver can separate
|
||||||
* the two layers. Both UEs are forced to the same MCS/TBS (we trust the leader
|
* the two layers. Both UEs are forced to the same MCS/TBS. */
|
||||||
* UE's PF coefficient and channel report). */
|
NR_UE_info_t *partner = NULL;
|
||||||
UEsched_t *partner = NULL;
|
|
||||||
int partner_CCE = -1;
|
int partner_CCE = -1;
|
||||||
int partner_alloc = -1;
|
int partner_alloc = -1;
|
||||||
|
|
||||||
@@ -952,83 +958,99 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
/* For MU-MIMO, the leader UE's DMRS group must be 2 so the second group is
|
/* 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. */
|
* available for the partner. Patch the just-built sched_pdsch in place. */
|
||||||
static int diag_count = 0;
|
static int diag_count = 0;
|
||||||
bool log_diag = (diag_count++ < 20);
|
/* Log every Nth call to avoid log flood, but never stop logging. */
|
||||||
|
bool log_diag = ((diag_count++ % 200) == 0);
|
||||||
if (log_diag) {
|
if (log_diag) {
|
||||||
int n_cand = 0;
|
int n_cand = 0;
|
||||||
for (UEsched_t *c = UE_sched; c->UE != NULL; c++)
|
UE_iterator (UE_list, c) {
|
||||||
if (c != iterator && !c->mumimo_paired) n_cand++;
|
if (c != iterator->UE && nr_mac_ue_is_active(c) &&
|
||||||
LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d ENTER partner-search after RNTI %04x; eligible_others=%d\n",
|
c->UE_sched_ctrl.available_dl_harq.head >= 0)
|
||||||
|
n_cand++;
|
||||||
|
}
|
||||||
|
LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d ENTER partner-search after RNTI %04x; eligible_others=%d (from connected_ue_list)\n",
|
||||||
frame, slot, iterator->UE->rnti, n_cand);
|
frame, slot, iterator->UE->rnti, n_cand);
|
||||||
}
|
}
|
||||||
/* Scan ALL entries in UE_sched (not just iterator+1) — partner can be at any
|
/* nakaolab Phase 3: scan ALL connected UEs (not just UE_sched). PF often has
|
||||||
* position. The leader's PF order doesn't constrain orthogonality. */
|
* just one UE per slot in UE_sched even with many UEs sending traffic; the
|
||||||
for (UEsched_t *cand = UE_sched; cand->UE != NULL; cand++) {
|
* MU-MIMO partner can be any orthogonal UE that has buffered data. */
|
||||||
if (cand == iterator)
|
UE_iterator (UE_list, cand_ue) {
|
||||||
|
if (cand_ue == iterator->UE)
|
||||||
continue;
|
continue;
|
||||||
if (cand->mumimo_paired) {
|
if (cand_ue->UE_sched_ctrl.mumimo_paired_slot == (int64_t)mumimo_pf_call_id) {
|
||||||
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: already paired\n", frame, slot, cand->UE->rnti);
|
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: already paired this slot\n", frame, slot, cand_ue->rnti);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (cand->UE->UE_sched_ctrl.available_dl_harq.head < 0) {
|
if (!nr_mac_ue_is_active(cand_ue)) {
|
||||||
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: no HARQ\n", frame, slot, cand->UE->rnti);
|
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: inactive\n", frame, slot, cand_ue->rnti);
|
||||||
continue;
|
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: PMI not orthogonal (leader ri=%d, cand ri=%d)\n",
|
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: no HARQ\n", frame, slot, cand_ue->rnti);
|
||||||
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;
|
continue;
|
||||||
}
|
}
|
||||||
/* Candidate's beam must match ours so the precoding is on the same TRX
|
/* Pull RLC bytes into the partner's buffer; if no data, skip. */
|
||||||
* resources. (Different beams would also work but our config has a
|
update_dlsch_buffer(pp_pdsch->frame, pp_pdsch->slot, cand_ue);
|
||||||
* single beam pool.) */
|
if (!dlsch_to_schedule(&cand_ue->UE_sched_ctrl)) {
|
||||||
|
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: no data\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 x1=%d x2=%d, cand x1=%d x2=%d)\n",
|
||||||
|
frame, slot, cand_ue->rnti,
|
||||||
|
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
||||||
|
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2,
|
||||||
|
cand_ue->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
||||||
|
cand_ue->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Candidate's beam must match ours. */
|
||||||
NR_beam_alloc_t pbeam = beam_allocation_procedure(&mac->beam_info, frame, slot,
|
NR_beam_alloc_t pbeam = beam_allocation_procedure(&mac->beam_info, frame, slot,
|
||||||
cand->UE->UE_beam_index,
|
cand_ue->UE_beam_index,
|
||||||
slots_per_frame);
|
slots_per_frame);
|
||||||
if (pbeam.idx != beam.idx) {
|
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);
|
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: beam %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,
|
reset_beam_status(&mac->beam_info, frame, slot, cand_ue->UE_beam_index,
|
||||||
slots_per_frame, pbeam.new_beam);
|
slots_per_frame, pbeam.new_beam);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* Need a free CCE for the partner's own DCI. */
|
/* Need a free CCE for the partner's own DCI. */
|
||||||
partner_CCE = get_cce_index(mac, CC_id, slot, cand->UE->rnti,
|
partner_CCE = get_cce_index(mac, CC_id, slot, cand_ue->rnti,
|
||||||
&cand->UE->UE_sched_ctrl.aggregation_level,
|
&cand_ue->UE_sched_ctrl.aggregation_level,
|
||||||
pbeam.idx,
|
pbeam.idx,
|
||||||
cand->UE->UE_sched_ctrl.search_space,
|
cand_ue->UE_sched_ctrl.search_space,
|
||||||
cand->UE->UE_sched_ctrl.coreset,
|
cand_ue->UE_sched_ctrl.coreset,
|
||||||
&cand->UE->UE_sched_ctrl.sched_pdcch,
|
&cand_ue->UE_sched_ctrl.sched_pdcch,
|
||||||
cand->UE->UE_sched_ctrl.pdcch_cl_adjust);
|
cand_ue->UE_sched_ctrl.pdcch_cl_adjust);
|
||||||
if (partner_CCE < 0) {
|
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);
|
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,
|
reset_beam_status(&mac->beam_info, frame, slot, cand_ue->UE_beam_index,
|
||||||
slots_per_frame, pbeam.new_beam);
|
slots_per_frame, pbeam.new_beam);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* And a PUCCH for HARQ feedback. */
|
/* And a PUCCH for HARQ feedback. */
|
||||||
if (!get_FeedbackDisabled(cand->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17,
|
if (!get_FeedbackDisabled(cand_ue->sc_info.downlinkHARQ_FeedbackDisabled_r17,
|
||||||
cand->UE->UE_sched_ctrl.available_dl_harq.head)) {
|
cand_ue->UE_sched_ctrl.available_dl_harq.head)) {
|
||||||
int p_pucch = nr_get_pucch_resource(cand->UE->UE_sched_ctrl.coreset,
|
int p_pucch = nr_get_pucch_resource(cand_ue->UE_sched_ctrl.coreset,
|
||||||
cand->UE->current_UL_BWP.pucch_Config,
|
cand_ue->current_UL_BWP.pucch_Config,
|
||||||
partner_CCE);
|
partner_CCE);
|
||||||
partner_alloc = nr_acknack_scheduling(mac, cand->UE, frame, slot,
|
partner_alloc = nr_acknack_scheduling(mac, cand_ue, frame, slot,
|
||||||
cand->UE->UE_beam_index, p_pucch, 0);
|
cand_ue->UE_beam_index, p_pucch, 0);
|
||||||
if (partner_alloc < 0) {
|
if (partner_alloc < 0) {
|
||||||
reset_beam_status(&mac->beam_info, frame, slot, cand->UE->UE_beam_index,
|
if (log_diag) LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d skip RNTI %04x: no PUCCH\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);
|
slots_per_frame, pbeam.new_beam);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
partner = cand;
|
partner = cand_ue;
|
||||||
LOG_D(NR_MAC, "[nakaolab MU-MIMO] %4d.%2d pair RNTI %04x (x1=%d x2=%d) + RNTI %04x (x1=%d x2=%d) on RBs [%d..%d]\n",
|
LOG_W(NR_MAC, "[nakaolab MU-MIMO] %4d.%2d pair RNTI %04x (x1=%d x2=%d) + RNTI %04x (x1=%d x2=%d) on RBs [%d..%d]\n",
|
||||||
frame, slot,
|
frame, slot,
|
||||||
iterator->UE->rnti,
|
iterator->UE->rnti,
|
||||||
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
||||||
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2,
|
iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2,
|
||||||
cand->UE->rnti,
|
cand_ue->rnti,
|
||||||
cand->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
cand_ue->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1,
|
||||||
cand->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2,
|
cand_ue->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2,
|
||||||
sched_pdsch.rbStart, sched_pdsch.rbStart + sched_pdsch.rbSize - 1);
|
sched_pdsch.rbStart, sched_pdsch.rbStart + sched_pdsch.rbSize - 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1039,11 +1061,13 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
}
|
}
|
||||||
|
|
||||||
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
|
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
|
||||||
|
if (mumimo_enabled())
|
||||||
|
iterator->UE->UE_sched_ctrl.mumimo_paired_slot = (int64_t)mumimo_pf_call_id;
|
||||||
|
|
||||||
/* nakaolab: now allocate the MU-MIMO partner on the same RBs. */
|
/* nakaolab: now allocate the MU-MIMO partner on the same RBs. */
|
||||||
if (partner != NULL) {
|
if (partner != NULL) {
|
||||||
NR_UE_sched_ctrl_t *p_sched_ctrl = &partner->UE->UE_sched_ctrl;
|
NR_UE_sched_ctrl_t *p_sched_ctrl = &partner->UE_sched_ctrl;
|
||||||
NR_UE_DL_BWP_t *p_dl_bwp = &partner->UE->current_DL_BWP;
|
NR_UE_DL_BWP_t *p_dl_bwp = &partner->current_DL_BWP;
|
||||||
|
|
||||||
p_sched_ctrl->cce_index = partner_CCE;
|
p_sched_ctrl->cce_index = partner_CCE;
|
||||||
fill_pdcch_vrb_map(mac, CC_id, &p_sched_ctrl->sched_pdcch, partner_CCE,
|
fill_pdcch_vrb_map(mac, CC_id, &p_sched_ctrl->sched_pdcch, partner_CCE,
|
||||||
@@ -1059,10 +1083,10 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
.tb_size = sched_pdsch.tb_size,
|
.tb_size = sched_pdsch.tb_size,
|
||||||
.dl_harq_pid = p_sched_ctrl->available_dl_harq.head,
|
.dl_harq_pid = p_sched_ctrl->available_dl_harq.head,
|
||||||
.pucch_allocation = partner_alloc,
|
.pucch_allocation = partner_alloc,
|
||||||
.pm_index = get_pm_index(mac, partner->UE, p_dl_bwp->dci_format, p_l,
|
.pm_index = get_pm_index(mac, partner, p_dl_bwp->dci_format, p_l,
|
||||||
mac->radio_config.pdsch_AntennaPorts.XP),
|
mac->radio_config.pdsch_AntennaPorts.XP),
|
||||||
.nrOfLayers = p_l,
|
.nrOfLayers = p_l,
|
||||||
.bwp_info = get_pdsch_bwp_start_size(mac, partner->UE),
|
.bwp_info = get_pdsch_bwp_start_size(mac, partner),
|
||||||
.dmrs_parms = get_dl_dmrs_params(scc, p_dl_bwp, &tda_info, p_l),
|
.dmrs_parms = get_dl_dmrs_params(scc, p_dl_bwp, &tda_info, p_l),
|
||||||
.time_domain_allocation = tda,
|
.time_domain_allocation = tda,
|
||||||
.tda_info = tda_info,
|
.tda_info = tda_info,
|
||||||
@@ -1074,8 +1098,8 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
p_sched_pdsch.dmrs_parms.dmrs_ports_id = 2;
|
p_sched_pdsch.dmrs_parms.dmrs_ports_id = 2;
|
||||||
p_sched_pdsch.dmrs_parms.numDmrsCdmGrpsNoData = 2;
|
p_sched_pdsch.dmrs_parms.numDmrsCdmGrpsNoData = 2;
|
||||||
|
|
||||||
post_process_dlsch(mac, pp_pdsch, partner->UE, &p_sched_pdsch);
|
post_process_dlsch(mac, pp_pdsch, partner, &p_sched_pdsch);
|
||||||
partner->mumimo_paired = true;
|
p_sched_ctrl->mumimo_paired_slot = (int64_t)mumimo_pf_call_id;
|
||||||
remainUEs[beam.idx]--;
|
remainUEs[beam.idx]--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
#include "LAYER2/NR_MAC_COMMON/nr_mac_common.h"
|
#include "LAYER2/NR_MAC_COMMON/nr_mac_common.h"
|
||||||
#include "LAYER2/NR_MAC_gNB/mac_config.h"
|
#include "LAYER2/NR_MAC_gNB/mac_config.h"
|
||||||
#include "NR_TAG.h"
|
#include "NR_TAG.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
/* Defs */
|
/* Defs */
|
||||||
#define MAX_NUM_BWP 5
|
#define MAX_NUM_BWP 5
|
||||||
@@ -663,6 +664,10 @@ typedef struct {
|
|||||||
int release_timer;
|
int release_timer;
|
||||||
CSI_report_t CSI_report;
|
CSI_report_t CSI_report;
|
||||||
bool SR;
|
bool SR;
|
||||||
|
/// nakaolab MU-MIMO: encoded frame*1000+slot when this UE was last allocated as
|
||||||
|
/// a MU-MIMO partner (or leader). -1 = never. Lets pf_dl skip UEs already paired
|
||||||
|
/// in the current slot when scanning connected_ue_list for partners.
|
||||||
|
int64_t mumimo_paired_slot;
|
||||||
/// information about every HARQ process
|
/// information about every HARQ process
|
||||||
NR_UE_harq_t harq_processes[NR_MAX_HARQ_PROCESSES];
|
NR_UE_harq_t harq_processes[NR_MAX_HARQ_PROCESSES];
|
||||||
/// HARQ processes that are free
|
/// HARQ processes that are free
|
||||||
@@ -965,6 +970,10 @@ typedef struct gNB_MAC_INST_s {
|
|||||||
nr_mac_config_t radio_config;
|
nr_mac_config_t radio_config;
|
||||||
nr_rlc_configuration_t rlc_config;
|
nr_rlc_configuration_t rlc_config;
|
||||||
|
|
||||||
|
/// Pointer to PHY antenna state (port mute mask) — set in scheduler init,
|
||||||
|
/// used by get_dl_nrOfLayers to cap MIMO rank to active TX port count.
|
||||||
|
nr_antenna_state_t *antenna_state;
|
||||||
|
|
||||||
NR_sched_ctrl_sib1_t *sched_ctrlSIB1;
|
NR_sched_ctrl_sib1_t *sched_ctrlSIB1;
|
||||||
NR_sched_pdcch_t *sched_pdcch_otherSI;
|
NR_sched_pdcch_t *sched_pdcch_otherSI;
|
||||||
uint16_t cset0_bwp_start;
|
uint16_t cset0_bwp_start;
|
||||||
|
|||||||
@@ -1324,7 +1324,29 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
|
|||||||
rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps);
|
rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1)) {
|
/* nakaolab MU-MIMO: when OAI_UE_CHANNEL_CLASS is set in the env, the UE
|
||||||
|
* applies a deterministic rank-1 channel whose dominant right-singular-vector
|
||||||
|
* matches Type I 4-port codebook entry w_class. This makes the UE report a
|
||||||
|
* stable PMI = class, giving the gNB scheduler natural PMI diversity across
|
||||||
|
* UEs assigned to different classes. Four classes give four mutually-orthogonal
|
||||||
|
* codewords, supporting up to 4-way MU-MIMO.
|
||||||
|
*
|
||||||
|
* Codeword w_k[a] for 4-port rank-1 (Type I, N1=2, N2=1):
|
||||||
|
* w_0 = [1, 1, 1, 1]/2
|
||||||
|
* w_1 = [1, j, -1, -j]/2
|
||||||
|
* w_2 = [1, -1, 1, -1]/2
|
||||||
|
* w_3 = [1, -j, -1, j]/2
|
||||||
|
* The 4x4 channel H^(k) is rank-1: H^(k)[r,a] = w_k[a]* (per-row constant), so
|
||||||
|
* the dominant right singular vector is w_k. */
|
||||||
|
static int ue_class_cached = -2;
|
||||||
|
if (ue_class_cached == -2) {
|
||||||
|
const char *env = getenv("OAI_UE_CHANNEL_CLASS");
|
||||||
|
ue_class_cached = (env && atoi(env) >= 0 && atoi(env) < 4) ? atoi(env) : -1;
|
||||||
|
if (ue_class_cached >= 0)
|
||||||
|
LOG_W(HW, "[nakaolab MU-MIMO] rfsim DL channel: orthogonal class %d\n", ue_class_cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1) && ue_class_cached < 0) {
|
||||||
// optimization: The buffer is uninitialized so samples can be written directly in the buffer
|
// optimization: The buffer is uninitialized so samples can be written directly in the buffer
|
||||||
combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, 1, nsamps, rx_beam_id, samples);
|
combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, 1, nsamps, rx_beam_id, samples);
|
||||||
} else {
|
} else {
|
||||||
@@ -1334,6 +1356,52 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
|
|||||||
input[aatx] = ant_buffers[aatx].data();
|
input[aatx] = ant_buffers[aatx].data();
|
||||||
}
|
}
|
||||||
combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, ptr->nbAnt, nsamps, rx_beam_id, input);
|
combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, ptr->nbAnt, nsamps, rx_beam_id, input);
|
||||||
|
|
||||||
|
if (ue_class_cached >= 0 && ant_buffers.size() == 4) {
|
||||||
|
/* nakaolab MU-MIMO: per-UE channel that combines the default symmetric
|
||||||
|
* rfsim kernel with a class-specific TX-antenna weighting w_k[a]:
|
||||||
|
*
|
||||||
|
* H^(k)[r, a] = H_orig[r,a] * w_k[a]
|
||||||
|
*
|
||||||
|
* The default kernel H_orig[r,a] = (a==r ? 1.0 : 0.2/|a-r|) is full-rank,
|
||||||
|
* preserving SSB and reference-signal sync (signal reaches all RX). The
|
||||||
|
* class-specific weighting rotates each TX antenna's contribution
|
||||||
|
* differently per class, so the optimal PMI for class k is exactly
|
||||||
|
* codeword w_k from the Type I 4-port codebook (modulo diagonal scaling).
|
||||||
|
*
|
||||||
|
* w_0 = [1, 1, 1, 1]/2
|
||||||
|
* w_1 = [1, j, -1, -j]/2
|
||||||
|
* w_2 = [1, -1, 1, -1]/2
|
||||||
|
* w_3 = [1, -j, -1, j]/2 */
|
||||||
|
static const double W_RE[4][4] = {
|
||||||
|
{ 0.5, 0.5, 0.5, 0.5 },
|
||||||
|
{ 0.5, 0.0, -0.5, 0.0 },
|
||||||
|
{ 0.5, -0.5, 0.5, -0.5 },
|
||||||
|
{ 0.5, 0.0, -0.5, 0.0 }
|
||||||
|
};
|
||||||
|
static const double W_IM[4][4] = {
|
||||||
|
{ 0.0, 0.0, 0.0, 0.0 },
|
||||||
|
{ 0.0, 0.5, 0.0, -0.5 },
|
||||||
|
{ 0.0, 0.0, 0.0, 0.0 },
|
||||||
|
{ 0.0, -0.5, 0.0, 0.5 }
|
||||||
|
};
|
||||||
|
const int k = ue_class_cached;
|
||||||
|
for (int aarx = 0; aarx < nbAnt; aarx++) {
|
||||||
|
for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) {
|
||||||
|
const uint32_t ant_diff = (uint32_t)std::abs(aatx - aarx);
|
||||||
|
const double h_orig = ant_diff ? (0.2 / ant_diff) : 1.0;
|
||||||
|
/* Effective channel coeff: h_orig * w_k[a] (complex multiply). */
|
||||||
|
const double hre = h_orig * W_RE[k][aatx];
|
||||||
|
const double him = h_orig * W_IM[k][aatx];
|
||||||
|
for (int i = 0; i < nsamps; i++) {
|
||||||
|
const double xr = ant_buffers[aatx][i].r;
|
||||||
|
const double xi = ant_buffers[aatx][i].i;
|
||||||
|
samples[aarx][i].r += (int16_t)(hre * xr - him * xi);
|
||||||
|
samples[aarx][i].i += (int16_t)(hre * xi + him * xr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for (int aarx = 0; aarx < nbAnt; aarx++) {
|
for (int aarx = 0; aarx < nbAnt; aarx++) {
|
||||||
double H_awgn_mimo_coeff[ant_buffers.size()];
|
double H_awgn_mimo_coeff[ant_buffers.size()];
|
||||||
for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) {
|
for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) {
|
||||||
@@ -1351,6 +1419,7 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is_first_peer = false;
|
is_first_peer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user