From 4b9e70b2ba644f83527ec84063d5bb8edae3bb4a Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 2 Jan 2025 12:54:57 +0100 Subject: [PATCH 1/7] Fix 2 issues causing warning when compiling headers from g++ --- openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h b/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h index 14d0e9fb2d..485f50f7b1 100644 --- a/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h +++ b/openair1/PHY/CODING/nrPolar_tools/nr_polar_defs.h @@ -292,7 +292,7 @@ static inline void nr_polar_deinterleaver(uint8_t *input, uint8_t *output, uint1 * De-interleaving of coded bits implementation * TS 138.212: Section 5.4.1.3 - Interleaving of coded bits */ -static inline void nr_polar_rm_deinterleaving_lut(uint16_t *out, const uint E) +static inline void nr_polar_rm_deinterleaving_lut(uint16_t *out, const int E) { int16_t in[E]; for (uint i = 0; i < E; i++) From ab75a7ccfbc6af409b5b62773e9613880246e127 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 2 Jan 2025 12:56:28 +0100 Subject: [PATCH 2/7] Add IQ file recording and IQ file viewer to ImScope This commit introduces the following changes to ImScope: - Added the ability to record IQ data using the same mechanism as ImScope. - Added a new executable target `imscope_iq_file_viewer` for viewing IQ files. - Introduced a command line argument `--imscope-record` that enables data recording mode. - Refactored a lot of the scope code Usage: - To record IQ data, run a modem with `--imscope-record` flag. The recording happens whenever a modem calls dumpScopeData, which currently is done only in gNB on PUSCH DTX and PUSCH CRC NOK. There is a limit of ~1GB of files written by the thread in a signle run to avoid accidentally exhausting disk space. If a directory `imscope-dump` is available the data will be recorded there in .imscope files. Otherwise files are written to the directory the modem was run. This is done so that the directory can be mapped in a docker container. - To view recorded IQ files, use the new executable `imscope_iq_file_viewer`. This can be done by running: ``` ./imscope_iq_file_viewer ``` Replace `` with the path to the IQ file you want to view. --- executables/nr-softmodem.c | 7 +- executables/nr-uesoftmodem.c | 5 + executables/softmodem-common.c | 5 + executables/softmodem-common.h | 5 + openair1/PHY/TOOLS/imscope/CMakeLists.txt | 10 +- openair1/PHY/TOOLS/imscope/imscope.cpp | 292 ++++-------------- openair1/PHY/TOOLS/imscope/imscope_common.cpp | 190 ++++++++++++ openair1/PHY/TOOLS/imscope/imscope_init.cpp | 52 ++++ openair1/PHY/TOOLS/imscope/imscope_internal.h | 160 ++++++++++ .../TOOLS/imscope/imscope_iq_file_viewer.cpp | 74 +++++ openair1/PHY/TOOLS/imscope/imscope_record.cpp | 131 ++++++++ openair1/PHY/TOOLS/phy_scope_interface.h | 19 ++ openair1/PHY/TOOLS/readme.md | 18 ++ openair1/SCHED_NR/phy_procedures_nr_gNB.c | 3 + 14 files changed, 729 insertions(+), 242 deletions(-) create mode 100644 openair1/PHY/TOOLS/imscope/imscope_common.cpp create mode 100644 openair1/PHY/TOOLS/imscope/imscope_init.cpp create mode 100644 openair1/PHY/TOOLS/imscope/imscope_internal.h create mode 100644 openair1/PHY/TOOLS/imscope/imscope_iq_file_viewer.cpp create mode 100644 openair1/PHY/TOOLS/imscope/imscope_record.cpp diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index e14e482b6d..3f7ef86b58 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -701,7 +701,7 @@ int main( int argc, char **argv ) { wait_RUs(); // once all RUs are ready initialize the rest of the gNBs ((dependence on final RU parameters after configuration) - if (IS_SOFTMODEM_DOSCOPE || IS_SOFTMODEM_IMSCOPE_ENABLED) { + if (IS_SOFTMODEM_DOSCOPE || IS_SOFTMODEM_IMSCOPE_ENABLED || IS_SOFTMODEM_IMSCOPE_RECORD_ENABLED) { sleep(1); scopeParms_t p; p.argc = &argc; @@ -714,6 +714,11 @@ int main( int argc, char **argv ) { if (IS_SOFTMODEM_IMSCOPE_ENABLED) { load_softscope("im", &p); } + AssertFatal(!(IS_SOFTMODEM_IMSCOPE_ENABLED && IS_SOFTMODEM_IMSCOPE_RECORD_ENABLED), + "Data recoding and ImScope cannot be enabled at the same time\n"); + if (IS_SOFTMODEM_IMSCOPE_RECORD_ENABLED) { + load_module_shlib("imscope_record", NULL, 0, &p); + } } if (NFAPI_MODE != NFAPI_MODE_PNF && NFAPI_MODE != NFAPI_MODE_VNF && NFAPI_MODE != NFAPI_MODE_AERIAL) { diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index ca92f30d4f..9dc24ff005 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -532,6 +532,11 @@ int main(int argc, char **argv) if (IS_SOFTMODEM_IMSCOPE_ENABLED) { load_softscope("im", PHY_vars_UE_g[0][0]); } + AssertFatal(!(IS_SOFTMODEM_IMSCOPE_ENABLED && IS_SOFTMODEM_IMSCOPE_RECORD_ENABLED), + "Data recoding and ImScope cannot be enabled at the same time\n"); + if (IS_SOFTMODEM_IMSCOPE_RECORD_ENABLED) { + load_module_shlib("imscope_record", NULL, 0, PHY_vars_UE_g[0][0]); + } for (int inst = 0; inst < NB_UE_INST; inst++) { LOG_I(PHY,"Intializing UE Threads for instance %d ...\n", inst); diff --git a/executables/softmodem-common.c b/executables/softmodem-common.c index 92c913c88e..09fced4aa3 100644 --- a/executables/softmodem-common.c +++ b/executables/softmodem-common.c @@ -77,6 +77,7 @@ void get_common_options(configmodule_interface_t *cfg) uint32_t noS1 = 0, nonbiot = 0; uint32_t rfsim = 0, do_forms = 0; uint32_t enable_imscope = 0; + uint32_t enable_imscope_record = 0; int nfapi_index = 0; char *logmem_filename = NULL; @@ -141,6 +142,10 @@ void get_common_options(configmodule_interface_t *cfg) IS_SOFTMODEM_IMSCOPE_ENABLED = true; } + if (enable_imscope_record) { + IS_SOFTMODEM_IMSCOPE_RECORD_ENABLED = true; + } + if (start_websrv) { load_module_shlib("websrv", NULL, 0, NULL); } diff --git a/executables/softmodem-common.h b/executables/softmodem-common.h index 2e7aaacc21..cdbe1d7e5a 100644 --- a/executables/softmodem-common.h +++ b/executables/softmodem-common.h @@ -80,6 +80,7 @@ extern "C" #define CONFIG_HLP_CHESTFREQ "Set channel estimation type in frequency domain. 0-Linear interpolation (default). 1-PRB based averaging of channel estimates in frequency. \n" #define CONFIG_HLP_CHESTTIME "Set channel estimation type in time domain. 0-Symbols take estimates of the last preceding DMRS symbol (default). 1-Symbol based averaging of channel estimates in time. \n" #define CONFIG_HLP_IMSCOPE "Enable phy scope based on imgui and implot" +#define CONFIG_HLP_IMSCOPE_RECORD "Enable recording scope data to filesystem" #define CONFIG_HLP_NONSTOP "Go back to frame sync mode after 100 consecutive PBCH failures\n" //#define CONFIG_HLP_NUMUES "Set the number of UEs for the emulation" @@ -186,6 +187,7 @@ extern int usrp_tx_thread; {"A" , CONFIG_HLP_TADV, 0, .iptr=&softmodem_params.command_line_sample_advance,.defintval=0, TYPE_INT, 0}, \ {"E" , CONFIG_HLP_TQFS, PARAMFLAG_BOOL, .iptr=&softmodem_params.threequarter_fs, .defintval=0, TYPE_INT, 0}, \ {"imscope" , CONFIG_HLP_IMSCOPE, PARAMFLAG_BOOL, .uptr=&enable_imscope, .defintval=0, TYPE_UINT, 0}, \ + {"imscope-record" , CONFIG_HLP_IMSCOPE_RECORD,PARAMFLAG_BOOL, .uptr=&enable_imscope_record, .defintval=0, TYPE_UINT, 0}, \ } // clang-format on @@ -231,6 +233,7 @@ extern int usrp_tx_thread; { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ + { .s5 = { NULL } }, \ } // clang-format on @@ -285,6 +288,7 @@ extern int usrp_tx_thread; #define IS_SOFTMODEM_5GUE (get_softmodem_optmask()->bit.SOFTMODEM_5GUE_BIT) #define IS_SOFTMODEM_NOSTATS (get_softmodem_optmask()->bit.SOFTMODEM_NOSTATS_BIT) #define IS_SOFTMODEM_IMSCOPE_ENABLED (get_softmodem_optmask()->bit.SOFTMODEM_IMSCOPE_BIT) +#define IS_SOFTMODEM_IMSCOPE_RECORD_ENABLED (get_softmodem_optmask()->bit.SOFTMODEM_IMSCOPE_RECORD_BIT) typedef struct optmask_s { union { struct { @@ -304,6 +308,7 @@ typedef struct optmask_s { uint64_t SOFTMODEM_5GUE_BIT: 1; uint64_t SOFTMODEM_NOSTATS_BIT: 1; uint64_t SOFTMODEM_IMSCOPE_BIT: 1; + uint64_t SOFTMODEM_IMSCOPE_RECORD_BIT : 1; } bit; uint64_t v; // allow to export entire bit set, force to 64 bit processor atomic size }; diff --git a/openair1/PHY/TOOLS/imscope/CMakeLists.txt b/openair1/PHY/TOOLS/imscope/CMakeLists.txt index 26eb855100..0b1ecf0835 100644 --- a/openair1/PHY/TOOLS/imscope/CMakeLists.txt +++ b/openair1/PHY/TOOLS/imscope/CMakeLists.txt @@ -29,7 +29,15 @@ add_library(implot target_link_libraries(implot PUBLIC imgui) target_include_directories(implot PUBLIC ${implot_SOURCE_DIR}) -add_library(imscope MODULE imscope.cpp ../phy_scope_interface.c) +add_library(imscope MODULE imscope.cpp ../phy_scope_interface.c imscope_common.cpp imscope_init.cpp) target_link_libraries(imscope PUBLIC imgui_glfw_backend glfw imgui_opengl_renderer OpenGL::OpenGL implot UTIL) set_target_properties(imscope PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) configure_file(imgui.ini ${CMAKE_BINARY_DIR}/imscope-init.ini COPYONLY) + +add_library(imscope_record MODULE ../phy_scope_interface.c imscope_common.cpp imscope_record.cpp) +target_link_libraries(imscope_record PRIVATE UTIL) +set_target_properties(imscope_record PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_executable(imscope_iq_file_viewer imscope.cpp imscope_iq_file_viewer.cpp ../phy_scope_interface.c imscope_common.cpp) +target_link_libraries(imscope_iq_file_viewer PRIVATE imgui_glfw_backend glfw imgui_opengl_renderer OpenGL::OpenGL implot UTIL minimal_lib shlib_loader) +set_target_properties(imscope_iq_file_viewer PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/openair1/PHY/TOOLS/imscope/imscope.cpp b/openair1/PHY/TOOLS/imscope/imscope.cpp index 9b025a8843..48010e5883 100644 --- a/openair1/PHY/TOOLS/imscope/imscope.cpp +++ b/openair1/PHY/TOOLS/imscope/imscope.cpp @@ -42,8 +42,11 @@ extern "C" { #include #include #include +#include +#include "imscope_internal.h" +#include +#include -#define MAX_OFFSETS 14 #define NR_MAX_RB 273 #define N_SC_PER_RB NR_NB_SC_PER_RB @@ -66,77 +69,9 @@ static void glfw_error_callback(int error, const char *description) fprintf(stderr, "GLFW Error %d: %s\n", error, description); } -typedef struct ImScopeData { - std::mutex write_mutex; - scopeGraphData_t *scope_graph_data; - bool is_data_ready; - metadata meta; - uint64_t time_taken_in_ns; - uint64_t time_taken_in_ns_per_offset[MAX_OFFSETS]; - size_t data_copied_per_offset[MAX_OFFSETS]; -} ImScopeData; - -typedef struct MovingAverageTimer { - uint64_t sum = 0; - float average = 0; - float last_update_time = 0; - void UpdateAverage(float time) - { - if (time > last_update_time + 1) { - float new_average = sum / (float)((time - last_update_time) / 1000); - average = 0.95 * average + 0.05 * new_average; - sum = 0; - } - } - void Add(uint64_t ns) - { - sum += ns; - } -} MovingAverageTimer; - MovingAverageTimer iq_procedure_timer; -static ImScopeData scope_array[EXTRA_SCOPE_TYPES]; - -typedef struct IQData { - std::vector real; - std::vector imag; - int16_t max_iq; - std::vector power; - float max_power; - float timestamp; - int nonzero_count; - int len = 0; - metadata meta; - - bool TryCollect(ImScopeData *imscope_data, float time, float epsilon) - { - if (imscope_data->is_data_ready) { - iq_procedure_timer.Add(imscope_data->time_taken_in_ns); - timestamp = time; - scopeGraphData_t *iq_header = imscope_data->scope_graph_data; - len = iq_header->lineSz; - real.reserve(len); - imag.reserve(len); - power.reserve(len); - c16_t *source = (c16_t *)(iq_header + 1); - max_iq = 0; - nonzero_count = 0; - for (auto i = 0; i < len; i++) { - real[i] = source[i].r; - imag[i] = source[i].i; - max_iq = std::max(max_iq, (int16_t)std::abs(source[i].r)); - max_iq = std::max(max_iq, (int16_t)std::abs(source[i].i)); - nonzero_count = std::abs(source[i].r) > epsilon || std::abs(source[i].i) > epsilon ? nonzero_count + 1 : nonzero_count; - power[i] = std::sqrt(std::pow(source[i].r, 2) + std::pow(source[i].i, 2)); - } - meta = imscope_data->meta; - imscope_data->is_data_ready = false; - return true; - } - return false; - } -} IQData; +ImScopeDataWrapper scope_array[EXTRA_SCOPE_TYPES]; class LLRPlot { int len = 0; @@ -163,19 +98,19 @@ class LLRPlot { ImGui::EndDisabled(); } - ImScopeData &scope_data = scope_array[type]; + ImScopeDataWrapper &scope_data = scope_array[type]; if (ImPlot::BeginPlot(label)) { if (!frozen || next) { if (scope_data.is_data_ready) { - iq_procedure_timer.Add(scope_data.time_taken_in_ns); + iq_procedure_timer.Add(scope_data.data.time_taken_in_ns); timestamp = time; - const int16_t *tmp = (int16_t *)(scope_data.scope_graph_data + 1); - len = scope_data.scope_graph_data->lineSz; + const int16_t *tmp = (int16_t *)(scope_data.data.scope_graph_data + 1); + len = scope_data.data.scope_graph_data->lineSz; llr.reserve(len); for (auto i = 0; i < len; i++) { llr[i] = tmp[i]; } - meta = scope_data.meta; + meta = scope_data.data.meta; scope_data.is_data_ready = false; if (frozen) { next = false; @@ -323,14 +258,14 @@ class IQSlotHeatmap { bool next = false; float timestamp = 0; std::vector power; - ImScopeData *scope_data; + ImScopeDataWrapper *scope_data; std::string label; int len = 0; float max = 0; float stop_at_min = 1000; public: - IQSlotHeatmap(ImScopeData *scope_data_, const char *label_) + IQSlotHeatmap(ImScopeDataWrapper *scope_data_, const char *label_) { scope_data = scope_data_; label = label_; @@ -341,7 +276,7 @@ class IQSlotHeatmap { auto num_sc = num_rb * NR_NB_SC_PER_RB; if (!frozen || next) { if (scope_data->is_data_ready) { - iq_procedure_timer.Add(scope_data->time_taken_in_ns); + iq_procedure_timer.Add(scope_data->data.time_taken_in_ns); uint16_t first_sc = first_carrier_offset; uint16_t last_sc = first_sc + num_rb * NR_NB_SC_PER_RB; bool wrapped = false; @@ -354,7 +289,7 @@ class IQSlotHeatmap { wrapped_last_sc = wrapped_first_sc + num_sc_left - 1; } timestamp = time; - scopeGraphData_t *iq_header = scope_data->scope_graph_data; + scopeGraphData_t *iq_header = scope_data->data.scope_graph_data; len = iq_header->lineSz; c16_t *source = (c16_t *)(iq_header + 1); @@ -458,8 +393,9 @@ struct ScrollingBuffer { } }; -void ShowUeScope(PHY_VARS_NR_UE *ue, float t) +void ShowUeScope(void *data_void_ptr, float t) { + PHY_VARS_NR_UE *ue = (PHY_VARS_NR_UE *)data_void_ptr; ImGui::Begin("UE KPI"); if (ImPlot::BeginPlot("##Scrolling", ImVec2(-1, 150))) { static float history = 10.0f; @@ -490,7 +426,7 @@ void ShowUeScope(PHY_VARS_NR_UE *ue, float t) static auto pdsch_iq_hist = new IQHist("PDSCH IQ"); bool new_data = false; if (pdsch_iq_hist->ShouldReadData()) { - new_data = iq_data->TryCollect(&scope_array[pdschRxdataF_comp], t, pdsch_iq_hist->GetEpsilon()); + new_data = iq_data->TryCollect(&scope_array[pdschRxdataF_comp], t, pdsch_iq_hist->GetEpsilon(), iq_procedure_timer); } pdsch_iq_hist->Draw(iq_data, t, new_data); } @@ -503,7 +439,7 @@ void ShowUeScope(PHY_VARS_NR_UE *ue, float t) static auto time_domain_iq = new IQHist("Time domain samples", disable_scatterplot); bool new_data = false; if (time_domain_iq->ShouldReadData()) { - new_data = iq_data->TryCollect(&scope_array[ueTimeDomainSamples], t, time_domain_iq->GetEpsilon()); + new_data = iq_data->TryCollect(&scope_array[ueTimeDomainSamples], t, time_domain_iq->GetEpsilon(), iq_procedure_timer); } time_domain_iq->Draw(iq_data, t, new_data); } @@ -516,7 +452,7 @@ void ShowUeScope(PHY_VARS_NR_UE *ue, float t) static auto time_domain_iq = new IQHist("Time domain samples - before sync", disable_scatterplot); bool new_data = false; if (time_domain_iq->ShouldReadData()) { - new_data = iq_data->TryCollect(&scope_array[ueTimeDomainSamplesBeforeSync], t, time_domain_iq->GetEpsilon()); + new_data = iq_data->TryCollect(&scope_array[ueTimeDomainSamplesBeforeSync], t, time_domain_iq->GetEpsilon(), iq_procedure_timer); } time_domain_iq->Draw(iq_data, t, new_data); } @@ -531,7 +467,7 @@ void ShowUeScope(PHY_VARS_NR_UE *ue, float t) if (broadcast_iq_hist->ShouldReadData()) { new_data = iq_data->TryCollect(&scope_array[ue->sl_mode ? psbchRxdataF_comp : pbchRxdataF_comp], t, - broadcast_iq_hist->GetEpsilon()); + broadcast_iq_hist->GetEpsilon(), iq_procedure_timer); } broadcast_iq_hist->Draw(iq_data, t, new_data); ImGui::TreePop(); @@ -543,7 +479,7 @@ void ShowUeScope(PHY_VARS_NR_UE *ue, float t) if (broadcast_iq_chest->ShouldReadData()) { new_data = chest_iq_data->TryCollect(&scope_array[ue->sl_mode ? psbchDlChEstimateTime : pbchDlChEstimateTime], t, - broadcast_iq_chest->GetEpsilon()); + broadcast_iq_chest->GetEpsilon(), iq_procedure_timer); } broadcast_iq_chest->Draw(chest_iq_data, t, new_data); ImGui::TreePop(); @@ -568,8 +504,9 @@ void ShowUeScope(PHY_VARS_NR_UE *ue, float t) // ImGui::End(); } -void ShowGnbScope(PHY_VARS_gNB *gNB, float t) +void ShowGnbScope(void *data_void_ptr, float t) { + (void)data_void_ptr; // if (ImGui::TreeNode("RX IQ")) { // static auto gnb_heatmap = new IQSlotHeatmap(&scope_array[gNBRxdataF], "common RX IQ"); @@ -585,7 +522,7 @@ void ShowGnbScope(PHY_VARS_gNB *gNB, float t) static auto pusch_iq_display = new IQHist("PUSCH compensated IQ"); bool new_data = false; if (pusch_iq_display->ShouldReadData()) { - new_data = pusch_iq->TryCollect(&scope_array[gNBPuschRxIq], t, pusch_iq_display->GetEpsilon()); + new_data = pusch_iq->TryCollect(&scope_array[gNBPuschRxIq], t, pusch_iq_display->GetEpsilon(), iq_procedure_timer); } pusch_iq_display->Draw(pusch_iq, t, new_data); } @@ -604,13 +541,33 @@ void ShowGnbScope(PHY_VARS_gNB *gNB, float t) static auto time_domain_iq = new IQHist("Time domain samples", disable_scatterplot); bool new_data = false; if (time_domain_iq->ShouldReadData()) { - new_data = iq_data->TryCollect(&scope_array[gNbTimeDomainSamples], t, time_domain_iq->GetEpsilon()); + new_data = iq_data->TryCollect(&scope_array[gNbTimeDomainSamples], t, time_domain_iq->GetEpsilon(), iq_procedure_timer); } time_domain_iq->Draw(iq_data, t, new_data); } ImGui::End(); } +void ShowIQFileViewer(void *data_void_ptr) +{ + auto iq_data = static_cast *>(data_void_ptr); + if (ImGui::Begin("Scope selection")) { + static int selected_scope = 0; + ImGui::Combo( + "Select scope", + &selected_scope, + [](void *userdata, int idx) { + std::vector *iq_data = static_cast*>(userdata); + return scope_id_to_string(static_cast((*iq_data)[idx].scope_id)); + }, + iq_data, + iq_data->size()); + static auto iq_display = new IQHist("IQ File Viewer"); + iq_display->Draw(&(*iq_data)[selected_scope], 0, false); + } + ImGui::End(); +} + void *imscope_thread(void *data_void_ptr) { glfwSetErrorCallback(glfw_error_callback); @@ -678,6 +635,7 @@ void *imscope_thread(void *data_void_ptr) static int target_fps = 24; bool is_ue = IS_SOFTMODEM_5GUE; + bool is_gnb = IS_SOFTMODEM_GNB; bool close_window = false; while (!glfwWindowShouldClose(window) && close_window == false) { // Poll and handle events (inputs, window resize, etc.) @@ -694,8 +652,7 @@ void *imscope_thread(void *data_void_ptr) ImGui_ImplGlfw_NewFrame(); static bool reset_ini_settings = false; - if (reset_ini_settings) - { + if (reset_ini_settings) { ImGui::LoadIniSettingsFromDisk("imscope-init.ini"); reset_ini_settings = false; } @@ -722,7 +679,7 @@ void *imscope_thread(void *data_void_ptr) } if (ImGui::BeginMenu("Layout")) { if (ImGui::MenuItem("Reset")) { - reset_ini_settings = true; + reset_ini_settings = true; } ImGui::EndMenu(); } @@ -750,12 +707,11 @@ void *imscope_thread(void *data_void_ptr) iq_procedure_timer.UpdateAverage(t); if (is_ue) { - PHY_VARS_NR_UE *ue = (PHY_VARS_NR_UE *)data_void_ptr; - ShowUeScope(ue, t); + ShowUeScope(data_void_ptr, t); + } else if (is_gnb) { + ShowGnbScope(data_void_ptr, t); } else { - scopeParms_t *scope_params = (scopeParms_t *)data_void_ptr; - PHY_VARS_gNB *gNB = scope_params->gNB; - ShowGnbScope(gNB, t); + ShowIQFileViewer(data_void_ptr); } // For reference @@ -791,147 +747,3 @@ void *imscope_thread(void *data_void_ptr) return nullptr; } - -extern "C" void imscope_autoinit(void *dataptr) -{ - AssertFatal(IS_SOFTMODEM_5GUE || IS_SOFTMODEM_GNB, "Scope cannot find NRUE or GNB context"); - - for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { - scope_array[i].is_data_ready = false; - scope_array[i].scope_graph_data = nullptr; - scope_array[i].meta = {-1, -1}; - } - - if (IS_SOFTMODEM_GNB) { - scopeParms_t *scope_params = (scopeParms_t *)dataptr; - scopeData_t *scope = (scopeData_t *)calloc(1, sizeof(scopeData_t)); - scope->copyData = copyDataThreadSafe; - scope->tryLockScopeData = tryLockScopeData; - scope->copyDataUnsafeWithOffset = copyDataUnsafeWithOffset; - scope->unlockScopeData = unlockScopeData; - scope_params->gNB->scopeData = scope; - scope_params->ru->scopeData = scope; - } else { - PHY_VARS_NR_UE *ue = (PHY_VARS_NR_UE *)dataptr; - scopeData_t *scope = (scopeData_t *)calloc(1, sizeof(scopeData_t)); - scope->copyData = copyDataThreadSafe; - ue->scopeData = scope; - } - pthread_t thread; - threadCreate(&thread, imscope_thread, dataptr, (char *)"imscope", -1, sched_get_priority_min(SCHED_RR)); -} - -void copyDataThreadSafe(void *scopeData, - enum scopeDataType type, - void *dataIn, - int elementSz, - int colSz, - int lineSz, - int offset, - metadata *meta) -{ - ImScopeData &scope_data = scope_array[type]; - - if (scope_data.is_data_ready) { - // data is ready, wasn't consumed yet by scope - return; - } - - if (scope_data.write_mutex.try_lock()) { - auto start = std::chrono::high_resolution_clock::now(); - scopeGraphData_t *data = scope_data.scope_graph_data; - int oldDataSz = data ? data->dataSize : 0; - int newSz = elementSz * colSz * lineSz; - if (data == NULL || oldDataSz < newSz) { - free(data); - scopeGraphData_t *ptr = (scopeGraphData_t *)malloc(sizeof(scopeGraphData_t) + newSz); - if (!ptr) { - LOG_E(PHY, "can't realloc\n"); - return; - } else { - data = ptr; - } - } - - data->elementSz = elementSz; - data->colSz = colSz; - data->lineSz = lineSz; - data->dataSize = newSz; - memcpy(((void *)(data + 1)), dataIn, newSz); - scope_data.scope_graph_data = data; - scope_data.meta = *meta; - scope_data.time_taken_in_ns = - std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); - scope_data.is_data_ready = true; - scope_data.write_mutex.unlock(); - } -} - -bool tryLockScopeData(enum scopeDataType type, int elementSz, int colSz, int lineSz, metadata *meta) -{ - ImScopeData &scope_data = scope_array[type]; - - if (scope_data.is_data_ready) { - // data is ready, wasn't consumed yet by scope - return false; - } - - if (scope_data.write_mutex.try_lock()) { - auto start = std::chrono::high_resolution_clock::now(); - scopeGraphData_t *data = scope_data.scope_graph_data; - int oldDataSz = data ? data->dataSize : 0; - int newSz = elementSz * colSz * lineSz; - if (data == NULL || oldDataSz < newSz) { - free(data); - scopeGraphData_t *ptr = (scopeGraphData_t *)malloc(sizeof(scopeGraphData_t) + newSz); - if (!ptr) { - LOG_E(PHY, "can't realloc\n"); - return false; - } else { - data = ptr; - } - } - - data->elementSz = elementSz; - data->colSz = colSz; - data->lineSz = lineSz; - data->dataSize = newSz; - scope_data.scope_graph_data = data; - scope_data.meta = *meta; - scope_data.time_taken_in_ns = - std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); - memset(scope_data.time_taken_in_ns_per_offset, 0, sizeof(scope_data.time_taken_in_ns_per_offset)); - memset(scope_data.data_copied_per_offset, 0, sizeof(scope_data.data_copied_per_offset)); - return true; - } - return false; -} - -void copyDataUnsafeWithOffset(enum scopeDataType type, void *dataIn, size_t size, size_t offset, int copy_index) -{ - AssertFatal(copy_index < MAX_OFFSETS, "Unexpected number of copies per sink. copy_index = %d\n", copy_index); - ImScopeData &scope_data = scope_array[type]; - auto start = std::chrono::high_resolution_clock::now(); - scopeGraphData_t *data = scope_data.scope_graph_data; - uint8_t *outptr = (uint8_t *)(data + 1); - memcpy(&outptr[offset], dataIn, size); - scope_data.time_taken_in_ns_per_offset[copy_index] = - std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); - scope_data.data_copied_per_offset[copy_index] = size; -} - -void unlockScopeData(enum scopeDataType type) -{ - ImScopeData &scope_data = scope_array[type]; - size_t total_size = 0; - for (auto i = 0; i < MAX_OFFSETS; i++) { - scope_data.time_taken_in_ns += scope_data.time_taken_in_ns_per_offset[i]; - total_size += scope_data.data_copied_per_offset[i]; - } - if (total_size != (uint64_t)scope_data.scope_graph_data->dataSize) { - LOG_E(PHY, - "Scope is missing data - not all data that was expected was copied - possibly missed copyDataUnsafeWithOffset call\n"); - } - scope_data.is_data_ready = true; - scope_data.write_mutex.unlock(); -} diff --git a/openair1/PHY/TOOLS/imscope/imscope_common.cpp b/openair1/PHY/TOOLS/imscope/imscope_common.cpp new file mode 100644 index 0000000000..9c86ce9925 --- /dev/null +++ b/openair1/PHY/TOOLS/imscope/imscope_common.cpp @@ -0,0 +1,190 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "imscope_internal.h" +#include + +void copyDataThreadSafe(void *scopeData, + enum scopeDataType type, + void *dataIn, + int elementSz, + int colSz, + int lineSz, + int offset, + metadata *meta) +{ + ImScopeDataWrapper &scope_data = scope_array[type]; + + if (scope_data.is_data_ready) { + // data is ready, wasn't consumed yet by scope + return; + } + + if (scope_data.write_mutex.try_lock()) { + auto start = std::chrono::high_resolution_clock::now(); + scopeGraphData_t *data = scope_data.data.scope_graph_data; + int oldDataSz = data ? data->dataSize : 0; + int newSz = elementSz * colSz * lineSz; + if (data == NULL || oldDataSz < newSz) { + free(data); + scopeGraphData_t *ptr = (scopeGraphData_t *)malloc(sizeof(scopeGraphData_t) + newSz); + if (!ptr) { + LOG_E(PHY, "can't realloc\n"); + return; + } else { + data = ptr; + } + } + + data->elementSz = elementSz; + data->colSz = colSz; + data->lineSz = lineSz; + data->dataSize = newSz; + memcpy(((void *)(data + 1)), dataIn, newSz); + scope_data.data.scope_graph_data = data; + scope_data.data.meta = *meta; + scope_data.data.time_taken_in_ns = + std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + scope_data.data.scope_id = type; + scope_data.is_data_ready = true; + scope_data.write_mutex.unlock(); + } +} + +bool tryLockScopeData(enum scopeDataType type, int elementSz, int colSz, int lineSz, metadata *meta) +{ + ImScopeDataWrapper &scope_data = scope_array[type]; + + if (scope_data.is_data_ready) { + // data is ready, wasn't consumed yet by scope + return false; + } + + if (scope_data.write_mutex.try_lock()) { + auto start = std::chrono::high_resolution_clock::now(); + scopeGraphData_t *data = scope_data.data.scope_graph_data; + int oldDataSz = data ? data->dataSize : 0; + int newSz = elementSz * colSz * lineSz; + if (data == NULL || oldDataSz < newSz) { + free(data); + scopeGraphData_t *ptr = (scopeGraphData_t *)malloc(sizeof(scopeGraphData_t) + newSz); + if (!ptr) { + LOG_E(PHY, "can't realloc\n"); + return false; + } else { + data = ptr; + } + } + + data->elementSz = elementSz; + data->colSz = colSz; + data->lineSz = lineSz; + data->dataSize = newSz; + scope_data.data.scope_graph_data = data; + scope_data.data.meta = *meta; + scope_data.data.time_taken_in_ns = + std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + memset(scope_data.data.time_taken_in_ns_per_offset, 0, sizeof(scope_data.data.time_taken_in_ns_per_offset)); + memset(scope_data.data.data_copied_per_offset, 0, sizeof(scope_data.data.data_copied_per_offset)); + return true; + } + return false; +} + +void copyDataUnsafeWithOffset(enum scopeDataType type, void *dataIn, size_t size, size_t offset, int copy_index) +{ + AssertFatal(copy_index < MAX_OFFSETS, "Unexpected number of copies per sink. copy_index = %d\n", copy_index); + ImScopeDataWrapper &scope_data = scope_array[type]; + auto start = std::chrono::high_resolution_clock::now(); + scopeGraphData_t *data = scope_data.data.scope_graph_data; + uint8_t *outptr = (uint8_t *)(data + 1); + memcpy(&outptr[offset], dataIn, size); + scope_data.data.time_taken_in_ns_per_offset[copy_index] = + std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + scope_data.data.data_copied_per_offset[copy_index] = size; +} + +void unlockScopeData(enum scopeDataType type) +{ + ImScopeDataWrapper &scope_data = scope_array[type]; + size_t total_size = 0; + for (auto i = 0; i < MAX_OFFSETS; i++) { + scope_data.data.time_taken_in_ns += scope_data.data.time_taken_in_ns_per_offset[i]; + total_size += scope_data.data.data_copied_per_offset[i]; + } + if (total_size != (uint64_t)scope_data.data.scope_graph_data->dataSize) { + LOG_E(PHY, + "Scope is missing data - not all data that was expected was copied - possibly missed copyDataUnsafeWithOffset call\n"); + } + scope_data.data.scope_id = type; + scope_data.is_data_ready = true; + scope_data.write_mutex.unlock(); +} + +const char *scope_id_to_string(scopeDataType type) { + switch (type) { + case pbchDlChEstimateTime: + return "pbchDlChEstimateTime"; + case pbchLlr: + return "pbchLlr"; + case pbchRxdataF_comp: + return "pbchRxdataF_comp"; + case pdcchLlr: + return "pdcchLlr"; + case pdcchRxdataF_comp: + return "pdcchRxdataF_comp"; + case pdschLlr: + return "pdschLlr"; + case pdschRxdataF_comp: + return "pdschRxdataF_comp"; + case commonRxdataF: + return "commonRxdataF"; + case gNBRxdataF: + return "gNBRxdataF"; + case psbchDlChEstimateTime: + return "psbchDlChEstimateTime"; + case psbchLlr: + return "psbchLlr"; + case psbchRxdataF_comp: + return "psbchRxdataF_comp"; + case gNBPuschRxIq: + return "gNBPuschRxIq"; + case gNBPuschLlr: + return "gNBPuschLlr"; + case ueTimeDomainSamples: + return "ueTimeDomainSamples"; + case ueTimeDomainSamplesBeforeSync: + return "ueTimeDomainSamplesBeforeSync"; + case gNbTimeDomainSamples: + return "gNbTimeDomainSamples"; + default: + return "unknown type"; + } +} + +std::ostream &operator<<(std::ostream &os, const ImScopeData &dt) +{ + os << "dataSize: " << dt.scope_graph_data->dataSize << " elementSz: " << dt.scope_graph_data->elementSz + << " colSz: " << dt.scope_graph_data->colSz << " lineSz: " << dt.scope_graph_data->lineSz; + os << " meta: " << dt.meta.frame << "." << dt.meta.slot + << " scope_id: " << scope_id_to_string(static_cast(dt.scope_id)); + return os; +} diff --git a/openair1/PHY/TOOLS/imscope/imscope_init.cpp b/openair1/PHY/TOOLS/imscope/imscope_init.cpp new file mode 100644 index 0000000000..2bb3d8dd33 --- /dev/null +++ b/openair1/PHY/TOOLS/imscope/imscope_init.cpp @@ -0,0 +1,52 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "imscope_internal.h" + +extern "C" void imscope_autoinit(void *dataptr) +{ + AssertFatal(IS_SOFTMODEM_5GUE || IS_SOFTMODEM_GNB, + "Scope cannot find NRUE or GNB context"); + + for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { + scope_array[i].is_data_ready = false; + scope_array[i].data.scope_graph_data = nullptr; + scope_array[i].data.meta = {-1, -1}; + } + + scopeData_t *scope = (scopeData_t *)calloc(1, sizeof(scopeData_t)); + scope->copyData = copyDataThreadSafe; + scope->tryLockScopeData = tryLockScopeData; + scope->copyDataUnsafeWithOffset = copyDataUnsafeWithOffset; + scope->unlockScopeData = unlockScopeData; + scope->copyData = copyDataThreadSafe; + if (IS_SOFTMODEM_GNB) { + scopeParms_t *scope_params = (scopeParms_t *)dataptr; + scope_params->gNB->scopeData = scope; + scope_params->ru->scopeData = scope; + } else { + PHY_VARS_NR_UE *ue = (PHY_VARS_NR_UE *)dataptr; + ue->scopeData = scope; + } + + pthread_t thread; + threadCreate(&thread, imscope_thread, dataptr, (char *)"imscope", -1, sched_get_priority_min(SCHED_RR)); +} diff --git a/openair1/PHY/TOOLS/imscope/imscope_internal.h b/openair1/PHY/TOOLS/imscope/imscope_internal.h new file mode 100644 index 0000000000..f0f738bf80 --- /dev/null +++ b/openair1/PHY/TOOLS/imscope/imscope_internal.h @@ -0,0 +1,160 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef IMSCOPE_INTERNAL_H +#define IMSCOPE_INTERNAL_H + +#include +#include +#include +#include +#include "openair1/PHY/defs_nr_UE.h" +extern "C" { +#include "../phy_scope_interface.h" +} + +#define MAX_OFFSETS 14 + +const char *scope_id_to_string(scopeDataType type); + +typedef struct ImScopeData { + scopeGraphData_t *scope_graph_data; + metadata meta; + uint64_t time_taken_in_ns; + uint64_t time_taken_in_ns_per_offset[MAX_OFFSETS]; + size_t data_copied_per_offset[MAX_OFFSETS]; + int scope_id; + + void dump_to_file(std::ofstream& stream) { + stream.write((char*)scope_graph_data, sizeof(*scope_graph_data)); + stream.write((char*)&meta, sizeof(meta)); + stream.write((char*)&scope_id, sizeof(scope_id)); + char *source = (char *)(scope_graph_data + 1); + stream.write(source, scope_graph_data->dataSize); + } + + void read_from_file(std::ifstream& stream) { + scopeGraphData_t temp; + stream.read((char*)&temp, sizeof(temp)); + stream.read((char*)&meta, sizeof(meta)); + stream.read((char*)&scope_id, sizeof(scope_id)); + scope_graph_data = static_cast(malloc(temp.dataSize + sizeof(*scope_graph_data))); + *scope_graph_data = temp; + char *dest = (char *)(scope_graph_data + 1); + stream.read(dest, temp.dataSize); + } + + friend std::ostream& operator<<(std::ostream& os, const ImScopeData& dt); +} ImScopeData; + +typedef struct MovingAverageTimer { + uint64_t sum = 0; + float average = 0; + float last_update_time = 0; + void UpdateAverage(float time) + { + if (time > last_update_time + 1) { + float new_average = sum / (float)((time - last_update_time) / 1000); + average = 0.95 * average + 0.05 * new_average; + sum = 0; + } + } + void Add(uint64_t ns) + { + sum += ns; + } +} MovingAverageTimer; + +typedef struct ImScopeDataWrapper { + std::mutex write_mutex; + bool is_data_ready; + ImScopeData data; +} ImScopeDataWrapper; + +typedef struct IQData { + std::vector real; + std::vector imag; + int16_t max_iq; + std::vector power; + float max_power; + float timestamp; + int nonzero_count; + int len = 0; + metadata meta; + int scope_id; + + bool TryCollect(ImScopeDataWrapper *imscope_data_wrapper, float time, float epsilon, MovingAverageTimer &iq_procedure_timer) + { + if (imscope_data_wrapper->is_data_ready) { + Collect(&imscope_data_wrapper->data, time, epsilon); + iq_procedure_timer.Add(imscope_data_wrapper->data.time_taken_in_ns); + imscope_data_wrapper->is_data_ready = false; + return true; + } + return false; + } + + void Collect(ImScopeData *scope_data, float time, float epsilon) { + timestamp = time; + scopeGraphData_t *iq_header = scope_data->scope_graph_data; + len = iq_header->lineSz; + real.resize(len); + imag.resize(len); + power.resize(len); + c16_t *source = (c16_t *)(iq_header + 1); + max_iq = 0; + nonzero_count = 0; + for (auto i = 0; i < len; i++) { + real[i] = source[i].r; + imag[i] = source[i].i; + max_iq = std::max(max_iq, (int16_t)std::abs(source[i].r)); + max_iq = std::max(max_iq, (int16_t)std::abs(source[i].i)); + nonzero_count = std::abs(source[i].r) > epsilon || std::abs(source[i].i) > epsilon ? nonzero_count + 1 : nonzero_count; + power[i] = std::sqrt(std::pow(source[i].r, 2) + std::pow(source[i].i, 2)); + } + meta = scope_data->meta; + scope_id = scope_data->scope_id; + } +} IQData; + +extern ImScopeDataWrapper scope_array[EXTRA_SCOPE_TYPES]; + +void copyDataThreadSafe(void *scopeData, + enum scopeDataType type, + void *dataIn, + int elementSz, + int colSz, + int lineSz, + int offset, + metadata *meta); + +bool tryLockScopeData(enum scopeDataType type, int elementSz, int colSz, int lineSz, metadata *meta); + +void copyDataUnsafeWithOffset(enum scopeDataType type, void *dataIn, size_t size, size_t offset, int copy_index); + +void unlockScopeData(enum scopeDataType type); + +void dumpScopeData(int slot, int frame); + +void *imscope_record_thread(void *data_void_ptr); +void *imscope_thread(void *data_void_ptr); + +#endif diff --git a/openair1/PHY/TOOLS/imscope/imscope_iq_file_viewer.cpp b/openair1/PHY/TOOLS/imscope/imscope_iq_file_viewer.cpp new file mode 100644 index 0000000000..5f9eaf46ab --- /dev/null +++ b/openair1/PHY/TOOLS/imscope/imscope_iq_file_viewer.cpp @@ -0,0 +1,74 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include +#include "imscope_internal.h" + +static extended_kpi_ue kpiStructure; + +extern "C" { +static softmodem_params_t softmodem_params; +softmodem_params_t *get_softmodem_params(void) +{ + return &softmodem_params; +} +extended_kpi_ue *getKPIUE(void) +{ + return &kpiStructure; +} +} + +void *imscope_thread(void *data_void_ptr); + + +std::vector LoadFromFile(char *filename) { + std::ifstream file(filename, std::ios::binary); + std::vector iq_data; + if (file.is_open()) { + while (file.peek() != EOF) { + ImScopeData source; + source.read_from_file(file); + std::cout << "Loaded from IQ from file: " << source << std::endl; + IQData iq; + iq.Collect(&source, 0, 0); + iq_data.push_back(iq); + } + } + return iq_data; +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + std::vector iq_data = LoadFromFile(argv[1]); + if (iq_data.size() == 0) { + printf("Error: No IQ data found in file\n"); + return 1; + } + + imscope_thread(static_cast(&iq_data)); +} diff --git a/openair1/PHY/TOOLS/imscope/imscope_record.cpp b/openair1/PHY/TOOLS/imscope/imscope_record.cpp new file mode 100644 index 0000000000..c67b4b0efe --- /dev/null +++ b/openair1/PHY/TOOLS/imscope/imscope_record.cpp @@ -0,0 +1,131 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "imscope_internal.h" +#include "openair1/PHY/TOOLS/phy_scope_interface.h" +#include + +#define NUM_CACHED_DATA 4 +// Set maximum number of kilobytes written per recording session to 1GB. This is a safeguard against +// recoding thread writing too many files and exhausting disk space. +#define MAX_KBYTES_WRITTEN_PER_SESSION (1000 * 1000) + +ImScopeDataWrapper scope_array[EXTRA_SCOPE_TYPES]; +volatile int slot_to_dump = -1; +volatile int frame_to_dump = -1; + +extern "C" void imscope_record_autoinit(void *dataptr) +{ + AssertFatal(IS_SOFTMODEM_5GUE || IS_SOFTMODEM_GNB, + "Scope cannot find NRUE or GNB context"); + LOG_A(PHY, "Imscope recording started\n"); + + for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { + scope_array[i].is_data_ready = false; + scope_array[i].data.scope_graph_data = nullptr; + scope_array[i].data.meta = {-1, -1}; + } + + scopeData_t *scope = (scopeData_t *)calloc(1, sizeof(scopeData_t)); + scope->copyData = copyDataThreadSafe; + scope->tryLockScopeData = tryLockScopeData; + scope->copyDataUnsafeWithOffset = copyDataUnsafeWithOffset; + scope->unlockScopeData = unlockScopeData; + scope->copyData = copyDataThreadSafe; + scope->dumpScopeData = dumpScopeData; + if (IS_SOFTMODEM_GNB) { + scopeParms_t *scope_params = (scopeParms_t *)dataptr; + scope_params->gNB->scopeData = scope; + scope_params->ru->scopeData = scope; + } else { + PHY_VARS_NR_UE *ue = (PHY_VARS_NR_UE *)dataptr; + ue->scopeData = scope; + } + + pthread_t thread; + threadCreate(&thread, imscope_record_thread, dataptr, (char *)"imscope_record", -1, sched_get_priority_min(SCHED_RR)); +} + +void dumpScopeData(int slot, int frame) { + if (slot_to_dump == -1) { + LOG_E(PHY, "Will dump scope data\n"); + frame_to_dump = frame; + slot_to_dump = slot; + } +} + +void *imscope_record_thread(void *data_void_ptr) +{ + const char *path = "imscope-dump"; + struct stat info; + bool has_dir = false; + if (stat(path, &info) == 0) { + if (info.st_mode & S_IFDIR) { + has_dir = true; + } + } + if (has_dir) { + LOG_A(PHY, "ImScope recorder thread starts, recording data to %s\n", path); + } else { + LOG_A(PHY, "ImScope recorder thread starts, recording data to cwd\n"); + } + ImScopeData scope_cache[EXTRA_SCOPE_TYPES][NUM_CACHED_DATA]; + memset(scope_cache, 0, sizeof(scope_cache)); + uint scope_cache_index[EXTRA_SCOPE_TYPES] = {0}; + float kbytes_written = 0.0f; + while (kbytes_written < MAX_KBYTES_WRITTEN_PER_SESSION) { + if (slot_to_dump != -1) { + std::string filename = std::string(path); + filename += has_dir ? "/" : "-"; + filename += std::to_string(frame_to_dump) + std::string("-") + std::to_string(slot_to_dump) + std::string(".imscope"); + std::ofstream outf(filename, std::ios_base::out | std::ios_base::binary); + if (!outf) { + LOG_E(PHY, "Could not open file %s for writing\n", filename.c_str()); + break; + } + for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { + for (auto j = 0U; j < NUM_CACHED_DATA; j++) { + ImScopeData &scope_data = scope_cache[i][j]; + if (scope_data.meta.frame == frame_to_dump && scope_data.meta.slot == slot_to_dump) { + scope_data.dump_to_file(outf); + } + } + } + long pos = outf.tellp(); + kbytes_written += pos / 1000.0f; + outf.close(); + LOG_D(PHY, "ImScope done writing to %s\n", filename.c_str()); + slot_to_dump = -1; + } + for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { + ImScopeDataWrapper &scope_data = scope_array[i]; + if (scope_data.is_data_ready) { + uint cache_index = scope_cache_index[i]++ % NUM_CACHED_DATA; + ImScopeData tmp = scope_cache[i][cache_index]; + scope_cache[i][cache_index] = scope_data.data; + scope_data.data = tmp; + scope_data.is_data_ready = false; + } + } + } + LOG_E(PHY, "ImScope recorder thread exits!\n"); + return nullptr; +} diff --git a/openair1/PHY/TOOLS/phy_scope_interface.h b/openair1/PHY/TOOLS/phy_scope_interface.h index 2fa97880b4..def4eba959 100644 --- a/openair1/PHY/TOOLS/phy_scope_interface.h +++ b/openair1/PHY/TOOLS/phy_scope_interface.h @@ -112,6 +112,7 @@ typedef struct scopeData_s { bool (*tryLockScopeData)(enum scopeDataType type, int elementSz, int colSz, int lineSz, metadata *meta); void (*copyDataUnsafeWithOffset)(enum scopeDataType type, void *dataIn, size_t size, size_t offset, int copy_index); void (*unlockScopeData)(enum scopeDataType type); + void (*dumpScopeData)(int slot, int frame); } scopeData_t; int load_softscope(char *exectype, void *initarg); @@ -157,6 +158,12 @@ void copyData(void *, enum scopeDataType type, void *dataIn, int elementSz, int scope_data->unlockScopeData(type); \ } +#define gNBdumpScopeData(gnb, slot, frame) \ + scopeData_t *scope_data = (scopeData_t *)gnb->scopeData; \ + if (scope_data && scope_data->dumpScopeData) { \ + scope_data->dumpScopeData(slot, frame); \ + } + #define UEScopeHasTryLock(ue) \ (ue->scopeData && ((scopeData_t *)ue->scopeData)->tryLockScopeData) @@ -176,6 +183,18 @@ void copyData(void *, enum scopeDataType type, void *dataIn, int elementSz, int scope_data->unlockScopeData(type); \ } +#define UEdumpScopeData(ue, slot, frame) \ + scopeData_t *scope_data = (scopeData_t *)ue->scopeData; \ + if (scope_data && scope_data->dumpScopeData) { \ + scope_data->dumpScopeData(slot, frame); \ + } \ + +#ifdef __cplusplus +extern "C" { +#endif extended_kpi_ue* getKPIUE(); +#ifdef __cplusplus +} +#endif #endif diff --git a/openair1/PHY/TOOLS/readme.md b/openair1/PHY/TOOLS/readme.md index 9d1f4d465b..bff976c529 100644 --- a/openair1/PHY/TOOLS/readme.md +++ b/openair1/PHY/TOOLS/readme.md @@ -69,3 +69,21 @@ Run with `--imscope` flag Report bugs and feature requests on [gitlab](https://gitlab.eurecom.fr/oai/openairinterface5g/-/issues). There is two demo windows enabled in the scope that showcase imgui/implot, if you find something of interest it can be implemented in the scope. + +## Recording scope data and reviewing recorded IQ files + +It is possible to record scope data from the modems. + - To record IQ data, run a modem with `--imscope-record` flag. + The recording happens whenever a modem calls `dumpScopeData`, which currently is + done only in gNB on PUSCH DTX and PUSCH CRC NOK. + There is a limit of ~1GB of files written by the thread in a single run to avoid + accidentally exhausting disk space. + If a directory `imscope-dump` is available the data will be recorded there in .imscope + files. Otherwise files are written to the directory the modem was run. This is done so that + the directory can be mapped in a docker container. + + - To view recorded IQ files, use the new executable `imscope_iq_file_viewer`. This can be done by running: + ``` + ./imscope_iq_file_viewer + ``` + Replace `` with the path to the IQ file you want to view. diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index 9e1ec84d9c..7d1bfca240 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -40,6 +40,7 @@ #include "assertions.h" #include #include +#include //#define DEBUG_RXDATA //#define SRS_IND_DEBUG @@ -438,6 +439,7 @@ static int nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, boo ulsch_harq->ulsch_pdu.rb_size, ulsch_harq->TBS); nr_fill_indication(gNB, ulsch->frame, ulsch->slot, ULSCH_id, ulsch->harq_pid, 1, 0, crc, pdu); + gNBdumpScopeData(gNB, ulsch->slot, ulsch->frame); ulsch->handled = 1; LOG_D(PHY, "ULSCH %d in error\n",ULSCH_id); ulsch->last_iteration_cnt = ulsch->max_ldpc_iterations + 1; // Setting to max_ldpc_iterations + 1 is sufficient given that this variable is only used for checking for failure @@ -868,6 +870,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, N nfapi_nr_rx_data_pdu_t *pdu = &UL_INFO->rx_ind.pdu_list[UL_INFO->rx_ind.number_of_pdus++]; nr_fill_indication(gNB, frame_rx, slot_rx, ULSCH_id, ulsch->harq_pid, 1, 1, crc, pdu); pusch_DTX++; + gNBdumpScopeData(gNB, ulsch->slot, ulsch->frame); continue; } } else { From c01adc5e005e2a637a49b135956b4026b1f1ed11 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 3 Jan 2025 12:45:30 +0100 Subject: [PATCH 3/7] Fix pdschRxdataF_comp scope for UE. Correctly calculate the amount of data to be copied. Copy at correct offset. Imporve error message in case data is insufficient. --- openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c | 6 +++--- openair1/PHY/TOOLS/imscope/imscope_common.cpp | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c index e5442aab48..97cacd77df 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c @@ -724,13 +724,13 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue, metadata mt = {.frame = proc->frame_rx, .slot = proc->nr_slot_rx }; int total_valid_res = 0; for (int i = startSymbIdx; i < startSymbIdx + nbSymb; i++) { - total_valid_res = dl_valid_re[i - 1]; + total_valid_res += dl_valid_re[i]; } if (UETryLockScopeData(ue, pdschRxdataF_comp, sizeof(c16_t), 1, total_valid_res, &mt)) { size_t offset = 0; for (int i = startSymbIdx; i < startSymbIdx + nbSymb; i++) { - size_t data_size = sizeof(c16_t) * dl_valid_re[i - i]; - UEscopeCopyUnsafe(ue, pdschRxdataF_comp, &rxdataF_comp[0][0][rx_size_symbol * i], data_size, offset, i - startSymbIdx); + size_t data_size = sizeof(c16_t) * dl_valid_re[i]; + UEscopeCopyUnsafe(ue, pdschRxdataF_comp, &rxdataF_comp[0][0][rx_size_symbol * i], data_size, offset, i); offset += data_size; } UEunlockScopeData(ue, pdschRxdataF_comp) diff --git a/openair1/PHY/TOOLS/imscope/imscope_common.cpp b/openair1/PHY/TOOLS/imscope/imscope_common.cpp index 9c86ce9925..b1bdda0905 100644 --- a/openair1/PHY/TOOLS/imscope/imscope_common.cpp +++ b/openair1/PHY/TOOLS/imscope/imscope_common.cpp @@ -132,7 +132,11 @@ void unlockScopeData(enum scopeDataType type) } if (total_size != (uint64_t)scope_data.data.scope_graph_data->dataSize) { LOG_E(PHY, - "Scope is missing data - not all data that was expected was copied - possibly missed copyDataUnsafeWithOffset call\n"); + "Scope is missing data - not all data that was expected was copied - possibly missed copyDataUnsafeWithOffset call. " + "scope = %s, total_size = %lu, dataSize = %d\n", + scope_id_to_string(type), + total_size, + scope_data.data.scope_graph_data->dataSize); } scope_data.data.scope_id = type; scope_data.is_data_ready = true; From 2198e311876daadcd60513296207e8f118f13436 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 3 Jan 2025 15:57:59 +0100 Subject: [PATCH 4/7] More updates to imscope IQ recorder - Added event parameter for recording which is reflected in the filename - Added event in the UE for DLSCH NACK - Added program name to the filename for ease of identification - Reworked spinlock to use atomics --- .../PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c | 1 + openair1/PHY/TOOLS/imscope/CMakeLists.txt | 2 +- openair1/PHY/TOOLS/imscope/imscope_internal.h | 2 +- openair1/PHY/TOOLS/imscope/imscope_record.cpp | 57 ++++++++++++++----- openair1/PHY/TOOLS/phy_scope_interface.h | 10 ++-- openair1/SCHED_NR/phy_procedures_nr_gNB.c | 4 +- 6 files changed, 53 insertions(+), 23 deletions(-) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c index 4fb88ec969..a3138f5d7f 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_decoding.c @@ -321,6 +321,7 @@ uint32_t nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue, LOG_D(PHY, "DLSCH received nok \n"); kpiStructure.nb_nack++; dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations + 1; + UEdumpScopeData(phy_vars_ue, proc->nr_slot_rx, proc->frame_rx, "DLSCH_NACK"); } uint8_t dmrs_Type = dlsch_config->dmrsConfigType; diff --git a/openair1/PHY/TOOLS/imscope/CMakeLists.txt b/openair1/PHY/TOOLS/imscope/CMakeLists.txt index 0b1ecf0835..060b288419 100644 --- a/openair1/PHY/TOOLS/imscope/CMakeLists.txt +++ b/openair1/PHY/TOOLS/imscope/CMakeLists.txt @@ -35,7 +35,7 @@ set_target_properties(imscope PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY configure_file(imgui.ini ${CMAKE_BINARY_DIR}/imscope-init.ini COPYONLY) add_library(imscope_record MODULE ../phy_scope_interface.c imscope_common.cpp imscope_record.cpp) -target_link_libraries(imscope_record PRIVATE UTIL) +target_link_libraries(imscope_record PRIVATE UTIL atomic) set_target_properties(imscope_record PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_executable(imscope_iq_file_viewer imscope.cpp imscope_iq_file_viewer.cpp ../phy_scope_interface.c imscope_common.cpp) diff --git a/openair1/PHY/TOOLS/imscope/imscope_internal.h b/openair1/PHY/TOOLS/imscope/imscope_internal.h index f0f738bf80..7b0b04758d 100644 --- a/openair1/PHY/TOOLS/imscope/imscope_internal.h +++ b/openair1/PHY/TOOLS/imscope/imscope_internal.h @@ -152,7 +152,7 @@ void copyDataUnsafeWithOffset(enum scopeDataType type, void *dataIn, size_t size void unlockScopeData(enum scopeDataType type); -void dumpScopeData(int slot, int frame); +void dumpScopeData(int slot, int frame, const char *cause_string); void *imscope_record_thread(void *data_void_ptr); void *imscope_thread(void *data_void_ptr); diff --git a/openair1/PHY/TOOLS/imscope/imscope_record.cpp b/openair1/PHY/TOOLS/imscope/imscope_record.cpp index c67b4b0efe..a59366d504 100644 --- a/openair1/PHY/TOOLS/imscope/imscope_record.cpp +++ b/openair1/PHY/TOOLS/imscope/imscope_record.cpp @@ -22,6 +22,18 @@ #include "imscope_internal.h" #include "openair1/PHY/TOOLS/phy_scope_interface.h" #include +#include +#include +#include + +#ifdef __cpp_lib_hardware_interference_size + using std::hardware_constructive_interference_size; + using std::hardware_destructive_interference_size; +#else + // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ... + constexpr std::size_t hardware_constructive_interference_size = 64; + constexpr std::size_t hardware_destructive_interference_size = 64; +#endif #define NUM_CACHED_DATA 4 // Set maximum number of kilobytes written per recording session to 1GB. This is a safeguard against @@ -29,8 +41,13 @@ #define MAX_KBYTES_WRITTEN_PER_SESSION (1000 * 1000) ImScopeDataWrapper scope_array[EXTRA_SCOPE_TYPES]; -volatile int slot_to_dump = -1; -volatile int frame_to_dump = -1; +typedef struct alignas(hardware_destructive_interference_size) ImScopeDumpInstruction { + int slot; + int frame; + const char *cause; +} ImScopeDumpInstruction; + +std::atomic dump_instruction; extern "C" void imscope_record_autoinit(void *dataptr) { @@ -44,6 +61,8 @@ extern "C" void imscope_record_autoinit(void *dataptr) scope_array[i].data.meta = {-1, -1}; } + dump_instruction.store({-1, -1, nullptr}); + scopeData_t *scope = (scopeData_t *)calloc(1, sizeof(scopeData_t)); scope->copyData = copyDataThreadSafe; scope->tryLockScopeData = tryLockScopeData; @@ -64,12 +83,11 @@ extern "C" void imscope_record_autoinit(void *dataptr) threadCreate(&thread, imscope_record_thread, dataptr, (char *)"imscope_record", -1, sched_get_priority_min(SCHED_RR)); } -void dumpScopeData(int slot, int frame) { - if (slot_to_dump == -1) { - LOG_E(PHY, "Will dump scope data\n"); - frame_to_dump = frame; - slot_to_dump = slot; - } +void dumpScopeData(int slot, int frame, const char *cause_string) +{ + ImScopeDumpInstruction to_dump = {slot, frame, cause_string}; + ImScopeDumpInstruction expected = {-1, -1, nullptr}; + dump_instruction.compare_exchange_weak(expected, to_dump); } void *imscope_record_thread(void *data_void_ptr) @@ -87,15 +105,26 @@ void *imscope_record_thread(void *data_void_ptr) } else { LOG_A(PHY, "ImScope recorder thread starts, recording data to cwd\n"); } + + std::string program_name(program_invocation_name); + size_t last_slash = program_name.find_last_of("/"); + if (last_slash != std::string::npos) { + program_name = program_name.substr(last_slash + 1); + } + ImScopeData scope_cache[EXTRA_SCOPE_TYPES][NUM_CACHED_DATA]; memset(scope_cache, 0, sizeof(scope_cache)); uint scope_cache_index[EXTRA_SCOPE_TYPES] = {0}; float kbytes_written = 0.0f; while (kbytes_written < MAX_KBYTES_WRITTEN_PER_SESSION) { - if (slot_to_dump != -1) { - std::string filename = std::string(path); - filename += has_dir ? "/" : "-"; - filename += std::to_string(frame_to_dump) + std::string("-") + std::to_string(slot_to_dump) + std::string(".imscope"); + ImScopeDumpInstruction dump = dump_instruction.load(std::memory_order::memory_order_relaxed); + if (dump.slot != -1) { + std::string filename; + if (has_dir) { + filename += std::string(path) + "/"; + } + filename += program_name + std::string("-") + dump.cause + std::string("-"); + filename += std::to_string(dump.frame) + std::string("-") + std::to_string(dump.slot) + std::string(".imscope"); std::ofstream outf(filename, std::ios_base::out | std::ios_base::binary); if (!outf) { LOG_E(PHY, "Could not open file %s for writing\n", filename.c_str()); @@ -104,7 +133,7 @@ void *imscope_record_thread(void *data_void_ptr) for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { for (auto j = 0U; j < NUM_CACHED_DATA; j++) { ImScopeData &scope_data = scope_cache[i][j]; - if (scope_data.meta.frame == frame_to_dump && scope_data.meta.slot == slot_to_dump) { + if (scope_data.meta.frame == dump.frame && scope_data.meta.slot == dump.slot) { scope_data.dump_to_file(outf); } } @@ -113,7 +142,7 @@ void *imscope_record_thread(void *data_void_ptr) kbytes_written += pos / 1000.0f; outf.close(); LOG_D(PHY, "ImScope done writing to %s\n", filename.c_str()); - slot_to_dump = -1; + dump_instruction.store({-1, -1, nullptr}, std::memory_order::memory_order_relaxed); } for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { ImScopeDataWrapper &scope_data = scope_array[i]; diff --git a/openair1/PHY/TOOLS/phy_scope_interface.h b/openair1/PHY/TOOLS/phy_scope_interface.h index def4eba959..1bdb52181a 100644 --- a/openair1/PHY/TOOLS/phy_scope_interface.h +++ b/openair1/PHY/TOOLS/phy_scope_interface.h @@ -112,7 +112,7 @@ typedef struct scopeData_s { bool (*tryLockScopeData)(enum scopeDataType type, int elementSz, int colSz, int lineSz, metadata *meta); void (*copyDataUnsafeWithOffset)(enum scopeDataType type, void *dataIn, size_t size, size_t offset, int copy_index); void (*unlockScopeData)(enum scopeDataType type); - void (*dumpScopeData)(int slot, int frame); + void (*dumpScopeData)(int slot, int frame, const char *cause_string); } scopeData_t; int load_softscope(char *exectype, void *initarg); @@ -158,10 +158,10 @@ void copyData(void *, enum scopeDataType type, void *dataIn, int elementSz, int scope_data->unlockScopeData(type); \ } -#define gNBdumpScopeData(gnb, slot, frame) \ +#define gNBdumpScopeData(gnb, slot, frame, cause_string) \ scopeData_t *scope_data = (scopeData_t *)gnb->scopeData; \ if (scope_data && scope_data->dumpScopeData) { \ - scope_data->dumpScopeData(slot, frame); \ + scope_data->dumpScopeData(slot, frame, cause_string); \ } #define UEScopeHasTryLock(ue) \ @@ -183,10 +183,10 @@ void copyData(void *, enum scopeDataType type, void *dataIn, int elementSz, int scope_data->unlockScopeData(type); \ } -#define UEdumpScopeData(ue, slot, frame) \ +#define UEdumpScopeData(ue, slot, frame, cause_string) \ scopeData_t *scope_data = (scopeData_t *)ue->scopeData; \ if (scope_data && scope_data->dumpScopeData) { \ - scope_data->dumpScopeData(slot, frame); \ + scope_data->dumpScopeData(slot, frame, cause_string); \ } \ #ifdef __cplusplus diff --git a/openair1/SCHED_NR/phy_procedures_nr_gNB.c b/openair1/SCHED_NR/phy_procedures_nr_gNB.c index 7d1bfca240..7020386c6e 100644 --- a/openair1/SCHED_NR/phy_procedures_nr_gNB.c +++ b/openair1/SCHED_NR/phy_procedures_nr_gNB.c @@ -439,7 +439,7 @@ static int nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, boo ulsch_harq->ulsch_pdu.rb_size, ulsch_harq->TBS); nr_fill_indication(gNB, ulsch->frame, ulsch->slot, ULSCH_id, ulsch->harq_pid, 1, 0, crc, pdu); - gNBdumpScopeData(gNB, ulsch->slot, ulsch->frame); + gNBdumpScopeData(gNB, ulsch->slot, ulsch->frame, "ULSCH_NACK"); ulsch->handled = 1; LOG_D(PHY, "ULSCH %d in error\n",ULSCH_id); ulsch->last_iteration_cnt = ulsch->max_ldpc_iterations + 1; // Setting to max_ldpc_iterations + 1 is sufficient given that this variable is only used for checking for failure @@ -870,7 +870,7 @@ int phy_procedures_gNB_uespec_RX(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, N nfapi_nr_rx_data_pdu_t *pdu = &UL_INFO->rx_ind.pdu_list[UL_INFO->rx_ind.number_of_pdus++]; nr_fill_indication(gNB, frame_rx, slot_rx, ULSCH_id, ulsch->harq_pid, 1, 1, crc, pdu); pusch_DTX++; - gNBdumpScopeData(gNB, ulsch->slot, ulsch->frame); + gNBdumpScopeData(gNB, ulsch->slot, ulsch->frame, "ULSCH_DTX"); continue; } } else { From c520a4e295c261e46e096e2542f0b82892a8bc3a Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 23 Jan 2025 12:56:53 +0100 Subject: [PATCH 5/7] Refactor some scope macros Use do {} while (0) for UEdumpScopeData/gNBdumpScopeData macro --- openair1/PHY/TOOLS/phy_scope_interface.h | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/openair1/PHY/TOOLS/phy_scope_interface.h b/openair1/PHY/TOOLS/phy_scope_interface.h index 1bdb52181a..59e7d93dd9 100644 --- a/openair1/PHY/TOOLS/phy_scope_interface.h +++ b/openair1/PHY/TOOLS/phy_scope_interface.h @@ -158,11 +158,13 @@ void copyData(void *, enum scopeDataType type, void *dataIn, int elementSz, int scope_data->unlockScopeData(type); \ } -#define gNBdumpScopeData(gnb, slot, frame, cause_string) \ - scopeData_t *scope_data = (scopeData_t *)gnb->scopeData; \ - if (scope_data && scope_data->dumpScopeData) { \ - scope_data->dumpScopeData(slot, frame, cause_string); \ - } +#define gNBdumpScopeData(gnb, slot, frame, cause_string) \ + do { \ + scopeData_t *scope_data = (scopeData_t *)gnb->scopeData; \ + if (scope_data && scope_data->dumpScopeData) { \ + scope_data->dumpScopeData((slot), (frame), (cause_string)); \ + } \ + } while (0) #define UEScopeHasTryLock(ue) \ (ue->scopeData && ((scopeData_t *)ue->scopeData)->tryLockScopeData) @@ -183,11 +185,13 @@ void copyData(void *, enum scopeDataType type, void *dataIn, int elementSz, int scope_data->unlockScopeData(type); \ } -#define UEdumpScopeData(ue, slot, frame, cause_string) \ - scopeData_t *scope_data = (scopeData_t *)ue->scopeData; \ - if (scope_data && scope_data->dumpScopeData) { \ - scope_data->dumpScopeData(slot, frame, cause_string); \ - } \ +#define UEdumpScopeData(ue, slot, frame, cause_string) \ + do { \ + scopeData_t *scope_data = (scopeData_t *)ue->scopeData; \ + if (scope_data && scope_data->dumpScopeData) { \ + scope_data->dumpScopeData((slot), (frame), (cause_string)); \ + } \ + } while (0) #ifdef __cplusplus extern "C" { From 30de011fa3a7ca02a97b4bbd41bb7560b5fa4981 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 23 Jan 2025 13:18:08 +0100 Subject: [PATCH 6/7] Add build_oai option for imscope_record --- CMakeLists.txt | 1 + cmake_targets/build_oai | 2 +- openair1/PHY/TOOLS/CMakeLists.txt | 2 +- openair1/PHY/TOOLS/imscope/CMakeLists.txt | 74 ++++++++++++----------- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e2457630a..252673daf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,6 +298,7 @@ message(STATUS "Selected KPM Version: ${KPM_VERSION}") add_boolean_option(ENABLE_IMSCOPE OFF "Enable phy scope based on imgui" OFF) +add_boolean_option(ENABLE_IMSCOPE_RECORD OFF "Enable recording IQ data for imscope" OFF) ################################################## # ASN.1 grammar C code generation & dependencies # diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai index d6d3720bca..27bb63ff89 100755 --- a/cmake_targets/build_oai +++ b/cmake_targets/build_oai @@ -46,7 +46,7 @@ BUILD_DOXYGEN=0 DISABLE_HARDWARE_DEPENDENCY="False" CMAKE_BUILD_TYPE="RelWithDebInfo" CMAKE_CMD="$CMAKE" -OPTIONAL_LIBRARIES="telnetsrv enbscope uescope nrscope ldpc_cuda ldpc_t2 ldpc_xdma websrv oai_iqplayer imscope" +OPTIONAL_LIBRARIES="telnetsrv enbscope uescope nrscope ldpc_cuda ldpc_t2 ldpc_xdma websrv oai_iqplayer imscope imscope_record" TARGET_LIST="" BUILD_TOOL_OPT="-j$(nproc)" diff --git a/openair1/PHY/TOOLS/CMakeLists.txt b/openair1/PHY/TOOLS/CMakeLists.txt index 58db255f0c..8a3d912caa 100644 --- a/openair1/PHY/TOOLS/CMakeLists.txt +++ b/openair1/PHY/TOOLS/CMakeLists.txt @@ -64,6 +64,6 @@ if (ENABLE_TESTS) add_subdirectory(tests) endif() -if (ENABLE_IMSCOPE) +if (ENABLE_IMSCOPE OR ENABLE_IMSCOPE_RECORD) add_subdirectory(imscope) endif() diff --git a/openair1/PHY/TOOLS/imscope/CMakeLists.txt b/openair1/PHY/TOOLS/imscope/CMakeLists.txt index 060b288419..9a40e9cdd7 100644 --- a/openair1/PHY/TOOLS/imscope/CMakeLists.txt +++ b/openair1/PHY/TOOLS/imscope/CMakeLists.txt @@ -1,43 +1,47 @@ +if (ENABLE_IMSCOPE) + CPMAddPackage("gh:ocornut/imgui#v1.91.3-docking") + add_library(imgui + ${imgui_SOURCE_DIR}/imgui_draw.cpp + ${imgui_SOURCE_DIR}/imgui.cpp + ${imgui_SOURCE_DIR}/imgui_widgets.cpp + ${imgui_SOURCE_DIR}/imgui_tables.cpp + ${imgui_SOURCE_DIR}/imgui_demo.cpp + ) + target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR}) -CPMAddPackage("gh:ocornut/imgui#v1.91.3-docking") -add_library(imgui - ${imgui_SOURCE_DIR}/imgui_draw.cpp - ${imgui_SOURCE_DIR}/imgui.cpp - ${imgui_SOURCE_DIR}/imgui_widgets.cpp - ${imgui_SOURCE_DIR}/imgui_tables.cpp - ${imgui_SOURCE_DIR}/imgui_demo.cpp -) -target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR}) + add_library(imgui_opengl_renderer ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp) + target_include_directories(imgui_opengl_renderer PUBLIC ${imgui_SOURCE_DIR}/backends/) + target_link_libraries(imgui_opengl_renderer PUBLIC imgui) -add_library(imgui_opengl_renderer ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp) -target_include_directories(imgui_opengl_renderer PUBLIC ${imgui_SOURCE_DIR}/backends/) -target_link_libraries(imgui_opengl_renderer PUBLIC imgui) + add_library(imgui_glfw_backend ${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp) + target_include_directories(imgui_glfw_backend PUBLIC ${imgui_SOURCE_DIR}/backends/) + target_link_libraries(imgui_glfw_backend PUBLIC imgui) -add_library(imgui_glfw_backend ${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp) -target_include_directories(imgui_glfw_backend PUBLIC ${imgui_SOURCE_DIR}/backends/) -target_link_libraries(imgui_glfw_backend PUBLIC imgui) + find_package(OpenGL REQUIRED) + find_package(glfw3 3.3 REQUIRED) -find_package(OpenGL REQUIRED) -find_package(glfw3 3.3 REQUIRED) + CPMAddPackage("gh:epezent/implot#v0.16") + add_library(implot + ${implot_SOURCE_DIR}/implot.cpp + ${implot_SOURCE_DIR}/implot_demo.cpp + ${implot_SOURCE_DIR}/implot_items.cpp + ) + target_link_libraries(implot PUBLIC imgui) + target_include_directories(implot PUBLIC ${implot_SOURCE_DIR}) -CPMAddPackage("gh:epezent/implot#v0.16") -add_library(implot - ${implot_SOURCE_DIR}/implot.cpp - ${implot_SOURCE_DIR}/implot_demo.cpp - ${implot_SOURCE_DIR}/implot_items.cpp -) -target_link_libraries(implot PUBLIC imgui) -target_include_directories(implot PUBLIC ${implot_SOURCE_DIR}) + add_library(imscope MODULE imscope.cpp ../phy_scope_interface.c imscope_common.cpp imscope_init.cpp) + target_link_libraries(imscope PUBLIC imgui_glfw_backend glfw imgui_opengl_renderer OpenGL::OpenGL implot UTIL) + set_target_properties(imscope PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + configure_file(imgui.ini ${CMAKE_BINARY_DIR}/imscope-init.ini COPYONLY) -add_library(imscope MODULE imscope.cpp ../phy_scope_interface.c imscope_common.cpp imscope_init.cpp) -target_link_libraries(imscope PUBLIC imgui_glfw_backend glfw imgui_opengl_renderer OpenGL::OpenGL implot UTIL) -set_target_properties(imscope PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -configure_file(imgui.ini ${CMAKE_BINARY_DIR}/imscope-init.ini COPYONLY) + add_executable(imscope_iq_file_viewer imscope.cpp imscope_iq_file_viewer.cpp ../phy_scope_interface.c imscope_common.cpp) + target_link_libraries(imscope_iq_file_viewer PRIVATE imgui_glfw_backend glfw imgui_opengl_renderer OpenGL::OpenGL implot UTIL minimal_lib shlib_loader) + set_target_properties(imscope_iq_file_viewer PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") +endif() -add_library(imscope_record MODULE ../phy_scope_interface.c imscope_common.cpp imscope_record.cpp) -target_link_libraries(imscope_record PRIVATE UTIL atomic) -set_target_properties(imscope_record PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +if (ENABLE_IMSCOPE_RECORD) + add_library(imscope_record MODULE ../phy_scope_interface.c imscope_common.cpp imscope_record.cpp) + target_link_libraries(imscope_record PRIVATE UTIL atomic) + set_target_properties(imscope_record PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +endif() -add_executable(imscope_iq_file_viewer imscope.cpp imscope_iq_file_viewer.cpp ../phy_scope_interface.c imscope_common.cpp) -target_link_libraries(imscope_iq_file_viewer PRIVATE imgui_glfw_backend glfw imgui_opengl_renderer OpenGL::OpenGL implot UTIL minimal_lib shlib_loader) -set_target_properties(imscope_iq_file_viewer PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") From 2ea6ac26bd1d02f3bb21977af9d8a19dd54c6c76 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 24 Jan 2025 09:14:44 +0100 Subject: [PATCH 7/7] Use a global "void_instruction" in imscope record thread This avoids the issue of compare_exchange_weak always failing. The issue was that `store()` is not equal to `exchange()`. `exchange` uses memory copy while `store()` copies the value representation of the argument, which is not always the same thing. --- openair1/PHY/TOOLS/imscope/imscope_record.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openair1/PHY/TOOLS/imscope/imscope_record.cpp b/openair1/PHY/TOOLS/imscope/imscope_record.cpp index a59366d504..6855e1b69d 100644 --- a/openair1/PHY/TOOLS/imscope/imscope_record.cpp +++ b/openair1/PHY/TOOLS/imscope/imscope_record.cpp @@ -48,6 +48,7 @@ typedef struct alignas(hardware_destructive_interference_size) ImScopeDumpInstru } ImScopeDumpInstruction; std::atomic dump_instruction; +ImScopeDumpInstruction void_instruction({-1, -1, nullptr}); extern "C" void imscope_record_autoinit(void *dataptr) { @@ -61,7 +62,7 @@ extern "C" void imscope_record_autoinit(void *dataptr) scope_array[i].data.meta = {-1, -1}; } - dump_instruction.store({-1, -1, nullptr}); + dump_instruction.exchange(void_instruction); scopeData_t *scope = (scopeData_t *)calloc(1, sizeof(scopeData_t)); scope->copyData = copyDataThreadSafe; @@ -86,8 +87,7 @@ extern "C" void imscope_record_autoinit(void *dataptr) void dumpScopeData(int slot, int frame, const char *cause_string) { ImScopeDumpInstruction to_dump = {slot, frame, cause_string}; - ImScopeDumpInstruction expected = {-1, -1, nullptr}; - dump_instruction.compare_exchange_weak(expected, to_dump); + dump_instruction.compare_exchange_weak(void_instruction, to_dump); } void *imscope_record_thread(void *data_void_ptr) @@ -142,7 +142,7 @@ void *imscope_record_thread(void *data_void_ptr) kbytes_written += pos / 1000.0f; outf.close(); LOG_D(PHY, "ImScope done writing to %s\n", filename.c_str()); - dump_instruction.store({-1, -1, nullptr}, std::memory_order::memory_order_relaxed); + dump_instruction.exchange(void_instruction); } for (auto i = 0U; i < EXTRA_SCOPE_TYPES; i++) { ImScopeDataWrapper &scope_data = scope_array[i];