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 {