Commit ab75a7cc authored by Bartosz Podrygajlo's avatar Bartosz Podrygajlo

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 <path_to_iq_file>
      ```
   Replace `<path_to_iq_file>` with the path to the IQ file you want to view.
parent 4b9e70b2
......@@ -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) {
......
......@@ -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);
......
......@@ -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);
}
......
......@@ -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
};
......
......@@ -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}")
This diff is collapsed.
/*
* 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 <chrono>
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::nanoseconds>(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::nanoseconds>(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::nanoseconds>(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<scopeDataType>(dt.scope_id));
return os;
}
/*
* 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));
}
/*
* 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 <mutex>
#include <fstream>
#include <vector>
#include <fstream>
#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<scopeGraphData_t *>(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<int16_t> real;
std::vector<int16_t> imag;
int16_t max_iq;
std::vector<float> 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
/*
* 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 <stdint.h>
#include <vector>
#include <iostream>
#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<IQData> LoadFromFile(char *filename) {
std::ifstream file(filename, std::ios::binary);
std::vector<IQData> 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 <filename>\n", argv[0]);
return 1;
}
std::vector<IQData> 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<void *>(&iq_data));
}
/*
* 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 <sys/stat.h>
#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;
}
......@@ -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
......@@ -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 <path_to_iq_file>
```
Replace `<path_to_iq_file>` with the path to the IQ file you want to view.
......@@ -40,6 +40,7 @@
#include "assertions.h"
#include <time.h>
#include <stdint.h>
#include <openair1/PHY/TOOLS/phy_scope_interface.h>
//#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 {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment