diff --git a/CMakeLists.txt b/CMakeLists.txt index b54130b30a..def27eeed6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai index 578eaed570..31f2281e2d 100755 --- a/cmake_targets/build_oai +++ b/cmake_targets/build_oai @@ -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=() diff --git a/cmake_targets/tools/build_helper b/cmake_targets/tools/build_helper index d2dc1696bf..b8ff564ae8 100755 --- a/cmake_targets/tools/build_helper +++ b/cmake_targets/tools/build_helper @@ -551,6 +551,7 @@ check_install_additional_tools (){ PACKAGE_LIST="\ doxygen \ libpthread-stubs0-dev \ + libqt5charts5-dev \ tshark \ uml-utilities \ iperf3 \ diff --git a/doc/Doxyfile b/doc/Doxyfile index 1fb88bba74..ff038a9d8e 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -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 \ diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index 025cc34481..43eb725f78 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.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(); diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index 1630ef8ef0..d64d2cdb2e 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -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]); } diff --git a/executables/softmodem-common.c b/executables/softmodem-common.c index 7c0b9cd1cd..2b289b5352 100644 --- a/executables/softmodem-common.c +++ b/executables/softmodem-common.c @@ -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); } diff --git a/executables/softmodem-common.h b/executables/softmodem-common.h index 1263d9713b..5a8fed95b1 100644 --- a/executables/softmodem-common.h +++ b/executables/softmodem-common.h @@ -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:¶llel_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) diff --git a/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h b/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h index 79af311e45..4591e8e0a7 100644 --- a/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h +++ b/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h @@ -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, diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c index a5760d7fc0..db6a15213c 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c @@ -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; diff --git a/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp new file mode 100644 index 0000000000..04e42873ea --- /dev/null +++ b/openair1/PHY/TOOLS/nr_phy_qt_scope.cpp @@ -0,0 +1,1165 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "nr_phy_qt_scope.h" + +extern "C" { +#include "PHY/CODING/nrPolar_tools/nr_polar_defs.h" +#include +} + +#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(PlotTypeGnb::empty)); + this->addItem("RX Signal-Time", static_cast(PlotTypeGnb::waterFall)); + this->addItem("Channel Response", static_cast(PlotTypeGnb::CIR)); + + this->addItem("LLR PUSCH", static_cast(PlotTypeGnb::puschLLR)); + this->addItem("I/Q PUSCH", static_cast(PlotTypeGnb::puschIQ)); + this->addItem("UL SNR", static_cast(PlotTypeGnb::puschSNR)); + this->addItem("UL BLER", static_cast(PlotTypeGnb::puschBLER)); + this->addItem("UL MCS", static_cast(PlotTypeGnb::puschMCS)); + this->addItem("UL Retrans.", static_cast(PlotTypeGnb::puschRETX)); + this->addItem("UL Throughput", static_cast(PlotTypeGnb::puschThroughput)); + + this->addItem("DL SNR (CQI)", static_cast(PlotTypeGnb::pdschSNR)); + this->addItem("DL BLER", static_cast(PlotTypeGnb::pdschBLER)); + this->addItem("DL MCS", static_cast(PlotTypeGnb::pdschMCS)); + this->addItem("DL Retrans.", static_cast(PlotTypeGnb::pdschRETX)); + this->addItem("DL Throughput", static_cast(PlotTypeGnb::pdschThroughput)); + this->addItem("Nof Sched. RBs", static_cast(PlotTypeGnb::pdschRBs)); + + this->addItem("Configs", static_cast(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(PlotTypeUE::empty)); + this->addItem("RX Signal-Time", static_cast(PlotTypeUE::waterFall)); + this->addItem("Channel Response", static_cast(PlotTypeUE::CIR)); + + this->addItem("LLR PBCH", static_cast(PlotTypeUE::pbchLLR)); + this->addItem("I/Q PBCH", static_cast(PlotTypeUE::pbchIQ)); + this->addItem("LLR PDCCH", static_cast(PlotTypeUE::pdcchLLR)); + this->addItem("I/Q PDCCH", static_cast(PlotTypeUE::pdcchIQ)); + this->addItem("LLR PDSCH", static_cast(PlotTypeUE::pdschLLR)); + this->addItem("I/Q PDSCH", static_cast(PlotTypeUE::pdschIQ)); + + this->addItem("DL SNR", static_cast(PlotTypeUE::pdschSNR)); + this->addItem("DL BLER", static_cast(PlotTypeUE::pdschBLER)); + this->addItem("DL MCS", static_cast(PlotTypeUE::pdschMCS)); + this->addItem("Throughput", static_cast(PlotTypeUE::pdschThroughput)); + this->addItem("Nof Sched. RBs", static_cast(PlotTypeUE::pdschRBs)); + this->addItem("Freq. Offset", static_cast(PlotTypeUE::frequencyOffset)); + this->addItem("Time Adv.", static_cast(PlotTypeUE::timingAdvance)); + + this->addItem("Configs", static_cast(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 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 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 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(&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(type); + + if (plotType == this->plotType) + return; + + if (plotType == PlotTypeGnb::config) { + config->show(); + this->comboBox->setCurrentIndex(static_cast(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(&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(type); + + if (plotType == this->plotType) + return; + + if (plotType == PlotTypeUE::config) { + config->show(); + this->comboBox->setCurrentIndex(static_cast(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(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(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(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(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(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(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(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(PlotTypeGnb::waterFall)); + PainterWidgetGnb pwidgetGnbCombo1(&config, &combo1, p); + + mainLayout.addWidget(&combo1, 0, 0); + mainLayout.addWidget(&pwidgetGnbCombo1, 1, 0); + + KPIListSelectGnb combo2; + combo2.setCurrentIndex(static_cast(PlotTypeGnb::CIR)); + PainterWidgetGnb pwidgetGnbCombo2(&config, &combo2, p); + + mainLayout.addWidget(&combo2, 0, 1); + mainLayout.addWidget(&pwidgetGnbCombo2, 1, 1); + + KPIListSelectGnb combo3; + combo3.setCurrentIndex(static_cast(PlotTypeGnb::puschLLR)); + PainterWidgetGnb pwidgetGnbCombo3(&config, &combo3, p); + + mainLayout.addWidget(&combo3, 2, 0); + mainLayout.addWidget(&pwidgetGnbCombo3, 3, 0); + + KPIListSelectGnb combo4; + combo4.setCurrentIndex(static_cast(PlotTypeGnb::puschIQ)); + PainterWidgetGnb pwidgetGnbCombo4(&config, &combo4, p); + + mainLayout.addWidget(&combo4, 2, 1); + mainLayout.addWidget(&pwidgetGnbCombo4, 3, 1); + + KPIListSelectGnb combo5; + combo5.setCurrentIndex(static_cast(PlotTypeGnb::empty)); + PainterWidgetGnb pwidgetGnbCombo5(&config, &combo5, p); + + mainLayout.addWidget(&combo5, 4, 0); + mainLayout.addWidget(&pwidgetGnbCombo5, 5, 0); + + KPIListSelectGnb combo6; + combo6.setCurrentIndex(static_cast(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(PlotTypeUE::waterFall)); + PainterWidgetUE pwidgetueCombo1(&config, &combo1, ue); + + mainLayout.addWidget(&combo1, 0, 0); + mainLayout.addWidget(&pwidgetueCombo1, 1, 0); + + KPIListSelectUE combo2; + combo2.setCurrentIndex(static_cast(PlotTypeUE::CIR)); + PainterWidgetUE pwidgetueCombo2(&config, &combo2, ue); + + mainLayout.addWidget(&combo2, 0, 1); + mainLayout.addWidget(&pwidgetueCombo2, 1, 1); + + KPIListSelectUE combo3; + combo3.setCurrentIndex(static_cast(PlotTypeUE::pbchLLR)); + PainterWidgetUE pwidgetueCombo3(&config, &combo3, ue); + + mainLayout.addWidget(&combo3, 2, 0); + mainLayout.addWidget(&pwidgetueCombo3, 3, 0); + + KPIListSelectUE combo4; + combo4.setCurrentIndex(static_cast(PlotTypeUE::pbchIQ)); + PainterWidgetUE pwidgetueCombo4(&config, &combo4, ue); + + mainLayout.addWidget(&combo4, 2, 1); + mainLayout.addWidget(&pwidgetueCombo4, 3, 1); + + KPIListSelectUE combo5; + combo5.setCurrentIndex(static_cast(PlotTypeUE::pdschLLR)); + PainterWidgetUE pwidgetueCombo5(&config, &combo5, ue); + + mainLayout.addWidget(&combo5, 4, 0); + mainLayout.addWidget(&pwidgetueCombo5, 5, 0); + + KPIListSelectUE combo6; + combo6.setCurrentIndex(static_cast(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); +} diff --git a/openair1/PHY/TOOLS/nr_phy_qt_scope.h b/openair1/PHY/TOOLS/nr_phy_qt_scope.h new file mode 100644 index 0000000000..f10fef9d61 --- /dev/null +++ b/openair1/PHY/TOOLS/nr_phy_qt_scope.h @@ -0,0 +1,468 @@ +/* + * 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 + +extern "C" { +#include +#include +#include "common/ran_context.h" +#include +#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 +#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 diff --git a/openair1/PHY/TOOLS/nr_phy_scope.c b/openair1/PHY/TOOLS/nr_phy_scope.c index 7fd73a1b64..5a4562cb58 100644 --- a/openair1/PHY/TOOLS/nr_phy_scope.c +++ b/openair1/PHY/TOOLS/nr_phy_scope.c @@ -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)),""); diff --git a/openair1/PHY/TOOLS/phy_scope.h b/openair1/PHY/TOOLS/phy_scope.h index 06c85f3641..ac3eb62814 100644 --- a/openair1/PHY/TOOLS/phy_scope.h +++ b/openair1/PHY/TOOLS/phy_scope.h @@ -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; diff --git a/openair1/PHY/TOOLS/phy_scope_interface.c b/openair1/PHY/TOOLS/phy_scope_interface.c index 7cd68c53d2..b567e96b03 100644 --- a/openair1/PHY/TOOLS/phy_scope_interface.c +++ b/openair1/PHY/TOOLS/phy_scope_interface.c @@ -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; + } +} diff --git a/openair1/PHY/TOOLS/phy_scope_interface.h b/openair1/PHY/TOOLS/phy_scope_interface.h index 00efbdff24..b6d4480762 100644 --- a/openair1/PHY/TOOLS/phy_scope_interface.h +++ b/openair1/PHY/TOOLS/phy_scope_interface.h @@ -34,6 +34,14 @@ #include #include +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 diff --git a/openair1/PHY/TOOLS/readme.md b/openair1/PHY/TOOLS/readme.md index f6671acc6d..18fc178c30 100644 --- a/openair1/PHY/TOOLS/readme.md +++ b/openair1/PHY/TOOLS/readme.md @@ -1,12 +1,31 @@ - -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. diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index a75529b1d3..e8259b5dae 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -36,8 +36,6 @@ #include "defs_nr_common.h" #include "CODING/nrPolar_tools/nr_polar_pbch_defs.h" - -#define _GNU_SOURCE #include #include #include diff --git a/openair1/PHY/impl_defs_nr.h b/openair1/PHY/impl_defs_nr.h index 8d6437af74..afbe6830f5 100644 --- a/openair1/PHY/impl_defs_nr.h +++ b/openair1/PHY/impl_defs_nr.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; /*********************************************************************** diff --git a/openair1/PHY/phy_extern_nr_ue.h b/openair1/PHY/phy_extern_nr_ue.h index 66d554f245..4701a16012 100644 --- a/openair1/PHY/phy_extern_nr_ue.h +++ b/openair1/PHY/phy_extern_nr_ue.h @@ -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 ; diff --git a/openair1/SCHED_NR/phy_frame_config_nr.c b/openair1/SCHED_NR/phy_frame_config_nr.c index 9ed7255a84..49afffcdc6 100644 --- a/openair1/SCHED_NR/phy_frame_config_nr.c +++ b/openair1/SCHED_NR/phy_frame_config_nr.c @@ -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;