Commit a0594dd6 authored by Robert Schmidt's avatar Robert Schmidt

Merge remote-tracking branch 'origin/goa-5g-kpi-gui' into integration_2023_w08b

parents 263436d8 0740b596
......@@ -1005,7 +1005,7 @@ add_dependencies(ldpc_cl nrLDPC_decoder_kernels_CL)
# Base CUDA setting
##############################################
add_boolean_option(BUILD_CUDA False "Build support for CUDA" OFF)
add_boolean_option(ENABLE_LDPC_CUDA OFF "Build support for CUDA" OFF)
if (ENABLE_LDPC_CUDA)
find_package(CUDA REQUIRED)
SET(CUDA_NVCC_FLAG "${CUDA_NVCC_FLAGS};-arch=sm_60;")
......@@ -2040,6 +2040,19 @@ add_library(SIMU STATIC ${SIMUSRC} )
target_include_directories(SIMU PUBLIC ${OPENAIR1_DIR}/SIMULATION/TOOLS ${OPENAIR1_DIR}/SIMULATION/RF)
target_link_libraries(SIMU PRIVATE asn1_nr_rrc asn1_lte_rrc)
# Qt-based scope
add_boolean_option(ENABLE_NRQTSCOPE OFF "Build the Qt-Scope" OFF)
if (ENABLE_NRQTSCOPE)
find_package(Qt5 REQUIRED COMPONENTS Widgets Charts)
message ("Qt5 Widgets and Charts found for nrqtscope")
set(QTSCOPE_SOURCE_NR ${OPENAIR1_DIR}/PHY/TOOLS/nr_phy_qt_scope.cpp)
# Creates rules for calling the Meta-Object Compiler (moc) on the given source files
qt5_wrap_cpp(QTSCOPE_SOURCE_NR ${OPENAIR1_DIR}/PHY/TOOLS/nr_phy_qt_scope.h)
add_library(nrqtscope MODULE ${QTSCOPE_SOURCE_NR})
target_link_libraries(nrqtscope PRIVATE Qt5::Widgets Qt5::Charts)
target_link_libraries(nrqtscope PRIVATE asn1_nr_rrc asn1_lte_rrc)
endif()
add_library(SIMU_ETH
${OPENAIR1_DIR}/SIMULATION/ETH_TRANSPORT/netlink_init.c
${OPENAIR1_DIR}/SIMULATION/ETH_TRANSPORT/multicast_link.c
......
......@@ -53,9 +53,10 @@ BUILD_COVERITY_SCAN=0
DISABLE_HARDWARE_DEPENDENCY="False"
CMAKE_BUILD_TYPE="RelWithDebInfo"
CMAKE_CMD="$CMAKE"
MAKE_CMD=make
BUILD_ECLIPSE=0
NR="False"
OPTIONAL_LIBRARIES="telnetsrv enbscope uescope nrscope ldpc_cuda ldpc_t1 websrv"
OPTIONAL_LIBRARIES="telnetsrv enbscope uescope nrscope nrqtscope ldpc_cuda ldpc_t1 websrv"
RU=0
CMAKE_C_FLAGS=()
CMAKE_CXX_FLAGS=()
......
......@@ -551,6 +551,7 @@ check_install_additional_tools (){
PACKAGE_LIST="\
doxygen \
libpthread-stubs0-dev \
libqt5charts5-dev \
tshark \
uml-utilities \
iperf3 \
......
......@@ -2218,11 +2218,13 @@ INPUT = \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/LTE_ESTIMATION/lte_dl_bf_channel_estimation.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/LTE_ESTIMATION/lte_sync_timefreq.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/LTE_ESTIMATION/filt16_32.h \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/nr_phy_scope.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/cdot_prod.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/nr_phy_scope.h \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/phy_scope_interface.h \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/phy_scope_interface.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/nr_phy_scope.h \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/nr_phy_scope.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/nr_phy_qt_scope.h \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/nr_phy_qt_scope.cpp \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/file_output.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/dfts_load.c \
@CMAKE_CURRENT_SOURCE_DIR@/../openair1/PHY/TOOLS/alaw_lut.h \
......
......@@ -35,4 +35,4 @@ COPY . .
RUN /bin/sh oaienv && \
cd cmake_targets && \
mkdir -p log && \
./build_oai --eNB --gNB --RU --UE --nrUE --ninja --build-lib "telnetsrv enbscope uescope nrscope" -w USRP --verbose-ci --noavx512 -c
./build_oai --eNB --gNB --RU --UE --nrUE --ninja --build-lib "telnetsrv enbscope uescope nrscope nrqtscope" -w USRP --verbose-ci --noavx512 -c
......@@ -711,6 +711,15 @@ int main( int argc, char **argv ) {
load_softscope("nr",&p);
}
if(IS_SOFTMODEM_DOSCOPE_QT) {
scopeParms_t p;
p.argc = &argc;
p.argv = argv;
p.gNB = RC.gNB[0];
p.ru = RC.ru[0];
load_softscope("nrqt", &p);
}
if (NFAPI_MODE != NFAPI_MODE_PNF && NFAPI_MODE != NFAPI_MODE_VNF) {
printf("Not NFAPI mode - call init_eNB_afterRU()\n");
init_eNB_afterRU();
......
......@@ -522,6 +522,10 @@ int main( int argc, char **argv ) {
memset (&UE_PF_PO[0][0], 0, sizeof(UE_PF_PO_t)*NUMBER_OF_UE_MAX*MAX_NUM_CCs);
set_latency_target();
if(IS_SOFTMODEM_DOSCOPE_QT) {
load_softscope("nrqt",PHY_vars_UE_g[0][0]);
}
if(IS_SOFTMODEM_DOSCOPE) {
load_softscope("nr",PHY_vars_UE_g[0][0]);
}
......
......@@ -97,7 +97,7 @@ void get_common_options(uint32_t execmask) {
uint32_t start_telnetsrv = 0, start_telnetclt = 0;
uint32_t start_websrv = 0;
uint32_t noS1 = 0, nokrnmod = 1, nonbiot = 0;
uint32_t rfsim = 0, do_forms = 0;
uint32_t rfsim = 0, do_forms = 0, do_forms_qt = 0;
int nfapi_index = 0;
char *logmem_filename = NULL;
check_execmask(execmask);
......@@ -161,6 +161,10 @@ void get_common_options(uint32_t execmask) {
set_softmodem_optmask(SOFTMODEM_DOSCOPE_BIT);
}
if (do_forms_qt) {
set_softmodem_optmask(SOFTMODEM_DOSCOPE_QT_BIT);
}
if (start_websrv) {
load_module_shlib("websrv", NULL, 0, NULL);
}
......
......@@ -67,6 +67,7 @@ extern "C"
#define CONFIG_HLP_ULF "Set the uplink frequency offset for all component carriers\n"
#define CONFIG_HLP_CHOFF "Channel id offset\n"
#define CONFIG_HLP_SOFTS "Enable soft scope and L1 and L2 stats (Xforms)\n"
#define CONFIG_HLP_SOFTS_QT "Enable soft scope and L1 and L2 stats (QT)\n"
#define CONFIG_HLP_ITTIL "Generate ITTI analyzser logs (similar to wireshark logs but with more details)\n"
#define CONFIG_HLP_DLMCS "Set the maximum downlink MCS\n"
#define CONFIG_HLP_STMON "Enable processing timing measurement of lte softmodem on per subframe basis \n"
......@@ -151,17 +152,18 @@ extern int usrp_tx_thread;
{"C" , CONFIG_HLP_DLF, 0, u64ptr:&(downlink_frequency[0][0]), defuintval:0, TYPE_UINT64, 0}, \
{"CO" , CONFIG_HLP_ULF, 0, iptr:&(uplink_frequency_offset[0][0]), defintval:0, TYPE_INT, 0}, \
{"a" , CONFIG_HLP_CHOFF, 0, iptr:&CHAIN_OFFSET, defintval:0, TYPE_INT, 0}, \
{"d" , CONFIG_HLP_SOFTS, PARAMFLAG_BOOL, uptr:(uint32_t *)&do_forms, defintval:0, TYPE_INT8, 0}, \
{"d" , CONFIG_HLP_SOFTS, PARAMFLAG_BOOL, uptr:&do_forms, defintval:0, TYPE_UINT, 0}, \
{"dqt" , CONFIG_HLP_SOFTS_QT, PARAMFLAG_BOOL, uptr:&do_forms_qt, defintval:0, TYPE_UINT, 0}, \
{"q" , CONFIG_HLP_STMON, PARAMFLAG_BOOL, iptr:&opp_enabled, defintval:0, TYPE_INT, 0}, \
{"numerology" , CONFIG_HLP_NUMEROLOGY, PARAMFLAG_BOOL, iptr:&NUMEROLOGY, defintval:1, TYPE_INT, 0}, \
{"band" , CONFIG_HLP_BAND, PARAMFLAG_BOOL, iptr:&BAND, defintval:78, TYPE_INT, 0}, \
{"emulate-rf" , CONFIG_HLP_EMULATE_RF, PARAMFLAG_BOOL, iptr:&EMULATE_RF, defintval:0, TYPE_INT, 0}, \
{"parallel-config", CONFIG_HLP_PARALLEL_CMD, 0, strptr:&parallel_config, defstrval:NULL, TYPE_STRING, 0}, \
{"worker-config", CONFIG_HLP_WORKER_CMD, 0, strptr:&worker_config, defstrval:NULL, TYPE_STRING, 0}, \
{"noS1", CONFIG_HLP_NOS1, PARAMFLAG_BOOL, uptr:&noS1, defintval:0, TYPE_INT, 0}, \
{"rfsim", CONFIG_HLP_RFSIM, PARAMFLAG_BOOL, uptr:&rfsim, defintval:0, TYPE_INT, 0}, \
{"nokrnmod", CONFIG_HLP_NOKRNMOD, PARAMFLAG_BOOL, uptr:&nokrnmod, defintval:0, TYPE_INT, 0}, \
{"nbiot-disable", CONFIG_HLP_DISABLNBIOT, PARAMFLAG_BOOL, uptr:&nonbiot, defuintval:0, TYPE_INT, 0}, \
{"noS1", CONFIG_HLP_NOS1, PARAMFLAG_BOOL, uptr:&noS1, defintval:0, TYPE_UINT, 0}, \
{"rfsim", CONFIG_HLP_RFSIM, PARAMFLAG_BOOL, uptr:&rfsim, defintval:0, TYPE_UINT, 0}, \
{"nokrnmod", CONFIG_HLP_NOKRNMOD, PARAMFLAG_BOOL, uptr:&nokrnmod, defintval:0, TYPE_UINT, 0}, \
{"nbiot-disable", CONFIG_HLP_DISABLNBIOT, PARAMFLAG_BOOL, uptr:&nonbiot, defuintval:0, TYPE_UINT, 0}, \
{"chest-freq", CONFIG_HLP_CHESTFREQ, 0, iptr:&CHEST_FREQ, defintval:0, TYPE_INT, 0}, \
{"chest-time", CONFIG_HLP_CHESTTIME, 0, iptr:&CHEST_TIME, defintval:0, TYPE_INT, 0}, \
{"nsa", CONFIG_HLP_NSA, PARAMFLAG_BOOL, iptr:&NSA, defintval:0, TYPE_INT, 0}, \
......@@ -208,6 +210,7 @@ extern int usrp_tx_thread;
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s3a = { config_checkstr_assign_integer, \
{"MONOLITHIC", "PNF", "VNF","UE_STUB_PNF","UE_STUB_OFFNET","STANDALONE_PNF"}, \
{NFAPI_MONOLITHIC, NFAPI_MODE_PNF, NFAPI_MODE_VNF,NFAPI_UE_STUB_PNF,NFAPI_UE_STUB_OFFNET,NFAPI_MODE_STANDALONE_PNF}, \
......@@ -216,7 +219,6 @@ extern int usrp_tx_thread;
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
}
#define CONFIG_HLP_NSA "Enable NSA mode \n"
......@@ -264,6 +266,8 @@ extern int usrp_tx_thread;
#define SOFTMODEM_4GUE_BIT (1<<22)
#define SOFTMODEM_5GUE_BIT (1<<23)
#define SOFTMODEM_NOSTATS_BIT (1<<24)
#define SOFTMODEM_DOSCOPE_QT_BIT (1<<25)
#define SOFTMODEM_FUNC_BITS (SOFTMODEM_ENB_BIT | SOFTMODEM_GNB_BIT | SOFTMODEM_5GUE_BIT | SOFTMODEM_4GUE_BIT)
#define MAPPING_SOFTMODEM_FUNCTIONS {{"enb",SOFTMODEM_ENB_BIT},{"gnb",SOFTMODEM_GNB_BIT},{"4Gue",SOFTMODEM_4GUE_BIT},{"5Gue",SOFTMODEM_5GUE_BIT}}
......@@ -275,6 +279,7 @@ extern int usrp_tx_thread;
#define IS_SOFTMODEM_SIML1 ( get_softmodem_optmask() & SOFTMODEM_SIML1_BIT)
#define IS_SOFTMODEM_DLSIM ( get_softmodem_optmask() & SOFTMODEM_DLSIM_BIT)
#define IS_SOFTMODEM_DOSCOPE ( get_softmodem_optmask() & SOFTMODEM_DOSCOPE_BIT)
#define IS_SOFTMODEM_DOSCOPE_QT ( get_softmodem_optmask() & SOFTMODEM_DOSCOPE_QT_BIT)
#define IS_SOFTMODEM_IQPLAYER ( get_softmodem_optmask() & SOFTMODEM_RECPLAY_BIT)
#define IS_SOFTMODEM_TELNETCLT_BIT ( get_softmodem_optmask() & SOFTMODEM_TELNETCLT_BIT)
#define IS_SOFTMODEM_ENB_BIT ( get_softmodem_optmask() & SOFTMODEM_ENB_BIT)
......
......@@ -300,6 +300,7 @@ void nr_sort_asc_int16_1D_array_ind(int32_t *matrix,
void nr_free_double_2D_array(double **input, uint16_t xlen);
#ifndef __cplusplus
void updateLLR(uint8_t listSize,
uint16_t row,
uint16_t col,
......@@ -327,7 +328,7 @@ void updatePathMetric2(double *pathMetric,
int ylen,
int zlen,
double llr[xlen][ylen][zlen]);
#endif
//Also nr_polar_rate_matcher
static inline void nr_polar_interleaver(uint8_t *input,
uint8_t *output,
......
......@@ -141,8 +141,8 @@ void nr_ue_measurements(PHY_VARS_NR_UE *ue,
for (gNB_id = 0; gNB_id < ue->n_connected_gNB; gNB_id++) {
ue->measurements.rx_power_avg_dB[gNB_id] = dB_fixed( ue->measurements.rx_power_avg[gNB_id]);
ue->measurements.wideband_cqi_tot[gNB_id] = dB_fixed2(ue->measurements.rx_power_tot[gNB_id], ue->measurements.n0_power_tot);
ue->measurements.wideband_cqi_avg[gNB_id] = dB_fixed2(ue->measurements.rx_power_avg[gNB_id], ue->measurements.n0_power_avg);
ue->measurements.wideband_cqi_tot[gNB_id] = ue->measurements.rx_power_tot_dB[gNB_id] - ue->measurements.n0_power_tot_dB;
ue->measurements.wideband_cqi_avg[gNB_id] = ue->measurements.rx_power_avg_dB[gNB_id] - dB_fixed(ue->measurements.n0_power_avg);
ue->measurements.rx_rssi_dBm[gNB_id] = ue->measurements.rx_power_avg_dB[gNB_id] + 30 - 10*log10(pow(2, 30)) - ((int)openair0_cfg[0].rx_gain[0] - (int)openair0_cfg[0].rx_gain_offset[0]) - dB_fixed(ue->frame_parms.ofdm_symbol_size);
LOG_D(PHY, "[gNB %d] Slot %d, RSSI %d dB (%d dBm/RE), WBandCQI %d dB, rxPwrAvg %d, n0PwrAvg %d\n",
......@@ -185,9 +185,6 @@ void nr_ue_ssb_rsrp_measurements(PHY_VARS_NR_UE *ue,
uint8_t l_sss = (symbol_offset + 2) % ue->frame_parms.symbols_per_slot;
if (ssb_offset>= ue->frame_parms.ofdm_symbol_size)
ssb_offset -= ue->frame_parms.ofdm_symbol_size;
uint32_t rsrp = 0;
LOG_D(PHY, "In %s: [UE %d] slot %d l_sss %d ssb_offset %d\n", __FUNCTION__, ue->Mod_id, slot, l_sss, ssb_offset);
......@@ -195,15 +192,17 @@ void nr_ue_ssb_rsrp_measurements(PHY_VARS_NR_UE *ue,
for (int aarx = 0; aarx < ue->frame_parms.nb_antennas_rx; aarx++) {
int16_t *rxF_sss = (int16_t *)&rxdataF[aarx][(l_sss*ue->frame_parms.ofdm_symbol_size) + ssb_offset];
int16_t *rxF_sss = (int16_t *)&rxdataF[aarx][l_sss*ue->frame_parms.ofdm_symbol_size];
for(int k = k_start; k < k_end; k++){
int re = (ssb_offset + k) % ue->frame_parms.ofdm_symbol_size;
#ifdef DEBUG_MEAS_UE
LOG_I(PHY, "In %s rxF_sss %d %d\n", __FUNCTION__, rxF_sss[k*2], rxF_sss[k*2 + 1]);
LOG_I(PHY, "In %s rxF_sss %d %d\n", __FUNCTION__, rxF_sss[re*2], rxF_sss[re*2 + 1]);
#endif
rsrp += (((int32_t)rxF_sss[k*2]*rxF_sss[k*2]) + ((int32_t)rxF_sss[k*2 + 1]*rxF_sss[k*2 + 1]));
rsrp += (((int32_t)rxF_sss[re*2]*rxF_sss[re*2]) + ((int32_t)rxF_sss[re*2 + 1]*rxF_sss[re*2 + 1]));
nb_re++;
}
......@@ -232,11 +231,11 @@ void nr_ue_rrc_measurements(PHY_VARS_NR_UE *ue,
uint8_t k;
int slot = proc->nr_slot_rx;
int aarx, nb_nulls;
int aarx;
int16_t *rxF_sss;
uint8_t k_left = 48;
uint8_t k_right = 183;
uint8_t k_length = 8;
const uint8_t k_left = 48;
const uint8_t k_right = 183;
const uint8_t k_length = 8;
uint8_t l_sss = (ue->symbol_offset + 2) % ue->frame_parms.symbols_per_slot;
unsigned int ssb_offset = ue->frame_parms.first_carrier_offset + ue->frame_parms.ssb_start_subcarrier;
double rx_gain = openair0_cfg[0].rx_gain[0];
......@@ -248,41 +247,42 @@ void nr_ue_rrc_measurements(PHY_VARS_NR_UE *ue,
for (aarx = 0; aarx<ue->frame_parms.nb_antennas_rx; aarx++) {
nb_nulls = 0;
ue->measurements.n0_power[aarx] = 0;
rxF_sss = (int16_t *)&rxdataF[aarx][(l_sss*ue->frame_parms.ofdm_symbol_size) + ssb_offset];
rxF_sss = (int16_t *)&rxdataF[aarx][l_sss*ue->frame_parms.ofdm_symbol_size];
//-ve spectrum from SSS
for(k = k_left; k < k_left + k_length; k++){
int re = (ssb_offset + k) % ue->frame_parms.ofdm_symbol_size;
#ifdef DEBUG_MEAS_RRC
LOG_I(PHY, "In %s -rxF_sss %d %d\n", __FUNCTION__, rxF_sss[k*2], rxF_sss[k*2 + 1]);
LOG_I(PHY, "In %s -rxF_sss %d %d\n", __FUNCTION__, rxF_sss[re*2], rxF_sss[re*2 + 1]);
#endif
ue->measurements.n0_power[aarx] += (((int32_t)rxF_sss[k*2]*rxF_sss[k*2]) + ((int32_t)rxF_sss[k*2 + 1]*rxF_sss[k*2 + 1]));
nb_nulls++;
ue->measurements.n0_power[aarx] += (((int32_t)rxF_sss[re*2]*rxF_sss[re*2]) + ((int32_t)rxF_sss[re*2 + 1]*rxF_sss[re*2 + 1]));
}
//+ve spectrum from SSS
for(k = k_right; k < k_right + k_length; k++){
int re = (ssb_offset + k) % ue->frame_parms.ofdm_symbol_size;
#ifdef DEBUG_MEAS_RRC
LOG_I(PHY, "In %s +rxF_sss %d %d\n", __FUNCTION__, rxF_sss[k*2], rxF_sss[k*2 + 1]);
LOG_I(PHY, "In %s +rxF_sss %d %d\n", __FUNCTION__, rxF_sss[re*2], rxF_sss[re*2 + 1]);
#endif
ue->measurements.n0_power[aarx] += (((int32_t)rxF_sss[k*2]*rxF_sss[k*2]) + ((int32_t)rxF_sss[k*2 + 1]*rxF_sss[k*2 + 1]));
nb_nulls++;
ue->measurements.n0_power[aarx] += (((int32_t)rxF_sss[re*2]*rxF_sss[re*2]) + ((int32_t)rxF_sss[re*2 + 1]*rxF_sss[re*2 + 1]));
}
ue->measurements.n0_power[aarx] /= nb_nulls;
ue->measurements.n0_power[aarx] /= 2*k_length;
ue->measurements.n0_power_dB[aarx] = (unsigned short) dB_fixed(ue->measurements.n0_power[aarx]);
ue->measurements.n0_power_tot += ue->measurements.n0_power[aarx];
}
ue->measurements.n0_power_tot_dB = (unsigned short) dB_fixed(ue->measurements.n0_power_tot/aarx);
ue->measurements.n0_power_tot_dB = (unsigned short) dB_fixed(ue->measurements.n0_power_tot);
#ifdef DEBUG_MEAS_RRC
const int psd_awgn = -174;
......
......@@ -44,6 +44,7 @@
#include "executables/nr-uesoftmodem.h"
#include "PHY/CODING/nrLDPC_extern.h"
#include "common/utils/nr/nr_common.h"
#include "openair1/PHY/TOOLS/phy_scope_interface.h"
//#define ENABLE_PHY_PAYLOAD_DEBUG 1
......@@ -53,10 +54,16 @@
static uint64_t nb_total_decod =0;
static uint64_t nb_error_decod =0;
static extended_kpi_ue kpiStructure = {0};
notifiedFIFO_t freeBlocks_dl;
notifiedFIFO_elt_t *msgToPush_dl;
int nbDlProcessing =0;
extended_kpi_ue* getKPIUE(void) {
return &kpiStructure;
}
void nr_ue_dlsch_init(NR_UE_DLSCH_t *dlsch_list, int num_dlsch, uint8_t max_ldpc_iterations) {
for (int i=0; i < num_dlsch; i++) {
NR_UE_DLSCH_t *dlsch = dlsch_list + i;
......@@ -99,6 +106,11 @@ bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool
// if all segments are done
if (last) {
kpiStructure.nb_total++;
kpiStructure.blockSize = dlsch->dlsch_config.TBS;
kpiStructure.dl_mcs = dlsch->dlsch_config.mcs;
kpiStructure.nofRBs = dlsch->dlsch_config.number_rbs;
if (decodeSuccess) {
//LOG_D(PHY,"[UE %d] DLSCH: Setting ACK for nr_slot_rx %d TBS %d mcs %d nb_rb %d harq_process->round %d\n",
// phy_vars_ue->Mod_id,nr_slot_rx,harq_process->TBS,harq_process->mcs,harq_process->nb_rb, harq_process->round);
......@@ -114,6 +126,7 @@ bool nr_ue_postDecode(PHY_VARS_NR_UE *phy_vars_ue, notifiedFIFO_elt_t *req, bool
dlsch->last_iteration_cnt = rdata->decodeIterations;
LOG_D(PHY, "DLSCH received ok \n");
} else {
kpiStructure.nb_nack++;
//LOG_D(PHY,"[UE %d] DLSCH: Setting NAK for SFN/SF %d/%d (pid %d, status %d, round %d, TBS %d, mcs %d) Kr %d r %d harq_process->round %d\n",
// phy_vars_ue->Mod_id, frame, nr_slot_rx, harq_pid,harq_process->status, harq_process->round,harq_process->TBS,harq_process->mcs,Kr,r,harq_process->round);
harq_process->ack = 0;
......
/*
* 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
*
* Authors and copyright: Bo Zhao, Marwan Hammouda, Thomas Schlichter (Fraunhofer IIS)
*
* 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
*/
#include <QApplication>
#include <QtWidgets>
#include <QPainter>
#include <QtGui>
#include <QLineEdit>
#include <QFormLayout>
#include <QtCharts>
#include <QValueAxis>
#include <iostream>
#include <cassert>
#include <cmath>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "nr_phy_qt_scope.h"
extern "C" {
#include "PHY/CODING/nrPolar_tools/nr_polar_defs.h"
#include <openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h>
}
#define ScaleZone 4;
#define SquaredNorm(VaR) ((VaR).r * (VaR).r + (VaR).i * (VaR).i)
/*
@gNB: These are the (default) lower and upper threshold values for BLER and Throughput at the gNB side.
These threshold values can be further updated in run-time through the option 'Configs' in the drop-down list.
*/
float Limits_KPI_gNB[4][2] = {
// {lower Limit, Upper Limit}
{0.0, 0.8}, // UL BLER
{0.2, 10}, // UL Throughput in Mbs
{0.0, 0.8}, // DL BLER
{0.2, 10} // DL Throughput in Mbs
};
/*
@UE: These are the (default) lower and upper threshold values for BLER and Throughput at the UE side.
These threshold values can be further updated in run-time through the option 'Configs' in the drop-down list
*/
float Limits_KPI_ue[2][2] = {
// {lower Limit, Upper Limit}
{0.0, 0.8}, // DL BLER
{0.2, 10} // Throughput in Mbs
};
/* This class creates the window when choosing the option 'Configs' to configure the threshold values. */
ConfigBoxFloat::ConfigBoxFloat(float *valuePtr, QWidget *parent) : QLineEdit(parent), valuePtr(valuePtr)
{
this->setText(QString::number(*valuePtr));
connect(this, &ConfigBoxFloat::editingFinished, this, &ConfigBoxFloat::readText);
}
/* This function reads the input config values, entered by user, and update the Limits_KPI_* accordignly. */
void ConfigBoxFloat::readText()
{
QString text_e1 = this->text();
*this->valuePtr = text_e1.toFloat();
}
/* @gNB: create configuration window */
KPIConfigGnb::KPIConfigGnb(QWidget *parent) : QWidget(parent)
{
this->resize(300, 300);
this->setWindowTitle("gNB Configs");
ConfigBoxFloat *configItem1 = new ConfigBoxFloat(&Limits_KPI_gNB[0][0]);
ConfigBoxFloat *configItem2 = new ConfigBoxFloat(&Limits_KPI_gNB[0][1]);
ConfigBoxFloat *configItem3 = new ConfigBoxFloat(&Limits_KPI_gNB[1][0]);
ConfigBoxFloat *configItem4 = new ConfigBoxFloat(&Limits_KPI_gNB[1][1]);
ConfigBoxFloat *configItem5 = new ConfigBoxFloat(&Limits_KPI_gNB[2][0]);
ConfigBoxFloat *configItem6 = new ConfigBoxFloat(&Limits_KPI_gNB[2][1]);
ConfigBoxFloat *configItem7 = new ConfigBoxFloat(&Limits_KPI_gNB[3][0]);
ConfigBoxFloat *configItem8 = new ConfigBoxFloat(&Limits_KPI_gNB[3][1]);
QFormLayout *flo = new QFormLayout(this);
flo->addRow("U-BLER lower", configItem1);
flo->addRow("U-BLER upper", configItem2);
flo->addRow("U-Throughput lower[Mbs]", configItem3);
flo->addRow("U-Throughput upper[Mbs]", configItem4);
flo->addRow("D-BLER lower", configItem5);
flo->addRow("D-BLER upper", configItem6);
flo->addRow("D-Throughput lower[Mbs]", configItem7);
flo->addRow("D-Throughput upper[Mbs]", configItem8);
}
/* @UE: create configuration window */
KPIConfigUE::KPIConfigUE(QWidget *parent) : QWidget(parent)
{
this->resize(300, 300);
this->setWindowTitle("UE Configs");
ConfigBoxFloat *configItem1 = new ConfigBoxFloat(&Limits_KPI_ue[0][0]);
ConfigBoxFloat *configItem2 = new ConfigBoxFloat(&Limits_KPI_ue[0][1]);
ConfigBoxFloat *configItem3 = new ConfigBoxFloat(&Limits_KPI_ue[1][0]);
ConfigBoxFloat *configItem4 = new ConfigBoxFloat(&Limits_KPI_ue[1][1]);
QFormLayout *flo = new QFormLayout(this);
flo->addRow("BLER lower", configItem1);
flo->addRow("BLER upper", configItem2);
flo->addRow("Throughput lower[Mbs]", configItem3);
flo->addRow("Throughput upper[Mbs]", configItem4);
}
/* @gNB: This class creates the drop-down list at the gNB side. Each item correspinds to an implemented KPI. */
KPIListSelectGnb::KPIListSelectGnb(QWidget *parent) : QComboBox(parent)
{
this->addItem("- empty -", static_cast<int>(PlotTypeGnb::empty));
this->addItem("RX Signal-Time", static_cast<int>(PlotTypeGnb::waterFall));
this->addItem("Channel Response", static_cast<int>(PlotTypeGnb::CIR));
this->addItem("LLR PUSCH", static_cast<int>(PlotTypeGnb::puschLLR));
this->addItem("I/Q PUSCH", static_cast<int>(PlotTypeGnb::puschIQ));
this->addItem("UL SNR", static_cast<int>(PlotTypeGnb::puschSNR));
this->addItem("UL BLER", static_cast<int>(PlotTypeGnb::puschBLER));
this->addItem("UL MCS", static_cast<int>(PlotTypeGnb::puschMCS));
this->addItem("UL Retrans.", static_cast<int>(PlotTypeGnb::puschRETX));
this->addItem("UL Throughput", static_cast<int>(PlotTypeGnb::puschThroughput));
this->addItem("DL SNR (CQI)", static_cast<int>(PlotTypeGnb::pdschSNR));
this->addItem("DL BLER", static_cast<int>(PlotTypeGnb::pdschBLER));
this->addItem("DL MCS", static_cast<int>(PlotTypeGnb::pdschMCS));
this->addItem("DL Retrans.", static_cast<int>(PlotTypeGnb::pdschRETX));
this->addItem("DL Throughput", static_cast<int>(PlotTypeGnb::pdschThroughput));
this->addItem("Nof Sched. RBs", static_cast<int>(PlotTypeGnb::pdschRBs));
this->addItem("Configs", static_cast<int>(PlotTypeGnb::config));
}
/* @UE: This class creates the drop-down list at the UE side. Each item correspinds to an implemented KPI. */
KPIListSelectUE::KPIListSelectUE(QWidget *parent) : QComboBox(parent)
{
this->addItem("- empty -", static_cast<int>(PlotTypeUE::empty));
this->addItem("RX Signal-Time", static_cast<int>(PlotTypeUE::waterFall));
this->addItem("Channel Response", static_cast<int>(PlotTypeUE::CIR));
this->addItem("LLR PBCH", static_cast<int>(PlotTypeUE::pbchLLR));
this->addItem("I/Q PBCH", static_cast<int>(PlotTypeUE::pbchIQ));
this->addItem("LLR PDCCH", static_cast<int>(PlotTypeUE::pdcchLLR));
this->addItem("I/Q PDCCH", static_cast<int>(PlotTypeUE::pdcchIQ));
this->addItem("LLR PDSCH", static_cast<int>(PlotTypeUE::pdschLLR));
this->addItem("I/Q PDSCH", static_cast<int>(PlotTypeUE::pdschIQ));
this->addItem("DL SNR", static_cast<int>(PlotTypeUE::pdschSNR));
this->addItem("DL BLER", static_cast<int>(PlotTypeUE::pdschBLER));
this->addItem("DL MCS", static_cast<int>(PlotTypeUE::pdschMCS));
this->addItem("Throughput", static_cast<int>(PlotTypeUE::pdschThroughput));
this->addItem("Nof Sched. RBs", static_cast<int>(PlotTypeUE::pdschRBs));
this->addItem("Freq. Offset", static_cast<int>(PlotTypeUE::frequencyOffset));
this->addItem("Time Adv.", static_cast<int>(PlotTypeUE::timingAdvance));
this->addItem("Configs", static_cast<int>(PlotTypeUE::config));
}
WaterFall::WaterFall(complex16 *values, NR_DL_FRAME_PARMS *frame_parms, QWidget *parent) : QWidget(parent), values(values), frame_parms(frame_parms)
{
this->iteration = 0;
this->image = nullptr;
this->waterFallAvg = nullptr;
startTimer(100);
}
/* this function to plot the waterfall graph for the RX signal in time domain for one frame. x-axis shows the frame divided into slots
and the y-axis is a color map depending on the SquaredNorm of the received signal at the correspoinding slot. */
void WaterFall::timerEvent(QTimerEvent *event)
{
if (!this->isVisible())
return;
const int datasize = frame_parms->samples_per_frame;
const int samplesPerPixel = datasize / this->width();
const int displayPart = this->height() - ScaleZone;
if (!this->image) {
this->image = new QImage(this->width(), this->height(), QImage::Format_RGB32);
this->image->fill(QColor(240, 240, 240));
this->iteration = 0;
this->waterFallAvg = (double *)realloc(this->waterFallAvg, this->height() * sizeof(double));
memset(this->waterFallAvg, 0, this->height() * sizeof(double));
// Plot vertical Lines
QRgb *pixels = (QRgb *)this->image->bits();
for (int slot = 1; slot < frame_parms->slots_per_frame; slot++) {
int lineX = frame_parms->get_samples_slot_timestamp(slot, frame_parms, 0) / samplesPerPixel;
for (int row = displayPart; row < this->height(); row++)
pixels[row * this->width() + lineX] = 0xFF000000; // black
}
this->update();
}
QRgb *pixels = (QRgb *)this->image->bits();
double avg = 0;
for (int i = 0; i < displayPart; i++)
avg += this->waterFallAvg[i];
avg /= displayPart;
const int row = this->iteration % displayPart;
this->waterFallAvg[row] = 0;
for (int pix = 0; pix < this->width(); pix++) {
complex16 *end = values + (pix + 1) * samplesPerPixel;
end -= 2;
double val = 0;
for (complex16 *s = values + pix * samplesPerPixel; s < end; s++)
val += SquaredNorm(*s);
val /= samplesPerPixel;
this->waterFallAvg[row] += val;
QRgb col;
if (val > avg * 100)
col = 0xFFFF0000; // red
else if (val > avg * 10)
col = 0xFFFFFF00; // yellow
else if (val > avg)
col = 0xFF00FF00; // green
else
col = 0xFF0000FF; // blue
pixels[row * this->width() + pix] = col;
}
this->waterFallAvg[row] /= this->width();
this->iteration++;
this->update(0, row, this->width(), 1);
}
void WaterFall::paintEvent(QPaintEvent *event)
{
if (!this->image)
return;
QPainter painter(this);
painter.drawImage(event->rect(), *this->image, event->rect()); // paint image on widget
}
void WaterFall::resizeEvent(QResizeEvent *event)
{
if (this->image) {
delete this->image;
this->image = nullptr;
}
}
CIRPlot::CIRPlot(complex16 *data, int len) : data(data), len(len)
{
this->legend()->hide();
// add new series to the chart
this->series = new QLineSeries();
this->series->setColor(Qt::blue);
this->addSeries(series);
// add new X axis
this->axisX = new QValueAxis();
this->axisX->setLabelFormat("%d");
this->axisX->setRange(-len / 2, len / 2);
this->addAxis(this->axisX, Qt::AlignBottom);
this->series->attachAxis(this->axisX);
// add new Y axis
this->axisY = new QValueAxis();
this->axisY->setLabelFormat("%.1e");
this->addAxis(this->axisY, Qt::AlignLeft);
this->series->attachAxis(this->axisY);
startTimer(1000);
}
void CIRPlot::timerEvent(QTimerEvent *event)
{
if (!this->isVisible())
return;
QVector<QPointF> points(this->len);
float maxY = this->axisY->max();
for (int i = 0; i < this->len / 2; i++) {
float value = SquaredNorm(this->data[i + this->len / 2]);
points[i] = QPointF(i - this->len / 2, value);
maxY = std::max(maxY, value);
}
for (int i = 0; i < this->len / 2; i++) {
float value = SquaredNorm(this->data[i]);
points[i + this->len / 2] = QPointF(i, value);
maxY = std::max(maxY, value);
}
this->axisY->setMax(maxY);
this->series->replace(points);
}
LLRPlot::LLRPlot(int16_t *data, int len) : data(data), len(len)
{
this->legend()->hide();
// add new series to the chart
this->series = new QScatterSeries();
this->series->setMarkerSize(3);
this->series->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
this->series->setColor(Qt::blue);
this->series->setPen(Qt::NoPen);
this->addSeries(series);
// add new X axis
this->axisX = new QValueAxis();
this->axisX->setLabelFormat("%d");
this->axisX->setRange(0, len);
this->addAxis(this->axisX, Qt::AlignBottom);
this->series->attachAxis(this->axisX);
// add new Y axis
this->axisY = new QValueAxis();
this->addAxis(this->axisY, Qt::AlignLeft);
this->series->attachAxis(this->axisY);
startTimer(1000);
}
void LLRPlot::timerEvent(QTimerEvent *event)
{
if (!this->isVisible())
return;
QVector<QPointF> points(this->len);
int maxY = this->axisY->max();
for (int i = 0; i < this->len; i++) {
points[i] = QPointF(i, this->data[i]);
maxY = std::max(maxY, abs(this->data[i]));
}
this->axisY->setRange(-maxY, maxY);
this->series->replace(points);
}
IQPlot::IQPlot(complex16 *data, int len) : data(data), len(len)
{
this->legend()->hide();
// add new series to the chart
this->series = new QScatterSeries();
this->series->setMarkerSize(3);
this->series->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
this->series->setColor(Qt::blue);
this->series->setPen(Qt::NoPen);
this->addSeries(series);
// add new X axis
this->axisX = new QValueAxis();
this->addAxis(this->axisX, Qt::AlignBottom);
this->series->attachAxis(this->axisX);
// add new Y axis
this->axisY = new QValueAxis();
this->addAxis(this->axisY, Qt::AlignLeft);
this->series->attachAxis(this->axisY);
startTimer(1000);
}
void IQPlot::timerEvent(QTimerEvent *event)
{
if (!this->isVisible())
return;
QVector<QPointF> points(this->len);
int maxX = this->axisX->max();
int maxY = this->axisY->max();
for (int i = 0; i < this->len; i++) {
points[i] = QPointF(this->data[i].r, this->data[i].i);
maxX = std::max(maxX, abs(this->data[i].r));
maxY = std::max(maxY, abs(this->data[i].i));
}
this->axisX->setRange(-maxX, maxX);
this->axisY->setRange(-maxY, maxY);
this->series->replace(points);
}
KPIPlot::KPIPlot(ValueProvider *valueProvider, float *limits) : valueProvider(valueProvider), limits(limits)
{
this->series = new QLineSeries();
this->series->setColor(Qt::blue);
this->addSeries(series);
this->seriesMin = new QLineSeries();
this->seriesMin->setColor(Qt::red);
this->addSeries(seriesMin);
this->seriesMax = new QLineSeries();
this->seriesMax->setColor(Qt::red);
this->addSeries(seriesMax);
this->seriesAvg = new QLineSeries();
this->seriesAvg->setColor(Qt::green);
this->seriesAvg->setName("Average");
this->addSeries(seriesAvg);
if (limits) {
this->seriesMin->setName("Upper Limit");
this->seriesMax->setName("Lower Limit");
this->minValue = limits[0];
this->maxValue = limits[1];
} else {
this->seriesMin->setName("Minimum");
this->seriesMax->setName("Maximum");
this->minValue = 0;
this->maxValue = 0;
}
this->sumValue = 0;
this->plotIdx = 0;
// add new X axis
this->axisX = new QValueAxis();
this->axisX->setLabelFormat("%d");
this->axisX->setRange(0, 300);
this->addAxis(this->axisX, Qt::AlignBottom);
this->series->attachAxis(this->axisX);
this->seriesMin->attachAxis(this->axisX);
this->seriesMax->attachAxis(this->axisX);
this->seriesAvg->attachAxis(this->axisX);
// add new Y axis
this->axisY = new QValueAxis();
this->addAxis(this->axisY, Qt::AlignLeft);
this->series->attachAxis(this->axisY);
this->seriesMin->attachAxis(this->axisY);
this->seriesMax->attachAxis(this->axisY);
this->seriesAvg->attachAxis(this->axisY);
startTimer(1000);
}
void KPIPlot::timerEvent(QTimerEvent *event)
{
if (!this->isVisible())
return;
if (this->plotIdx >= 300) {
this->series->clear();
this->sumValue = 0;
this->plotIdx = 0;
}
float value = this->valueProvider->getValue();
this->series->append(this->plotIdx++, value);
this->minValue = std::min(this->minValue, value);
this->maxValue = std::max(this->maxValue, value);
this->seriesMin->clear();
this->seriesMax->clear();
this->seriesAvg->clear();
if (this->limits) {
this->seriesMin->append(0, this->limits[0]);
this->seriesMin->append(300, this->limits[0]);
this->seriesMax->append(0, this->limits[1]);
this->seriesMax->append(300, this->limits[1]);
} else {
this->seriesMin->append(0, this->minValue);
this->seriesMin->append(300, this->minValue);
this->seriesMax->append(0, this->maxValue);
this->seriesMax->append(300, this->maxValue);
}
this->sumValue += value;
float average = this->sumValue / this->plotIdx;
this->seriesAvg->append(0, average);
this->seriesAvg->append(300, average);
this->axisY->setRange(this->minValue, this->maxValue);
}
RTXPlot::RTXPlot(uint64_t *rounds) : rounds(rounds)
{
for (int i = 0; i < 4; i++)
this->lastRounds[i] = rounds[i];
this->maxValue = 0;
this->plotIdx = 0;
this->series[0] = new QLineSeries();
this->series[0]->setColor(Qt::blue);
this->series[0]->setName("round 1");
this->addSeries(this->series[0]);
this->series[1] = new QLineSeries();
this->series[1]->setColor(Qt::green);
this->series[1]->setName("round 2");
this->addSeries(this->series[1]);
this->series[2] = new QLineSeries();
this->series[2]->setColor(Qt::yellow);
this->series[2]->setName("round 3");
this->addSeries(this->series[2]);
this->series[3] = new QLineSeries();
this->series[3]->setColor(Qt::red);
this->series[3]->setName("round 4");
this->addSeries(this->series[3]);
// add new X axis
this->axisX = new QValueAxis();
this->axisX->setLabelFormat("%d");
this->axisX->setRange(0, 300);
this->addAxis(this->axisX, Qt::AlignBottom);
this->series[0]->attachAxis(this->axisX);
this->series[1]->attachAxis(this->axisX);
this->series[2]->attachAxis(this->axisX);
this->series[3]->attachAxis(this->axisX);
// add new Y axis
this->axisY = new QValueAxis();
this->addAxis(this->axisY, Qt::AlignLeft);
this->series[0]->attachAxis(this->axisY);
this->series[1]->attachAxis(this->axisY);
this->series[2]->attachAxis(this->axisY);
this->series[3]->attachAxis(this->axisY);
startTimer(1000);
}
void RTXPlot::timerEvent(QTimerEvent *event)
{
if (!this->isVisible())
return;
if (this->plotIdx >= 300) {
this->series[0]->clear();
this->series[1]->clear();
this->series[2]->clear();
this->series[3]->clear();
this->plotIdx = 0;
}
for (int i = 0; i < 4; i++) {
int value = this->rounds[i] - this->lastRounds[i];
this->lastRounds[i] += value;
this->maxValue = std::max(this->maxValue, value);
this->series[i]->append(this->plotIdx, value);
}
this->plotIdx++;
this->axisY->setRange(0, this->maxValue);
}
/* @gNB: This is the main function of the gNB sub-widgets, i.e., for each KPI. This function will be called
only once when the the sub-widget is created, and it mainly initializes the widget variables and structures. */
PainterWidgetGnb::PainterWidgetGnb(QWidget *config, QComboBox *comboBox, scopeData_t *p) : config(config), comboBox(comboBox), p(p)
{
this->chartView = new QChartView(this);
this->chartView->hide();
this->plotType = PlotTypeGnb::empty;
makeConnections(this->comboBox->currentIndex());
connect(this->comboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &PainterWidgetGnb::makeConnections);
}
float PainterWidgetGnb::getValue()
{
NR_DL_FRAME_PARMS *frame_parms = &this->p->gNB->frame_parms;
gNB_MAC_INST *gNBMac = (gNB_MAC_INST *)RC.nrmac[0];
NR_UE_info_t *targetUE = gNBMac->UE_info.list[0];
NR_UE_sched_ctrl_t *sched_ctrl = &targetUE->UE_sched_ctrl;
NR_sched_pdsch_t *sched_pdsch = &sched_ctrl->sched_pdsch;
switch (this->plotType) {
case PlotTypeGnb::puschSNR:
return sched_ctrl->pusch_snrx10 / 10.0;
case PlotTypeGnb::puschBLER:
return sched_ctrl->ul_bler_stats.bler;
case PlotTypeGnb::puschMCS:
return sched_ctrl->ul_bler_stats.mcs;
case PlotTypeGnb::puschThroughput: {
double slotDuration = 10.0 / (double)frame_parms->slots_per_frame;
double blerTerm = 1.0 - (double)sched_ctrl->ul_bler_stats.bler;
double blockSizeBits = (double)(targetUE->mac_stats.ul.current_bytes << 3);
double ThrouputKBitSec = blerTerm * blockSizeBits / slotDuration;
return (float)(ThrouputKBitSec / 1000); // Throughput in MBit/sec
}
case PlotTypeGnb::pdschSNR:
return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb;
case PlotTypeGnb::pdschBLER:
return sched_ctrl->dl_bler_stats.bler;
case PlotTypeGnb::pdschMCS:
return sched_ctrl->dl_bler_stats.mcs;
case PlotTypeGnb::pdschThroughput: {
double slotDuration = 10.0 / (double)frame_parms->slots_per_frame;
double blerTerm = 1.0 - (double)sched_ctrl->dl_bler_stats.bler;
double blockSizeBits = (double)(targetUE->mac_stats.dl.current_bytes << 3);
double ThrouputKBitSec = blerTerm * blockSizeBits / slotDuration;
return (float)(ThrouputKBitSec / 1000); // Throughput in MBit/sec
}
case PlotTypeGnb::pdschRBs:
return sched_ctrl->harq_processes[sched_pdsch->dl_harq_pid].sched_pdsch.rbSize;
default:
return 0;
}
}
/* @gNB Class: this function is called when a resize event is detected, then the widget will be adjusted accordignly. */
void PainterWidgetGnb::resizeEvent(QResizeEvent *event)
{
if (this->waterFall)
this->waterFall->resize(event->size());
this->chartView->resize(event->size());
}
/* @gNB: this function is to check which KPI to plot in the current widget based on the drop-down list selection
Then the widget will be connected with the correspinding KPI function. */
void PainterWidgetGnb::makeConnections(int type)
{
const PlotTypeGnb plotType = static_cast<PlotTypeGnb>(type);
if (plotType == this->plotType)
return;
if (plotType == PlotTypeGnb::config) {
config->show();
this->comboBox->setCurrentIndex(static_cast<int>(this->plotType));
return;
}
this->plotType = plotType;
this->chartView->hide();
QChart *prevChart = this->chartView->chart();
if (plotType == PlotTypeGnb::waterFall) {
this->chartView->setChart(new QChart);
this->waterFall = new WaterFall((complex16 *)this->p->ru->common.rxdata[0], &this->p->gNB->frame_parms, this);
this->waterFall->resize(this->size());
this->waterFall->show();
delete prevChart;
return;
}
if (this->waterFall) {
this->waterFall->hide();
delete this->waterFall;
this->waterFall = nullptr;
}
NR_DL_FRAME_PARMS *frame_parms = &this->p->gNB->frame_parms;
gNB_MAC_INST *gNBMac = (gNB_MAC_INST *)RC.nrmac[0];
NR_UE_info_t *targetUE = gNBMac->UE_info.list[0];
QChart *newChart = nullptr;
switch (plotType) {
case PlotTypeGnb::empty: {
newChart = new QChart();
break;
}
case PlotTypeGnb::CIR: {
newChart = new CIRPlot((complex16 *)p->gNB->pusch_vars[0]->ul_ch_estimates_time[0], frame_parms->ofdm_symbol_size);
break;
}
case PlotTypeGnb::puschLLR: {
int num_re = frame_parms->N_RB_UL * 12 * frame_parms->symbols_per_slot;
int Qm = 2;
int coded_bits_per_codeword = num_re * Qm;
newChart = new LLRPlot((int16_t *)p->gNB->pusch_vars[0]->llr, coded_bits_per_codeword);
break;
}
case PlotTypeGnb::puschIQ: {
int num_re = frame_parms->N_RB_UL * 12 * frame_parms->symbols_per_slot;
newChart = new IQPlot((complex16 *)p->gNB->pusch_vars[0]->rxdataF_comp[0], num_re);
break;
}
case PlotTypeGnb::puschSNR: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeGnb::puschBLER: {
newChart = new KPIPlot(this, Limits_KPI_gNB[0]);
break;
}
case PlotTypeGnb::puschMCS: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeGnb::puschRETX: {
newChart = new RTXPlot(targetUE->mac_stats.ul.rounds);
break;
}
case PlotTypeGnb::puschThroughput: {
newChart = new KPIPlot(this, Limits_KPI_gNB[1]);
break;
}
case PlotTypeGnb::pdschSNR: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeGnb::pdschBLER: {
newChart = new KPIPlot(this, Limits_KPI_gNB[2]);
break;
}
case PlotTypeGnb::pdschMCS: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeGnb::pdschRETX: {
newChart = new RTXPlot(targetUE->mac_stats.dl.rounds);
break;
}
case PlotTypeGnb::pdschThroughput: {
newChart = new KPIPlot(this, Limits_KPI_gNB[3]);
break;
}
case PlotTypeGnb::pdschRBs: {
newChart = new KPIPlot(this);
break;
}
default:
break;
}
this->chartView->setChart(newChart);
this->chartView->show();
delete prevChart;
}
/* @UE: This is the main function of the UE sub-widgets, i.e., for each KPI. This function will be called
only once when the the sub-widget is created, and it mainly initializes the widget variables and structures. */
PainterWidgetUE::PainterWidgetUE(QWidget *config, QComboBox *comboBox, PHY_VARS_NR_UE *ue) : config(config), comboBox(comboBox), ue(ue)
{
this->chartView = new QChartView(this);
this->chartView->hide();
this->plotType = PlotTypeUE::empty;
makeConnections(this->comboBox->currentIndex());
connect(this->comboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &PainterWidgetUE::makeConnections);
}
float PainterWidgetUE::getValue()
{
NR_DL_FRAME_PARMS *frame_parms = &this->ue->frame_parms;
switch (this->plotType) {
case PlotTypeUE::pdschSNR:
return (float)this->ue->measurements.wideband_cqi_avg[0];
case PlotTypeUE::pdschBLER: {
if (getKPIUE()->nb_total == 0)
return 0;
return (float)getKPIUE()->nb_nack / (float)getKPIUE()->nb_total;
}
case PlotTypeUE::pdschMCS:
return (float)getKPIUE()->dl_mcs;
case PlotTypeUE::pdschThroughput: {
if (getKPIUE()->nb_total == 0)
return 0;
double slotDuration = 10.0 / (double)frame_parms->slots_per_frame;
double blerTerm = 1.0 - (double)(getKPIUE()->nb_nack) / (double)getKPIUE()->nb_total;
double blockSizeBits = (double)getKPIUE()->blockSize;
double ThrouputKBitSec = blerTerm * blockSizeBits / slotDuration;
return (float)(ThrouputKBitSec / 1000); // Throughput in MBit/sec
}
case PlotTypeUE::pdschRBs:
return (float)getKPIUE()->nofRBs;
case PlotTypeUE::frequencyOffset:
return (float)this->ue->common_vars.freq_offset;
case PlotTypeUE::timingAdvance:
return (float)this->ue->timing_advance;
default:
return 0;
}
}
/* @UE Class: this function is called when a resize event is detected, then the widget will be adjusted accordignly. */
void PainterWidgetUE::resizeEvent(QResizeEvent *event)
{
if (this->waterFall)
this->waterFall->resize(event->size());
this->chartView->resize(event->size());
}
/* @UE: this function is to check which KPI to plot in the current widget based on the drop-down list selection.
Then the widget will be connected with the correspinding KPI function. */
void PainterWidgetUE::makeConnections(int type)
{
const PlotTypeUE plotType = static_cast<PlotTypeUE>(type);
if (plotType == this->plotType)
return;
if (plotType == PlotTypeUE::config) {
config->show();
this->comboBox->setCurrentIndex(static_cast<int>(this->plotType));
return;
}
this->plotType = plotType;
this->chartView->hide();
QChart *prevChart = this->chartView->chart();
if (plotType == PlotTypeUE::waterFall) {
this->chartView->setChart(new QChart);
this->waterFall = new WaterFall((complex16 *)this->ue->common_vars.rxdata[0], &this->ue->frame_parms, this);
this->waterFall->resize(this->size());
this->waterFall->show();
delete prevChart;
return;
}
if (this->waterFall) {
this->waterFall->hide();
delete this->waterFall;
this->waterFall = nullptr;
}
scopeData_t *scope = (scopeData_t *)this->ue->scopeData;
scopeGraphData_t **data = (scopeGraphData_t **)scope->liveData;
QChart *newChart = nullptr;
switch (plotType) {
case PlotTypeUE::empty: {
newChart = new QChart();
break;
}
case PlotTypeUE::CIR: {
if (!data[pbchDlChEstimateTime]) {
newChart = new QChart();
this->plotType = PlotTypeUE::empty;
this->comboBox->setCurrentIndex(static_cast<int>(PlotTypeUE::empty));
break;
}
newChart = new CIRPlot((complex16 *)(data[pbchDlChEstimateTime] + 1), data[pbchDlChEstimateTime]->lineSz);
break;
}
case PlotTypeUE::pbchLLR: {
if (!data[pbchLlr]) {
newChart = new QChart();
this->plotType = PlotTypeUE::empty;
this->comboBox->setCurrentIndex(static_cast<int>(PlotTypeUE::empty));
break;
}
newChart = new LLRPlot((int16_t *)(data[pbchLlr] + 1), data[pbchLlr]->lineSz);
break;
}
case PlotTypeUE::pbchIQ: {
if (!data[pbchRxdataF_comp]) {
newChart = new QChart();
this->plotType = PlotTypeUE::empty;
this->comboBox->setCurrentIndex(static_cast<int>(PlotTypeUE::empty));
break;
}
newChart = new IQPlot((complex16 *)(data[pbchRxdataF_comp] + 1), data[pbchRxdataF_comp]->lineSz);
break;
}
case PlotTypeUE::pdcchLLR: {
if (!data[pdcchLlr]) {
newChart = new QChart();
this->plotType = PlotTypeUE::empty;
this->comboBox->setCurrentIndex(static_cast<int>(PlotTypeUE::empty));
break;
}
newChart = new LLRPlot((int16_t *)(data[pdcchLlr] + 1), data[pdcchLlr]->lineSz);
break;
}
case PlotTypeUE::pdcchIQ: {
if (!data[pdcchRxdataF_comp]) {
newChart = new QChart();
this->plotType = PlotTypeUE::empty;
this->comboBox->setCurrentIndex(static_cast<int>(PlotTypeUE::empty));
break;
}
newChart = new IQPlot((complex16 *)(data[pdcchRxdataF_comp] + 1), data[pdcchRxdataF_comp]->lineSz);
break;
}
case PlotTypeUE::pdschLLR: {
if (!data[pdschLlr]) {
newChart = new QChart();
this->plotType = PlotTypeUE::empty;
this->comboBox->setCurrentIndex(static_cast<int>(PlotTypeUE::empty));
break;
}
newChart = new LLRPlot((int16_t *)(data[pdschLlr] + 1), data[pdschLlr]->lineSz);
break;
}
case PlotTypeUE::pdschIQ: {
if (!data[pdschRxdataF_comp]) {
newChart = new QChart();
this->plotType = PlotTypeUE::empty;
this->comboBox->setCurrentIndex(static_cast<int>(PlotTypeUE::empty));
break;
}
newChart = new IQPlot((complex16 *)(data[pdschRxdataF_comp] + 1), data[pdschRxdataF_comp]->lineSz);
break;
}
case PlotTypeUE::pdschSNR: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeUE::pdschBLER: {
newChart = new KPIPlot(this, Limits_KPI_ue[0]);
break;
}
case PlotTypeUE::pdschMCS: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeUE::pdschThroughput: {
newChart = new KPIPlot(this, Limits_KPI_ue[1]);
break;
}
case PlotTypeUE::pdschRBs: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeUE::frequencyOffset: {
newChart = new KPIPlot(this);
break;
}
case PlotTypeUE::timingAdvance: {
newChart = new KPIPlot(this);
break;
}
default:
break;
}
this->chartView->setChart(newChart);
this->chartView->show();
delete prevChart;
}
// main thread of gNB
void *nrgNBQtscopeThread(void *arg)
{
scopeData_t *p = (scopeData_t *)arg;
sleep(3);
int argc = 1;
char *argv[] = {(char *)"nrqt_scopegNB"};
QApplication a(argc, argv);
// Create a main window (widget)
QWidget window;
window.resize(800, 800);
window.setWindowTitle("gNB Scope");
// Create gNB configuration window
KPIConfigGnb config;
// Main layout
QGridLayout mainLayout(&window);
KPIListSelectGnb combo1;
combo1.setCurrentIndex(static_cast<int>(PlotTypeGnb::waterFall));
PainterWidgetGnb pwidgetGnbCombo1(&config, &combo1, p);
mainLayout.addWidget(&combo1, 0, 0);
mainLayout.addWidget(&pwidgetGnbCombo1, 1, 0);
KPIListSelectGnb combo2;
combo2.setCurrentIndex(static_cast<int>(PlotTypeGnb::CIR));
PainterWidgetGnb pwidgetGnbCombo2(&config, &combo2, p);
mainLayout.addWidget(&combo2, 0, 1);
mainLayout.addWidget(&pwidgetGnbCombo2, 1, 1);
KPIListSelectGnb combo3;
combo3.setCurrentIndex(static_cast<int>(PlotTypeGnb::puschLLR));
PainterWidgetGnb pwidgetGnbCombo3(&config, &combo3, p);
mainLayout.addWidget(&combo3, 2, 0);
mainLayout.addWidget(&pwidgetGnbCombo3, 3, 0);
KPIListSelectGnb combo4;
combo4.setCurrentIndex(static_cast<int>(PlotTypeGnb::puschIQ));
PainterWidgetGnb pwidgetGnbCombo4(&config, &combo4, p);
mainLayout.addWidget(&combo4, 2, 1);
mainLayout.addWidget(&pwidgetGnbCombo4, 3, 1);
KPIListSelectGnb combo5;
combo5.setCurrentIndex(static_cast<int>(PlotTypeGnb::empty));
PainterWidgetGnb pwidgetGnbCombo5(&config, &combo5, p);
mainLayout.addWidget(&combo5, 4, 0);
mainLayout.addWidget(&pwidgetGnbCombo5, 5, 0);
KPIListSelectGnb combo6;
combo6.setCurrentIndex(static_cast<int>(PlotTypeGnb::empty));
PainterWidgetGnb pwidgetGnbCombo6(&config, &combo6, p);
mainLayout.addWidget(&combo6, 4, 1);
mainLayout.addWidget(&pwidgetGnbCombo6, 5, 1);
// display the main window
window.show();
a.exec();
return nullptr;
}
// main thread of UE
void *nrUEQtscopeThread(void *arg)
{
PHY_VARS_NR_UE *ue = (PHY_VARS_NR_UE *)arg;
sleep(1);
int argc = 1;
char *argv[] = {(char *)"nrqt_scopeUE"};
QApplication a(argc, argv);
// Create a main window (widget)
QWidget window;
window.resize(800, 800);
window.setWindowTitle("UE Scope");
// Create UE configuration window
KPIConfigUE config;
// Main layout
QGridLayout mainLayout(&window);
KPIListSelectUE combo1;
combo1.setCurrentIndex(static_cast<int>(PlotTypeUE::waterFall));
PainterWidgetUE pwidgetueCombo1(&config, &combo1, ue);
mainLayout.addWidget(&combo1, 0, 0);
mainLayout.addWidget(&pwidgetueCombo1, 1, 0);
KPIListSelectUE combo2;
combo2.setCurrentIndex(static_cast<int>(PlotTypeUE::CIR));
PainterWidgetUE pwidgetueCombo2(&config, &combo2, ue);
mainLayout.addWidget(&combo2, 0, 1);
mainLayout.addWidget(&pwidgetueCombo2, 1, 1);
KPIListSelectUE combo3;
combo3.setCurrentIndex(static_cast<int>(PlotTypeUE::pbchLLR));
PainterWidgetUE pwidgetueCombo3(&config, &combo3, ue);
mainLayout.addWidget(&combo3, 2, 0);
mainLayout.addWidget(&pwidgetueCombo3, 3, 0);
KPIListSelectUE combo4;
combo4.setCurrentIndex(static_cast<int>(PlotTypeUE::pbchIQ));
PainterWidgetUE pwidgetueCombo4(&config, &combo4, ue);
mainLayout.addWidget(&combo4, 2, 1);
mainLayout.addWidget(&pwidgetueCombo4, 3, 1);
KPIListSelectUE combo5;
combo5.setCurrentIndex(static_cast<int>(PlotTypeUE::pdschLLR));
PainterWidgetUE pwidgetueCombo5(&config, &combo5, ue);
mainLayout.addWidget(&combo5, 4, 0);
mainLayout.addWidget(&pwidgetueCombo5, 5, 0);
KPIListSelectUE combo6;
combo6.setCurrentIndex(static_cast<int>(PlotTypeUE::pdschIQ));
PainterWidgetUE pwidgetueCombo6(&config, &combo6, ue);
mainLayout.addWidget(&combo6, 4, 1);
mainLayout.addWidget(&pwidgetueCombo6, 5, 1);
// display the main window
window.show();
a.exec();
return nullptr;
}
// gNB scope initialization
void nrgNBinitQtScope(scopeParms_t *p)
{
scopeData_t *scope = (scopeData_t *)malloc(sizeof(scopeData_t));
scope->gNB = p->gNB;
scope->argc = p->argc;
scope->argv = p->argv;
scope->ru = p->ru;
p->gNB->scopeData = scope;
pthread_t qtscope_thread;
threadCreate(&qtscope_thread, nrgNBQtscopeThread, scope, (char *)"qtscope", -1, sched_get_priority_min(SCHED_RR));
}
// UE scope initialization
void nrUEinitQtScope(PHY_VARS_NR_UE *ue)
{
scopeData_t *scope = (scopeData_t *)malloc(sizeof(scopeData_t));
scope->liveData = calloc(sizeof(scopeGraphData_t *), UEdataTypeNumberOfItems);
scope->copyData = UEcopyData;
ue->scopeData = scope;
pthread_t qtscope_thread;
threadCreate(&qtscope_thread, nrUEQtscopeThread, ue, (char *)"qtscope", -1, sched_get_priority_min(SCHED_RR));
}
extern "C" void nrqtscope_autoinit(void *dataptr)
{
AssertFatal((IS_SOFTMODEM_GNB_BIT || IS_SOFTMODEM_5GUE_BIT), "Scope cannot find NRUE or GNB context");
if (IS_SOFTMODEM_GNB_BIT)
nrgNBinitQtScope((scopeParms_t *)dataptr);
else
nrUEinitQtScope((PHY_VARS_NR_UE *)dataptr);
}
/*
* 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
*
* Authors and copyright: Bo Zhao, Marwan Hammouda, Thomas Schlichter (Fraunhofer IIS)
*
* 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
*/
#ifndef QT_SCOPE_MAINWINDOW_H
#define QT_SCOPE_MAINWINDOW_H
#include <QtCharts>
extern "C" {
#include <simple_executable.h>
#include <common/utils/system.h>
#include "common/ran_context.h"
#include <openair1/PHY/defs_gNB.h>
#include "PHY/defs_gNB.h"
#include "PHY/defs_nr_UE.h"
#include "PHY/defs_RU.h"
#include "executables/softmodem-common.h"
#include "phy_scope_interface.h"
#include <openair2/LAYER2/NR_MAC_gNB/mac_proto.h>
#include "PHY/CODING/nrPolar_tools/nr_polar_defs.h"
extern RAN_CONTEXT_t RC;
}
/// This is an enum class for the different gNB plot types
enum class PlotTypeGnb {
empty,
waterFall,
CIR,
puschLLR,
puschIQ,
puschSNR,
puschBLER,
puschMCS,
puschRETX,
puschThroughput,
pdschSNR,
pdschBLER,
pdschMCS,
pdschRETX,
pdschThroughput,
pdschRBs,
config
};
/// This is an enum class fpr the different UE plot types
enum class PlotTypeUE {
empty,
waterFall,
CIR,
pbchLLR,
pbchIQ,
pdcchLLR,
pdcchIQ,
pdschLLR,
pdschIQ,
pdschSNR,
pdschBLER,
pdschMCS,
pdschThroughput,
pdschRBs,
frequencyOffset,
timingAdvance,
config
};
/// This abstract class defines an interface how the KPIPlot class can access values for the different KPI plot types
class ValueProvider {
public:
/// This pure virtual function is meant to provide the KPI value to be plotted
virtual float getValue() = 0;
};
/// An editable GUI field for a dialog box to set certain KPI configurations
class ConfigBoxFloat : public QLineEdit {
Q_OBJECT
public:
/// Constructor
/// \param valuePtr Pointer to a float value, which can be edited through this ConfigBoxFloat
/// \param parent Optional pointer to parent QWidget
ConfigBoxFloat(float *valuePtr, QWidget *parent = nullptr);
public slots:
/// This function converts the value in the editable GUI field to float and writes it back to valuePtr
void readText();
private:
/// Member variable keeping the pointer to the float value
float *valuePtr;
};
/// Dialog box for configuring gNB KPI Limits
class KPIConfigGnb : public QWidget {
Q_OBJECT
public:
/// Constructor
/// \param parent Optional pointer to parent QWidget
explicit KPIConfigGnb(QWidget *parent = nullptr);
};
/// Dialog box for configuring UE KPI Limits
class KPIConfigUE : public QWidget {
Q_OBJECT
public:
/// Constructor
/// \param parent Optional pointer to parent QWidget
explicit KPIConfigUE(QWidget *parent = nullptr);
};
/// drop-down list gNB
class KPIListSelectGnb : public QComboBox {
Q_OBJECT
public:
/// Constructor
/// \param parent Optional pointer to parent QWidget
explicit KPIListSelectGnb(QWidget *parent = nullptr);
};
/// drop-down list UE
class KPIListSelectUE : public QComboBox {
Q_OBJECT
public:
/// Constructor
/// \param parent Optional pointer to parent QWidget
explicit KPIListSelectUE(QWidget *parent = nullptr);
};
/// Waterfall plot of RX signal power
class WaterFall : public QWidget {
Q_OBJECT
public:
/// Constructor
/// \param values Pointer to the digital I/Q samples
/// \param frame_parms Pointer to the NR_DL_FRAME_PARMS
/// \param parent Optional pointer to parent QWidget
WaterFall(complex16 *values, NR_DL_FRAME_PARMS *frame_parms, QWidget *parent = nullptr);
protected:
/// This function is triggered when the own timer expires. It reads data from values and updates the image
/// \param event Pointer to the timer event
virtual void timerEvent(QTimerEvent *event) override;
/// This function is used to draw the image on the GUI
/// \param event Pointer to the paint event
virtual void paintEvent(QPaintEvent *event) override;
/// This function is called to change the WaterFall size
/// \param event Pointer to the resize event
virtual void resizeEvent(QResizeEvent *event) override;
private:
/// Pointer to the digital I/Q samples
complex16 *values;
/// Pointer to the NR_DL_FRAME_PARMS
NR_DL_FRAME_PARMS *frame_parms;
/// Pointer to the image
QImage *image;
/// Counter of the drawn lines
int iteration;
/// pointer to an array storing per-row average power values
double *waterFallAvg;
};
/// Chart class for plotting the Channel Impulse Response
class CIRPlot : public QChart {
Q_OBJECT
public:
/// Constructor
/// \param data Pointer to the CIR data
/// \param len Length of the CIR data
CIRPlot(complex16 *data, int len);
protected:
/// This function is triggered when the own timer expires. It updates the plotted CIR
/// \param event Pointer to the timer event
virtual void timerEvent(QTimerEvent *event) override;
private:
/// Pointer to the CIR data
complex16 *data;
/// Length of the CIR data
int len;
/// Line series used to plot the CIR in the chart
QLineSeries *series;
/// Horizontal axis of the chart
QValueAxis *axisX;
/// Vertical axis of the chart
QValueAxis *axisY;
};
/// Chart class for plotting LLRs
class LLRPlot : public QChart {
Q_OBJECT
public:
/// Constructor
/// \param data Pointer to the LLR data
/// \param len Length of the LLR data
LLRPlot(int16_t *data, int len);
protected:
/// This function is triggered when the own timer expires. It updates the plotted LLR
/// \param event Pointer to the timer event
virtual void timerEvent(QTimerEvent *event) override;
private:
/// Pointer to the LLR data
int16_t *data;
/// Length of the LLR data
int len;
/// Scatter series used to plot the LLR in the chart
QScatterSeries *series;
/// Horizontal axis of the chart
QValueAxis *axisX;
/// Vertical axis of the chart
QValueAxis *axisY;
};
/// Chart class for plotting the I/Q constellation diagram
class IQPlot : public QChart {
Q_OBJECT
public:
/// Constructor
/// \param data Pointer to the complex I/Q data
/// \param len Length of the I/Q data
IQPlot(complex16 *data, int len);
protected:
/// This function is triggered when the own timer expires. It updates the plotted I/Q constellation diagram
/// \param event Pointer to the timer event
virtual void timerEvent(QTimerEvent *event) override;
private:
/// Pointer to the I/Q data
complex16 *data;
/// Length of the I/Q data
int len;
/// Scatter series used to plot the I/Q constellation diagram
QScatterSeries *series;
/// Horizontal axis of the chart
QValueAxis *axisX;
/// Vertical axis of the chart
QValueAxis *axisY;
};
/// Generic class for plotting KPI values with min., max. and average bars
class KPIPlot : public QChart {
Q_OBJECT
public:
/// Constructor
/// \param valueProvider Pointer to an instance of a class that implements the ValueProvider interface
/// \param limits Optional parameter pointing to an array of two floating point values indicating lower and upper bounds
KPIPlot(ValueProvider *valueProvider, float *limits = nullptr);
protected:
/// This function is triggered when the own timer expires. It updates the plotted KPI chart
/// \param event Pointer to the timer event
virtual void timerEvent(QTimerEvent *event) override;
private:
/// Pointer to an instance of a class that implements the ValueProvider interface
ValueProvider *valueProvider;
/// Pointer to an array of two floating point values indicating lower and upper bounds
float *limits;
/// smallest observed KPI value
float minValue;
/// biggest observed KPI value
float maxValue;
/// Accumulated KPI valus, used to compute the average
float sumValue;
/// Index (horizontal position) of the last plotted KPI value
int plotIdx;
/// Line series used to plot the KPI values
QLineSeries *series;
/// Line series used to plot an indication of the smallest observed KPI value
QLineSeries *seriesMin;
/// Line series used to plot an indication of the biggest observed KPI value
QLineSeries *seriesMax;
/// Line series used to plot an indication of the average KPI value
QLineSeries *seriesAvg;
/// Horizontal axis of the chart
QValueAxis *axisX;
/// Vertical axis of the chart
QValueAxis *axisY;
};
/// Crart class for plotting HARQ retransmission counters
class RTXPlot : public QChart {
Q_OBJECT
public:
/// Constructor
/// \param rounds Pointer to the HARQ round counters
RTXPlot(uint64_t *rounds);
protected:
/// This function is triggered when the own timer expires. It updates the plotted HARQ retransmission counters
/// \param event Pointer to the timer event
virtual void timerEvent(QTimerEvent *event) override;
private:
/// Pointer to the HARQ round counters
uint64_t *rounds;
/// Last stored HARQ round counters
uint64_t lastRounds[4];
/// Maximum observed HARQ retransmissions
int maxValue;
/// Index (horizontal position) of the last plotted HARQ retransmission value
int plotIdx;
/// Line series used to plot the four HARQ retransmission counters
QLineSeries *series[4];
/// Horizontal axis of the chart
QValueAxis *axisX;
/// Vertical axis of the chart
QValueAxis *axisY;
};
/// Widget showing one selectable gNB KPI
class PainterWidgetGnb : public QWidget, public ValueProvider {
Q_OBJECT
public:
/// Constructor
/// \param config Pointer to the dialog box for configuring gNB KPI Limits
/// \param comboBox Pointer to the drop-down list selecting the KPI to be shown here
/// \param p Pointer to the gNB parameters
PainterWidgetGnb(QWidget *config, QComboBox *comboBox, scopeData_t *p);
/// This function provides the current KPI value to be plotted
virtual float getValue() override;
protected:
/// This function is called to change the widget size
/// \param event Pointer to the resize event
virtual void resizeEvent(QResizeEvent *event) override;
public slots:
/// This function is called when a different KPI is selected
/// \param type selected KPI type
void makeConnections(int type);
private:
/// Pointer to the dialog box for configuring gNB KPI Limits
QWidget *config;
/// Pointer to the drop-down list selecting the KPI to be shown here
QComboBox *comboBox;
/// Pointer to the gNB parameters
scopeData_t *p;
/// Pointer to a class to view all QChart based KPIs
QChartView *chartView;
/// Pointer to the waterfall diagram
WaterFall *waterFall;
/// Currently plotted KPI type
PlotTypeGnb plotType;
};
/// Widget showing one selectable UE KPI
class PainterWidgetUE : public QWidget, public ValueProvider {
Q_OBJECT
public:
/// Constructor
/// \param config Pointer to the dialog box for configuring UE KPI Limits
/// \param comboBox Pointer to the drop-down list selecting the KPI to be shown here
/// \param ue Pointer to the UE parameters
PainterWidgetUE(QWidget *config, QComboBox *comboBox, PHY_VARS_NR_UE *ue);
/// This function provides the current KPI value to be plotted
virtual float getValue() override;
protected:
/// This function is called to change the widget size
/// \param event Pointer to the resize event
virtual void resizeEvent(QResizeEvent *event) override;
public slots:
/// This function is called when a different KPI is selected
/// \param type selected KPI type
void makeConnections(int type);
private:
/// Pointer to the dialog box for configuring UE KPI Limits
QWidget *config;
/// Pointer to the drop-down list selecting the KPI to be shown here
QComboBox *comboBox;
/// Pointer to the UE parameters
PHY_VARS_NR_UE *ue;
/// Pointer to a class to view all QChart based KPIs
QChartView *chartView;
/// Pointer to the waterfall diagram
WaterFall *waterFall;
/// Currently plotted KPI type
PlotTypeUE plotType;
};
#endif // QT_SCOPE_MAINWINDOW_H
......@@ -1042,6 +1042,7 @@ STATICFORXSCOPE OAI_phy_scope_t *create_phy_scope_nrue(int ID)
// LLR of PDCCH
fdui->graph[5] = nrUEcommonGraph(uePcchLLR,
FL_POINTS_XYPLOT, 0, curY, 500, 100, "PDCCH Log-Likelihood Ratios (LLR, mag)", FL_CYAN );
fl_set_xyplot_xgrid(fdui->graph[5].graph,FL_GRID_MAJOR);
fdui->graph[5].chartid = SCOPEMSG_DATAID_LLR; // tells websrv frontend to use LLR chart for displaying
fdui->graph[5].datasetid = 1; // tells websrv frontend to use dataset index 1 in LLR chart
// I/Q PDCCH comp
......@@ -1054,6 +1055,7 @@ STATICFORXSCOPE OAI_phy_scope_t *create_phy_scope_nrue(int ID)
// LLR of PDSCH
fdui->graph[7] = nrUEcommonGraph(uePdschLLR,
FL_POINTS_XYPLOT, 0, curY, 500, 200, "PDSCH Log-Likelihood Ratios (LLR, mag)", FL_YELLOW );
fl_set_xyplot_xgrid(fdui->graph[7].graph,FL_GRID_MAJOR);
fdui->graph[7].chartid = SCOPEMSG_DATAID_LLR; // tells websrv frontend to use LLR chart for displaying
fdui->graph[7].datasetid = 2; // tells websrv frontend to use dataset index 2 in LLR chart
// I/Q PDSCH comp
......@@ -1139,49 +1141,6 @@ static void *nrUEscopeThread(void *arg) {
pthread_mutex_t UEcopyDataMutex;
void UEcopyData(PHY_VARS_NR_UE *ue, enum UEdataType type, void *dataIn, int elementSz, int colSz, int lineSz) {
// Local static copy of the scope data bufs
// The active data buf is alterned to avoid interference between the Scope thread (display) and the Rx thread (data input)
// Index of "2" could be set to the number of Rx threads + 1
static scopeGraphData_t *copyDataBufs[UEdataTypeNumberOfItems][3] = {0};
static int copyDataBufsIdx[UEdataTypeNumberOfItems] = {0};
scopeData_t *tmp=(scopeData_t *)ue->scopeData;
if (tmp) {
// Begin of critical zone between UE Rx threads that might copy new data at the same time:
pthread_mutex_lock(&UEcopyDataMutex);
int newCopyDataIdx = (copyDataBufsIdx[type]<2)?copyDataBufsIdx[type]+1:0;
copyDataBufsIdx[type] = newCopyDataIdx;
pthread_mutex_unlock(&UEcopyDataMutex);
// End of critical zone between UE Rx threads
// New data will be copied in a different buffer than the live one
scopeGraphData_t *copyData= copyDataBufs[type][newCopyDataIdx];
if (copyData == NULL || copyData->dataSize < elementSz*colSz*lineSz) {
scopeGraphData_t *ptr=realloc(copyData, sizeof(scopeGraphData_t) + elementSz*colSz*lineSz);
if (!ptr) {
LOG_E(PHY,"can't realloc\n");
return;
} else {
copyData=ptr;
}
}
copyData->dataSize=elementSz*colSz*lineSz;
copyData->elementSz=elementSz;
copyData->colSz=colSz;
copyData->lineSz=lineSz;
memcpy(copyData+1, dataIn, elementSz*colSz*lineSz);
copyDataBufs[type][newCopyDataIdx] = copyData;
// The new data just copied in the local static buffer becomes live now
((scopeGraphData_t **)tmp->liveData)[type]=copyData;
}
}
STATICFORXSCOPE void nrUEinitScope(PHY_VARS_NR_UE *ue)
{
AssertFatal(ue->scopeData=malloc(sizeof(scopeData_t)),"");
......
......@@ -40,13 +40,6 @@
typedef c16_t scopeSample_t;
#define SquaredNorm(VaR) ((VaR).r * (VaR).r + (VaR).i * (VaR).i)
typedef struct {
int dataSize;
int elementSz;
int colSz;
int lineSz;
} scopeGraphData_t;
typedef struct OAIgraph {
FL_OBJECT *graph;
FL_OBJECT *text;
......
......@@ -52,3 +52,44 @@ int end_forms(void) {
return -1;
}
void UEcopyData(PHY_VARS_NR_UE *ue, enum UEdataType type, void *dataIn, int elementSz, int colSz, int lineSz) {
// Local static copy of the scope data bufs
// The active data buf is alterned to avoid interference between the Scope thread (display) and the Rx thread (data input)
// Index of "2" could be set to the number of Rx threads + 1
static scopeGraphData_t *copyDataBufs[UEdataTypeNumberOfItems][2] = {0};
static int copyDataBufsIdx[UEdataTypeNumberOfItems] = {0};
scopeData_t *tmp = (scopeData_t *)ue->scopeData;
if (tmp) {
// Begin of critical zone between UE Rx threads that might copy new data at the same time: might require a mutex
int newCopyDataIdx = (copyDataBufsIdx[type]==0)?1:0;
copyDataBufsIdx[type] = newCopyDataIdx;
// End of critical zone between UE Rx threads
// New data will be copied in a different buffer than the live one
scopeGraphData_t *copyData = copyDataBufs[type][newCopyDataIdx];
if (copyData == NULL || copyData->dataSize < elementSz*colSz*lineSz) {
scopeGraphData_t *ptr = (scopeGraphData_t*) realloc(copyData, sizeof(scopeGraphData_t) + elementSz*colSz*lineSz);
if (!ptr) {
LOG_E(PHY,"can't realloc\n");
return;
} else {
copyData = ptr;
}
}
copyData->dataSize = elementSz*colSz*lineSz;
copyData->elementSz = elementSz;
copyData->colSz = colSz;
copyData->lineSz = lineSz;
memcpy(copyData+1, dataIn, elementSz*colSz*lineSz);
copyDataBufs[type][newCopyDataIdx] = copyData;
// The new data just copied in the local static buffer becomes live now
((scopeGraphData_t **)tmp->liveData)[type] = copyData;
}
}
......@@ -34,6 +34,14 @@
#include <openair1/PHY/defs_gNB.h>
#include <openair1/PHY/defs_nr_UE.h>
typedef struct {
uint32_t nb_total;
uint32_t nb_nack;
uint32_t blockSize; // block size, to be used for throughput calculation
uint16_t nofRBs;
uint8_t dl_mcs;
} extended_kpi_ue;
typedef struct {
int *argc;
char **argv;
......@@ -63,8 +71,19 @@ typedef struct scopeData_s {
void (*copyData)(PHY_VARS_NR_UE *,enum UEdataType, void *data, int elementSz, int colSz, int lineSz);
} scopeData_t;
typedef struct {
int dataSize;
int elementSz;
int colSz;
int lineSz;
} scopeGraphData_t;
int load_softscope(char *exectype, void *initarg);
int end_forms(void) ;
void UEcopyData(PHY_VARS_NR_UE *ue, enum UEdataType type, void *dataIn, int elementSz, int colSz, int lineSz);
#define UEscopeCopy(ue, type, ...) if(ue->scopeData) ((scopeData_t*)ue->scopeData)->copyData(ue, type, ##__VA_ARGS__);
extended_kpi_ue* getKPIUE();
#endif
To use the scope, run the xNB or the UE with option "-d"
## xForms-based Scope
To use the scope, run the xNB or the UE with option "-d"
Usage in gdb
In gdb, when you break, you can refresh immediatly the scope view by calling the display function.
The first paramter is the graph context, nevertheless we keep the last value for a dirty call in gdb (so you can use '0')
The first paramter is the graph context, nevertheless we keep the last value for a dirty call in gdb (so you can use '0')
Example with no variable known
phy_scope_nrUE(0, PHY_vars_UE_g[0][0], 0, 0, 0)
or
phy_scope_gNB(0, phy_vars_gnb, phy_vars_ru, UE_id)
# Qt-based Scope
## Building Instuctions
For the new qt-based scopo designed for NR, please consider the following:
1. run the gNB or the UE with the option '--dqt'.
2. make sure to install the Qt5 packages before running the scope. Otherwise, the scope will NOT be displayed!
3. if you need only to build the new scope, then add 'nrqtscope' after the '--lib-build' option. So, the complete
command would be
```
./build_oai --gNB -w USRP --nrUE --build-lib nrqtscope
```
## New Features
1. New KPIs for both gNB and UE, e.g., BLER, MCS, throughout, and number of scheduled RBs.
2. For each of the gNB and UE, a main widget is created with a 3x2 grid of sub-widgets, each to dispaly one KPI.
3. Each of the sub-widgets has a drop-down list to choose the KPI to show in that sub-widget.
4. Both of the gNB and UE scopes can be resized using the mouse movement.
......@@ -36,8 +36,6 @@
#include "defs_nr_common.h"
#include "CODING/nrPolar_tools/nr_polar_pbch_defs.h"
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
......
......@@ -139,7 +139,7 @@ typedef struct TDD_UL_DL_configCommon_s {
struct TDD_UL_DL_configCommon_s *p_next;
} TDD_UL_DL_configCommon_t;
typedef struct {
typedef struct TDD_UL_DL_SlotConfig_s {
/// \ Identifies a slot within a dl-UL-TransmissionPeriodicity (given in tdd-UL-DL-configurationCommon)
uint16_t slotIndex;
/// \ The direction (downlink or uplink) for the symbols in this slot. "allDownlink" indicates that all symbols in this slot are used
......@@ -154,7 +154,7 @@ typedef struct {
/// Corresponds to L1 parameter 'number-of-UL-symbols-dedicated' (see 38.211, section FFS_Section)
uint16_t nrofUplinkSymbols;
/// \ for setting a sequence
struct TDD_UL_DL_SlotConfig_t *p_next_TDD_UL_DL_SlotConfig;
struct TDD_UL_DL_SlotConfig_s *p_next_TDD_UL_DL_SlotConfig;
} TDD_UL_DL_SlotConfig_t;
/***********************************************************************
......
......@@ -27,7 +27,7 @@
#ifdef XFORMS
#include "PHY/TOOLS/nr_phy_scope.h"
extern char do_forms;
extern uint32_t do_forms;
#endif
extern char* namepointer_chMag ;
......
......@@ -187,7 +187,7 @@ void add_tdd_dedicated_configuration_nr(NR_DL_FRAME_PARMS *frame_parms, int slot
if (next == 0) {
frame_parms->p_TDD_UL_DL_ConfigDedicated = p_TDD_UL_DL_ConfigDedicated;
} else {
p_previous_TDD_UL_DL_ConfigDedicated->p_next_TDD_UL_DL_SlotConfig = (struct TDD_UL_DL_SlotConfig_t *)p_TDD_UL_DL_ConfigDedicated;
p_previous_TDD_UL_DL_ConfigDedicated->p_next_TDD_UL_DL_SlotConfig = (TDD_UL_DL_SlotConfig_t *)p_TDD_UL_DL_ConfigDedicated;
}
p_TDD_UL_DL_ConfigDedicated->slotIndex = slotIndex;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment