Compare commits

...

18 Commits

Author SHA1 Message Date
Rúben Soares Silva
08d280f357 log 2026-04-17 09:20:41 +01:00
Rúben Soares Silva
a3f6109d7c Revert "log"
This reverts commit 26b74de4bd.
2026-04-16 21:03:16 +01:00
Rúben Soares Silva
bc751da474 test prgMask 2026-04-16 21:01:55 +01:00
Rúben Soares Silva
26b74de4bd log 2026-04-16 20:36:00 +01:00
Rúben Soares Silva
7247d7daa8 Test changing antenna count 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
7c0c318df7 test limit available prbs for cumac 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
7e2b55cc67 Pad number of connected UEs with dummy data for cuMAC UE selection 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
845d735dae Change log level of timing print from sch_tti.request to response 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
c38962f530 Get SINR from MCS and use that to fill wbSinr and postEqSinr arrays in gNB_scheduler_dlsch.c 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
516cb2902a Change printf to LOG_D to not flood the terminal with logs 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
0b54e9ab81 test MCS and Layer allocation 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
79f423052b Add cuMAC last solution buffers to UE and populate after each scheduling 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
61390d8a9c Add cuMAC last solution buffers to SCH_TTI.request 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
876fc5b205 Revert "test Logs"
This reverts commit d22fa90df7edabd382d2ea6888689aac325805d6.
2026-04-16 20:31:32 +01:00
Rúben Soares Silva
284105da7c test PRB allocation 2026-04-16 20:31:32 +01:00
Rúben Soares Silva
64b2a513b7 test Logs 2026-04-16 17:25:37 +01:00
Rúben Soares Silva
3a8c182e37 Initial implementation for reading SCH_TTI.response data 2026-04-16 17:25:37 +01:00
Rúben Soares Silva
3860b51fde CuMAC initial test 2026-04-16 13:38:43 +01:00
23 changed files with 3483 additions and 8 deletions

View File

@@ -1333,6 +1333,9 @@ target_include_directories(L2_NR PRIVATE ${F1AP_DIR}/lib)
if(E2_AGENT)
target_compile_definitions(L2_NR PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT)
endif()
if (OAI_AERIAL_CUMAC)
target_compile_definitions(L2_NR PUBLIC ENABLE_CUMAC)
endif()
add_library(e1_if
${NR_RRC_DIR}/cucp_cuup_direct.c
@@ -1856,6 +1859,11 @@ if (OAI_AERIAL)
target_compile_definitions(nr-softmodem PUBLIC ENABLE_AERIAL)
target_link_libraries(nr-softmodem PUBLIC aerial_lib)
endif()
add_boolean_option(OAI_AERIAL_CUMAC OFF "Activate OAI's AERIAL_CUMAC driver" OFF)
if (OAI_AERIAL_CUMAC)
target_link_libraries(nr-softmodem PUBLIC aerial_cumac_lib)
target_compile_definitions(nr-softmodem PUBLIC ENABLE_CUMAC)
endif()
if(E2_AGENT)
target_link_libraries(nr-softmodem PUBLIC e2_agent e2_agent_arg e2_ran_func_du_cucp_cuup)
target_compile_definitions(nr-softmodem PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT)

View File

@@ -282,6 +282,9 @@ function main() {
;;
esac
shift 2;;
"--AERIAL_CUMAC"|"--Aerial_CuMAC"|"--aerial_cumac"|"--CUMAC"|"--cuMAC"|"--cumac")
CMAKE_CMD="$CMAKE_CMD -DOAI_AERIAL_CUMAC=ON"
shift;;
-P | --phy_simulators)
SIMUS_PHY=1
CMAKE_CMD="$CMAKE_CMD -DENABLE_PHYSIM_TESTS=ON"

62
doc/cuMAC.md Normal file
View File

@@ -0,0 +1,62 @@
Differences between SCH_TTI.request and PFM
Header is the same, effectively FAPI header
then PFM has an offset of data followed by sfn, slot
SCH_TTI.request after the header, has the sfn/slot, order which is shared to the other slot messages
SCH_TTI request then is followed by info regarding the algorithm to run, to which cell the message refers to and some more cell info (num Ues, num Antennas, etc)
PFM sort doesn't have any cell information, only related to UEs
struct cumac_pfm_tti_req_t {
cumac_msg_header_t header;
{
uint8_t message_count; //!< Number of messages in this transmission
uint8_t handle_id; //!< handle_id is used as cell_id
uint16_t type_id; //!< Message type identifier (cumac_msg_t)
uint32_t body_len; //!< Length of message body in bytes
uint8_t body[0]; //!< Variable-length message body (flexible array member)
}
uint32_t offset_ue_info_arr; // Offset of the PFM_UE_INFO data array in nvipc_buf->data_buf
uint16_t sfn;
uint16_t slot;
uint16_t num_ue; // total number of UEs in the payload
uint16_t num_output_sorted_lc[10];
// number of output sorted LCs/LCGs per DL/UL QoS type, 0-9 are the indices of the QoS types:
// 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical,
// 5 - ul_gbr_critical, 6 - ul_gbr_non_critical, 7 - ul_ngbr_critical, 8 - ul_ngbr_non_critical, 9 - ul_mbr_non_critical.
};
#define MAX_NUM_LCG 4
#define MAX_NUM_LC 4
struct PFM_UE_INFO {
PFM_DL_LC_INFO dl_lc_info[MAX_NUM_LC];
PFM_UL_LCG_INFO ul_lcg_info[MAX_NUM_LCG];
uint32_t ambr;
uint32_t rcurrent_dl;
uint32_t rcurrent_ul;
uint16_t rnti;
uint8_t num_layers_dl;
uint8_t num_layers_ul;
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - is_scheduled_dl, flags & 0x04 - is_scheduled_ul
uint8_t carrier_id;
uint8_t num_dl_lcs;
uint8_t num_ul_lcgs;
};
struct PFM_DL_LC_INFO {
uint32_t tbs_scheduled;
uint32_t ravg;
uint32_t pfm;
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - reset ravg to 1.0
uint8_t qos_type; // 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical
uint8_t padding[2]; // Padding to align to 32-bit
};
struct PFM_UL_LCG_INFO {
uint32_t tbs_scheduled;
uint32_t ravg;
uint32_t pfm;
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - reset ravg to 1.0
uint8_t qos_type; // 0 - ul_gbr_critical, 1 - ul_gbr_non_critical, 2 - ul_ngbr_critical, 3 - ul_ngbr_non_critical, 4 - ul_mbr_non_critical
uint8_t padding[2]; // Padding to align to 32-bit
};

View File

@@ -0,0 +1,120 @@
#/*
# * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
# * contributor license agreements. See the NOTICE file distributed with
# * this work for additional information regarding copyright ownership.
# * The OpenAirInterface Software Alliance licenses this file to You under
# * the OAI Public License, Version 1.1 (the "License"); you may not use this file
# * except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.openairinterface.org/?page_id=698
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# *-------------------------------------------------------------------------------
# * For more information about the OpenAirInterface (OAI) Software Alliance:
# * contact@openairinterface.org
# */
#---------------------------------------------------------------------
#
# Dockerfile for the Open-Air-Interface BUILD service
# Valid for Ubuntu 24.04
#
#---------------------------------------------------------------------
FROM ran-base:latest AS ran-build
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get upgrade --yes && \
DEBIAN_FRONTEND=noninteractive apt-get install --yes \
wget \
ninja-build \
#unzip is needed for protobuf
unzip \
moreutils \
vim-tiny \
nano \
# To build libnvipc
cmake libpcap-dev libcunit1-dev libnuma-dev pkg-config
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
RUN /bin/sh oaienv && \
tar -xvzf nvipc_src.*.tar.gz && \
cd nvipc_src.* && \
rm -rf build && mkdir build && cd build && \
cmake .. -DNVIPC_DPDK_ENABLE=OFF -DNVIPC_DOCA_ENABLE=OFF -DNVIPC_CUDA_ENABLE=OFF -DENABLE_SLT_RSP=ON && \
make -j && make install
RUN /bin/sh oaienv && \
cd cmake_targets && \
mkdir -p log && \
./build_oai -c -w USRP --gNB --ninja --cumac --build-tool-opt -k10
#\
# --cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror"
#start from scratch for target executable
FROM ubuntu:noble AS oai-gnb-aerial
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe
RUN apt-get update && \
apt-get upgrade --yes && \
apt-get install --yes \
software-properties-common \
procps \
libsctp1 \
tzdata \
libblas3 \
libconfig9 \
openssl \
net-tools \
iperf \
iproute2 \
iputils-ping \
gdb \
moreutils \
libusb-1.0-0 \
nvidia-cuda-toolkit \
libyaml-cpp-dev && \
rm -rf /var/lib/apt/lists/*
WORKDIR /opt/oai-gnb/bin
COPY --from=ran-build \
/oai-ran/cmake_targets/ran_build/build/nr-softmodem ./
COPY --from=ran-build \
/oai-ran/cmake_targets/ran_build/build/openair2/LAYER2/NR_MAC_gNB/integration/cuMAC/cumac_test ./
COPY ./docker/scripts/gnb_entrypoint.sh ./entrypoint.sh
COPY --from=ran-build \
/oai-ran/cmake_targets/ran_build/build/libparams_libconfig.so \
/oai-ran/cmake_targets/ran_build/build/libparams_yaml.so \
/usr/local/lib/
#copy files from nvIPC
COPY --from=ran-build \
/usr/local/lib/libfmtlog-shared.so \
/usr/local/lib/libnvlog.so \
/usr/local/lib/libnvipc.so \
/usr/local/lib/
RUN ldconfig && \
echo "---- ldd on nr-softmodem ----" && \
ldd /opt/oai-gnb/bin/nr-softmodem && \
echo "---- ldd on shared libraries ----" && \
ldd /usr/local/lib/libparams_libconfig.so \
/usr/local/lib/libfmtlog-shared.so \
/usr/local/lib/libnvlog.so \
/usr/local/lib/libnvipc.so
WORKDIR /opt/oai-gnb
COPY --from=ran-build /usr/local/bin/yq /usr/local/bin/yq
ENTRYPOINT ["/opt/oai-gnb/bin/entrypoint.sh"]
CMD ["/opt/oai-gnb/bin/cumac_test"]

View File

@@ -3,3 +3,4 @@
add_subdirectory(nr_rlc)
add_subdirectory(nr_pdcp)
add_subdirectory(NR_MAC_UE)
add_subdirectory(NR_MAC_gNB/integration/cuMAC)

View File

@@ -19,6 +19,11 @@
/*Softmodem params*/
#include "executables/softmodem-common.h"
#include "../../../nfapi/oai_integration/vendor_ext.h"
#include "PHY/defs_gNB.h"
#ifdef ENABLE_CUMAC
#include "openair2/LAYER2/NR_MAC_gNB/integration/cuMAC/cumac_nvipc.h"
#endif
////////////////////////////////////////////////////////
/////* DLSCH MAC PDU generation (6.1.2 TS 38.321) */////
@@ -627,8 +632,210 @@ static void pf_dl(gNB_MAC_INST *mac,
int CC_id = 0;
int slots_per_frame = mac->frame_structure.numb_slots_frame;
#ifdef ENABLE_CUMAC
uint8_t connected_ues = 0;
UE_iterator (UE_list, UE) {
connected_ues++;
}
const slot_data_entry_t* cumac_slot_data = NULL;
if (connected_ues!=0 && cumac_can_schedule()) {
LOG_D(NR_MAC,"CUMAC SENDING SCH TTI_REQ with %d connected UES\n", connected_ues);
/* const double k = 1.38064852e-23; // Boltzmann constant
const double T = 290.0; // Temperature in K
double thermalNoise = k * T * RC.gNB[0]->frame_parms.N_RB_DL * 12 * RC.gNB[0]->frame_parms.subcarrier_spacing;
double nfLinear = pow(10.0, 20 / 10.0);
double sigmasqrd = thermalNoise * nfLinear;
// for RFSim
double SNR_db = 20.0;
float sigmaSqrd = pow(10.0, -SNR_db / 10.0); */
const uint8_t nBsAnt = RC.nrmac[0]->config->carrier_config.num_tx_ant.value;
const uint8_t nUeAnt = RC.nrmac[0]->config->carrier_config.num_tx_ant.value;
// Pad nActiveUe to nMaxSchUePerCell so cuMAC UE selection fills all scheduler
// slots with real indices. Without padding, unselected slots get avgRates=0 in
// PRB allocation, which gives Inf PF and beats the real UE for the only PRG.
// Dummy UEs use wbSinr=0 (lose in UE selection) and avgRatesActUe=1e38f (lose
// in PRB allocation PF even if selected by the cuMAC scheduler).
const uint16_t nMaxSchUe = cumac_nMax_schUePerCell();
const uint16_t nActiveUe_padded = nMaxSchUe;
cumac_tti_req_bufs_t buffers;
// calloc zero-initialises: dummy slots get CRNTI=0 and wbSinr=0.
buffers.CRNTI = calloc(nActiveUe_padded, sizeof(uint16_t));
buffers.avgRatesActUe = malloc(nActiveUe_padded * sizeof(float));
buffers.newDataActUe = malloc(nActiveUe_padded * sizeof(int8_t));
buffers.allocSolLastTxActUe = calloc(nActiveUe_padded * 2, sizeof(int16_t));
buffers.mcsSelSolLastTxActUe = calloc(nActiveUe_padded, sizeof(int16_t));
buffers.layerSelSolLastTxActUe = calloc(nActiveUe_padded, sizeof(int8_t));
// wbSinr layout: [nActiveUE × nUeAnt] in row-major order.
// calloc zero-initialises so dummy slots get wbSinr=0 (lowest PF in UE selection).
buffers.wbSinr = calloc(nActiveUe_padded * nUeAnt, sizeof(float));
for (int i = 0; i < nActiveUe_padded; i++) {
buffers.newDataActUe[i] = -1;
buffers.avgRatesActUe[i] = 1e38f; // dummy sentinel; overwritten for real UEs below
}
size_t idx = 0;
UE_iterator (UE_list, UE) {
buffers.avgRatesActUe[idx] = UE->dl_thr_ue;
buffers.CRNTI[idx] = UE->rnti;
if (UE->cumac_last_sol.has_prev_sol) {
buffers.allocSolLastTxActUe[idx * 2] = UE->cumac_last_sol.alloc_sol[0];
buffers.allocSolLastTxActUe[idx * 2 + 1] = UE->cumac_last_sol.alloc_sol[1];
buffers.mcsSelSolLastTxActUe[idx] = UE->cumac_last_sol.mcs_sol;
buffers.layerSelSolLastTxActUe[idx] = UE->cumac_last_sol.layer_sol;
}
const CSI_report_t *csi = &UE->UE_sched_ctrl.CSI_report;
int sinrx10; // 0.1 dB units
if (csi->csirs_rsrp_report.nb > 0)
sinrx10 = csi->csirs_rsrp_report.r[0].SINRx10;
else if (csi->ssb_rsrp_report.nb > 0)
sinrx10 = csi->ssb_rsrp_report.r[0].SINRx10;
else
sinrx10 = get_SINRx10_for_mcs(UE->UE_sched_ctrl.dl_max_mcs);
// sinrx10/10 = SINR in dB; divide by 10 again for the dB-to-linear exponent
const float sinr_lin = powf(10.0f, sinrx10 / 100.0f);
for (int l = 0; l < nUeAnt; l++)
buffers.wbSinr[idx * nUeAnt + l] = sinr_lin;
//check retransmissions
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
if (!nr_mac_ue_is_active(UE)) {
idx++;
continue;
}
/* get the PID of a HARQ process awaiting retrnasmission, or -1 otherwise */
int harq_pid = sched_ctrl->retrans_dl_harq.head;
/* check if retransmision or not */
buffers.newDataActUe[idx] = harq_pid >= 0? CUMAC_UE_RETRANSMISSION: CUMAC_UE_INTIAL_TRANSMISSION;
idx++;
}
const uint16_t prb_per_prg = cumac_nPrbPerPrg();
// nPrbGrp must equal nMaxPrg from CONFIG.request — cuMAC validates data_num == buf_num.
// prgMsk uses carrier-absolute PRG indices to guide cuMAC's metric computation
// (masking PRGs outside the BWP gives them zero SINR). Note: the 4T4R scheduler
// ignores prgMsk during the allocation phase, so the allocSol will span [0, nPrbGrp)
// regardless; the response handler clips it to the BWP.
const uint16_t nPrbGrp = cumac_nMaxPrg() > 0 ? cumac_nMaxPrg() : 1;
buffers.prgMsk = calloc(nPrbGrp, sizeof(uint8_t)); // zero-init: unavailable by default
if (prb_per_prg > 0) {
UE_iterator(UE_list, UE) {
bwp_info_t ue_bwp = get_pdsch_bwp_start_size(mac, UE);
if (ue_bwp.bwpSize == 0)
continue;
const uint16_t first_prg = ue_bwp.bwpStart / prb_per_prg;
const uint16_t end_prg = (ue_bwp.bwpStart + ue_bwp.bwpSize + prb_per_prg - 1) / prb_per_prg;
for (uint16_t i = first_prg; i < end_prg && i < nPrbGrp; i++)
buffers.prgMsk[i] = 1;
}
}
cumac_wait_to_send();
const uint32_t taskBitMap = TASK_BIT(CUMAC_TASK_UE_SELECTION) | TASK_BIT(CUMAC_TASK_PRB_ALLOCATION);
cumac_sch_tti_req_args_t args = {.frame = frame,
.slot = slot,
.payload.cellID = 0,
.payload.taskBitMask = taskBitMap,
.payload.ULDLSch = SCH_TTI_DL,
.payload.nActiveUe = nActiveUe_padded,
.payload.nSrsUe = 0,
.payload.nPrbGrp = nPrbGrp,
.payload.nBsAnt = nBsAnt,
.payload.nUeAnt = nUeAnt,
.payload.sigmaSqrd = 1.0f, // hardcoded in cuMAC source code
.buffers = &buffers};
if (taskBitMap & TASK_BIT(CUMAC_TASK_PRB_ALLOCATION)) {
uint16_t numData = args.payload.nActiveUe * args.payload.nPrbGrp * args.payload.nUeAnt;
buffers.postEqSinr = malloc(numData * sizeof(float));
for (int ue = 0; ue < args.payload.nActiveUe; ue++) {
for (int prg = 0; prg < args.payload.nPrbGrp; prg++) {
for (int l = 0; l < args.payload.nUeAnt; l++) {
buffers.postEqSinr[ue * args.payload.nPrbGrp * args.payload.nUeAnt
+ prg * args.payload.nUeAnt + l] =
buffers.wbSinr[ue * args.payload.nUeAnt + l];
}
}
}
numData = cumac_nMax_schUePerCell() * args.payload.nPrbGrp * cumac_nPrbPerPrg() * args.payload.nUeAnt;
buffers.sinVal = malloc(numData * sizeof(float));
for (int i = 0; i < numData; i++) {
buffers.sinVal[i] = 20.0f;
}
const uint32_t prdLen = cumac_nMax_schUePerCell() * args.payload.nPrbGrp * cumac_nPrbPerPrg() * args.payload.nBsAnt * args.payload.nBsAnt;
const uint32_t detLen = cumac_nMax_schUePerCell() * args.payload.nPrbGrp * cumac_nPrbPerPrg() * args.payload.nUeAnt * args.payload.nUeAnt;
const uint32_t hLen = args.payload.nPrbGrp * cumac_nPrbPerPrg() * cumac_nMax_schUePerCell() * /*nMaxCell*/ 1 * args.payload.nBsAnt * args.payload.nUeAnt;
numData = detLen;
buffers.detMat = malloc(numData * sizeof(cuComplex));
for (int i = 0; i < numData; i++) {
buffers.detMat[i].x = 0.5f;
buffers.detMat[i].y = 0.5f;
}
numData = hLen;
buffers.estH_fr = malloc(numData * sizeof(cuComplex));
for (int i = 0; i < numData; i++) {
buffers.estH_fr[i].x = 0.5f;
buffers.estH_fr[i].y = 0.5f;
}
numData = prdLen;
buffers.prdMat = malloc(numData * sizeof(cuComplex));
for (int i = 0; i < numData; i++) {
buffers.prdMat[i].x = 0.5f;
buffers.prdMat[i].y = 0.5f;
}
}
cumac_send_msg(CUMAC_SCH_TTI_REQUEST, l2_build_sch_tti_request, &args);
cumac_sch_tti_end_args_t tti_end_args = {.frame = frame, .slot = slot};
cumac_send_msg(CUMAC_TTI_END, l2_build_tti_end, &tti_end_args);
// we have to wait for the response
cumac_wait_to_process();
cumac_slot_data = get_slot_data(frame, slot);
}
#else
#endif
/* Loop UE_info->list to check retransmission */
UE_iterator(UE_list, UE) {
#ifdef ENABLE_CUMAC
if (cumac_slot_data != NULL) {
int ue_id = cumac_get_UE_ID_by_RNTI(frame, slot, UE->rnti);
if (ue_id == -1)
continue; // UE not in this TTI's active list
if (cumac_slot_data->taskBitMask & TASK_BIT(CUMAC_TASK_UE_SELECTION)) {
// Check that this UE appears in the set cuMAC selected for scheduling
bool selected = false;
for (int i = 0; i < cumac_slot_data->nMaxSchUePerCell; i++) {
if (cumac_slot_data->setSchdUePerCellTTI[i] == (uint16_t)ue_id) {
selected = true;
break;
}
}
if (!selected)
continue;
}
}
#endif
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *current_BWP = &UE->current_DL_BWP;
@@ -728,7 +935,10 @@ static void pf_dl(gNB_MAC_INST *mac,
}
}
//For the moment, prevent sorting so that the indexes match what comes from cumac
#ifndef ENABLE_CUMAC
qsort(UE_sched, numUE, sizeof(UEsched_t), comparator);
#endif
UEsched_t *iterator = UE_sched;
const int min_rbSize = 5;
@@ -786,7 +996,14 @@ static void pf_dl(gNB_MAC_INST *mac,
int rbStart = 0; // WRT BWP start
int bwp_start = bwp_info.bwpStart;
int bwp_size = bwp_info.bwpSize;
// Freq-demain allocation
#ifdef ENABLE_CUMAC
int cumac_ue_id = (cumac_slot_data != NULL)
? cumac_get_UE_ID_by_RNTI(frame, slot, iterator->UE->rnti)
: -1;
#endif
#ifndef ENABLE_CUMAC
// Freq-domain allocation
int max_rbSize = 0;
if (!get_rb_alloc(min_rbSize, bwp_size, bwp_start, bwp_size, rballoc_mask, slbitmap, &rbStart, &max_rbSize)) {
LOG_D(NR_MAC,
@@ -800,7 +1017,51 @@ static void pf_dl(gNB_MAC_INST *mac,
iterator++;
continue;
}
#else
if (cumac_ue_id < 0 || cumac_slot_data->allocSol == NULL
|| !(cumac_slot_data->taskBitMask & TASK_BIT(CUMAC_TASK_PRB_ALLOCATION))) {
// PRB allocation not available for this UE; skip
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
iterator++;
continue;
}
// Refresh BWP bounds at response-apply time to catch RRC reconfigurations
bwp_info_t cur_bwp_info = get_pdsch_bwp_start_size(mac, iterator->UE);
if (cur_bwp_info.bwpSize > 0) {
bwp_start = cur_bwp_info.bwpStart;
bwp_size = cur_bwp_info.bwpSize;
}
// allocSol gives [prgStart, prgEndExclusive] in PRG units; -1 means no allocation
if (cumac_slot_data->allocSol[cumac_ue_id * 2] < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] cuMAC returned no PRB allocation, skipping\n", rnti, frame, slot);
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
iterator++;
continue;
}
const int prb_per_prg = cumac_nPrbPerPrg();
// allocSol gives carrier-absolute PRG indices. The 4T4R scheduler ignores prgMsk
// during allocation and always returns [0, nPrbGrp); clip to the UE's current BWP.
const int alloc_abs_start = cumac_slot_data->allocSol[cumac_ue_id * 2] * prb_per_prg;
const int alloc_abs_end = cumac_slot_data->allocSol[cumac_ue_id * 2 + 1] * prb_per_prg;
const int clipped_abs_start = alloc_abs_start > bwp_start ? alloc_abs_start : bwp_start;
const int clipped_abs_end = alloc_abs_end < bwp_start + bwp_size ? alloc_abs_end : bwp_start + bwp_size;
if (clipped_abs_end <= clipped_abs_start) {
LOG_W(NR_MAC,
"[UE %04x][%4d.%2d] cuMAC allocation [%d,%d) PRBs has no overlap with BWP [%d,%d), skipping\n",
rnti,
frame,
slot,
alloc_abs_start,
alloc_abs_end,
bwp_start,
bwp_start + bwp_size);
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
iterator++;
continue;
}
rbStart = clipped_abs_start - bwp_start;
uint16_t max_rbSize = clipped_abs_end - clipped_abs_start;
#endif
int CCEIndex = get_cce_index(mac,
CC_id,
slot,
@@ -838,12 +1099,23 @@ static void pf_dl(gNB_MAC_INST *mac,
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
int mcs = iterator->selected_mcs;
#ifdef ENABLE_CUMAC
if (cumac_ue_id >= 0 && cumac_slot_data != NULL) {
if ((cumac_slot_data->taskBitMask & TASK_BIT(CUMAC_TASK_MCS_SELECTION))
&& cumac_slot_data->mcsSelSol != NULL)
mcs = cumac_slot_data->mcsSelSol[cumac_ue_id];
if ((cumac_slot_data->taskBitMask & TASK_BIT(CUMAC_TASK_LAYER_SELECTION))
&& cumac_slot_data->layerSelSol != NULL)
l = cumac_slot_data->layerSelSol[cumac_ue_id];
}
#endif
NR_sched_pdsch_t sched_pdsch = {
// rbSize below
.rbStart = rbStart,
.mcs = iterator->selected_mcs,
.R = nr_get_code_rate_dl(iterator->selected_mcs, dl_bwp->mcsTableIdx),
.Qm = nr_get_Qm_dl(iterator->selected_mcs, dl_bwp->mcsTableIdx),
.mcs = mcs,
.R = nr_get_code_rate_dl(mcs, dl_bwp->mcsTableIdx),
.Qm = nr_get_Qm_dl(mcs, dl_bwp->mcsTableIdx),
// tb_size below
.dl_harq_pid = sched_ctrl->available_dl_harq.head,
.pucch_allocation = alloc,
@@ -880,6 +1152,17 @@ static void pf_dl(gNB_MAC_INST *mac,
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
#ifdef ENABLE_CUMAC
// Store the scheduled solutions back into per-UE state for the next TTI's request
if (cumac_ue_id >= 0 && cumac_slot_data != NULL) {
iterator->UE->cumac_last_sol.has_prev_sol = true;
iterator->UE->cumac_last_sol.alloc_sol[0] = (int16_t)sched_pdsch.rbStart;
iterator->UE->cumac_last_sol.alloc_sol[1] = (int16_t)sched_pdsch.rbSize;
iterator->UE->cumac_last_sol.mcs_sol = (int16_t)sched_pdsch.mcs;
iterator->UE->cumac_last_sol.layer_sol = (int8_t)sched_pdsch.nrOfLayers;
}
#endif
/* transmissions: directly allocate */
n_rb_sched[beam.idx] -= sched_pdsch.rbSize;
@@ -889,6 +1172,10 @@ static void pf_dl(gNB_MAC_INST *mac,
remainUEs[beam.idx]--;
iterator++;
}
#ifdef ENABLE_CUMAC
cumac_allow_send();
#endif
}
static void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)

View File

@@ -242,6 +242,15 @@ static const int SINRx10_MCS_mapping[29] = {
162, 170, 178, 186, 194, 202, 212, 220, 245 // 20..28
};
int get_SINRx10_for_mcs(int mcs)
{
if (mcs < 0)
mcs = 0;
if (mcs > 28)
mcs = 28;
return SINRx10_MCS_mapping[mcs];
}
int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl)
{
if (mcs_table != 0) {

View File

@@ -0,0 +1,44 @@
if (OAI_AERIAL_CUMAC)
message(STATUS "Building with support for Aerial cuMAC")
add_library(aerial_cumac_lib
cumac_msg.c
cumac_msg_funcs.c
cumac_nvipc.c
)
find_library(NVIPC_LIB nvipc REQUIRED)
if (NOT NVIPC_LIB)
message(FATAL_ERROR "-- nvipc library not found")
else ()
message(STATUS " nvipc library found in ${NVIPC_LIB}")
endif ()
find_library(NVLOG_LIB nvlog REQUIRED)
if (NOT NVLOG_LIB)
message(FATAL_ERROR "-- nvlog library not found")
else ()
message(STATUS " nvlog library found in ${NVLOG_LIB}")
endif ()
#target_compile_definitions(aerial_cumac_lib PUBLIC ENABLE_L2_SLT_RSP)
target_link_libraries(aerial_cumac_lib PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs)
#target_link_libraries(aerial_cumac_lib PRIVATE nr_fapi_p5 nr_fapi_p7)
target_link_libraries(aerial_cumac_lib PRIVATE "${NVLOG_LIB}" "${NVIPC_LIB}")
find_package(CUDA REQUIRED)
target_link_libraries(aerial_cumac_lib PUBLIC cudart)
#add_executable(cumac_test
# cumac_test.c
# cumac_msg_funcs.c
# cumac_msg.c)
#target_link_libraries(cumac_test PRIVATE "${NVLOG_LIB}" "${NVIPC_LIB}")
##target_link_libraries(cumac_test PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs)
#target_link_libraries(cumac_test PRIVATE nr_fapi_p5 nr_fapi_p7)
#target_link_libraries(cumac_test PRIVATE utils pthread UTIL ${T_LIB} minimal_lib )
##flag for nvIPC
#target_compile_definitions(cumac_test PUBLIC ENABLE_L2_SLT_RSP)
## This makes it so cumac_test is always compiled whenever nr-softmodem is compiled
#add_dependencies(nr-softmodem cumac_test )
else ()
message(STATUS "No Support for Aerial cuMAC")
endif ()

View File

@@ -0,0 +1,893 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "cuda_fp16.h"
#include "cuda.h"
#include <cuda_runtime.h>
#include <cuComplex.h>
#include "cuda_bf16.h"
// cuMAC namespace
namespace cumac {
// defined constants
constexpr uint16_t maxNumCoorCells_ = 20;
constexpr uint16_t maxNumActUePerCell_ = 1024; // must be power of 2, max 1024
constexpr uint16_t maxNumUegPerCell_ = 128; // must be power of 2, max 128
constexpr uint16_t maxNumSchdUePerCellTTI_ = 16;
constexpr uint16_t maxNumUeSchdPerCell_ = 16;
constexpr uint16_t maxNumLayerPerGrpDL_ = 16;
constexpr uint16_t maxNumLayerPerGrpUL_ = 8;
constexpr uint16_t totNumPuschDmrsPort_ = 8;
constexpr uint16_t totNumPdschDmrsPort_ = 8;
constexpr uint16_t maxNumBsAnt_ = 64;
constexpr uint16_t maxNumUeAnt_ = 4;
constexpr uint16_t maxNumPrgPerCell_ = 273;
constexpr uint16_t maxNumUeForGrpPerCell_ = 128; // must be power of 2, max 128
constexpr uint16_t maxNumUeGrpSchdPerCell_ = 16;
constexpr uint16_t maxNumLayerPerUe_ = 4;
constexpr uint16_t minPow2MaxNumUeGrpPercell_ = 16; // smallest power of 2 >= MaxNumUeGrpSchdPerCell_
// multi-cell MU-MIMO UE grouping list
struct multiCellMuGrpList {
uint16_t* numUeInGrp = nullptr;
// array of number of UEs in each UEG in the coordinated cell group
// format: one dimensional array. array size: maxNumCoorCells_*maxNumUegPerCell_
// denote cIdx = 0, 1, ... maxNumCoorCells_-1 as the coordinated cell index
// denote uegIdx = 0, 1, ... maxNumUegPerCell_-1 as the UEG index
// numUeInGrp[cIdx*maxNumUegPerCell_ + uegIdx] is the number of UEs in the uegIdx-th UEG in the cIdx-th coordinated cell
uint16_t* ueId = nullptr;
// array of UE IDs for each UEG in the coordinated cell group
// format: one dimensional array. array size: maxNumCoorCells_*maxNumUegPerCell_*maxNumLayerPerGrpDL_
// denote cIdx = 0, 1, ... nCell-1 as the coordinated cell index
// denote uegIdx = 0, 1, ... maxNumUegPerCell_-1 as the UEG index
// denote uIdx = 0, 1, ... maxNumLayerPerGrpDL_-1 as the UE index
// ueId[cIdx*maxNumUegPerCell_*maxNumLayerPerGrpDL_ + uegIdx*maxNumLayerPerGrpDL_ + uIdx] is the uIdx-th UE's active UE ID in the uegIdx-th UEG in the cIdx-th coordinated cell
// 0xFFFF indicates an invalid element
int16_t* subbandId = nullptr;
// array of subband IDs for each UEG in the coordinated cell group
// format: one dimensional array. array size: maxNumCoorCells_*maxNumUegPerCell_
// denote cIdx = 0, 1, ... maxNumCoorCells_-1 as the coordinated cell index
// denote uegIdx = 0, 1, ... maxNumUegPerCell_-1 as the UEG index
// subbandId[cIdx*maxNumUegPerCell_ + uegIdx] is the subband ID for the uegIdx-th UEG in the cIdx-th coordinated cell
// -1 indicates no subband allocation for a UEG
};
// cuMAC API data structures ////////////////////////////////////////////////////
struct cumacCellGrpUeStatus { // per-UE information
//----------------- data buffers -----------------
uint32_t* lastSchdSlotActUe = nullptr;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// last scheduled slot index for each active UE in the coordinated cells
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the active UE index in the coordinated cells
// lastSchdSlotActUe[uIdx] is the last scheduled slot index for the uIdx-th active UE in the coordinated cells
// range of each element: [0, 0xFFFFFFFE]
// 0xFFFFFFFF indicates a UE has not been scheduled yet
// ** should be maintained throughout the session where cuMAC modules are being used
float* ueTxPow = nullptr;
// array of each active UEs total uplink transmit power (Watt) across all transmit antennas in the coordinated cell group.
// format: one dimensional array. array size: nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// ueTxPow[uIdx] is the uIdx-th active UEs total transmit power in Watt.
float* noiseVarActUe = nullptr;
// array of each active UEs per-Rx-antenna noise variance (Watt) in the coordinated cell group.
// format: one dimensional array. array size: nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// noiseVarActUe[uIdx] is the uIdx-th active UEs per-Rx-antenna noise variance in Watt.
float* avgRates = nullptr;
//* (For 4TR SU-MIMO only)
// per-UE long-term average data rates of the selected UEs per TTI in the coordinated cells
// format: one dimensional array, array size = nUe
// denote uIdx = 0, 1, ..., nUe-1 as the selected UE index in the coordinated cells
// avgRates[uIdx] is the uIdx-th selected UE's long-term average data rate
float* avgRatesActUe = nullptr;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// per-UE long-term average data rates of all active UEs in the coordinated cells
// ** should be maintained throughout the session where cuMAC modules are being used
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// avgRatesActUe[uIdx] is the uIdx-th active UE's long-term average data rate
int8_t* cqiActUe = nullptr;
// array of the most recent reported CQI levels of all active UEs in the coordinated cells
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// cqiActUe[uIdx] is the uIdx-th active UE's most recent reported CQI level
// -1 indicates an invalid element
// ! If no memory is allocated for this buffer, the SRS-based MCS selection is used. Otherwise the CQI-based MCS selection is used.
int8_t* riActUe = nullptr;
// array of the most recent reported RI values of all active UEs in the coordinated cells
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// riActUe[uIdx] is the uIdx-th active UE's most recent reported RI value
// ** For each active UE uIdx, riActUe[uIdx] should be initialized to 1
// -1 indicates an invalid element
// ! If no memory is allocated for this buffer, the SRS-based layer selection is used. Otherwise the RI-based layer selection is used.
int8_t* pmiActUe = nullptr;
// array of the most recent reported PMI values of all active UEs in the coordinated cells
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// pmiActUe[uIdx] is the uIdx-th active UE's most recent reported PMI value
// -1 indicates an invalid element
// ! assuming that for each active UE uIdx, cqiActUe[uIdx], riActUe[uIdx] and pmiActUe[uIdx] are all from the UE's most recent CSI report
uint16_t* prioWeightActUe = nullptr;
//* (For 4TR SU-MIMO only)
// Priority weights of all active UEs in the coordinated cells
// For priority-based UE selection
// ** used/accessed by both the priority-based Round Robin UE selection and Round Robin PRG allocation modules
// ** should be maintained throughout the session where cuMAC modules are being used
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// prioWeightActUe[uIdx] is the uIdx-th active UE's priority weight
// ** For each active UE uIdx, prioWeightActUe[uIdx] should be initialized to 0
// 0xFFFF indicates an invalid element
uint32_t* bufferSize = nullptr;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// per-UE buffer depth in bytes of UEs
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the selected UE index in the coordinated cells
// bufferSize[uIdx] is the buffer depth in bytes of the uIdx-th active UE
int8_t* tbErrLastActUe = nullptr;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// TB decoding error indicators of all active UEs in the coordinated cells.
// ** should be maintained throughout the session where cuMAC modules are being used
// format: one dimensional array.Array size: nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the active UE index in the coordinated cells.
// tbErrLastActUe[uIdx] is the uIdx-th active UE's TB decoding error indicator:
// -1 - the last transmission is not a new transmission (is a re-transmission)
// 0 CRC pass
// 1 CRC failure
// *Note that if the last transmission of a UE is not a new transmission, tbErrLastActUe of that UE should be set to -1.
int8_t* tbErrLast = nullptr;
// TB decoding error indicators of the selected UEs's last transmission
//* (for 4TR SU-MIMO)
// format: one dimensional array, array size = nUe
// denote uIdx = 0, 1, ..., nUe-1 as the selected UE index in the coordinated cells
// tbErrLast[uIdx] is the uIdx-th selected UE's TB decoding error indicator
//* (For 64TR MU-MIMO)
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the active UE index in the coordinated cells.
// tbErrLast[uIdx] is the uIdx-th active UE's TB decoding error indicator:
// -1 - the last transmission is not a new transmission (is a re-transmission)
// 0 CRC pass
// 1 CRC failure
// *Note that if the last transmission of a UE is not a new transmission, tbErrLast of that UE should be set to -1.
//----------------- HARQ related parameter fields -----------------
// HARQ-based scheduling currently only supported for type-1 PRG allocation
int8_t* newDataActUe = nullptr;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// Indicators of initial transmission/retransmission for all active UEs
// ** should be maintained throughout the session where cuMAC modules are being used
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// newDataActUe[uIdx] is the indicator of initial transmission/retransmission for the uIdx-th active UE in the coordinated cells
// 0 - retransmission, 1 - new data/initial transmission
// -1 indicates an invalid element
int16_t* allocSolLastTx = nullptr;
// The PRG allocation solution of the last transmission of the selected/scheduled UEs in the coordinated cells
// Currently only support type-1 PRG allocation
//* (For 4TR SU-MIMO)
// format: one dimensional array, array size = 2*nUe
// Two elements per selected UE, 1st element represents the starting PRG index and the 2nd element represents the ending PRG index plus one
// -1 indicates that a given UE is not being allocated to any PRG
// Using the UE ID mapping setSchdUePerCellTTI in the cumacSchdSol structure
//* (For 64TR MU-MIMO)
// format: one dimensional array.Array size: 2*nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// allocSolLastTx[2*uIdx] is the starting PRG index of the uIdx-th active UEs last transmission.
// allocSolLastTx[2*uIdx + 1] is the ending PRG index of the uIdx-th active UEs last transmission plus one.
// -1 indicates that a given UE is not being allocated to any PRG.
int16_t* mcsSelSolLastTx = nullptr;
// MCS selection solution of the last transmission of the selected/scheduled UEs in the coordinated cells
//* (For 4TR SU-MIMO)
// format: one dimensional array, array size = nUe
// denote uIdx = 0, 1, ..., nUe-1 as the 0-based UE index for the selected UEs in the coordinated cells
// mcsSelSol[uIdx] indicates the MCS level for the uIdx-th selected UE in the coordinated cells.
// Currently only support Table 5.1.3.1-2: MCS index table 2, 3GPP TS 38.214
// Value of each element: 0, 1, ..., 27
// -1 indicates an invalid element
// Using the UE ID mapping setSchdUePerCellTTI in the cumacSchdSol structure
//* (For 64TR MU-MIMO)
// format: one dimensional array.Array size: nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// mcsSelSolLastTx[uIdx] is the MCS level of the uIdx-th active UEs last transmission.
// Range of each element:
// 0, 1, …, 27 (Currently only support Table 5.1.3.1-2: MCS index table 2, 3GPP TS 38.214).
// -1 indicates an element is invalid.
uint8_t* layerSelSolLastTx = nullptr;
// layer selection solution of the last transmission of the selected/scheduled UEs in the coordinated cells
//* (For 4TR SU-MIMO)
// format: one dimensional array, array size = nUe
// Each element represents the number of layers selected for a UE
// Assumption's that the selected layers are the ones with the largest singular values
// 0xFF indicates an invalid element
// Using the UE ID mapping setSchdUePerCellTTI in the cumacSchdSol structure
//* (For 64TR MU-MIMO)
// format: one dimensional array.Array size: nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// layerSelSolLastTx[uIdx] is the number of layers of the uIdx-th active UEs last transmission.
// Range of each element: 0, 1, …, nUeAnt-1.
// 0xFF (255) indicates an element is invalid.
float* beamformGainCurrTx = nullptr;
//* (For 64TR MU-MIMO)
// per-UE beamforming gains in dB of all active UEs' current scheduled transmissions in the coordinated cell group
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// beamformGainCurrTx[uIdx] is the uIdx-th active UE's beamforming gain
// Initialize beamformGainCurrTx to -100.0 dB for all UEs
// -100.0 indicates an element is invalid
float* bfGainPrgCurrTx = nullptr;
//* (For 64TR MU-MIMO)
// per-UE and per-PRG beamforming gains in dB of all active UEs' current scheduled transmissions in the coordinated cell group
// format: one dimensional array, array size = nActiveUe*nPrbGrp
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// bfGainPrgCurrTx[uIdx*nPrbGrp + prgIdx] is the uIdx-th active UE's beamforming gain for the prgIdx-th PRG in the current scheduled transmission
// Initialize bfGainPrgCurrTx to -100.0 dB for all UEs and all PRGs
// -100.0 indicates an element is invalid
float* beamformGainLastTx = nullptr;
//* (For 64TR MU-MIMO)
// per-UE beamforming gains in dB of all active UEs' last scheduled transmissions in the coordinated cell group
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// beamformGainLastTx[uIdx] is the uIdx-th active UE's beamforming gain
// Initialize beamformGainLastTx to -100.0 dB for all UEs
// -100.0 indicates an element is invalid
};
struct cumacCellGrpPrms { // coordinated cell group information
//----------------- parameters -----------------
uint8_t dlSchInd = 1;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// DL/UL scheduling indicator
// 0 - UL scheduling
// 1 - DL scheduling
// default is 1 for DL scheduling
uint8_t harqEnabledInd = 1;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// HARQ enabled indicator
// 0 - HARQ disabled
// 1 - HARQ enabled
// default is 1 for HARQ enabled
uint16_t nCell;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// total number of cells in the coordinated cell group
// range: [1, maxNumCoorCells_]
uint16_t nUe;
//* (for 4TR SU-MIMO)
// total number of selected/scheduled UEs for all coordinated cells per TTI
// nUe = nCell*numUeSchdPerCellTTI
// it is the upperbound for the number of selected/scheduled UEs for all coordinated cells when numUeSchdPerCellTTIArr is populated (not equal to nullptr)
uint16_t nActiveUe;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// total number of active UEs in all coordinated cells
// range: [0, maxNumCoorCells_*maxNumActUePerCell_]
uint8_t numUeSchdPerCellTTI = 16;
//* (for 4TR SU-MIMO)
// number of UEs selected/scheduled per TTI per cell when numUeSchdPerCellTTIArr is not populated (assigned to nullptr)
// otherwise, it is the upperbound for the number of UEs that can be selected/scheduled per TTI per cell
// current assumption's that 6 <= numUeSchdPerCellTTI <= 16. nUe = numUeSchdPerCellTTI*nCell
//* (for 64TR MU-MIMO)
// maximum total number of SU-MIMO and MU-MIMO UEs scheduled per TTI per cell
// current assumption's that 6 <= numUeSchdPerCellTTI <= 16.
uint16_t numUeForGrpPerCell = 16;
//* (for 64TR MU-MIMO)
// number of UEs considered for MU-MIMO UE grouping per cell
uint8_t nMaxLayerPerUeSuDl = 4;
//* (for 64TR MU-MIMO)
// maximum number of layers per UE for SU-MIMO DL
uint8_t nMaxLayerPerUeSuUl = 4;
//* (for 64TR MU-MIMO)
// maximum number of layers per UE for SU-MIMO UL
uint8_t nMaxLayerPerUeMuDl = 2;
//* (for 64TR MU-MIMO)
// maximum number of layers per UE for MU-MIMO DL
uint8_t nMaxLayerPerUeMuUl = 2;
//* (for 64TR MU-MIMO)
// maximum number of layers per UE for MU-MIMO UL
uint8_t nMaxUePerGrpUl = 8;
//* (for 64TR MU-MIMO)
// maximum number of UEs per UEG for UL
uint8_t nMaxUePerGrpDl = 16;
//* (for 64TR MU-MIMO)
// maximum number of UEs per UEG for DL
uint8_t nMaxLayerPerGrpUl = 8;
//* (for 64TR MU-MIMO)
// maximum number of layers per UEG for UL
uint8_t nMaxLayerPerGrpDl = 16;
//* (for 64TR MU-MIMO)
// maximum number of layers per UEG for DL
uint8_t nMaxUegPerCellDl = 4;
//* (for 64TR MU-MIMO)
// maximum number of UEGs per cell for DL
// range: [1, 16]
uint8_t nMaxUegPerCellUl = 4;
//* (for 64TR MU-MIMO)
// maximum number of UEGs per cell for UL
// range: [1, 16]
float muGrpSrsSnrMaxGap = 100.0;
//* (for 64TR MU-MIMO)
// maximum gap among the SRS SNRs of UEs in the same MU-MIMO UEG
// default is 6.0 dB.
// set to a large value, e.g. 100.0 dB to disable the SRS SNR gap constraint for MU-MIMO UE grouping
float muGrpSrsSnrSplitThr = -100.0;
//* (for 64TR MU-MIMO)
// Threshold to split the SRS SNR range for grouping UEs for MU-MIMO separately
// default is 0 dB.
// -100.0 indicates that the SRS SNR range is not split for MU-MIMO grouping.
uint8_t ueGrpMode = 0;
//* (for 64TR MU-MIMO)
// UE grouping mode
// 0 - dynamic UE grouping per TTI
// 1 - flag-triggered UE grouping (controlled by the muUeGrpTrigger flag in cumacCellGrpPrms)
// default is 0 for dynamic UE grouping per TTI
uint8_t muGrpUpdate = 0;
//* (for 64TR MU-MIMO)
// trigger for performing MU-MIMO UE grouping in the current TTI
// 0 - not triggering UE grouping in the current TTI
// 1 - triggering UE grouping in the current TTI
// default is 0
uint8_t semiStatFreqAlloc = 0;
//* (For 64TR MU-MIMO)
// indication for whether or not to enable semi-static subband allocation for SU UEs/MU UEGs
// 0 - disable semi-static subband allocation
// 1 - enable semi-static subband allocation
// default is 0 to disable semi-static subband allocation
uint8_t bfPowAllocScheme = 1;
// power allocation scheme for beamforming weights computation
// * currently only support Scheme 0 and 1
// 0 - equal power allocation for RX side received per-layer beamforming gain
// 1 - equal power allocation for TX side per-layer beamforming gain
// 2 - water-filling power allocation for per-layer beamforming gain
// 3 - water-filling power allocation for per-UE beamforming gain
// default is 1
uint8_t mcsSelLutType = 0;
//* (for both 4TR SU-MIMO and 64TR MU-MIMO)
// MCS selection look-up table type
// 0 - LUT L1T1B024
// 1 - LUT L1T1B050PRGS01_GTC25
// default is 0
// Currently only support Table 5.1.3.1-2: MCS index table 2, 3GPP TS 38.214
float mcsSelSinrCapThr = 25.99;
//* (for both 4TR SU-MIMO and 64TR MU-MIMO)
// SINR capping threshold for MCS selection
// default is the minimum required SINR for MCS 27, i.e. 25.99 dB. May add a margin to this default value for performance tuning.
uint8_t mcsSelCqi = 0;
//* (for both 4TR SU-MIMO and 64TR MU-MIMO)
// whether to use wideband CQI or wideband SINR for MCS selection
// 0 - use wideband SINR for MCS selection
// 1 - use wideband CQI for MCS selection
// default is 0 to use wideband SINR for MCS selection
uint16_t nMaxActUePerCell = maxNumActUePerCell_; // Maximum number of active UEs per cell. Default value is maxNumActUePerCell
uint16_t nPrbGrp; // total number of PRGs
uint8_t nBsAnt; // Each RUs number of TX & RX antenna ports. Value: 4 or 64
uint8_t nUeAnt; // Each active UEs number of TX & RX antenna ports. Value: 2, 4. Assumption's that nUeAnt <= nBsAnt
float W; // frequency bandwidth (Hz) of a PRG: 12 * SCS * # of PRBs per PRG
float sigmaSqrd; // noise variance if channel is not normalized; 1/SNR if channel is normalized with transmit power
float Pt_Rbg; // NA
float Pt_rbgAnt; // NA
uint8_t precodingScheme; // precoder type: 0 - no precoding, 1 - SVD precoding
uint8_t receiverScheme; // receiver type: only support 1 - MMSE-IRC
uint8_t allocType; // PRB allocation type: 0 - non-consecutive type 0 allocate, 1 - consecutive type 1 allocate
float betaCoeff = 1.0; // coefficient for balancing cell-center and cell-edge UEs' performance in multi-cell scheduling. Default value is 1.0
float sinValThr = 0.1; // (For 4TR SU-MIMO only) singular value threshold for layer selection, value is in (0, 1). Default value is 0.1
float corrThr = 0.5; // channel vector correlation value threshold for layer selection, value is in (0, 1). Default value is 0.5
uint16_t prioWeightStep = 100; // step size for UE priority weight increment per TTI if UE does not get scheduled. Default is 100
float muCoeff = 1.5; // Coefficient for prioritizing UEs selected for MU-MIMO transmissions.
float zfCoeff = 1e-16; // Scalar coefficient used for regularizing the zero-forcing beamformer.
float srsSnrThr = -3.0; // Threshold on SRS reported SNR in dB for determining the feasibility of MU-MIMO transmission. Value: a real number in unit of dB. Default value is -3.0 (dB).
float chanCorrThr = 0.7; // Threshold for the squared channel vector correlation value in UE grouping. Value: a real number between 0 and 1.0. Default: 0.1
//----------------- data buffers -----------------
uint32_t* currSlotIdxPerCell = nullptr;
//* (for both 4TR SU-MIMO and 64TR MU-MIMO)
// current slot index for each cell in the coordinated cell group
// format: one dimensional array, array size = nCell
// denote cIdx = 0, 1, ..., nCell-1 as the coordinated cell index
// currSlotIdxPerCell[cIdx] is the current slot index for the cIdx-th cell
// range of each element: [0, 0xFFFFFFFE]
// 0xFFFFFFFF indicates an invalid element
uint16_t* cellId = nullptr;
//* (for 4TR SU-MIMO)
// coordinated cell indexes
// format: one dimensional array for coordinated cell indexes (0, 1, ... nCell-1)
// Currently only support coordinated cell indexes starting from 0
uint8_t* cellAssoc = nullptr;
//* (for 4TR SU-MIMO)
// cell-UE association profile for the selected UEs per TTI in the coordinated cells
// format: one dimensional array, size: nCell*nUe
// denote cIdx = 0, 1, ... nCell-1 as the coordinated cell index
// denote uIdx = 0, 1, ..., nUe-1 as the selected UE index in the coordinated cells
// cellAssoc[cIdx*nUe + uIdx] == 1 means the uIdx-th selected UE is associated with the cIdx-th coordinated cell, 0 otherwise
// ** cellAssoc[cIdx*nUe + uIdx] should be set to 0 for all cIdx = 0, 1, ... nCell-1 if setSchdUePerCellTTI[uIdx] is 0xFFFF
// ** using the global UE ID mapping setSchdUePerCellTTI in the cumacSchdSol structure
uint8_t* numUeSchdPerCellTTIArr = nullptr;
//* (for 4TR SU-MIMO)
// array of the numbers of UEs scheduled per TTI for each cell in the coordinated cell group
// format: one dimensional array, size: nCell
// denote cIdx = 0, 1, ... nCell-1 as the coordinated cell index
// numUeSchdPerCellTTIArr[cIdx] is the number of UEs scheduled per TTI for the cIdx-th cell
// assumption's that numUeSchdPerCellTTIArr[cIdx] <= numUeSchdPerCellTTI for all cIdx = 0, 1, ... nCell-1
uint8_t* cellAssocActUe = nullptr;
// cell-UE association profile for all active UEs in the coordinated cells
// format: one dimensional array, array size = nCell*nActiveUe
// denote cIdx = 0, 1, ... nCell-1 as the coordinated cell index,
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// cellAssocActUe[cIdx*nActiveUe + uIdx] == 1 means the uIdx-th active UE is associated to cIdx-th coordinated cell, 0 otherwise
float* blerTargetActUe = nullptr;
//* (for both 4TR SU-MIMO and 64TR MU-MIMO)
// array of BLER targets for all active UEs in the coordinated cells
// format: one dimensional array, array size = nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// blerTargetActUe[uIdx] is the BLER target for the uIdx-th active UE
// default value is 0.1
// floating type
//* For 4TR SU-MIMO
cuComplex* estH_fr = nullptr; // (FP32) estimated CFR channel coefficients per cell-UE link, per PRG, per Tx-Rx antenna pair for the selected UEs per TTI in the coordinated cells
cuComplex* estH_fr_actUe = nullptr; // estimated CFR channel coefficients per cell-UE link, per PRG, per Tx-Rx antenna pair for all active UEs in the coordinated cells
cuComplex* estH_fr_actUe_prd = nullptr;
cuComplex* prdMat = nullptr;
// DL precoding weights for all cells, per PRG, per pair of Tx antenna port and layer
// format: one dimensional array, array size = nCell*nPrbGrp*nBsAnt*MaxNumLayerPerGrpDL_
// denote cIdx = 0, 1, ..., nCell-1 as the coordinated cell index
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// denote antIdx = 0, 1, ..., nBsAnt-1 as the Tx antenna port index
// denote layerIdx = 0, 1, ..., MaxNumLayerPerGrpDL_-1 as the layer index
// prdMat[cIdx*nPrbGrp*nBsAnt*MaxNumLayerPerGrpDL_ + prgIdx*nBsAnt*MaxNumLayerPerGrpDL_ + antIdx*MaxNumLayerPerGrpDL_ + layerIdx] is the precoder weight from the layerIdx-th layer to the antIdx-th TX antenna port on the prgIdx-th PRG for the cIdx-th cell
// un-used precoder weights entries are set to 0
// prdMat is set to 0 for an un-allocated PRG in a cell
cuComplex* prdMat_actUe = nullptr; // precoder matrix coefficients per cell-UE link, per PRG, per symbol-Tx antenna pair for all active UEs in the coordinated cells
cuComplex* detMat = nullptr; // detector matrix coefficients per cell-UE link, per PRG, per symbol-Tx antenna pair for the selected/scheduled UEs
cuComplex* detMat_actUe = nullptr; // detector matrix coefficients per cell-UE link, per PRG, per symbol-Tx antenna pair for all active UEs in the coordinated cells
float* sinVal = nullptr; // singular values associated with each precoder matrix, per cell-UE link, per PRG, per layer (maximum number of layers is equal to the number of UE antennas) for the selected/scheduled UEs
//* (For 4TR SU-MIMO)
// Array of singular values of the PRG level SRS channel estimates for all active UEs in the coordinated cell group.
// format: one-dimensional array, size: nActiveUe*nPrbGrp*nUeAnt
// Per-UE, per-PRG, per-layer singular values obtained from the SVD of SRS channel matrices
// For each active UE and on each PRG, the singular values are stored in descending order:
// denote uIdx = 0, 1, ..., nActiveUe-1 as the 0-based active UE index in the coordinated cells
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// denote layerIdx = 0, 1, ..., nUeAnt-1 as the layer index
// sinVal[uIdx*nPrbGrp*nUeAnt + prgIdx*nUeAnt + layerIdx] is the UE uIdx's layerIdx-th largest singular value on PRG prgIdx
float* sinVal_actUe = nullptr; // singular values associated with each precoder matrix, per cell-UE link, per PRG, per layer (maximum number of layers is equal to the number of UE antennas) for all active UEs in the coordinated cells
cuComplex** estH_fr_perUeBuffer = nullptr; // estimated CFR channel coefficients organized as per-UE buffers
// half-precision
__nv_bfloat162* estH_fr_half = nullptr; // (FP16) estimated CFR channel coefficients per cell-UE link, per PRG, per Tx-Rx antenna pair for the selected UEs per TTI in the coordinated cells
float* postEqSinr = nullptr; // *** This SINR array will be used by multiple cuMAC modules throughput the simulation process. So it should be allocated at initialization phase and should not be allocated & freed per TTI
//* (For 4TR SU-MIMO only)
// per-PRG per-layer post-equalization SINRs for all active UEs in the coordinated cells
// ** should be maintained throughout the session where cuMAC modules are being used
// format: one dimensional array, array size = nActiveUe*nPrbGrp*nUeAnt
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// denote layerIdx = 0, 1, ..., nUeAnt-1 as the layer index
// postEqSinr[uIdx*nPrbGrp*nUeAnt + prgIdx*nUeAnt + layerIdx] is the uIdx-th active UE's post-equalization SINR on the prgIdx-th PRG and the layerIdx-th layer
float* wbSinr = nullptr;
//* (For both 4TR SU-MIMO and 64TR MU-MIMO)
// per-layer wideband SINRs for all active UEs in the coordinated cell group
// * wbSinr is used for the most recent wideband SINR values from either SU-MIMO or MU-MIMO transmissions
// format: one dimensional array, array size = nActiveUe*nUeAnt
// denote uIdx = 0, 1, ..., nActiveUe-1 as the global UE index for all active UEs in the coordinated cells
// denote layerIdx = 0, 1, ..., nUeAnt-1 as the layer index
// wbSinr[uIdx*nUeAnt + layerIdx] is the uIdx-th active UE's wideband SINR on the layerIdx-th layer
// if a single wideband SINR is available to each UE, set all elements with layerIdx > 0 to 0;
// Initialize wbSinr to MCS 0's required SINR for all UEs
//----------------- HARQ related parameter fields -----------------
// HARQ-based scheduling currently only supported for type-1 PRG allocation
uint8_t** prgMsk = nullptr;
//* (For 4TR SU-MIMO only)
// Per-cell bit map for the availability of each PRG for allocation
// format: two-dimensional array, size: nCell X nPrbGrp
// 1st dimension for 0-based coordinated cell indexes (0, 1, ..., nCell-1)
// 2nd dimension for the per-PRG indicators of availability for allocation
// denote cIdx = 0, 1, ... nCell-1 as the coordinated cell index
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// prgMsk[cIdx][prgIdx] is the availability indicator for the prgIdx-th PRG in the cIdx-th coordinated cell
// 0 - unavailable, 1 - available
//----------------- AODT specific data buffers -----------------
cuComplex* prdMat_asim = nullptr;
// Aerial Sim format SVD precoder array
// SVD of DL/UL channel H = U*Sigma*V^H, precoder matrix is V matrix (right matrix of DL/UL channel H)
// format: one-dimensional array, size: nUe*nPrbGrp*nBsAnt*nBsAnt (maximum size is used for mem allocate)
// Per UE, per PRG, per data symbol-TX antenna pair SVD precoder matrix coefficients
// denote uIdx = 0, 1, ..., nUe-1 as the 0-based UE index for the selected UEs in the coordinated cells
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// (for DL) denote antIdx = 0, 1, ..., nBsAnt-1 as the TX antenna port index
// (for UL) denote antIdx = 0, 1, ..., nUeAnt-1 as the TX antenna port index
// (for DL) denote symIdx = 0, 1, ..., nBsAnt-1 as the TX data symbol index
// (for UL) denote symIdx = 0, 1, ..., nUeAnt-1 as the TX data symbol index
// (for DL) prdMat_asim[uIdx*nPrbGrp*nBsAnt*nBsAnt + prgIdx*nBsAnt*nBsAnt + antIdx*nBsAnt + symIdx]
// (for UL) prdMat_asim[uIdx*nPrbGrp*nUeAnt*nUeAnt + prgIdx*nUeAnt*nUeAnt + antIdx*nUeAnt + symIdx]
// is the precoder weight of the uIdx-th selected UE on the prgIdx-th PRG and between the symIdx-th TX data symbol and the antIdx-th TX antenna port.
// Using the UE ID mapping setSchdUePerCellTTI in the cumacSchdSol structure
cuComplex* detMat_asim = nullptr;
// Aerial Sim format SVD detector array
// SVD of DL/UL channel H = U*Sigma*V^H, detector matrix is U matrix (left matrix of DL/UL channel H)
// (effective detector is U^H for DL and V^H for UL but the original U or V matrix is passed via detMat_asim)
// format: one-dimensional array, size: nUe*nPrbGrp*nBsAnt*nBsAnt (maximum size is used for mem allocate)
// Per UE, per PRG, per data symbol-RX antenna pair SVD detector matrix coefficients
// denote uIdx = 0, 1, ..., nUe-1 as the 0-based UE index for the selected UEs in the coordinated cells
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// (for DL) denote symIdx = 0, 1, ..., nUeAnt-1 as the RX data symbol index
// (for UL) denote symIdx = 0, 1, ..., nBsAnt-1 as the RX data symbol index
// (for DL) denote antIdx = 0, 1, ..., nUeAnt-1 as the RX antenna port index
// (for UL) denote antIdx = 0, 1, ..., nBsAnt-1 as the RX antenna port index
// (for DL) detMat_asim[uIdx*nPrbGrp*nUeAnt*nUeAnt + prgIdx*nUeAnt*nUeAnt + symIdx*nUeAnt + antIdx]
// (for UL) detMat_asim[uIdx*nPrbGrp*nBsAnt*nBsAnt + prgIdx*nBsAnt*nBsAnt + symIdx*nBsAnt + antIdx]
// is the detector weight of the uIdx-th selected UE on the prgIdx-th PRG and between the symIdx-th RX data symbol and the antIdx-th RX antenna port.
// Using the UE ID mapping setSchdUePerCellTTI in the cumacSchdSol structure
float* sinVal_asim = nullptr;
// Aerial Sim format singular value array
// format: one-dimensional array, size: nUe*nPrbGrp*nUeAnt
// Per-UE, per-PRG, per-layer singular values obtained from SVD of CFR channel matrices
// For each UE and on each PRG, the singular values are stored in descending order:
// denote uIdx = 0, 1, ..., nUe-1 as the 0-based UE index for the selected UEs in the coordinated cells
// denote prgIdx = 0, 1, ..., nPrbGrp-1 as the PRG index
// denote layerIdx = 0, 1, ..., nUeAnt-1 as the layer index
// sinVal_asim[uIdx*nPrbGrp*nUeAnt + prgIdx*nUeAnt + layerIdx] is the UE uIdx's layerIdx-th largest singular value on PRG prgIdx
// Using the UE ID mapping setSchdUePerCellTTI in the cumacSchdSol structure
////////////////////////////////////////
//* For 64TR MU-MIMO
float* bsTxPow = nullptr;
// array of each RUs total downlink transmit power (Watt) across all transmit antennas in the coordinated cell group.
// format: one dimensional array, array size = nCell
// denote cIdx = 0, 1, …, nCell-1 as the coordinated cell index.
// bsTxPow[cIdx] is the total transmit power of the cIdx-th cells RU.
cuComplex** srsEstChan = nullptr; // (FP32) estimated CFR channel coefficients per cell-UE link, per PRG, per Tx-Rx antenna pair for the selected UEs per TTI in the coordinated cells
//* (For 64TR MU-MIMO only)
// Per-cell array of the PRG level SRS channel estimates for the top-numUeForGrpPerCell UEs after UE sorting
// UE indexing for each cell is based on the srsUeMap array defined below.
// SRS channel estimates are stored as the downlink channels (from RU to UE).
// UE transmit power for the SRS signal is excluded from the channel coefficient magnitude.
// format: two-dimensional array: the 1st dimension is for cells, and the 2nd dimension is for UEs, PRGs, and UE/RU antenna ports.
// array size: nCell X (nCell*numUeForGrpPerCell*nPrbGrp*nUeAnt*nBsAnt)
// denote cIdx = 0, 1, …, nCell-1 as the coordinated cell index.
// denote uIdx = 0, 1, …, nCell*numUeForGrpPerCell-1 as the UE index (local to the srsEstChan[cIdx] array).
// denote prgIdx = 0, 1, …, nPrbGrp-1 as the PRG index.
// denote ueAntIdx = 0, 1, …, nUeAnt as the UE antenna port index
// denote bsAntIdx = 0, 1, …, nBsAnt-1 as the RU antenna port index
// srsEstChan[cIdx][uIdx*nPrbGrp*nUeAnt*nBsAnt + prgIdx*nUeAnt*nBsAnt + ueAntIdx*nBsAnt + bsAntIdx] is the complex (downlink) channel coefficient between the cIdx-th cell and the UE with local ID uIdx on the prgIdx-th PRG, the ueAntIdx-th UE antenna port and the bsAntIdx-th RU antenna port.
int32_t** srsUeMap = nullptr;
//* (For 64TR MU-MIMO only)
// Per-cell UE index mapping array for the SRS channel estimates stored in srsEstChan.
// format: two-dimensional array: the 1st dimension is for cells, and the 2nd dimension is for UEs.
// array size: nCell X nActiveUe
// denote cIdx = 0, 1, …, nCell-1 as the coordinated cell index.
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// srsUeMap[cIdx][uIdx] is the uIdx-th active UEs index in the srsEstChan[cIdx] array.
// srsUeMap[cIdx][uIdx] == -1 means that the uIdx-th active UEs SRS channel estimates are not available in the srsEstChan[cIdx] array.
// it is required that srsUeMap gets updated for the top numUeForGrpPerCell-th active UEs in each cell according to sortedUeList after the MU-MIMO UE sorting module.
float* srsWbSnr = nullptr;
//* (For 64TR MU-MIMO only)
// array of the reported wideband SNRs in dB measured within configured SRS bandwidth for all active UEs in the coordinated cell group.
// format: one dimensional array. array size: nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the active UE index in the coordinated cell group.
// srsWbSnr[uIdx] is the wideband SNR in dB measured within configured SRS bandwidth for the uIdx-th active UE in the coordinated cell group.
// set srsWbSnr[uIdx] to -100.0 (dB) when the uIdx-th active UE's SRS measurements are not available or refreshed.
};
struct cumacSchdSol { // scheduling solutions
//----------------- data buffers -----------------
multiCellMuGrpList* muGrpList = nullptr;
//* (For 64TR MU-MIMO only)
// multi-cell MU-MIMO UE grouping list
// format: a single pointer to the multiCellMuGrpList structure
uint16_t* ueOrderInGrp = nullptr;
//* (For 64TR MU-MIMO only)
// The order of each active UE in its MU-MIMO group
// format: one dimensional array. array size: nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the active UE index in the coordinated cells.
// ueOrderInGrp[uIdx] is the order of the uIdx-th active UE in its MU-MIMO group
// range of each element: 0, 1, ..., MaxNumLayerPerGrpDL_-1
// initialized to 0xFFFF
int16_t* allocSol = nullptr;
// PRB group allocation solution for the selected UEs per TTI in the coordinated cells
//* (For 4TR SU-MIMO)
// format: one dimensional array; array size and content differ for type-0 and type-1 allocations
// For type-0: array size = nCell*nPrbGrp, each element represents the selected UE index (0, 1, ..., nUe-1) that a PRG is allocated to in a coordinated cell
// -1 indicates that a give PRG is not allocated to any UE in a given cell
// For type-1: array size = 2*nUe, two elements per selected UE, 1st element represents the starting PRG index and the 2nd element represents the ending PRG index plus one
// -1 indicates that a given UE is not being allocated to any PRG
// Using the UE ID mapping setSchdUePerCellTTI
//* (For 64TR MU-MIMO)
// Currently only support type-1 PRB allocation
// format: one dimensional array.Array size: 2*nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// allocSol[2*uIdx] is the starting PRG index for the uIdx-th active UE.
// allocSol[2*uIdx + 1] is the ending PRG index for the uIdx-th active UE plus one.
// -1 indicates that a given UE is not being allocated to any PRG.
float* pfMetricArr = nullptr;
//* (for 4TR SU-MIMO)
// only applicable to type-1 PRB allocation, for storing computed PF metrices
// array size = nCell * the minimum power of 2 that is no less than nPrbGrp*numUeSchdPerCellTTI
// memory should be pre-allocated at initialization of cuMAC API
uint16_t* pfIdArr = nullptr;
//* (for 4TR SU-MIMO)
// only applicable to type-1 PRB allocation, for storing indices (indicating PRB and UE indecies) of computed PF metrices
// array size = nCell * the minimum power of 2 that is no less than nPrbGrp*numUeSchdPerCellTTI
// memory should be pre-allocated at initialization of cuMAC API
int16_t* mcsSelSol = nullptr;
// MCS selection solution for the selected UEs per TTI in the coordinated cells
//* (For 4TR SU-MIMO)
// format: one dimensional array, array size = nUe
// denote uIdx = 0, 1, ..., nUe-1 as the 0-based UE index for the selected UEs in the coordinated cells
// mcsSelSol[uIdx] indicates the MCS level for the uIdx-th selected UE in the coordinated cells.
// Currently only support Table 5.1.3.1-2: MCS index table 2, 3GPP TS 38.214
// Value of each element: 0, 1, ..., 27
// -1 indicates an invalid element
// Using the UE ID mapping setSchdUePerCellTTI
//* (For 64TR MU-MIMO)
// format: one dimensional array.Array size: nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the active UE index in the coordinated cells.
// mcsSelSol[uIdx] indicates the MCS level for the uIdx-th active UE in the coordinated cells.
// Range of each element:
// 0, 1, …, 27 (Currently only support Table 5.1.3.1-2: MCS index table 2, 3GPP TS 38.214).
// -1 indicates an element is invalid.
uint16_t* setSchdUePerCellTTI = nullptr;
// set of global IDs of the scheduled UEs per cell per TTI
//* (for 4TR SU-MIMO)
// format: one dimensional array, array size = nCell*numUeSchdPerCellTTI
// denote cIdx = 0, 1, ... nCell-1 as the coordinated cell index,
// denote i = 0, 1, ..., numUeSchdPerCellTTI-1 as the i-th scheduled UE in a cell
// setSchdUePerCellTTI[cIdx*numUeSchdPerCellTTI + i] is within {0, 1, ..., nActiveUe-1} and is the active UE ID of the i-th scheduled UE in the cIdx-th cell.
// 0xFFFF (65535) indicates an element is invalid (no scheduled UE).
//* (for 64TR MU-MIMO)
// format: one dimensional array, array size = nCell*numUeForGrpPerCell
// denote cIdx = 0, 1, ... nCell-1 as the coordinated cell index,
// denote i = 0, 1, ..., numUeForGrpPerCell-1 as the i-th scheduled UE in a cell
// setSchdUePerCellTTI[cIdx*numUeForGrpPerCell + i] is within {0, 1, ..., nActiveUe-1} and is the active UE ID of the i-th scheduled UE in the cIdx-th cell.
// 0xFFFF (65535) indicates an element is invalid (no scheduled UE).
uint8_t* layerSelSol = nullptr;
// layer selection solution for the selected UEs per TTI in the coordinated cells
//* (For 4TR SU-MIMO)
// format: one dimensional array, array size = nUe
// Each element represents the number of layers selected for a UE
// Assumption's that the selected layers are the ones with the largest singular values
// 0xFF indicates an invalid element
// Using the UE ID mapping setSchdUePerCellTTI
//* (For 64TR MU-MIMO)
// layer selection solution for each active UE in the coordinated cells.
// format: one dimensional array.Array size: nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// layerSelSol[uIdx] is the number of layers selected for the uIdx-th active UE.
// Range of each element: 0, 1, …, nUeAnt-1.
// 0xFF (255) indicates an element is invalid (the corresponding active is not being scheduled for transmission).
// *The selected layers are always corresponding to the UE TX/RX antenna ports starting from port 0. Note that for downlink transmissions, the UE side may use more RX antennas to improve receiver performance.
/////////////////////////////////////////////////
//* For 64TR MU-MIMO
uint8_t* muMimoInd = nullptr;
//* (For 64TR MU-MIMO only)
// indicators of MU-MIMO transmission feasibility for all active UEs in the coordinated cell group.
// format: one dimensional array. array size: nActiveUe
// denote uIdx = 0, 1, ..., nActiveUe-1 as the active UE index in the coordinated cell group.
// muMimoInd[uIdx] is the indicator of MU-MIMO transmission feasibility for the uIdx-th active UE in the coordinated cell group.
// muMimoInd[uIdx] == 1 means that UE uIdx is feasible for MU-MIMO.
// muMimoInd[uIdx] == 0 means that UE uIdx is not feasible for MU-MIMO (only feasible for SU-MIMO).
uint16_t** sortedUeList = nullptr;
//* (For 64TR MU-MIMO only)
// array of sorted UE list for each cell.
// two-dimensional array. array size: nCell X nMaxActUePerCell
// denote cIdx = 0, 1, …, nCell-1 as the coordinated cell index.
// denote uIdx = 0, 1, …, nMaxActUePerCell-1 as the UE rank in each cell.
// sortedUeList[cIdx][uIdx] is the active UE index of the uIdx-th ranked UE in the cIdx-th cell.
// 0xFFFF (65535) indicates an element is invalid.
uint8_t* nSCID = nullptr;
//* (For 64TR MU-MIMO only)
// array of the DMRS sequence initialization parameter n_SCID assigned for each active UE in the coordinated cells.
// format: one dimensional array.Array size = nActiveUe
// denote uIdx = 0, 1, …, nActiveUe-1 as the active UE index in the coordinated cells.
// nSCID[uIdx] is the uIdx-th active UEs assigned n_SCID value
// Range of each element: 0 -> 1
// 0xFF (255) indicates an element is invalid.
};
// Outer-loop link adaptation (OLLA) data structure
struct ollaParam {
float delta_ini;
float delta;
float delta_up;
float delta_down;
};
// Aerial Scheduler Acceleration API data structures ////////////////////////////////////////////////////
struct MAC_SCH_CONFIG_REQUEST { // static scheduler configuration parameters, passed from L2 stack host to cuMAC-CP at the initialization phase (one-time pass)
uint8_t harqEnabledInd; // Indicator for whether HARQ is enabled
uint8_t mcsSelCqi; // Indicator for whether MCS selection is based on CQI or SINR
uint8_t nMaxCell; // A constant integer for the maximum number of cells in the cell group
uint16_t nMaxActUePerCell; // A constant integer for the maximum number of active UEs per cell
uint8_t nMaxSchUePerCell; // A constant integer for the maximum number of UEs that can be scheduled per TTI per cell
uint16_t nMaxPrg; // A constant integer for the maximum number of PRGs for allocation in each cell
uint8_t nPrbPerPrg; // A constant integer for the number of PRBs per PRG (PRB group)
uint8_t nMaxBsAnt; // A constant integer for the maximum number of BS antenna ports.
uint8_t nMaxUeAnt; // A constant integer for the maximum number of UE antenna ports.
uint32_t scSpacing; // Subcarrier spacing of the carrier. Value: 15000, 30000, 60000, 120000 (Hz)
uint8_t allocType; // Indicator for type-0 or type-1 PRG allocation
uint8_t precoderType; // Indicator for the precoder type
uint8_t receiverType; // Indicator for the receiver type
uint8_t colMajChanAccess; // Indicator for whether the estimated narrow-band SRS channel matrices are stored in column-major order or in row-major order
float betaCoeff; // Coefficient for adjusting the cell-edge UEs' performance in multi-cell scheduling
float sinValThr; // Singular value threshold for layer selection
float corrThr; // Channel vector correlation value threshold for layer selection
float mcsSelSinrCapThr; // SINR capping threshold for MCS selection
uint8_t mcsSelLutType; // MCS selection LUT type
uint16_t prioWeightStep; // Step size for UE priority weight increment per TTI if UE does not get scheduled. For priority-based UE selection
float* blerTargetActUe = nullptr; // BLER targets of all active UEs in the cell
};
struct MAC_SCH_TTI_REQUEST {
uint16_t cellID; // cell ID
uint8_t ULDLSch = 1; // Indication for UL/DL scheduling. Value - 0: UL scheduling, 1: DL scheduling
uint16_t nActiveUe; // total number of active UEs in the cell
uint16_t nSrsUe; // the number of UEs in the cell that have refreshed SRS channel estimates
uint16_t nPrbGrp; // the number of PRGs that can be allocated for the current TTI, excluding the PRGs that need to be reserved for HARQ re-tx's
uint8_t nBsAnt; // number of BS antenna ports
uint8_t nUeAnt; // number of UE antenna ports
float sigmaSqrd = 1.0; // noise variance
// data buffer pointers
uint16_t* CRNTI = nullptr; // C-RNTIs of all active UEs in the cell
uint16_t* srsCRNTI = nullptr; // C-RNTIs of the UEs that have refreshed SRS channel estimates in the cell.
uint8_t* prgMsk = nullptr; // Bit map for the availability of each PRG for allocation
float* postEqSinr = nullptr; // array of the per-PRG per-layer post-equalizer SINRs of all active UEs in the cell
float* wbSinr = nullptr; // array of wideband per-layer post-equalizer SINRs of all active UEs in the cell
cuComplex* estH_fr = nullptr; // For FP32. array of the subband (per-PRG) SRS channel estimate coefficients for all active UEs in the cell
cuComplex* estH_fr_half = nullptr; // For FP16. array of the subband (per-PRG) SRS channel estimate coefficients for all active UEs in the cell
cuComplex* prdMat = nullptr; // array of the precoder/beamforming weights for all active UEs in the cell
cuComplex* detMat = nullptr; // array of the detector/beamforming weights for all active UEs in the cell
float* sinVal = nullptr; // array of the per-UE, per-PRG, per-layer singular values obtained from the SVD of the channel matrix
float* avgRatesActUe = nullptr; // array of the long-term average data rates of all active UEs in the cell
uint16_t* prioWeightActUe = nullptr; // For priority-based UE selection. Priority weights of all active UEs in the cell
int8_t* tbErrLastActUe = nullptr; // TB decoding error indicators of all active UEs in the cell
int8_t* newDataActUe = nullptr; // Indicators of initial transmission/retransmission for all active UEs in the cell
int16_t* allocSolLastTxActUe = nullptr; // The PRG allocation solution for the last transmissions of all active UEs in the cell
int16_t* mcsSelSolLastTxActUe = nullptr; // MCS selection solution for the last transmissions of all active UEs in the cell
uint8_t* layerSelSolLastTxActUe = nullptr; // Layer selection solution for the last transmissions of all active UEs in the cell
int8_t* cqiActUe = nullptr; // CQI values of all active UEs in the cell
};
struct MAC_SCH_TTI_RESPONSE {
uint16_t* setSchdUePerCellTTI = nullptr; // Set of IDs of the selected UEs for the cell
int16_t* allocSol = nullptr; // PRB group allocation solution for all active UEs in the cell
int16_t* mcsSelSol = nullptr; // MCS selection solution for all active UEs in the cell
uint8_t* layerSelSol = nullptr; // Layer selection solution for all active UEs in the cell
};
// cuMAC H5 TV saving/loading /////////////////////////////////////////////////
struct cumacSchedulerParam {
uint16_t nUe;
uint16_t nCell; // number of coordinated cells
uint16_t totNumCell; // number of all cells in the network. (not needed if channel buffer only contains channels within coordinated cells)
uint16_t nPrbGrp;
uint8_t nBsAnt;
uint8_t nUeAnt; // assumption's that nUeAnt <= nBsAnt
float W;
float sigmaSqrd; // noise variance if channel is not normalized; 1/SNR if channel is normalized with transmit power, limitation: SNR (per antenna) should be <= 111 dB
uint16_t maxNumUePerCell; // maximum number of selected UEs per cell per TTI
uint16_t nMaxSchdUePerRnd; // maximum number of UEs per cell that can be scheduled per round
float betaCoeff; // coefficient for improving cell edge UEs' performance in multi-cell scheduling
uint16_t nActiveUe; // number of active UEs for all coordinated cells
uint8_t numUeSchdPerCellTTI; // number of UEs scheduled per TTI per cell
uint8_t precodingScheme;
uint8_t receiverScheme;
uint8_t allocType;
uint8_t columnMajor;
float sinValThr; // singular value threshold for layer selection
uint16_t numUeForGrpPerCell;
float chanCorrThr;
float muCoeff;
float srsSnrThr;
uint16_t nMaxActUePerCell;
float zfCoeff;
uint8_t nMaxUePerGrpUl;
uint8_t nMaxUePerGrpDl;
uint8_t nMaxLayerPerGrpUl;
uint8_t nMaxLayerPerGrpDl;
uint8_t nMaxLayerPerUeSuUl;
uint8_t nMaxLayerPerUeSuDl;
uint8_t nMaxLayerPerUeMuUl;
uint8_t nMaxLayerPerUeMuDl;
uint8_t nMaxUegPerCellDl;
uint8_t nMaxUegPerCellUl;
float mcsSelSinrCapThr;
float muGrpSrsSnrMaxGap;
float muGrpSrsSnrSplitThr;
uint8_t bfPowAllocScheme;
uint8_t muGrpUpdate;
uint8_t mcsSelLutType;
uint8_t semiStatFreqAlloc;
uint8_t harqEnabledInd;
uint8_t mcsSelCqi;
uint8_t dlSchInd;
};
}

View File

@@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CU_MAC_API_H
#define CU_MAC_API_H
#include "cumac_msg.h"
// todo: move the following constants to test case YAML file
#define MAX_NUM_LCG 4
#define MAX_NUM_LC 4
#define MAX_NUM_UE 100 // a maximum of 512 UEs is supported
#define MAX_NUM_OUTPUT_SORTED_LC_PER_QOS MAX_NUM_UE*MAX_NUM_LC // maximum number of output sorted LCs/LCGs per DL/UL QoS type, used for GPU memory allocation
#define MAX_NUM_SCHEDULED_UE 16 // maximum number of scheduled UEs per time slot for both DL and UL
#define IIR_ALPHA 0.001
// #define SLOT_INTERVAL_NS (500)
#define SLOT_INTERVAL_NS 1E9 // Time slot interval in nanoseconds. Set to 1 second for debug only,
#define SLOT_DURATION 0.0005 // Time slot interval in seconds, 0.5ms
#define NUM_TIME_SLOTS 10 // total number of time slots to simulate
struct PFM_DL_LC_INFO {
uint32_t tbs_scheduled;
uint32_t ravg;
uint32_t pfm;
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - reset ravg to 1.0
uint8_t qos_type; // 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical
uint8_t padding[2]; // Padding to align to 32-bit
};
struct PFM_UL_LCG_INFO {
uint32_t tbs_scheduled;
uint32_t ravg;
uint32_t pfm;
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - reset ravg to 1.0
uint8_t qos_type; // 0 - ul_gbr_critical, 1 - ul_gbr_non_critical, 2 - ul_ngbr_critical, 3 - ul_ngbr_non_critical, 4 - ul_mbr_non_critical
uint8_t padding[2]; // Padding to align to 32-bit
};
struct PFM_UE_INFO {
struct PFM_DL_LC_INFO dl_lc_info[MAX_NUM_LC];
struct PFM_UL_LCG_INFO ul_lcg_info[MAX_NUM_LCG];
uint32_t ambr;
uint32_t rcurrent_dl;
uint32_t rcurrent_ul;
uint16_t rnti;
uint8_t num_layers_dl;
uint8_t num_layers_ul;
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - is_scheduled_dl, flags & 0x04 - is_scheduled_ul
uint8_t carrier_id;
uint8_t num_dl_lcs;
uint8_t num_ul_lcgs;
};
/* ======================================================= */
struct PFM_DL_OUTPUT_INFO {
uint16_t rnti;
uint8_t lc_id;
uint8_t padding; // Padding to align to 32-bit
};
struct PFM_UL_OUTPUT_INFO {
uint16_t rnti;
uint8_t lcg_id;
uint8_t padding; // Padding to align to 32-bit
};
// expected output from the GPU
struct PFM_DL_OUTPUT_INFO dl_gbr_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_DL_OUTPUT_INFO dl_gbr_non_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_DL_OUTPUT_INFO dl_ngbr_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_DL_OUTPUT_INFO dl_ngbr_non_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_DL_OUTPUT_INFO dl_mbr_non_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_UL_OUTPUT_INFO ul_gbr_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_UL_OUTPUT_INFO ul_gbr_non_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_UL_OUTPUT_INFO ul_ngbr_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_UL_OUTPUT_INFO ul_ngbr_non_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct PFM_UL_OUTPUT_INFO ul_mbr_non_critical[MAX_NUM_OUTPUT_SORTED_LC_PER_QOS];
struct cumac_pfm_tti_req_t {
cumac_msg_header_t header;
uint32_t offset_ue_info_arr; // Offset of the PFM_UE_INFO data array in nvipc_buf->data_buf
uint16_t sfn;
uint16_t slot;
uint16_t num_ue; // total number of UEs in the payload
uint16_t num_output_sorted_lc[10];
// number of output sorted LCs/LCGs per DL/UL QoS type, 0-9 are the indices of the QoS types:
// 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical,
// 5 - ul_gbr_critical, 6 - ul_gbr_non_critical, 7 - ul_ngbr_critical, 8 - ul_ngbr_non_critical, 9 - ul_mbr_non_critical.
};
struct cumac_pfm_tti_resp_t {
cumac_msg_header_t header;
uint16_t sfn;
uint16_t slot;
uint32_t offset_output_sorted_lc[10];
// offset of output sorted LCs/LCGs list per DL/UL QoS type, 0-9 are the indices of the QoS types:
// 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical,
// 5 - ul_gbr_critical, 6 - ul_gbr_non_critical, 7 - ul_ngbr_critical, 8 - ul_ngbr_non_critical, 9 - ul_mbr_non_critical.
uint16_t num_output_sorted_lc[10];
// number of output sorted LCs/LCGs per DL/UL QoS type, 0-9 are the indices of the QoS types:
// 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical,
// 5 - ul_gbr_critical, 6 - ul_gbr_non_critical, 7 - ul_ngbr_critical, 8 - ul_ngbr_non_critical, 9 - ul_mbr_non_critical.
};
#endif // CU_MAC_API_H

View File

@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <stddef.h>
#include "cumac_msg.h"
/**
* Convert cuMAC message ID to human-readable string
*
* Provides a lookup table for cuMAC message type identifiers,
* useful for logging and debugging purposes.
*
* @param[in] msg_id Message type identifier from cumac_msg_t enum
*
* @return Pointer to constant string containing message name.
* Returns "UNKNOWN_CUMAC_MSG" for unrecognized message IDs.
*/
const char* get_cumac_msg_name(int msg_id)
{
switch(msg_id)
{
case CUMAC_PARAM_REQUEST:
return "PARAM.req";
case CUMAC_PARAM_RESPONSE:
return "PARAM.resp";
case CUMAC_CONFIG_REQUEST:
return "CONFIG.req";
case CUMAC_CONFIG_RESPONSE:
return "CONFIG.resp";
case CUMAC_START_REQUEST:
return "START.req";
case CUMAC_START_RESPONSE:
return "START.resp";
case CUMAC_STOP_REQUEST:
return "STOP.req";
case CUMAC_STOP_RESPONSE:
return "STOP.resp";
case CUMAC_ERROR_INDICATION:
return "ERR.ind";
case CUMAC_TTI_ERROR_INDICATION:
return "TTI_ERR.ind";
case CUMAC_DL_TTI_REQUEST:
return "DL_TTI.req";
case CUMAC_UL_TTI_REQUEST:
return "UL_TTI.req";
case CUMAC_SCH_TTI_REQUEST:
return "SCH_TTI.req";
case CUMAC_SCH_TTI_RESPONSE:
return "SCH_TTI.resp";
case CUMAC_TTI_END:
return "TTI_END.req";
default:
return "UNKNOWN_CUMAC_MSG";
}
}

View File

@@ -0,0 +1,322 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _NV_CUMAC_MSG_
#define _NV_CUMAC_MSG_
#include <stdint.h>
#include <stddef.h>
#include "cumac_pfm_sort.h"
#if defined(__cplusplus)
extern "C" {
#endif
//! Invalid buffer offset marker (all bits set)
#define INVALID_CUMAC_BUF_OFFSET (0xFFFFFFFF)
/**
* Get human-readable name for cuMAC message ID
*
* @param[in] msg_id Message identifier from cumac_msg_t enum
*
* @return Pointer to message name string, or "UNKNOWN_CUMAC_MSG" if invalid
*/
const char* get_cumac_msg_name(int msg_id);
/**
* cuMAC message type identifiers
*
* Defines message types for cuMAC control plane protocol
* including configuration, lifecycle, and TTI scheduling messages
*/
typedef enum
{
CUMAC_PARAM_REQUEST = 0x00, //!< PARAM.request - Query parameters
CUMAC_PARAM_RESPONSE = 0x01, //!< PARAM.response - Parameter response
CUMAC_CONFIG_REQUEST = 0x02, //!< CONFIG.request - Configuration request
CUMAC_CONFIG_RESPONSE = 0x03, //!< CONFIG.response - Configuration response
CUMAC_START_REQUEST = 0x04, //!< START.request - Start cuMAC processing
CUMAC_STOP_REQUEST = 0x05, //!< STOP.request - Stop cuMAC processing
CUMAC_STOP_RESPONSE = 0x06, //!< STOP.response - Stop response
CUMAC_ERROR_INDICATION = 0x07, //!< ERROR.indication - Error notification
CUMAC_START_RESPONSE = 0x08, //!< START.response - Start response
CUMAC_DL_TTI_REQUEST = 0x80, //!< DL_TTI.request - Downlink TTI request (Not used currently)
CUMAC_UL_TTI_REQUEST = 0x81, //!< UL_TTI.request - Uplink TTI request (Not used currently)
CUMAC_SCH_TTI_REQUEST = 0x82, //!< SCH_TTI.request - Scheduling TTI request
CUMAC_SCH_TTI_RESPONSE = 0x83, //!< SCH_TTI.response - Scheduling TTI response
CUMAC_TTI_END = 0x8F, //!< TTI_END - End of TTI indication
CUMAC_TTI_ERROR_INDICATION = 0x90, //!< TTI_ERROR.indication - TTI-specific error
} cumac_msg_t;
/**
* cuMAC task type enumeration
*
* Identifies which cuMAC scheduling algorithm to execute
*/
typedef enum
{
CUMAC_TASK_UE_SELECTION = 0, //!< UE selection task (cumac::multiCellUeSelection)
CUMAC_TASK_PRB_ALLOCATION = 1, //!< PRB allocation task (cumac::multiCellScheduler)
CUMAC_TASK_LAYER_SELECTION = 2, //!< Layer selection task (cumac::multiCellLayerSel)
CUMAC_TASK_MCS_SELECTION = 3, //!< MCS selection task (cumac::mcsSelectionLUT)
CUMAC_TASK_PFM_SORT = 4, //!< PFM sorting task
CUMAC_TASK_TOTAL_NUM = 5 //!< Total number of task types
} cumac_task_type_t;
/**
* cuMAC message header structure
*
* Based on SCF FAPI header format (see SCF222 document). All cuMAC messages begin with this header.
*/
typedef struct
{
uint8_t message_count; //!< Number of messages in this transmission
uint8_t handle_id; //!< handle_id is used as cell_id
uint16_t type_id; //!< Message type identifier (cumac_msg_t)
uint32_t body_len; //!< Length of message body in bytes
uint8_t body[0]; //!< Variable-length message body (flexible array member)
} cumac_msg_header_t;
/**
* Slot-specific message header
*
* Extended header for TTI-related messages that includes timing information
*/
typedef struct
{
cumac_msg_header_t header; //!< Standard message header
uint16_t sfn; //!< Frame Number (0-1023)
uint16_t slot; //!< Slot number within frame
uint8_t payload[0]; //!< Variable-length payload (flexible array member)
} cumac_slot_msg_header_t;
/**
* CONFIG.request message structure
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_CONFIG_REQUEST
uint8_t body[]; //!< Configuration payload (cumac_config_req_payload_t)
} cumac_config_req_t;
/**
* CONFIG.response message structure
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_CONFIG_RESPONSE
int error_code; //!< Error code: 0 on success, other on failure
} cumac_config_resp_t;
/**
* START.request message structure
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_START_REQUEST
int start_param; //!< Start parameters (reserved for future use)
} cumac_start_req_t;
/**
* START.response message structure
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_START_RESPONSE
int error_code; //!< Error code: 0 on success, other on failure
} cumac_start_resp_t;
/**
* STOP.request message structure
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_STOP_REQUEST
int stop_param; //!< Stop parameters (reserved for future use)
} cumac_stop_req_t;
/**
* STOP.response message structure
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_STOP_RESPONSE
int error_code; //!< Error code: 0 on success, other on failure
} cumac_stop_resp_t;
/**
* ERROR.indication message structure
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_ERROR_INDICATION
int msg_id; //!< Message ID that caused the error
int error_code; //!< Error code identifier
int reason_code; //!< Detailed reason code for the error
} cumac_err_ind_t;
/**
* CONFIG.request payload structure
*
* Static scheduler configuration parameters passed from L2 stack to cuMAC-CP
* during initialization phase (one-time configuration).
*/
typedef struct
{
uint8_t harqEnabledInd; //!< Indicator for whether HARQ is enabled
uint8_t mcsSelCqi; //!< Indicator for whether MCS selection is based on CQI or SINR
uint8_t nMaxCell; //!< A constant integer for the maximum number of cells in the cell group
uint16_t nMaxActUePerCell; //!< A constant integer for the maximum number of active UEs per cell
uint8_t nMaxSchUePerCell; //!< A constant integer for the maximum number of UEs that can be scheduled per TTI per cell
uint16_t nMaxPrg; //!< A constant integer for the maximum number of PRGs for allocation in each cell
uint16_t nPrbPerPrg; //!< A constant integer for the number of PRBs per PRG (PRB group)
uint8_t nMaxBsAnt; //!< A constant integer for the maximum number of BS antenna ports.
uint8_t nMaxUeAnt; //!< A constant integer for the maximum number of UE antenna ports.
uint32_t scSpacing; //!< Subcarrier spacing of the carrier. Value: 15000, 30000, 60000, 120000 (Hz)
uint8_t allocType; //!< Indicator for type-0 or type-1 PRG allocation
uint8_t precoderType; //!< Indicator for the precoder type
uint8_t receiverType; //!< Indicator for the receiver type
uint8_t colMajChanAccess; //!< Indicator for whether the estimated narrow-band SRS channel matrices are stored in column-major order or in row-major order
float betaCoeff; //!< Coefficient for adjusting the cell-edge UEs' performance in multi-cell scheduling
float sinValThr; //!< Singular value threshold for layer selection
float corrThr; //!< Channel vector correlation value threshold for layer selection
float mcsSelSinrCapThr; //!< SINR capping threshold for MCS selection
uint8_t mcsSelLutType; //!< MCS selection LUT type
uint16_t prioWeightStep; //!< Step size for UE priority weight increment per TTI if UE does not get scheduled. For priority-based UE selection
float blerTarget; //!< BLER target (same for all active UEs; expanded per-UE in cuMAC-CP)
} cumac_config_req_payload_t;
/**
* SCH_TTI.request buffer offsets structure
*
* Specifies byte offsets for data buffers in SCH_TTI.request message.
* All offsets are relative to the start of the data payload.
*/
typedef struct
{
uint32_t CRNTI; //!< C-RNTIs of all active UEs in the cell
uint32_t srsCRNTI; //!< C-RNTIs of the UEs that have refreshed SRS channel estimates in the cell.
uint32_t prgMsk; //!< Bit map for the availability of each PRG for allocation
uint32_t postEqSinr; //!< Array of the per-PRG per-layer post-equalizer SINRs of all active UEs in the cell
uint32_t wbSinr; //!< Array of wideband per-layer post-equalizer SINRs of all active UEs in the cell
uint32_t estH_fr; //!< For FP32. Array of the subband (per-PRG) SRS channel estimate coefficients for all active UEs in the cell
uint32_t estH_fr_half; //!< For FP16. Array of the subband (per-PRG) SRS channel estimate coefficients for all active UEs in the cell
uint32_t prdMat; //!< Array of the precoder/beamforming weights for all active UEs in the cell
uint32_t detMat; //!< Array of the detector/beamforming weights for all active UEs in the cell
uint32_t sinVal; //!< Array of the per-UE, per-PRG, per-layer singular values obtained from the SVD of the channel matrix
uint32_t avgRatesActUe; //!< Array of the long-term average data rates of all active UEs in the cell
uint32_t prioWeightActUe; //!< For priority-based UE selection. Priority weights of all active UEs in the cell
uint32_t tbErrLastActUe; //!< TB decoding error indicators of all active UEs in the cell
uint32_t newDataActUe; //!< Indicators of initial transmission/retransmission for all active UEs in the cell
uint32_t allocSolLastTxActUe; //!< The PRG allocation solution for the last transmissions of all active UEs in the cell
uint32_t mcsSelSolLastTxActUe; //!< MCS selection solution for the last transmissions of all active UEs in the cell
uint32_t layerSelSolLastTxActUe; //!< Layer selection solution for the last transmissions of all active UEs in the cell
uint32_t pfmCellInfo; //!< PFM sorting input buffer
} cumac_tti_req_buf_offsets_t;
/**
* SCH_TTI.request payload structure
*
* Contains per-TTI scheduling parameters and buffer offset information
*/
// Payload of SCH_TTI.req
typedef struct
{
uint32_t taskBitMask; //!< Indicate which cuMAC tasks to be scheduled. Each bit represent 1 task type defined in cumac_task_type_t
uint16_t cellID; //!< cell ID
uint8_t ULDLSch; //!< Indication for UL/DL scheduling. Value - 0: UL scheduling, 1: DL scheduling
uint16_t nActiveUe; //!< total number of active UEs in the cell
uint16_t nSrsUe; //!< the number of UEs in the cell that have refreshed SRS channel estimates
uint16_t nPrbGrp; //!< the number of PRGs that can be allocated for the current TTI, excluding the PRGs that need to be reserved for HARQ re-tx's
uint8_t nBsAnt; //!< number of BS antenna ports
uint8_t nUeAnt; //!< number of UE antenna ports
float sigmaSqrd; //!< noise variance
cumac_tti_req_buf_offsets_t offsets; //!< Data buffer offsets for input arrays
} cumac_tti_req_payload_t;
/**
* SCH_TTI.request message structure
*
* Complete message including header, timing, and payload
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_SCH_TTI_REQUEST
uint16_t sfn; //!< Frame Number
uint16_t slot; //!< Slot number
cumac_tti_req_payload_t payload; //!< Request payload with scheduling parameters
} cumac_sch_tti_req_t;
/**
* SCH_TTI.response buffer offsets structure
*
* Specifies byte offsets for output buffers in SCH_TTI.response message
*/
typedef struct
{
uint32_t setSchdUePerCellTTI; //!< Set of IDs of the selected UEs for the cell
uint32_t allocSol; //!< PRB group allocation solution for all active UEs in the cell
uint32_t layerSelSol; //!< Layer selection solution for all active UEs in the cell
uint32_t mcsSelSol; //!< MCS selection solution for all active UEs in the cell
uint32_t pfmSortSol; //!< PFM sorting output buffer
} cumac_tti_resp_buf_offsets_t;
/**
* SCH_TTI.response message structure
*
* Returns scheduling solutions computed by cuMAC
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_SCH_TTI_RESPONSE
uint16_t sfn; //!< Frame Number
uint16_t slot; //!< Slot number
uint16_t nUeSchd; //!< Number of UEs actually scheduled this TTI for this cell
cumac_tti_resp_buf_offsets_t offsets; //!< Data buffer offsets for output arrays
} cumac_sch_tti_resp_t;
/**
* TTI_END message structure
*
* Indicates completion of TTI processing
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_TTI_END
uint16_t sfn; //!< Frame Number
uint16_t slot; //!< Slot number
int end_param; //!< End parameters (reserved for future use)
} cumac_tti_end_t;
/**
* TTI_ERROR.indication message structure
*
* Reports TTI-specific errors during processing
*/
typedef struct {
cumac_msg_header_t header; //!< Message header with type CUMAC_TTI_ERROR_INDICATION
uint16_t sfn; //!< Frame Number
uint16_t slot; //!< Slot number
int msg_id; //!< Message ID that caused the error
int error_code; //!< Error code identifier
int reason_code; //!< Detailed reason code for the error
} cumac_tti_err_ind_t;
#if defined(__cplusplus)
} /* extern "C" */
#endif
#endif /* _NV_CUMAC_MSG_ */

View File

@@ -0,0 +1,363 @@
#include "cumac_msg_funcs.h"
#include "cumac_nvipc.h"
#include <nv_ipc.h>
#include <nvlog.h>
#include <stdalign.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <asm-generic/errno-base.h>
static size_t get_padding(size_t offset, size_t alignment)
{
return (alignment - (offset % alignment)) % alignment;
}
int l2_build_start_request(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args) {
if (type != CUMAC_START_REQUEST)
return -EINVAL;
cumac_start_req_t* req = nvipc_buf->msg_buf;
nvipc_buf->msg_id = CUMAC_START_REQUEST;
nvipc_buf->cell_id = 0;
nvipc_buf->msg_len = sizeof(cumac_start_req_t);
nvipc_buf->data_len = 0;
nvipc_buf->data_buf = NULL;
nvipc_buf->data_pool = NV_IPC_MEMPOOL_CPU_MSG;
req->header.message_count = 1;
req->header.handle_id = 0;
req->header.type_id = CUMAC_START_REQUEST;
req->header.body_len = sizeof(req->start_param);
req->start_param = 0;
return 0;
}
int l2_build_config_request(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args) {
if (type != CUMAC_CONFIG_REQUEST)
return -EINVAL;
cumac_config_req_payload_t *payload = (cumac_config_req_payload_t*)args;
cumac_config_req_t* req = (cumac_config_req_t*)nvipc_buf->msg_buf;
nvipc_buf->msg_id = CUMAC_CONFIG_REQUEST;
nvipc_buf->cell_id = 0;
nvipc_buf->msg_len = sizeof(cumac_config_req_t);
nvipc_buf->data_len = 0;
nvipc_buf->data_buf = NULL;
nvipc_buf->data_pool = NV_IPC_MEMPOOL_CPU_MSG;
req->header.message_count = 1;
req->header.handle_id = 0;
req->header.type_id = CUMAC_CONFIG_REQUEST;
req->header.body_len = sizeof(cumac_config_req_payload_t);
memcpy(req->body, payload, sizeof(cumac_config_req_payload_t));
return 0;
}
int l2_build_sch_tti_request(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args) {
if (type != CUMAC_SCH_TTI_REQUEST)
return -EINVAL;
cumac_sch_tti_req_args_t *config_args = args;
uint16_t frame = config_args->frame;
uint16_t slot = config_args->slot;
uint16_t nActiveUE = config_args->payload.nActiveUe;
uint16_t nPRBGRP = config_args->payload.nPrbGrp;
uint8_t nUEAnt = config_args->payload.nUeAnt;
uint8_t nBSAnt = config_args->payload.nBsAnt;
cumac_tti_req_bufs_t *req_buf = config_args->buffers;
slot_data_entry_t *slot_entry = &slot_data[frame][slot];
// ensure buffers are free
if (slot_entry->setSchdUePerCellTTI != NULL) {
free(slot_entry->setSchdUePerCellTTI);
}
if (slot_entry->allocSol != NULL) {
free(slot_entry->allocSol);
}
if (slot_entry->mcsSelSol != NULL) {
free(slot_entry->mcsSelSol);
}
if (slot_entry->layerSelSol != NULL) {
free(slot_entry->layerSelSol);
}
if (slot_entry->c_rnti != NULL) {
free(slot_entry->c_rnti);
}
slot_entry->c_rnti = calloc(nActiveUE, sizeof(*req_buf->CRNTI));
memcpy(slot_entry->c_rnti, req_buf->CRNTI, sizeof(*req_buf->CRNTI) * nActiveUE);
cumac_sch_tti_req_t* req = (cumac_sch_tti_req_t*)nvipc_buf->msg_buf;
uint8_t* data_buf = (uint8_t*)nvipc_buf->data_buf;
nvipc_buf->msg_id = CUMAC_SCH_TTI_REQUEST;
nvipc_buf->cell_id = config_args->payload.cellID;
nvipc_buf->msg_len = sizeof(cumac_sch_tti_req_t);
memcpy(&req->payload, &config_args->payload, sizeof(cumac_tti_req_payload_t));
req->sfn =frame;
req->slot=slot;
req->header.message_count = 1;
req->header.handle_id = 0;
req->header.type_id = CUMAC_SCH_TTI_REQUEST;
req->header.body_len = sizeof(cumac_tti_req_payload_t);
// Set all bytes to 0xFF since the unused offsets are to be set to 0xFFFFFFFF
memset(&req->payload.offsets, 0xFF, sizeof req->payload.offsets);
// Copy data which need to be sent to GPU to the nvipc_buf->data_buf
uint32_t offset = 0;
uint32_t data_size = 0;
uint32_t num_data = 0;
// Fill Common buffers
offset += get_padding(offset, alignof(uint16_t));
req->payload.offsets.CRNTI = offset;
num_data = nActiveUE;
data_size = sizeof(*req_buf->CRNTI) * num_data;
LOG_D(NR_MAC,"CRNTI : ");
for (int i = 0; i < num_data; ++i)
LOG_D(NR_MAC,"%u ", req_buf->CRNTI[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->CRNTI, data_size);
offset += data_size;
offset += get_padding(offset, alignof(uint8_t));
req->payload.offsets.prgMsk = offset;
num_data = nPRBGRP;
data_size = sizeof(*req_buf->prgMsk) * num_data;
LOG_D(NR_MAC,"PRG MASK : ");
for (int i = 0; i < num_data; ++i)
LOG_D(NR_MAC,"%u ", req_buf->prgMsk[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->prgMsk, data_size);
offset += data_size;
offset += get_padding(offset, alignof(float));
req->payload.offsets.wbSinr = offset;
num_data = nActiveUE * nUEAnt;
data_size = sizeof(*req_buf->wbSinr) * num_data;
LOG_D(NR_MAC,"wbSinr : ");
for (int i = 0; i < num_data; ++i)
LOG_D(NR_MAC,"%f ", req_buf->wbSinr[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->wbSinr, data_size);
offset += data_size;
offset += get_padding(offset, alignof(float));
req->payload.offsets.avgRatesActUe = offset;
num_data = nActiveUE;
data_size = sizeof(*req_buf->avgRatesActUe) * num_data;
LOG_D(NR_MAC,"avgRatesActUe : ");
for (int i = 0; i < num_data; ++i)
LOG_D(NR_MAC,"%f ", req_buf->avgRatesActUe[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->avgRatesActUe, data_size);
offset += data_size;
// PRB Allocation specific buffers
if (req->payload.taskBitMask & TASK_BIT(CUMAC_TASK_PRB_ALLOCATION)) {
offset += get_padding(offset, alignof(float));
req->payload.offsets.postEqSinr = offset;
num_data = nActiveUE * nPRBGRP * nUEAnt;
data_size = sizeof(*req_buf->postEqSinr) * num_data;
LOG_D(NR_MAC,"postEqSinr : ");
for (int i = 0; i < num_data; ++i)
LOG_D(NR_MAC,"%f ", req_buf->postEqSinr[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->postEqSinr, data_size);
offset += data_size;
offset += get_padding(offset, alignof(float));
req->payload.offsets.sinVal = offset;
num_data = slot_entry->nMaxSchUePerCell * nPRBGRP * cumac_nPrbPerPrg() * nUEAnt;
data_size = sizeof(*req_buf->sinVal) * num_data;
LOG_D(NR_MAC,"sinVal : ");
for (int i = 0; i < num_data; ++i)
LOG_D(NR_MAC,"%f ", req_buf->sinVal[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->sinVal, data_size);
offset += data_size;
uint32_t prdLen, detLen;
if(req->payload.ULDLSch == 1)
{ // DL
prdLen = slot_entry->nMaxSchUePerCell * nPRBGRP * cumac_nPrbPerPrg() * nBSAnt*nBSAnt;
detLen = slot_entry->nMaxSchUePerCell * nPRBGRP * cumac_nPrbPerPrg() * nUEAnt * nUEAnt;
}
else
{ // UL
prdLen = slot_entry->nMaxSchUePerCell * nPRBGRP * cumac_nPrbPerPrg() * nUEAnt * nUEAnt;
detLen = slot_entry->nMaxSchUePerCell * nPRBGRP * cumac_nPrbPerPrg() * nBSAnt*nBSAnt;
}
const uint32_t hLen = nPRBGRP * cumac_nPrbPerPrg() * slot_entry->nMaxSchUePerCell * /*nMaxCell*/ 1 * nBSAnt * nUEAnt;
offset += get_padding(offset, alignof(cuComplex));
req->payload.offsets.detMat = offset;
data_size = sizeof(*req_buf->detMat) * detLen;
LOG_D(NR_MAC,"detMat : ");
for (int i = 0; i < detLen; ++i)
LOG_D(NR_MAC,"x = %f , y= %f ", req_buf->detMat[i].x, req_buf->detMat[i].y);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->detMat, data_size);
offset += data_size;
offset += get_padding(offset, alignof(cuComplex));
req->payload.offsets.estH_fr = offset;
data_size = sizeof(*req_buf->estH_fr) * hLen;
LOG_D(NR_MAC,"estH_fr : ");
for (int i = 0; i < hLen; ++i)
LOG_D(NR_MAC,"x = %f , y= %f ", req_buf->estH_fr[i].x, req_buf->estH_fr[i].y);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->estH_fr, data_size);
offset += data_size;
offset += get_padding(offset, alignof(cuComplex));
req->payload.offsets.prdMat = offset;
data_size = sizeof(*req_buf->prdMat) * prdLen;
LOG_D(NR_MAC,"prdMat : ");
for (int i = 0; i < prdLen; ++i)
LOG_D(NR_MAC,"x = %f , y= %f ", req_buf->prdMat[i].x, req_buf->prdMat[i].y);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->prdMat, data_size);
offset += data_size;
if (req_buf->allocSolLastTxActUe != NULL) {
offset += get_padding(offset, alignof(int16_t));
req->payload.offsets.allocSolLastTxActUe = offset;
data_size = sizeof(*req_buf->allocSolLastTxActUe) * nActiveUE * 2;
memcpy(data_buf + offset, req_buf->allocSolLastTxActUe, data_size);
offset += data_size;
}
}
if (req->payload.taskBitMask & TASK_BIT(CUMAC_TASK_MCS_SELECTION)) {
offset += get_padding(offset, alignof(int8_t));
req->payload.offsets.tbErrLastActUe = offset;
data_size = sizeof(*req_buf->tbErrLastActUe) * nActiveUE;
LOG_D(NR_MAC,"tbErrLastActUe : ");
for (int i = 0; i < nActiveUE; ++i)
LOG_D(NR_MAC,"%d ", req_buf->tbErrLastActUe[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->tbErrLastActUe, data_size);
offset += data_size;
if (req_buf->mcsSelSolLastTxActUe != NULL) {
offset += get_padding(offset, alignof(int16_t));
req->payload.offsets.mcsSelSolLastTxActUe = offset;
data_size = sizeof(*req_buf->mcsSelSolLastTxActUe) * nActiveUE;
memcpy(data_buf + offset, req_buf->mcsSelSolLastTxActUe, data_size);
offset += data_size;
}
}
if (req->payload.taskBitMask & TASK_BIT(CUMAC_TASK_LAYER_SELECTION)) {
if (req_buf->layerSelSolLastTxActUe != NULL) {
offset += get_padding(offset, alignof(int8_t));
req->payload.offsets.layerSelSolLastTxActUe = offset;
data_size = sizeof(*req_buf->layerSelSolLastTxActUe) * nActiveUE;
memcpy(data_buf + offset, req_buf->layerSelSolLastTxActUe, data_size);
offset += data_size;
}
}
offset += get_padding(offset, alignof(int8_t));
req->payload.offsets.newDataActUe = offset;
num_data = nActiveUE;
data_size = sizeof(*req_buf->newDataActUe) * num_data;
LOG_D(NR_MAC,"newDataActUe : ");
for (int i = 0; i < num_data; ++i)
LOG_D(NR_MAC,"%d ", req_buf->newDataActUe[i]);
LOG_D(NR_MAC,"\n");
memcpy(data_buf + offset, req_buf->newDataActUe, data_size);
offset += data_size;
nvipc_buf->data_len = offset;
return 0;
}
int l2_build_tti_end(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args) {
if (type != CUMAC_TTI_END)
return -EINVAL;
cumac_sch_tti_end_args_t *tti_end_args = args;
cumac_tti_end_t* req = nvipc_buf->msg_buf;
nvipc_buf->msg_id = CUMAC_TTI_END;
nvipc_buf->cell_id = 0;
nvipc_buf->msg_len = sizeof(cumac_tti_end_t);
nvipc_buf->data_len = 0;
nvipc_buf->data_buf = NULL;
nvipc_buf->data_pool = NV_IPC_MEMPOOL_CPU_MSG;
req->header.message_count = 1;
req->header.handle_id = 0;
req->header.type_id = CUMAC_TTI_END;
req->header.body_len = sizeof(req->end_param);
req->end_param = 0;
req->sfn = tti_end_args->frame;
req->slot = tti_end_args->slot;
return 0;
}
void cumac_handle_sch_tti_response(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, slot_data_entry_t *slot_data_entry)
{
if (type != CUMAC_SCH_TTI_RESPONSE)
return;
cumac_sch_tti_resp_t *resp = nvipc_buf->msg_buf;
uint8_t *buf_home = (nvipc_buf->data_buf);
//!< Set of IDs of the selected UEs for the cell
slot_data_entry->setSchdUePerCellTTI =
calloc(slot_data_entry->nMaxSchUePerCell, sizeof(uint16_t));
//!< PRB group allocation solution for all active UEs in the cell
slot_data_entry->allocSol = calloc(slot_data_entry->allocSolSize, sizeof(int16_t));
//!< MCS selection solution for all active UEs in the cell
slot_data_entry->mcsSelSol = calloc(slot_data_entry->nActiveUe, sizeof(int16_t));
//!< Layer selection solution for all active UEs in the cell
slot_data_entry->layerSelSol = calloc(slot_data_entry->nActiveUe, sizeof(uint8_t));
LOG_D(NR_MAC,"Received SCH_TTI.response with data\n");
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
LOG_D(NR_MAC,"message 0x%02x\n", type);
LOG_I(NR_MAC,"From TTI_REQ/TTI_END to RESPONSE %ld , %ld\n",
nvlog_timespec_interval(&slot_data_entry->tti_req_timestamp, &now),
nvlog_timespec_interval(&slot_data_entry->tti_end_timestamp, &now));
LOG_D(NR_MAC,"Used values in originating SCH_TTI.request:\n");
LOG_D(NR_MAC,"\tnMaxSchUePerCell = %d\n", slot_data_entry->nMaxSchUePerCell);
LOG_D(NR_MAC,"\tallocSolSize = %d\n", slot_data_entry->allocSolSize);
LOG_D(NR_MAC,"\tnActiveUe = %d\n", slot_data_entry->nActiveUe);
LOG_D(NR_MAC,"Offsets : \n\tsetSchdUePerCellTTI 0x%04x\n", resp->offsets.setSchdUePerCellTTI);
LOG_D(NR_MAC,"\tallocSol 0x%04x\n", resp->offsets.allocSol);
LOG_D(NR_MAC,"\tmcsSelSol 0x%04x\n", resp->offsets.mcsSelSol);
LOG_D(NR_MAC,"\tlayerSelSol 0x%04x\n", resp->offsets.layerSelSol);
if (resp->offsets.setSchdUePerCellTTI != 0xFFFFFFFF) {
const uint16_t *src = (uint16_t *)(buf_home + resp->offsets.setSchdUePerCellTTI);
memcpy(slot_data_entry->setSchdUePerCellTTI, src, slot_data_entry->nMaxSchUePerCell * sizeof(uint16_t));
LOG_D(NR_MAC,"setSchdUePerCellTTI :\n");
for (int i = 0; i < slot_data_entry->nMaxSchUePerCell; i++) {
LOG_D(NR_MAC,"\tIDX %d = %d \n", i, slot_data_entry->setSchdUePerCellTTI[i]);
}
}
if (resp->offsets.allocSol != 0xFFFFFFFF) {
const uint16_t *src = (uint16_t *)(buf_home + resp->offsets.allocSol);
memcpy(slot_data_entry->allocSol, src, slot_data_entry->allocSolSize * sizeof(*slot_data_entry->allocSol));
LOG_D(NR_MAC,"allocSol:\n");
for (int i = 0; i < slot_data_entry->allocSolSize; i++) {
LOG_D(NR_MAC,"\tIDX %d = 0x%02x \n", i, src[i]);
}
}
if (resp->offsets.mcsSelSol != 0xFFFFFFFF) {
const int16_t *src = (int16_t *)(buf_home + resp->offsets.mcsSelSol);
memcpy(slot_data_entry->mcsSelSol, src, slot_data_entry->nActiveUe * sizeof(*slot_data_entry->mcsSelSol));
LOG_D(NR_MAC,"mcsSelSol :\n");
for (int i = 0; i < slot_data_entry->nActiveUe; i++) {
LOG_D(NR_MAC,"\tIDX %d = 0x%02x \n", i, slot_data_entry->mcsSelSol[i]);
}
}
if (resp->offsets.layerSelSol != 0xFFFFFFFF) {
const uint8_t *src = (uint8_t *)(buf_home + resp->offsets.layerSelSol);
memcpy(slot_data_entry->layerSelSol, src, slot_data_entry->nActiveUe);
LOG_D(NR_MAC,"layerSelSol :\n");
for (int i = 0; i < slot_data_entry->nActiveUe; i++) {
LOG_D(NR_MAC,"\tIDX %d = 0x%02x \n", i, slot_data_entry->layerSelSol[i]);
}
}
}

View File

@@ -0,0 +1,87 @@
//
// Created by user on 10 Oct 2025.
//
#ifndef OPENAIRINTERFACE_CUMAC_MSG_FUNCS_H
#define OPENAIRINTERFACE_CUMAC_MSG_FUNCS_H
#include "cumac_msg.h"
#include <cuComplex.h>
#include <cuda_bf16.h>
#include <nv_ipc.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#define SCH_TTI_UL 0
#define SCH_TTI_DL 1
// Buffer offsets for SCH_TTI.req
typedef struct {
uint16_t *CRNTI; // C-RNTIs of all active UEs in the cell
uint16_t *srsCRNTI; // C-RNTIs of the UEs that have refreshed SRS channel estimates in the cell.
uint8_t *prgMsk; // Bit map for the availability of each PRG for allocation
float *postEqSinr; // Array of the per-PRG per-layer post-equalizer SINRs of all active UEs in the cell
float *wbSinr; // Array of wideband per-layer post-equalizer SINRs of all active UEs in the cell
cuComplex
*estH_fr; // For FP32. Array of the subband (per-PRG) SRS channel estimate coefficients for all active UEs in the cell
__nv_bfloat162_raw
*estH_fr_half; // For FP16. Array of the subband (per-PRG) SRS channel estimate coefficients for all active UEs in the cell
cuComplex *prdMat; // Array of the precoder/beamforming weights for all active UEs in the cell
cuComplex *detMat; // Array of the detector/beamforming weights for all active UEs in the cell
float *sinVal; // Array of the per-UE, per-PRG, per-layer singular values obtained from the SVD of the channel matrix
float *avgRatesActUe; // Array of the long-term average data rates of all active UEs in the cell
uint16_t *prioWeightActUe; // For priority-based UE selection. Priority weights of all active UEs in the cell
int8_t *tbErrLastActUe; // TB decoding error indicators of all active UEs in the cell
int8_t *newDataActUe; // Indicators of initial transmission/retransmission for all active UEs in the cell
int16_t *allocSolLastTxActUe; // The PRG allocation solution for the last transmissions of all active UEs in the cell
int16_t *mcsSelSolLastTxActUe; // MCS selection solution for the last transmissions of all active UEs in the cell
int8_t *layerSelSolLastTxActUe; // Layer selection solution for the last transmissions of all active UEs in the cell
} cumac_tti_req_bufs_t;
typedef struct {
int32_t cellId;
cumac_config_req_payload_t payload;
} cumac_config_req_args_t;
typedef struct {
uint16_t frame ;
uint16_t slot ;
cumac_tti_req_payload_t payload;
cumac_tti_req_bufs_t *buffers;
} cumac_sch_tti_req_args_t;
typedef struct {
uint16_t frame ;
uint16_t slot ;
} cumac_sch_tti_end_args_t;
typedef struct {
uint16_t nMaxSchUePerCell;
uint16_t allocSolSize;
uint8_t nActiveUe;
uint32_t taskBitMask; // task bits from the SCH_TTI.request that produced this entry
struct timespec tti_req_timestamp;
struct timespec tti_end_timestamp;
// Response data per slot
// Save RNTI when creating sch_tti_req
uint16_t *c_rnti;
uint16_t *setSchdUePerCellTTI;
int16_t *allocSol;
int16_t *mcsSelSol;
uint8_t *layerSelSol;
} slot_data_entry_t ;
#define TASK_BIT(t) (1u << (t))
#define CUMAC_UE_RETRANSMISSION 0
#define CUMAC_UE_INTIAL_TRANSMISSION 1
typedef int (*build_cumac_msg_fn_v_t)(cumac_msg_t type,nv_ipc_msg_t *nvipc_buf, void* args);
int l2_build_start_request(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args);
int l2_build_config_request(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args);
int l2_build_sch_tti_request(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args);
int l2_build_tti_end(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, void* args);
void cumac_handle_sch_tti_response(cumac_msg_t type, nv_ipc_msg_t *nvipc_buf, slot_data_entry_t *slot_data) ;
#endif // OPENAIRINTERFACE_CUMAC_MSG_FUNCS_H

View File

@@ -0,0 +1,327 @@
//
// Created by user on 27 Jan 2026.
//
#include "cumac_nvipc.h"
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdarg.h>
#include <unistd.h>
#include <termios.h>
#include <stdatomic.h>
#include <sys/queue.h>
#include <sys/epoll.h>
#include <time.h>
#include <math.h>
#include "nv_ipc_utils.h"
#include "cu_mac_api.h"
#include "cumac_msg.h"
#include "cumac_msg_funcs.h"
#include "LOG/log.h"
#ifdef CUMAC_TIMING_DEBUG
#endif
bool recv_task_running = false;
static nv_ipc_config_t nv_ipc_config;
static bool cumac_start = false;
nv_ipc_t* ipc;
#define MAX_EVENTS 10
#define RECV_BUF_LEN 8192
char cpu_buf_recv[RECV_BUF_LEN];
/* Log TAG configured in nvlog */
static int TAG = (NVLOG_TAG_BASE_NVIPC + 0);
static bool waiting_tti_resp = false;
static bool has_sent_tti_end = false;
static bool has_sent_conf_req = false;
slot_data_entry_t *slot_data[MAX_FRAME_NUMBER];
pthread_mutex_t cumac_can_send_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t cumac_can_process_mutex = PTHREAD_MUTEX_INITIALIZER;
static uint16_t nMaxSchUePerCell;
static uint8_t allocType;
static uint16_t nPrbPerPrg;
static uint16_t nMaxPrg;
uint16_t cumac_nMax_schUePerCell() {
return nMaxSchUePerCell;
}
uint16_t cumac_nPrbPerPrg() {
return nPrbPerPrg;
}
uint16_t cumac_nMaxPrg() {
return nMaxPrg;
}
int cumac_get_UE_ID_by_RNTI(const uint16_t frame, const uint16_t slot, const uint16_t rnti) {
const slot_data_entry_t *slot_data_entry = get_slot_data(frame,slot);
AssertFatal(slot_data_entry, "Entry should not be null!");
for (int i = 0; i < slot_data_entry->nActiveUe; ++i) {
if (slot_data_entry->c_rnti[i] == rnti) {
return i;
}
}
return -1;
}
slot_data_entry_t* get_slot_data(const uint16_t frame, const uint16_t slot) {
return &slot_data[frame][slot];
}
bool cumac_can_schedule() {
return !waiting_tti_resp;
}
void cumac_set_can_schedule(bool val) {
waiting_tti_resp = !val;
}
bool cumac_send_msg(cumac_msg_t type, build_cumac_msg_fn_v_t fn, void* args)
{
if ( has_sent_tti_end || (type == CUMAC_CONFIG_REQUEST && has_sent_conf_req) || (type == CUMAC_SCH_TTI_REQUEST && waiting_tti_resp)) {
return false;
}
if (type == CUMAC_SCH_TTI_REQUEST && !waiting_tti_resp) {
waiting_tti_resp = true;
cumac_sch_tti_req_args_t *sch_tti_args = args;
slot_data_entry_t *slot_data_entry = &slot_data[sch_tti_args->frame][sch_tti_args->slot];
slot_data_entry->nActiveUe = sch_tti_args->payload.nActiveUe;
slot_data_entry->allocSolSize = sch_tti_args->payload.nActiveUe * 2; // for Type 1 allocation
slot_data_entry->nMaxSchUePerCell = nMaxSchUePerCell;
slot_data_entry->taskBitMask = sch_tti_args->payload.taskBitMask;
}
if (type == CUMAC_TTI_END) {
has_sent_tti_end = true;
}
if (type == CUMAC_CONFIG_REQUEST) {
has_sent_conf_req = true;
printf("Got CONFIG.request, initializing slot data matrix\n");
// Determine number of slots per frame from SCS
const cumac_config_req_payload_t* conf_req_payload = args;
// Save nMax UE and allocation type for later
nMaxSchUePerCell = conf_req_payload->nMaxSchUePerCell;
allocType = conf_req_payload->allocType;
nPrbPerPrg = conf_req_payload->nPrbPerPrg;
nMaxPrg = conf_req_payload->nMaxPrg;
const uint8_t num_slots = 10 << conf_req_payload->scSpacing;
printf("Number of slots per frame: %d\n", num_slots);
// Init slot data structure for later use
for (int i = 0; i < MAX_FRAME_NUMBER; ++i) {
printf("Initializing frame %d\n", i);
slot_data[i] = calloc(num_slots, sizeof(slot_data_entry_t));
}
}
#ifdef CUMAC_TIMING_DEBUG
printf("message 0x%02x\n", type);
struct timespec t1, t2, t3, t4;
clock_gettime(CLOCK_REALTIME, &t1);
#endif
nv_ipc_msg_t send_msg;
send_msg.data_pool = NV_IPC_MEMPOOL_CPU_DATA;
if (ipc->tx_allocate(ipc, &send_msg, 0) != 0) {
NVLOGE(TAG, AERIAL_NVIPC_API_EVENT, "NVIPC memory pool full");
return NULL;
}
#ifdef CUMAC_TIMING_DEBUG
clock_gettime(CLOCK_REALTIME, &t2);
#endif
if (fn(type,&send_msg, args) < 0) {
ipc->tx_release(ipc, &send_msg);
return NULL;
}
#ifdef CUMAC_TIMING_DEBUG
clock_gettime(CLOCK_REALTIME, &t3);
#endif
if (ipc->tx_send_msg(ipc, &send_msg) < 0) {
ipc->tx_release(ipc, &send_msg);
return NULL;
}
ipc->tx_tti_sem_post(ipc);
if (type == CUMAC_SCH_TTI_REQUEST) {
const cumac_sch_tti_req_args_t *sch_tti_args = args;
slot_data_entry_t *slot_data_entry = &slot_data[sch_tti_args->frame][sch_tti_args->slot];
clock_gettime(CLOCK_REALTIME, &slot_data_entry->tti_req_timestamp);
}
if (type == CUMAC_TTI_END) {
const cumac_sch_tti_end_args_t *tti_end_args = args;
slot_data_entry_t *slot_data_entry = &slot_data[tti_end_args->frame][tti_end_args->slot];
clock_gettime(CLOCK_REALTIME, &slot_data_entry->tti_end_timestamp);
}
#ifdef CUMAC_TIMING_DEBUG
clock_gettime(CLOCK_REALTIME, &t4);
printf("message 0x%02x\n", type);
printf("alloc: %ld ns\n", nvlog_timespec_interval(&t1, &t2));
printf("build: %ld ns\n", nvlog_timespec_interval(&t2, &t3));
printf("send : %ld ns\n", nvlog_timespec_interval(&t3, &t4));
#endif
return true;
}
void* epoll_recv_task(void* arg) {
struct epoll_event ev, events[MAX_EVENTS];
printf("Aerial cuMAC recv task start \n");
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
printf("%s epoll_create failed\n", __func__);
return NULL;
}
int ipc_rx_event_fd = ipc->get_fd(ipc);
ev.events = EPOLLIN;
ev.data.fd = ipc_rx_event_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) {
printf("%s epoll_ctl failed\n", __func__);
return NULL;
}
while (1) {
if (!recv_task_running) {
recv_task_running = true;
LOG_D(NR_MAC,"%s: epoll_recv_task started\n", __func__);
}
LOG_D(NR_MAC,"%s: epoll_wait fd_rx=%d ...\n", __func__, ipc_rx_event_fd);
int nfds;
do {
// epoll_wait() may return EINTR when get unexpected signal SIGSTOP from system
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
} while (nfds == -1 && errno == EINTR);
if (nfds < 0) {
printf("epoll_wait failed: epoll_fd=%d nfds=%d err=%d - %s\n", epoll_fd, nfds, errno, strerror(errno));
}
int n = 0;
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == ipc_rx_event_fd) {
ipc->get_value(ipc);
nv_ipc_msg_t recv_msg;
while (cumac_recv_msg(&recv_msg) == 0)
;
}
}
}
close(epoll_fd);
return NULL;
}
bool cumac_nvipc_init() {
if (ipc != NULL) {
return false;
}
// Create configuration
nv_ipc_config.ipc_transport = NV_IPC_TRANSPORT_SHM;
if (set_nv_ipc_default_config(&nv_ipc_config, NV_IPC_MODULE_SECONDARY) < 0) {
printf("%s: set configuration failed\n", __func__);
return false;
}
int cuda_device_id = -1;
printf("CUDA device ID configured : %d \n", cuda_device_id);
nv_ipc_config.transport_config.shm.cuda_device_id = cuda_device_id;
// Create nv_ipc_t instance
printf("%s: creating IPC interface with prefix %s\n", __func__, "cumac");
strcpy(nv_ipc_config.transport_config.shm.prefix, "cumac");
if ((ipc = create_nv_ipc_interface(&nv_ipc_config)) == NULL) {
printf("%s: create IPC interface failed\n", __func__);
return false;
}
printf("%s: create IPC interface successful\n", __func__);
// setup receiver thread
sleep(1);
mutexinit(cumac_can_send_mutex);
mutexinit(cumac_can_process_mutex);
// Set initial lock state
pthread_mutex_unlock(&cumac_can_send_mutex);
pthread_mutex_lock(&cumac_can_process_mutex);
pthread_t thread_id;
pthread_create(&thread_id, NULL, epoll_recv_task, NULL);
return true;
}
int cumac_recv_msg(nv_ipc_msg_t* recv_msg)
{
if (ipc == NULL) {
return -1;
}
recv_msg->msg_buf = NULL;
recv_msg->data_buf = NULL;
// Allocate buffer for TX message
if (ipc->rx_recv_msg(ipc, recv_msg) < 0) {
LOG_D(NR_MAC,"%s: no more message available\n", __func__);
return -1;
}
LOG_D(NR_MAC,"recv: cell_id=%d msg_id=0x%02X msg_len=%d data_len=%d data_pool=%d\n",
recv_msg->cell_id,
recv_msg->msg_id,
recv_msg->msg_len,
recv_msg->data_len,
recv_msg->data_pool);
cumac_handle_rx_msg(recv_msg);
// Release buffer of RX message
int release_retval = ipc->rx_release(ipc, recv_msg);
if (release_retval != 0) {
LOG_D(NR_MAC,"%s error: release RX buffer failed Error: %d\n", __FUNCTION__, release_retval);
return release_retval;
}
return 0;
}
void cumac_handle_rx_msg(nv_ipc_msg_t* recv_msg) {
switch (recv_msg->msg_id) {
case CUMAC_CONFIG_RESPONSE:
// Send Start request
cumac_send_msg(CUMAC_START_REQUEST,l2_build_start_request, NULL);
break;
case CUMAC_START_RESPONSE:
cumac_start = true;
break;
case CUMAC_SCH_TTI_RESPONSE: {
// Handle SCH_TTI.request
cumac_sch_tti_resp_t* resp = recv_msg->msg_buf;
cumac_handle_sch_tti_response(CUMAC_SCH_TTI_RESPONSE, recv_msg, &slot_data[resp->sfn][resp->slot]);
waiting_tti_resp = false;
has_sent_tti_end = false;
cumac_allow_send();
cumac_allow_process();
break;
}
default:
break;
}
}
void cumac_wait_to_send() {
pthread_mutex_lock(&cumac_can_send_mutex);
}
void cumac_allow_send() {
pthread_mutex_unlock(&cumac_can_send_mutex);
}
void cumac_wait_to_process() {
pthread_mutex_lock(&cumac_can_process_mutex);
}
void cumac_allow_process() {
pthread_mutex_unlock(&cumac_can_process_mutex);
}

View File

@@ -0,0 +1,33 @@
//
// Created by user on 27 Jan 2026.
//
#ifndef OPENAIRINTERFACE_CUMAC_NVIPC_H
#define OPENAIRINTERFACE_CUMAC_NVIPC_H
#include "cumac_msg.h"
#include "cumac_msg_funcs.h"
#include "PHY/defs_common.h"
#include <nv_ipc.h>
#include <stdbool.h>
extern slot_data_entry_t *slot_data[MAX_FRAME_NUMBER];
extern pthread_mutex_t cumac_can_send_mutex;
extern pthread_mutex_t cumac_can_process_mutex;
bool cumac_nvipc_init();
int cumac_recv_msg(nv_ipc_msg_t* recv_msg);
uint16_t cumac_nMax_schUePerCell();
uint16_t cumac_nPrbPerPrg();
uint16_t cumac_nMaxPrg();
int cumac_get_UE_ID_by_RNTI(const uint16_t frame, const uint16_t slot, const uint16_t rnti);
slot_data_entry_t* get_slot_data(const uint16_t frame, const uint16_t slot);
bool cumac_can_schedule();
void cumac_set_can_schedule(bool val);
bool cumac_send_msg(cumac_msg_t type,build_cumac_msg_fn_v_t fn, void* args);
void cumac_handle_rx_msg(nv_ipc_msg_t* recv_msg);
void cumac_wait_to_send();
void cumac_allow_send();
void cumac_wait_to_process();
void cumac_allow_process();
#endif // OPENAIRINTERFACE_CUMAC_NVIPC_H

View File

@@ -0,0 +1,137 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _CUMAC_PFM_SORT_DATA_TYPE_
#define _CUMAC_PFM_SORT_DATA_TYPE_
#include <stdint.h>
#include <stddef.h>
#if defined(__cplusplus)
extern "C" {
#endif
// ************************************************** PFM sorting constants **************************************************
/// @brief Time slot interval in seconds
#define CUMAC_PFM_SLOT_DURATION (0.0005F) // 0.5 ms
/// @brief Maximum number of cells
#define CUMAC_PFM_MAX_NUM_CELL (40U)
/// @brief Maximum number of UEs for PFM sorting per cell
/// @note a maximum of 512 UEs per cell can be supported
#define CUMAC_PFM_MAX_NUM_UE_PER_CELL (512U)
/// @brief Number of QoS types for UL
/// @note 0 - ul_gbr_critical, 1 - ul_gbr_non_critical, 2 - ul_ngbr_critical, 3 - ul_ngbr_non_critical, 4 - ul_mbr_non_critical
#define CUMAC_PFM_NUM_QOS_TYPES_UL (5U)
/// @brief Number of QoS types for DL
/// @note 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical
#define CUMAC_PFM_NUM_QOS_TYPES_DL (5U)
/// @brief Maximum number of LCGs per UE (for UL)
#define CUMAC_PFM_MAX_NUM_LCG_PER_UE (4U)
/// @brief Maximum number of LCs per UE (for DL)
#define CUMAC_PFM_MAX_NUM_LC_PER_UE (4U)
/// @brief Maximum number of output sorted LCs per DL QoS type per cell
#define CUMAC_PFM_MAX_NUM_SORTED_LC_PER_QOS (CUMAC_PFM_MAX_NUM_UE_PER_CELL * CUMAC_PFM_MAX_NUM_LC_PER_UE)
/// @brief Maximum number of output sorted LCGs per UL QoS type per cell
#define CUMAC_PFM_MAX_NUM_SORTED_LCG_PER_QOS (CUMAC_PFM_MAX_NUM_UE_PER_CELL * CUMAC_PFM_MAX_NUM_LCG_PER_UE)
/// @brief Maximum number of scheduled UEs per cell per time slot for both DL and UL
#define CUMAC_PFM_MAX_NUM_SCHEDULED_UE_PER_CELL (16U)
/// @brief IIR alpha for ravg calculation
#define CUMAC_PFM_IIR_ALPHA (0.001F)
#define CUMAC_INVALID_RNTI (0U)
// ************************************************** PFM sorting request data structures (sent from L2 stack host to cuMAC-CP) **************************************************
/// @brief Downlink LC info structure
typedef struct {
uint32_t tbs_scheduled; // TBS scheduled last time for this LC
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - reset ravg to 1.0
uint8_t qos_type; // 0 - dl_gbr_critical, 1 - dl_gbr_non_critical, 2 - dl_ngbr_critical, 3 - dl_ngbr_non_critical, 4 - dl_mbr_non_critical
} cumac_pfm_dl_lc_info_t;
/// @brief Uplink LCG info structure
typedef struct {
uint32_t tbs_scheduled; // TBS scheduled last time for this LCG
uint8_t flags; // a collection of flags: flags & 0x01 - is_valid, flags & 0x02 - reset ravg to 1.0
uint8_t qos_type; // 0 - ul_gbr_critical, 1 - ul_gbr_non_critical, 2 - ul_ngbr_critical, 3 - ul_ngbr_non_critical, 4 - ul_mbr_non_critical
} cumac_pfm_ul_lcg_info_t;
/// @brief Per-UE info structure
typedef struct {
uint32_t rcurrent_dl; // current instantaneous rate of this UE for DL
uint32_t rcurrent_ul; // current instantaneous rate of this UE for UL
uint16_t rnti; // RNTI of this UE
uint16_t id; // cuMAC 0-based UE ID of this UE
uint8_t num_layers_dl; // number of layers scheduled for this UE last time for DL
uint8_t num_layers_ul; // number of layers scheduled for this UE last time for UL
uint8_t flags; // a collection of flags: flags & 0x01 - is_scheduled_dl, flags & 0x02 - is_scheduled_ul
cumac_pfm_dl_lc_info_t dl_lc_info[CUMAC_PFM_MAX_NUM_LC_PER_UE]; // LCs configured for this UE for DL
cumac_pfm_ul_lcg_info_t ul_lcg_info[CUMAC_PFM_MAX_NUM_LCG_PER_UE]; // LCGs configured for this UE for UL
} cumac_pfm_ue_info_t;
/// @brief PFM sorting request data structure per cell
typedef struct {
uint16_t num_ue; // number of UEs for the PFM sorting in the current slot for this cell
uint8_t num_lc_per_ue; // number of LCs per UE for the PFM sorting in the current slot for this cell
uint8_t num_lcg_per_ue; // number of LCGs per UE for the PFM sorting in the current slot for this cell
uint16_t num_output_sorted_lc[CUMAC_PFM_NUM_QOS_TYPES_UL + CUMAC_PFM_NUM_QOS_TYPES_DL]; // array of number of output sorted LCs/LCGs per DL/UL QoS type
cumac_pfm_ue_info_t ue_info[CUMAC_PFM_MAX_NUM_UE_PER_CELL]; // UE info for the PFM sorting in the current slot for this cell
} cumac_pfm_cell_info_t;
// ************************************************** PFM sorting device output data structures **************************************************
/// @brief DL output info structure for the GPU PFM sorting
typedef struct {
uint16_t rnti; // UE RNTI of this DL info struct
uint8_t lc_id; // LC ID of this DL info struct
} cumac_pfm_dl_output_info_t;
/// @brief UL output info structure for the GPU PFM sorting
typedef struct {
uint16_t rnti; // UE RNTI of this UL info struct
uint8_t lcg_id; // LCG ID of this UL info struct
} cumac_pfm_ul_output_info_t;
/// @brief Output info structure for the GPU PFM sorting per cell
typedef struct {
cumac_pfm_dl_output_info_t dl_gbr_critical[CUMAC_PFM_MAX_NUM_SORTED_LC_PER_QOS]; // array of sorted LCs for the GBR critical QoS type
cumac_pfm_dl_output_info_t dl_gbr_non_critical[CUMAC_PFM_MAX_NUM_SORTED_LC_PER_QOS]; // array of sorted LCs for the GBR non-critical QoS type
cumac_pfm_dl_output_info_t dl_ngbr_critical[CUMAC_PFM_MAX_NUM_SORTED_LC_PER_QOS]; // array of sorted LCs for the NGBR critical QoS type
cumac_pfm_dl_output_info_t dl_ngbr_non_critical[CUMAC_PFM_MAX_NUM_SORTED_LC_PER_QOS]; // array of sorted LCs for the NGBR non-critical QoS type
cumac_pfm_dl_output_info_t dl_mbr_non_critical[CUMAC_PFM_MAX_NUM_SORTED_LC_PER_QOS]; // array of sorted LCs for the MBR non-critical QoS type
cumac_pfm_ul_output_info_t ul_gbr_critical[CUMAC_PFM_MAX_NUM_SORTED_LCG_PER_QOS]; // array of sorted LCGs for the GBR critical QoS type
cumac_pfm_ul_output_info_t ul_gbr_non_critical[CUMAC_PFM_MAX_NUM_SORTED_LCG_PER_QOS]; // array of sorted LCGs for the GBR non-critical QoS type
cumac_pfm_ul_output_info_t ul_ngbr_critical[CUMAC_PFM_MAX_NUM_SORTED_LCG_PER_QOS]; // array of sorted LCGs for the NGBR critical QoS type
cumac_pfm_ul_output_info_t ul_ngbr_non_critical[CUMAC_PFM_MAX_NUM_SORTED_LCG_PER_QOS]; // array of sorted LCGs for the NGBR non-critical QoS type
cumac_pfm_ul_output_info_t ul_mbr_non_critical[CUMAC_PFM_MAX_NUM_SORTED_LCG_PER_QOS]; // array of sorted LCGs for the MBR non-critical QoS type
} cumac_pfm_output_cell_info_t;
#if defined(__cplusplus)
} /* extern "C" */
#endif
#endif /* _CUMAC_PFM_SORT_DATA_TYPE_ */

View File

@@ -0,0 +1,513 @@
#include "cumac_test.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdarg.h>
#include <unistd.h>
#include <termios.h>
#include <stdatomic.h>
#include <sys/queue.h>
#include <sys/epoll.h>
#include <time.h>
#include <math.h>
#include "nv_utils.h"
#include "nv_ipc.h"
#include "nv_ipc_utils.h"
#include "cu_mac_api.h"
#include "cumac_msg_funcs.h"
/* YAML config path */
#define YAML_CONFIG_PATH "./cuMAC-CP/examples/L2IntegrationExample/l2_nvipc.yaml"
#define MAX_PATH_LEN (1024)
nv_ipc_config_t nv_ipc_config;
bool can_start = false;
/* C replacement for std::pair<uint16_t,uint8_t> */
typedef struct {
uint16_t ue;
uint8_t lc;
} scheduled_ue_lc;
/* Log TAG configured in nvlog */
static int TAG = (NVLOG_TAG_BASE_NVIPC + 0);
nv_ipc_t* ipc = NULL;
#define SENDER_THREAD_CORE (8)
#define RECV_THREAD_CORE (9)
////////////////////////////////////////////////////////////////////////
// Build a TX message
static int l2_build_tx_msg(nv_ipc_msg_t* nvipc_buf,
uint32_t slot,
uint32_t sfn,
uint32_t num_ue,
struct PFM_UE_INFO* test_data)
{
if (ipc == NULL || nvipc_buf == NULL) {
NVLOGE(TAG, AERIAL_NVIPC_API_EVENT, "%s: ipc or msg buffer is NULL", __func__);
return -1;
}
struct cumac_pfm_tti_req_t* req = (struct cumac_pfm_tti_req_t*)nvipc_buf->msg_buf;
uint8_t* data_buf = (uint8_t*)nvipc_buf->data_buf;
req->header.type_id = CUMAC_SCH_TTI_REQUEST;
req->sfn = sfn;
req->slot = slot;
req->num_ue = num_ue;
for (int i = 0; i < 10; i++) {
req->num_output_sorted_lc[i] = MAX_NUM_OUTPUT_SORTED_LC_PER_QOS;
}
uint32_t offset = 0;
uint32_t data_size = sizeof(struct PFM_UE_INFO) * num_ue;
req->offset_ue_info_arr = offset;
memcpy(data_buf + offset, test_data, data_size);
offset += data_size;
nvipc_buf->msg_id = CUMAC_SCH_TTI_REQUEST;
nvipc_buf->cell_id = 0;
nvipc_buf->msg_len = sizeof(struct cumac_pfm_tti_req_t);
nvipc_buf->data_len = data_size;
return 0;
}
////////////////////////////////////////////////////////////////////////
static int test_l2_send_slot(uint32_t slot, uint32_t sfn, uint32_t num_ue, struct PFM_UE_INFO* test_data)
{
struct timespec t1, t2, t3, t4, t5, t6;
clock_gettime(CLOCK_REALTIME, &t1);
nv_ipc_msg_t send_msg;
send_msg.data_pool = NV_IPC_MEMPOOL_CPU_DATA;
if (ipc->tx_allocate(ipc, &send_msg, 0) != 0) {
NVLOGE(TAG, AERIAL_NVIPC_API_EVENT, "NVIPC memory pool full");
return -1;
}
clock_gettime(CLOCK_REALTIME, &t2);
clock_gettime(CLOCK_REALTIME, &t3);
if (l2_build_tx_msg(&send_msg, slot, sfn, num_ue, test_data) < 0) {
ipc->tx_release(ipc, &send_msg);
return -1;
}
clock_gettime(CLOCK_REALTIME, &t4);
clock_gettime(CLOCK_REALTIME, &t5);
if (ipc->tx_send_msg(ipc, &send_msg) < 0) {
ipc->tx_release(ipc, &send_msg);
return -1;
}
ipc->tx_tti_sem_post(ipc);
clock_gettime(CLOCK_REALTIME, &t6);
printf("alloc: %ld ns\n", nvlog_timespec_interval(&t1, &t2));
printf("build: %ld ns\n", nvlog_timespec_interval(&t3, &t4));
printf("send : %ld ns\n", nvlog_timespec_interval(&t5, &t6));
return 0;
}
static void get_next_slot_timespec(struct timespec* ts, uint64_t interval_nsec)
{
ts->tv_nsec += interval_nsec;
while (ts->tv_nsec >= 1000000000L) {
ts->tv_nsec -= 1000000000L;
ts->tv_sec++;
}
}
////////////////////////////////////////////////////////////////////////
// C versions of prepare_slot_data_dl / ul
void prepare_slot_data_dl(struct PFM_UE_INFO* test_data, scheduled_ue_lc* sched_list, int slot_idx)
{
if (slot_idx == 0) {
for (int i = 0; i < MAX_NUM_SCHEDULED_UE; i++) {
sched_list[i].ue = i;
sched_list[i].lc = rand() % MAX_NUM_LC;
}
for (int u = 0; u < MAX_NUM_UE; u++) {
for (int lc = 0; lc < MAX_NUM_LC; lc++) {
struct PFM_DL_LC_INFO* p = &test_data[u].dl_lc_info[lc];
p->pfm = 0;
p->ravg = 1;
p->flags = 0x03;
p->qos_type = rand() % 5;
p->tbs_scheduled = 0;
}
test_data[u].num_dl_lcs = MAX_NUM_LC;
test_data[u].ambr = 50000000;
test_data[u].rcurrent_dl = 1000000 + rand() % 20000000;
test_data[u].rnti = u;
test_data[u].num_layers_dl = rand() % 2 + 1;
test_data[u].flags = 0x07;
test_data[u].carrier_id = 0;
}
} else {
for (int u = 0; u < MAX_NUM_UE; u++)
for (int lc = 0; lc < MAX_NUM_LC; lc++)
test_data[u].dl_lc_info[lc].tbs_scheduled = 0;
for (int i = 0; i < MAX_NUM_SCHEDULED_UE; i++) {
uint16_t ue = sched_list[i].ue;
uint8_t lc = sched_list[i].lc;
test_data[ue].dl_lc_info[lc].tbs_scheduled = (uint32_t)floorf((float)test_data[ue].rcurrent_dl * SLOT_DURATION);
sched_list[i].ue = (ue + MAX_NUM_SCHEDULED_UE) % MAX_NUM_UE;
sched_list[i].lc = rand() % MAX_NUM_LC;
}
for (int u = 0; u < MAX_NUM_UE; u++) {
test_data[u].rcurrent_dl = 1000000 + rand() % 20000000;
test_data[u].num_layers_dl = rand() % 2 + 1;
for (int lc = 0; lc < MAX_NUM_LC; lc++)
test_data[u].dl_lc_info[lc].flags = 0x01;
}
}
}
////////////////////////////////////////////////////////////////////////
void prepare_slot_data_ul(struct PFM_UE_INFO* test_data, scheduled_ue_lc* sched_list, int slot_idx)
{
if (slot_idx == 0) {
for (int i = 0; i < MAX_NUM_SCHEDULED_UE; i++) {
sched_list[i].ue = i;
sched_list[i].lc = rand() % MAX_NUM_LCG;
}
for (int u = 0; u < MAX_NUM_UE; u++) {
for (int g = 0; g < MAX_NUM_LCG; g++) {
struct PFM_UL_LCG_INFO* p = &test_data[u].ul_lcg_info[g];
p->pfm = 0;
p->ravg = 1;
p->flags = 0x03;
p->qos_type = rand() % 5;
p->tbs_scheduled = 0;
}
test_data[u].num_ul_lcgs = MAX_NUM_LCG;
test_data[u].ambr = 20000000;
test_data[u].rcurrent_ul = 500000 + rand() % 10000000;
test_data[u].rnti = u;
test_data[u].num_layers_ul = rand() % 2 + 1;
test_data[u].flags = 0x07;
test_data[u].carrier_id = 0;
}
} else {
for (int u = 0; u < MAX_NUM_UE; u++)
for (int g = 0; g < MAX_NUM_LCG; g++)
test_data[u].ul_lcg_info[g].tbs_scheduled = 0;
for (int i = 0; i < MAX_NUM_SCHEDULED_UE; i++) {
uint16_t ue = sched_list[i].ue;
uint8_t g = sched_list[i].lc;
test_data[ue].ul_lcg_info[g].tbs_scheduled = (uint32_t)floorf((float)test_data[ue].rcurrent_ul * SLOT_DURATION);
sched_list[i].ue = (ue + MAX_NUM_SCHEDULED_UE) % MAX_NUM_UE;
sched_list[i].lc = rand() % MAX_NUM_LCG;
}
for (int u = 0; u < MAX_NUM_UE; u++) {
test_data[u].rcurrent_ul = 500000 + rand() % 10000000;
test_data[u].num_layers_ul = rand() % 2 + 1;
for (int g = 0; g < MAX_NUM_LCG; g++)
test_data[u].ul_lcg_info[g].flags = 0x01;
}
}
}
////////////////////////////////////////////////////////////////////////
int load_hard_code_config(nv_ipc_config_t* config, int module_type, nv_ipc_transport_t _transport)
{
// Create configuration
config->ipc_transport = _transport;
if (set_nv_ipc_default_config(config, module_type) < 0) {
printf("%s: set configuration failed\n", __func__);
return -1;
}
int test_cuda_device_id = -1;
printf("CUDA device ID configured : %d \n", test_cuda_device_id);
config->transport_config.shm.cuda_device_id = test_cuda_device_id;
if (test_cuda_device_id >= 0) {
config->transport_config.shm.mempool_size[NV_IPC_MEMPOOL_CUDA_DATA].pool_len = 128;
config->transport_config.shm.mempool_size[NV_IPC_MEMPOOL_CPU_DATA].pool_len = 1024;
config->transport_config.shm.mempool_size[NV_IPC_MEMPOOL_CPU_MSG].pool_len = 4096;
}
return 0;
}
int main(int argc, char** argv)
{
srand(time(NULL));
nvIPC_Init();
/*// Want to use transport SHM, type epoll, module secondary (reads the created shm from cuphycontroller)
load_hard_code_config(&nv_ipc_config, NV_IPC_MODULE_SECONDARY, NV_IPC_TRANSPORT_SHM);
// Create nv_ipc_t instance
printf("%s: creating IPC interface with prefix %s\n", __func__, "cumac");
strcpy(nv_ipc_config.transport_config.shm.prefix, "cumac");
if ((ipc = create_nv_ipc_interface(&nv_ipc_config)) == NULL) {
printf("%s: create IPC interface failed\n", __func__);
return -1;
}
printf("%s: create IPC interface successful\n", __func__);
pthread_t tid;
pthread_create(&tid, NULL, l2_blocking_recv_task, NULL);
*/
sleep(1);
usleep(1000000);
while (!can_start) {
sleep(1);
}
static struct PFM_UE_INFO test_data[MAX_NUM_UE];
static scheduled_ue_lc sched_dl[MAX_NUM_SCHEDULED_UE];
static scheduled_ue_lc sched_ul[MAX_NUM_SCHEDULED_UE];
uint16_t sfn = 0, slot = 0;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec = 0;
ts.tv_sec++;
// pthread_setname_np(pthread_self(), "l2_sender");
//nv_set_sched_fifo_priority(80);
//nv_assign_thread_cpu_core(SENDER_THREAD_CORE);
for (int i = 0; i < NUM_TIME_SLOTS; i++) {
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL);
prepare_slot_data_dl(test_data, sched_dl, i);
prepare_slot_data_ul(test_data, sched_ul, i);
test_l2_send_slot(slot, sfn, MAX_NUM_UE, test_data);
slot++;
if (slot >= 20) {
slot = 0;
sfn = (sfn + 1) % 1024;
}
get_next_slot_timespec(&ts, SLOT_INTERVAL_NS);
}
return 0;
}
bool recv_task_running = false;
nv_ipc_config_t nv_ipc_config;
#define MAX_EVENTS 10
#define RECV_BUF_LEN 8192
char cpu_buf_recv[RECV_BUF_LEN];
//nv_ipc_t* ipc;
typedef int (*build_cumac_msg_fn_v_t)(cumac_msg_t type,nv_ipc_msg_t *nvipc_buf, void * args);
bool cumac_test_send_msg(cumac_msg_t type,build_cumac_msg_fn_v_t fn, void* args)
{
struct timespec t1, t2, t3, t4;
clock_gettime(CLOCK_REALTIME, &t1);
nv_ipc_msg_t send_msg;
send_msg.data_pool = NV_IPC_MEMPOOL_CPU_DATA;
if (ipc->tx_allocate(ipc, &send_msg, 0) != 0) {
NVLOGE(TAG, AERIAL_NVIPC_API_EVENT, "NVIPC memory pool full");
return NULL;
}
clock_gettime(CLOCK_REALTIME, &t2);
if (fn(type,&send_msg, args) < 0) {
ipc->tx_release(ipc, &send_msg);
return NULL;
}
clock_gettime(CLOCK_REALTIME, &t3);
if (ipc->tx_send_msg(ipc, &send_msg) < 0) {
ipc->tx_release(ipc, &send_msg);
return NULL;
}
ipc->tx_tti_sem_post(ipc);
clock_gettime(CLOCK_REALTIME, &t4);
printf("alloc: %ld ns\n", nvlog_timespec_interval(&t1, &t2));
printf("build: %ld ns\n", nvlog_timespec_interval(&t2, &t3));
printf("send : %ld ns\n", nvlog_timespec_interval(&t3, &t4));
return true;
}
void ipc_handle_rx_msg(nv_ipc_msg_t* recv_msg) {
switch (recv_msg->msg_id) {
case CUMAC_CONFIG_RESPONSE:
// Send Start request
cumac_test_send_msg(CUMAC_START_REQUEST,l2_build_start_request, NULL);
case CUMAC_START_RESPONSE:
can_start = true;
default:;
}
}
// Always allocate message buffer, but allocate data buffer only when data_len > 0
static int cumac_recv_msg(nv_ipc_msg_t* recv_msg)
{
if (ipc == NULL) {
return -1;
}
recv_msg->msg_buf = NULL;
recv_msg->data_buf = NULL;
// Allocate buffer for TX message
if (ipc->rx_recv_msg(ipc, recv_msg) < 0) {
printf("%s: no more message available\n", __func__);
return -1;
}
printf("recv: cell_id=%d msg_id=0x%02X msg_len=%d data_len=%d data_pool=%d\n",
recv_msg->cell_id,
recv_msg->msg_id,
recv_msg->msg_len,
recv_msg->data_len,
recv_msg->data_pool);
ipc_handle_rx_msg(recv_msg);
// Release buffer of RX message
int release_retval = ipc->rx_release(ipc, recv_msg);
if (release_retval != 0) {
printf("%s error: release RX buffer failed Error: %d\n", __FUNCTION__, release_retval);
return release_retval;
}
return 0;
}
void* epoll_recv_task(void* arg)
{
struct epoll_event ev, events[MAX_EVENTS];
printf("Aerial cuMAC recv task start \n");
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
printf("%s epoll_create failed\n", __func__);
return NULL;
}
int ipc_rx_event_fd = ipc->get_fd(ipc);
ev.events = EPOLLIN;
ev.data.fd = ipc_rx_event_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) {
printf("%s epoll_ctl failed\n", __func__);
return NULL;
}
while (1) {
if (!recv_task_running) {
recv_task_running = true;
printf("%s: epoll_recv_task started\n", __func__);
cumac_config_req_payload_t conf_req_payload;
conf_req_payload.harqEnabledInd = 0;
conf_req_payload.mcsSelCqi = 0;
conf_req_payload.nMaxCell = 1;
conf_req_payload.nMaxActUePerCell = 16;
conf_req_payload.nMaxSchUePerCell = 1;
conf_req_payload.nMaxPrg = 1;
conf_req_payload.nPrbPerPrg = 255;
conf_req_payload.nMaxBsAnt = 4;
conf_req_payload.nMaxUeAnt = 4;
conf_req_payload.scSpacing = 1;
conf_req_payload.allocType = 1;
conf_req_payload.precoderType = 0;
conf_req_payload.receiverType = 0;
conf_req_payload.colMajChanAccess = 0;
conf_req_payload.betaCoeff = 1;
conf_req_payload.sinValThr = 0.1f;
conf_req_payload.corrThr = 0.1f;
conf_req_payload.mcsSelSinrCapThr = 0;
conf_req_payload.mcsSelLutType = 1;
conf_req_payload.prioWeightStep = 1;
cumac_test_send_msg(CUMAC_CONFIG_REQUEST,l2_build_config_request, &conf_req_payload);
printf("%s: cumac_send_msg called\n", __func__);
}
printf("%s: epoll_wait fd_rx=%d ...\n", __func__, ipc_rx_event_fd);
int nfds;
do {
// epoll_wait() may return EINTR when get unexpected signal SIGSTOP from system
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
} while (nfds == -1 && errno == EINTR);
if (nfds < 0) {
printf("epoll_wait failed: epoll_fd=%d nfds=%d err=%d - %s\n", epoll_fd, nfds, errno, strerror(errno));
}
int n = 0;
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == ipc_rx_event_fd) {
ipc->get_value(ipc);
nv_ipc_msg_t recv_msg;
while (cumac_recv_msg(&recv_msg) == 0)
;
}
}
}
close(epoll_fd);
return NULL;
}
int nvIPC_Init()
{
// Want to use transport SHM, type epoll, module secondary (reads the created shm from cuphycontroller)
load_hard_code_config(&nv_ipc_config, NV_IPC_MODULE_SECONDARY, NV_IPC_TRANSPORT_SHM);
// Create nv_ipc_t instance
printf("%s: creating IPC interface with prefix %s\n", __func__, "cumac");
strcpy(nv_ipc_config.transport_config.shm.prefix, "cumac");
if ((ipc = create_nv_ipc_interface(&nv_ipc_config)) == NULL) {
printf("%s: create IPC interface failed\n", __func__);
return -1;
}
printf("%s: create IPC interface successful\n", __func__);
sleep(1);
pthread_t thread_id;
pthread_create(&thread_id, NULL, epoll_recv_task, NULL);
//threadCreate(&thread_id, epoll_recv_task, NULL, "nvipc_cumac", 13, OAI_PRIORITY_RT);
while (!recv_task_running) {
usleep(100000);
}
return 0;
} /*
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
exit(1);
}*/
// int main(int argc, char* argv[])
// {
// nvIPC_Init();
// sleep(300);
// return 0;
// }

View File

@@ -0,0 +1,26 @@
//
// Created by user on 20 Nov 2025.
//
#ifndef OPENAIRINTERFACE_CUMAC_TEST_H
#define OPENAIRINTERFACE_CUMAC_TEST_H
#include "nv_ipc.h"
#include <bits/types/struct_timeval.h>
#include "nv_ipc_utils.h"
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include "cumac_msg.h"
#include "cumac_msg_funcs.h"
#include <sys/epoll.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include "../../../../../common/utils/system.h"
#include "../../../../../common/platform_types.h"
int nvIPC_Init();
#endif // OPENAIRINTERFACE_CUMAC_TEST_H

View File

@@ -373,6 +373,7 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
int xp_pdsch_antenna_ports);
int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl);
int get_SINRx10_for_mcs(int mcs);
uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx);
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);

View File

@@ -794,6 +794,15 @@ typedef struct NR_UE_info {
// dedicated BWP is always 1 from the UE's point of view, even if the gNB has multiple BWPs.
// The below ID is the "true" (non-consecutive) BWP ID from the gNB's point of view
NR_BWP_Id_t local_bwp_id;
#ifdef ENABLE_CUMAC
// Per-UE state carried from one SCH_TTI.response into the next SCH_TTI.request as feedback
struct {
bool has_prev_sol; // true once at least one SCH_TTI.response has been processed
int16_t alloc_sol[2]; // last [rbStart, rbSize] from allocSol
int16_t mcs_sol; // last MCS from mcsSelSol
int8_t layer_sol; // last layer count from layerSelSol
} cumac_last_sol;
#endif
} NR_UE_info_t;
typedef struct {

View File

@@ -20,7 +20,9 @@
#include "openair2/PHY_INTERFACE/queue_t.h"
#include "utils.h"
#include "nfapi/oai_integration/nfapi_pnf.h"
#ifdef ENABLE_CUMAC
#include "openair2/LAYER2/NR_MAC_gNB/integration/cuMAC/cumac_nvipc.h"
#endif
#define MAX_IF_MODULES 100
static NR_IF_Module_t *nr_if_inst[MAX_IF_MODULES];
@@ -441,6 +443,33 @@ NR_IF_Module_t *NR_IF_Module_init(int Mod_id) {
AssertFatal(Mod_id<MAX_MODULES,"Asking for Module %d > %d\n",Mod_id,MAX_IF_MODULES);
LOG_D(PHY, "Installing callbacks for IF_Module - UL_indication\n");
#ifdef ENABLE_CUMAC
cumac_nvipc_init();
cumac_config_req_payload_t conf_req_payload;
conf_req_payload.harqEnabledInd = 0;
conf_req_payload.mcsSelCqi = 0;
conf_req_payload.nMaxCell = 1;
conf_req_payload.nMaxActUePerCell = 16;
conf_req_payload.nMaxSchUePerCell = 8;
conf_req_payload.nMaxPrg = 106;
conf_req_payload.nPrbPerPrg = 1;
conf_req_payload.nMaxBsAnt = 1;
conf_req_payload.nMaxUeAnt = 1;
conf_req_payload.scSpacing = 1;
conf_req_payload.allocType = 1;
conf_req_payload.precoderType = 0;
conf_req_payload.receiverType = 0;
conf_req_payload.colMajChanAccess = 0;
conf_req_payload.betaCoeff = 1;
conf_req_payload.sinValThr = 0.1f;
conf_req_payload.corrThr = 0.1f;
conf_req_payload.mcsSelSinrCapThr = 0;
conf_req_payload.mcsSelLutType = 1;
conf_req_payload.prioWeightStep = 1;
conf_req_payload.blerTarget = 0.1f; //new parameter
cumac_send_msg(CUMAC_CONFIG_REQUEST,l2_build_config_request, &conf_req_payload);
#endif
if (nr_if_inst[Mod_id]==NULL) {
nr_if_inst[Mod_id] = (NR_IF_Module_t*)malloc(sizeof(NR_IF_Module_t));
memset((void*)nr_if_inst[Mod_id],0,sizeof(NR_IF_Module_t));

View File

@@ -206,14 +206,14 @@ RUs = (
);
rfsimulator :
rfsimulator = (
{
serveraddr = "server";
serverport = 4043;
options = (); #("saviq"); or/and "chanmod"
modelname = "AWGN";
IQfile = "/tmp/rfsimulator.iqs";
};
});
security = {
# preferred ciphering algorithms