Compare commits

...

23 Commits

Author SHA1 Message Date
abgaber
3810e0a8e9 Add comment to show the root cause of using py init file 2025-06-20 16:06:24 +02:00
abgaber
d737ccf7fe Enhance style + update based on comments from OAI Team 2025-06-20 15:45:08 +02:00
abgaber
f9d3e0bf6b Ehance to match OAI Style + functions optimization 2025-06-20 15:42:33 +02:00
abgaber
e3058d91bd Enable memories of Data Recording only if T-Tracer is enabled 2025-06-19 11:21:32 +02:00
abgaber
8b90d7594f Docu formatting 2025-06-19 09:32:26 +02:00
abgaber
5845393359 Enable data collection only if T-Tracer is selected and related event is enabled 2025-06-18 18:30:32 +02:00
abgaber
3a853735bd Update data recording config image to include control saving data in SigMF format 2025-05-30 16:16:59 +02:00
abgaber
daf8a45c40 Enhance Data Recording App structure
Support the new state machine of T-Tracers
Add new files for Config Interface, sync Service, def of data recording messages, and common utilties
Make part of logging on Terminal configurable
2025-05-30 16:10:43 +02:00
abgaber
b0d57a1670 Add the option of enable-Disable saving Data in SigMF format 2025-05-30 16:05:18 +02:00
abgaber
52bba03584 Enhace state machines of gNB+UE T-Tracers
Support the operation of : COnfig, Record, and Stop
Poll from Socket and not use get_events to mitigate long-time waiting if there is no data in buffer or the UE is already released or crashed
Add extra offset to improve data sync
2025-05-30 16:04:03 +02:00
abgaber
d4c4f08de9 Log gNB RX Payload for both CRC valid and invalid cases 2025-05-30 15:33:22 +02:00
Abdo-Gaber
e9e2cac954 Update limitation list in Readme of Data recording App 2025-03-10 14:34:38 +01:00
Abdo-Gaber
269e5ca3d5 Update picture of supported messages 2025-03-10 14:10:50 +01:00
Abdo-Gaber
b3ffc2d926 Increase the size of some images in Readme 2025-03-10 14:00:29 +01:00
Abdo-Gaber
42104a9158 Add Readme for the Data Recording Application
It provides and overview about the Architecture, Requirement Packages, Configuration Files, How to run Data Recording Application, and an Overview on Collected Data Set.
2025-03-10 13:08:22 +01:00
Abdo-Gaber
3f2d8a6815 Add Data Recording Application v1.0 (main app, SigMF Interface, Config files, Sync validation)
The data recording App includes the following files:
- The main app: data_recording_app_v1.0.py that has the data control service, interact with tracers, and data sync and conversion service
- The configuration file: common/utils/data_recording/config/config_data_recording.json
- The wireless dictionary file: common/utils/data_recording/config/wireless_link_parameter_map.yaml
- The SigMF Interface (common/utils/data_recording/lib/sigmf_interface.py) to store recorded data and meta-data in SigMF format
- A simple script (common/utils/data_recording/sync_validation_demo.py) to validate that the recorded bits from gNB and UE are in Sync
- For more info look to doc/data_recording.md
2025-03-10 13:03:52 +01:00
Abdo-Gaber
09a7219b90 Add Data Collection Services (T-Tracer for gNB and UE)
- Add _tracer_app_gnb and t_tracer_app_ue apps to common/utils/T/tracer/Makefile
- Add Data collection Service for gNB: common/utils/T/tracer/t_tracer_app_gnb.c
- Add Data Collection Service for UE: common/utils/T/tracer/t_tracer_app_ue.c
- Add Shared memory configuration (common/utils/T/tracer/shared_memory_config.h), since We will have too much memory consumption on the stack if the T-tracer is not fast enough to process the traces or if we have too much traces. As a result, tracers start to capture data, write data in shared memory, then close the T-Tracer after getting the recording of N slots
2025-03-10 12:49:05 +01:00
Abdo-Gaber
a97af9bf20 Add traces on UE PHY UL and related requirement: Tx Scrambled bits and Tx payload bits
- Update input parameters to nr_ulsch_encoding () in nr_transport_proto_ue.h to get number_dmrs_symbols to be added to meta-data.
- In nr_ulsch_coding.c: Trace UE PHY UL Payload TX bits (T_message: T_UE_PHY_UL_PAYLOAD_TX_BITS) including related meta-data and UTC time.
- In nr_ulsch_ue.c: Trace UE PHY UL Scrambled TX bits (T_message: T_UE_PHY_UL_SCRAMBLED_TX_BITS) including related meta-data and UTC time.
- Fix the input prameters in nr_ulsch_encoding () called in (openair1/SIMULATION/NR_PHY/ulschsim.c) since a new input parameter has been added.
2025-03-10 12:41:51 +01:00
Abdo-Gaber
388c4b4df0 Add Trace RX Payload on gNB (msg: GNB_PHY_UL_PAYLOAD_RX_BITS) including UTC time 2025-03-10 12:28:22 +01:00
Abdo-Gaber
97480b474b Add traces on gNB PHY UL and related requirement: FD PUSH IQ, FD DMRS, and UL Channel estimates wi/wo interpolation
•	The required memories have been created and initialized in: openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c
•	Those memories: memory for DMRS signals, memory for channel estimates based on DMRS positions, memory to store slot grid with channel coefficients based on DMRS positions after interpolation, and memory to store extracted data including PUSCH + DMRS.
•	The input parameters to nr_pusch_channel_estimation() has been updated to get the pointer to the added memories of pusch_dmrs_slot_mem and pusch_ch_est_dmrs_pos_slot_mem (openair1/PHY/NR_ESTIMATION/nr_ul_estimation.h)
•	The DMRS grid of NR slot and the estimated channel coefficients at DMRS locations are logged to created memories in: openair1/PHY/NR_ESTIMATION/nr_ul_channel_estimation.c
•	The function inner_rx in openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c is updated to log the extracted ULSCH PRBs and the interpolated channel coefficients.
•	The T-macro() has been called in (openair1/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c) to trace added traces: T_GNB_PHY_UL_FD_DMRS, T_GNB_PHY_UL_FD_CHAN_EST_DMRS_POS, T_GNB_PHY_UL_FD_PUSCH_IQ, T_GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL
2025-03-10 12:24:20 +01:00
Abdo-Gaber
94537d6aaf Add log tools to get and process UTC Time during data tracing 2025-03-10 10:50:40 +01:00
Abdo-Gaber
a746c6d742 Add new trace messages on UE and gNB
Those are:
GNB_PHY_UL_FD_PUSCH_IQ,
GNB_PHY_UL_FD_DMRS,
GNB_PHY_UL_FD_CHAN_EST_DMRS_POS,
GNB_PHY_UL_PAYLOAD_RX_BITS,
UE_PHY_UL_SCRAMBLED_TX_BITS,
UE_PHY_UL_PAYLOAD_TX_BITS
2025-03-10 10:44:18 +01:00
Abdo-Gaber
dead291da0 T_tracer Extension to Support up to 35 Parameters in the T() macro 2025-03-10 10:39:51 +01:00
39 changed files with 5987 additions and 18 deletions

View File

@@ -821,6 +821,7 @@ set(PHY_SRC_COMMON
${OPENAIR1_DIR}/PHY/TOOLS/sqrt.c
${OPENAIR1_DIR}/PHY/TOOLS/lut.c
${OPENAIR1_DIR}/PHY/TOOLS/simde_operations.c
${OPENAIR1_DIR}/PHY/log_tools.c
)
set(PHY_SRC

View File

@@ -100,12 +100,14 @@ struct T_header;
/* T macro tricks */
extern int T_stdout;
#define TN(...) TN_N(__VA_ARGS__,39,38,37,36,35,34,33,32,31,30,29,28,27,26, \
25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7, \
6,5,4,3,2,1,0)(__VA_ARGS__)
#define TN_N(n0,n1,n2,n3,n4,n5,n6,n7,n8,n9,n10,n11,n12,n13,n14,n15,n16,n17, \
n18,n19,n20,n21,n22,n23,n24,n25,n26,n27,n28,n29,n30,n31,n32, \
n33,n34,n35,n36,n37,n38,n,...) T##n
#define TN(...) TN_N(__VA_ARGS__,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,\
56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,\
37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,\
18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)(__VA_ARGS__)
#define TN_N(n0,n1,n2,n3,n4,n5,n6,n7,n8,n9,n10,n11,n12,n13,n14,n15,n16,n17,n18,n19,n20,\
n21,n22,n23,n24,n25,n26,n27,n28,n29,n30,n31,n32,n33,n34,n35,n36,n37,n38,\
n39,n40,n41,n42,n43,n44,n45,n46,n47,n48,n49,n50,n51,n52,n53,n54,n55,n56,\
n57,n58,n59,n60,n61,n62,n63,n64,n65,n66,n67,n68,n69,n70,n,...) T##n
#define T(...) do { if (T_stdout == 0 || T_stdout == 2) TN(__VA_ARGS__); } while (0)
/* type used to send arbitrary buffer data */
@@ -620,6 +622,633 @@ extern int *T_active;
} \
} while (0)
#define T41(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_COMMIT(); \
} \
} while (0)
#define T43(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_COMMIT(); \
} \
} while (0)
#define T45(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_COMMIT(); \
} \
} while (0)
#define T47(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_COMMIT(); \
} \
} while (0)
#define T49(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_COMMIT(); \
} \
} while (0)
#define T51(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_COMMIT(); \
} \
} while (0)
#define T53(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_COMMIT(); \
} \
} while (0)
#define T55(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_COMMIT(); \
} \
} while (0)
#define T57(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_COMMIT(); \
} \
} while (0)
#define T59(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27,t28,x28) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_PUT_##t28(30, x28); \
T_COMMIT(); \
} \
} while (0)
#define T61(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27,t28,x28,t29,x29) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_PUT_##t28(30, x28); \
T_PUT_##t29(31, x29); \
T_COMMIT(); \
} \
} while (0)
#define T63(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27,t28,x28,t29,x29,t30,x30) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_PUT_##t28(30, x28); \
T_PUT_##t29(31, x29); \
T_PUT_##t30(32, x30); \
T_COMMIT(); \
} \
} while (0)
#define T65(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27,t28,x28,t29,x29,t30,x30,t31,x31) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_PUT_##t28(30, x28); \
T_PUT_##t29(31, x29); \
T_PUT_##t30(32, x30); \
T_PUT_##t31(33, x31); \
T_COMMIT(); \
} \
} while (0)
#define T67(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27,t28,x28,t29,x29,t30,x30,t31,x31,t32,x32) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_PUT_##t28(30, x28); \
T_PUT_##t29(31, x29); \
T_PUT_##t30(32, x30); \
T_PUT_##t31(33, x31); \
T_PUT_##t32(34, x32); \
T_COMMIT(); \
} \
} while (0)
#define T69(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27,t28,x28,t29,x29,t30,x30,t31,x31,t32,x32,t33,x33) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_PUT_##t28(30, x28); \
T_PUT_##t29(31, x29); \
T_PUT_##t30(32, x30); \
T_PUT_##t31(33, x31); \
T_PUT_##t32(34, x32); \
T_PUT_##t33(35, x33); \
T_COMMIT(); \
} \
} while (0)
#define T71(t,t0,x0,t1,x1,t2,x2,t3,x3,t4,x4,t5,x5,t6,x6,t7,x7,t8,x8,t9,x9,\
t10,x10,t11,x11,t12,x12,t13,x13,t14,x14,t15,x15,t16,x16,t17,x17,\
t18,x18,t19,x19,t20,x20,t21,x21,t22,x22,t23,x23,t24,x24,t25,x25,\
t26,x26,t27,x27,t28,x28,t29,x29,t30,x30,t31,x31,t32,x32,t33,x33,t34,x34) \
do { \
if (T_ACTIVE(t)) { \
T_LOCAL_DATA \
T_HEADER(t); \
T_PUT_##t0(2, x0); \
T_PUT_##t1(3, x1); \
T_PUT_##t2(4, x2); \
T_PUT_##t3(5, x3); \
T_PUT_##t4(6, x4); \
T_PUT_##t5(7, x5); \
T_PUT_##t6(8, x6); \
T_PUT_##t7(9, x7); \
T_PUT_##t8(10, x8); \
T_PUT_##t9(11, x9); \
T_PUT_##t10(12, x10); \
T_PUT_##t11(13, x11); \
T_PUT_##t12(14, x12); \
T_PUT_##t13(15, x13); \
T_PUT_##t14(16, x14); \
T_PUT_##t15(17, x15); \
T_PUT_##t16(18, x16); \
T_PUT_##t17(19, x17); \
T_PUT_##t18(20, x18); \
T_PUT_##t19(21, x19); \
T_PUT_##t20(22, x20); \
T_PUT_##t21(23, x21); \
T_PUT_##t22(24, x22); \
T_PUT_##t23(25, x23); \
T_PUT_##t24(26, x24); \
T_PUT_##t25(27, x25); \
T_PUT_##t26(28, x26); \
T_PUT_##t27(29, x27); \
T_PUT_##t28(30, x28); \
T_PUT_##t29(31, x29); \
T_PUT_##t30(32, x30); \
T_PUT_##t31(33, x31); \
T_PUT_##t32(34, x32); \
T_PUT_##t33(35, x33); \
T_PUT_##t34(36, x34); \
T_COMMIT(); \
} \
} while (0)
#define T_CALL_ERROR \
do { \
printf("%s:%d:%s: error calling T, you have to use T_INT() or T_XX()\n", \
@@ -645,6 +1274,22 @@ extern int *T_active;
#define T34(...) T_CALL_ERROR
#define T36(...) T_CALL_ERROR
#define T38(...) T_CALL_ERROR
#define T40(...) T_CALL_ERROR
#define T42(...) T_CALL_ERROR
#define T44(...) T_CALL_ERROR
#define T46(...) T_CALL_ERROR
#define T48(...) T_CALL_ERROR
#define T50(...) T_CALL_ERROR
#define T52(...) T_CALL_ERROR
#define T54(...) T_CALL_ERROR
#define T56(...) T_CALL_ERROR
#define T58(...) T_CALL_ERROR
#define T60(...) T_CALL_ERROR
#define T62(...) T_CALL_ERROR
#define T64(...) T_CALL_ERROR
#define T66(...) T_CALL_ERROR
#define T68(...) T_CALL_ERROR
#define T70(...) T_CALL_ERROR
/* special cases for VCD logs */

View File

@@ -111,6 +111,26 @@ ID = GNB_PHY_PUCCH_PUSCH_IQ
DESC = gNodeB input data in the frequency domain for a slot where some PUCCH or PUSCH detection was done
GROUP = ALL:PHY:GRAPHIC:HEAVY:GNB
FORMAT = int,frame : int,slot : buffer,rxdataF
ID = GNB_PHY_UL_FD_PUSCH_IQ
DESC = gNodeB UL PUSCH IQ Data in the frequency domain
GROUP = ALL:PHY:GRAPHIC:HEAVY:GNB
FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : int,Nid_cell : int,rnti : int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid : int,nb_antennas_rx : int,number_of_bits : buffer,data
ID = GNB_PHY_UL_FD_DMRS
DESC = gNodeB UL DMRS Signal in the frequency domain
GROUP = ALL:PHY:GRAPHIC:HEAVY:GNB
FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : int,Nid_cell : int,rnti : int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid : int,nb_antennas_rx : int,number_of_bits : buffer,data
ID = GNB_PHY_UL_FD_CHAN_EST_DMRS_POS
DESC = gNodeB channel estimation in the frequency domain based on DMRS and at DMRS pilots location without any further interploation.
GROUP = ALL:PHY:GRAPHIC:HEAVY:GNB
FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : int,Nid_cell : int,rnti : int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid : int,nb_antennas_rx : int,number_of_bits : buffer,data
ID = GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL
DESC = gNodeB channel estimation in the frequency domain based on DMRS and interploation.
GROUP = ALL:PHY:GRAPHIC:HEAVY:GNB
FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : int,Nid_cell : int,rnti : int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid : int,nb_antennas_rx : int,number_of_bits : buffer,dataI
ID = GNB_PHY_UL_PAYLOAD_RX_BITS
DESC = gNodeB slot payload after decoder
GROUP = ALL:PHY:GRAPHIC:HEAVY:GNB
FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : int,Nid_cell : int,rnti : int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid : int,nb_antennas_rx : int,number_of_bits : buffer,data
ID = GNB_PHY_UL_FREQ_CHANNEL_ESTIMATE
DESC = gNodeB channel estimation in the frequency domain
GROUP = ALL:PHY:GRAPHIC:HEAVY:GNB
@@ -1352,6 +1372,14 @@ ID = UE_PHY_MEAS
DESC = UE PHY measurements
GROUP = ALL:PHY:GRAPHIC:HEAVY:UE
FORMAT = int,eNB_ID : int,frame : int,subframe : int,rsrp : int,rssi : int,snr: int,rx_power: int,noise_power: int,w_cqi: int,freq_offset
ID = UE_PHY_UL_PAYLOAD_TX_BITS
DESC = UE UL Tx bits in one slot before decoder and scrambler
GROUP = ALL:PHY:GRAPHIC:HEAVY:UE
FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : int,Nid_cell : int,rnti : int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid : int,nb_antennas_tx : int,number_of_bits : buffer,data
ID = UE_PHY_UL_SCRAMBLED_TX_BITS
DESC = UE UL Tx bits in one slot after scrambler
GROUP = ALL:PHY:GRAPHIC:HEAVY:UE
FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm : int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot : int,Nid_cell : int,rnti : int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols : int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers : int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid : int,nb_antennas_tx : int,number_of_bits : buffer,data
#for debug/test - not used
ID = first

View File

@@ -8,7 +8,7 @@ XLIBS=-lX11 -lpng -lXft
all: record replay extract_config textlog enb ue vcd macpdu2wireshark \
extract_input_subframe extract_output_subframe to_vcd extract multi \
gnb
gnb t_tracer_app_gnb t_tracer_app_ue
record: utils.o record.o database.o configuration.o
$(CC) $(CFLAGS) -o record $^ $(LIBS)
@@ -65,6 +65,14 @@ gnb: utils.o gnb.o database.o event.o handler.o configuration.o \
filter/filter.a
$(CC) $(CFLAGS) -o gnb $^ $(LIBS) $(XLIBS)
t_tracer_app_gnb: utils.o t_tracer_app_gnb.o database.o event.o handler.o configuration.o \
logger/logger.a filter/filter.a
$(CC) $(CFLAGS) -o t_tracer_app_gnb $^ $(LIBS) $(XLIBS)
t_tracer_app_ue: utils.o t_tracer_app_ue.o database.o event.o handler.o configuration.o \
logger/logger.a filter/filter.a
$(CC) $(CFLAGS) -o t_tracer_app_ue $^ $(LIBS) $(XLIBS)
multi.o: ../incgen/T_IDs.h
../incgen/T_IDs.h: ../T_messages.txt
@@ -90,7 +98,7 @@ filter/filter.a:
clean:
rm -f *.o core tracer_remote textlog enb ue vcd record replay
rm -f extract_config macpdu2wireshark extract_input_subframe
rm -f extract_output_subframe to_vcd extract multi gnb
rm -f extract_output_subframe to_vcd extract multi gnb t_tracer_app_gnb t_tracer_app_ue
cd gui && $(MAKE) clean
cd view && $(MAKE) clean
cd logger && $(MAKE) clean

View File

@@ -8,7 +8,7 @@
#endif
/* maximum number of arguments for the T macro */
#define T_MAX_ARGS 32
#define T_MAX_ARGS 72
enum event_arg_type {
EVENT_INT,

View File

@@ -0,0 +1,64 @@
/*
* 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
*/
/*! \file common/utils/T/tracer/shared_memory_config.h
* \brief shared memory to store data captured by T-Tracer services to be processed by data recording application
* \author Abdo Gaber
* \date 2024
* \version 1.0
* \company Emerson, NI Test and Measurement
* \email:
* \note
* \warning
*/
#ifndef SHARED_MEMORY_CONFIG_H_
#define SHARED_MEMORY_CONFIG_H_
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>
#define SHMSIZE ((122.88e6/(100*20)) * 1000*8) // Assume capture in 1000, each I+Q represented by 8 byts
// 122.88e6: 10ms at 100 MHz BW of 5G NR
// 20: 20 slots in 10ms
// subcarrier spacing: 30 KHz
// for gNB T Tracer App
#define GETKEYDIR1_gNB ("/tmp/gnb_app1")
#define GETKEYDIR2_gNB ("/tmp/gnb_app2")
#define PROJECTID_gNB (2335)
// for UET Tracer App
#define GETKEYDIR1_UE ("/tmp/ue_app1")
#define GETKEYDIR2_UE ("/tmp/ue_app2")
#define PROJECTID_UE (2336)
void err_exit(char *buf);
int create_shm(char **addrN, const char *pathname, int projectId);
void del_shm(char *addr, int shm_id);
#endif /* SHARED_MEMORY_CONFIG_H_ */

View File

@@ -0,0 +1,951 @@
/*
* 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
*/
/*! \file common/utils/T/tracer/t_tracer_app_gnb.c
* \brief T-Tracer gnb service to capture tracee Messages from gNB, it is used by data recording application
* \author Abdo Gaber
* \date 2025
* \version 1.0
* \company Emerson, NI Test and Measurement
* \email:
* \note
* \warning
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include "database.h"
#include "event.h"
#include "handler.h"
#include "logger/logger.h"
#include "utils.h"
#include "event_selector.h"
#include "configuration.h"
#include "shared_memory_config.h"
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <poll.h>
#define MAX_FRAME_INDEX 1023
#define MAX_SLOT_INDEX 19
#define DEBUG_T_Tracer
#define DEBUG_BUFFER
// Duration to discard recording in milliseconds to mitigate reading data from
// the buffer in the stack from the previous record
#define DISCARD_RECORD_DURATION_MS 10
// Combine bytes in - little-endian format
int combine_bytes(const uint8_t *bytes, size_t num_bytes)
{
int result = 0;
for (size_t i = 0; i < num_bytes; ++i) {
result |= (bytes[i] << (i * 8));
}
return result;
}
// Convert an integer to an array of bytes - little-endian format
void int_to_bytes(int num, uint8_t *bytes, size_t num_bytes)
{
for (size_t i = 0; i < num_bytes; ++i) {
bytes[i] = (num >> (i * 8)) & 0xFF;
}
}
// Check if the message is in the list of bits messages
bool is_bits_messages(int traces_bits_support_data_Collection_format_idx[], int n_bits_msgs, int msg_id)
{
for (int i = 0; i < n_bits_msgs; i++) {
if (msg_id == traces_bits_support_data_Collection_format_idx[i]) {
return true;
}
}
return false;
}
// Get the current time
struct timespec get_current_time()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return time;
}
// Function to convert timespec to microseconds
long long timespec_to_microseconds(struct timespec time)
{
return (time.tv_sec * 1000000LL) + (time.tv_nsec / 1000);
}
// Calculate the time difference in milliseconds
double calculate_time_difference(struct timespec start, struct timespec end)
{
double start_ms = start.tv_sec * 1000.0 + start.tv_nsec / 1000000.0;
double end_ms = end.tv_sec * 1000.0 + end.tv_nsec / 1000000.0;
return end_ms - start_ms;
}
// Get Time Stamp in microseconds in YYYYMMDDHHMMSSmmmuuu format
char* get_time_stamp_usec(char time_stamp_str[])
{
// initialization to measure time stamp --This part should be moved to inilization part
time_t my_time;
struct tm * timeinfo;
time (&my_time);
struct timeval tv;
// get time stamp
timeinfo = localtime (&my_time);
gettimeofday(&tv,NULL);
// Add time stamp: yyyy mm dd hh mm ss usec
uint16_t year= timeinfo->tm_year+1900;
uint8_t mon= timeinfo->tm_mon+1;
uint8_t mday= timeinfo->tm_mday;
uint8_t hour= timeinfo->tm_hour;
uint8_t min= timeinfo->tm_min;
uint8_t sec= timeinfo->tm_sec;
uint16_t usec= (tv.tv_usec);
//printf ("Time stamp: %d_%d_%d_%d_%d_%d_%d \n",year,mon,mday,hour,min,sec,usec);
//sprintf(time_stamp_str, "%d_%d_%d_%d_%d_%d_%d",year,mon,mday,hour,min,sec,usec);
sprintf(time_stamp_str, "%04d%02d%02d%02d%02d%02d%06d", year, mon, mday, hour, min, sec, usec);
return time_stamp_str;
}
// Convert timestamp string to integer
int convert_time_stamp_to_int(const char* timestamp)
{
return atoi(timestamp);
}
// Split timestamp string and convert to integer
int split_time_stamp_and_convert_to_int (char time_stamp_str[], int shift, int length)
{
char time_part[length+1]; // Buffer to hold the date part YYYYMMDD or HHMMSSmmm
// Copy the first 8 or 9 characters (YYYYMMDD) to HHMMSSmmm
strncpy(time_part, time_stamp_str + shift, length);
time_part[length] = '\0'; // Null-terminate the string
// Convert timestamp string to integer
return convert_time_stamp_to_int(time_part);
}
void err_exit(char *buf)
{
fprintf(stderr, "%s\n", buf);
exit(1);
}
// create shared memory
int create_shm(char **addrN, const char *shm_path, int projectId)
{
key_t key;
if (-1 != open(shm_path, O_CREAT, 0777)) {
key = ftok(shm_path, projectId);
} else {
err_exit("Error: open shared memory");
}
if ( key < 0 ) {
err_exit("Error: ftok error");
}
int shm_id;
shm_id = shmget(key, SHMSIZE, IPC_CREAT | IPC_EXCL | 0664);
if ( shm_id == -1 ) {
if ( errno == EEXIST ) {
printf("Error: shared memeory already exist\n");
shm_id = shmget(key ,0, 0);
printf("reference shm_id = %d\n", shm_id);
} else {
perror("errno");
err_exit("shmget error");
}
}
char *addr;
// address to attach - attach for read & write
if ( (addr = shmat(shm_id, 0, 0) ) == (void*)-1) {
if (shmctl(shm_id, IPC_RMID, NULL) == -1)
err_exit("Error: shmctl error");
else {
printf("Attach shared memory failed\n");
printf("remove shared memory identifier successful\n");
}
err_exit("shmat error");
}
*addrN = addr;
return shm_id;
}
// delete shared memory
void del_shm(char *addr, int shm_id)
{
if ( shmdt(addr) < 0) err_exit("shmdt error");
if (shmctl(shm_id, IPC_RMID, NULL) == -1)
err_exit("shmctl error");
else {
printf("Remove shared memory identifier successful\n");
}
}
/* this function sends the activated traces to the nr-softmodem */
void activate_traces(int socket, int number_of_events, int *is_on)
{
char t = 1;
if (socket_send(socket, &t, 1) == -1 ||
socket_send(socket, &number_of_events, sizeof(int)) == -1 ||
socket_send(socket, is_on, number_of_events * sizeof(int)) == -1)
abort();
}
void usage(void)
{
printf(
"options:\n"
" -d <database file> this option is mandatory\n"
" -ip <host> connect to given IP address (default %s)\n"
" -p <port> connect to given port (default %d)\n",
DEFAULT_REMOTE_IP,
DEFAULT_REMOTE_PORT
);
exit(1);
}
// class to store the message and the action to enable capture selected data
// 0: do not record message
// 1: record message
struct trace_struct {
char on_off_name[100];
int on_off_action;
};
// struct for trace message based on Data Collection Trace Messages Structure
// you need to define the vararibles of each message
typedef struct {
/* Data Collection Trace Message Structure */
int frame;
int slot;
int datetime_yyyymmdd;
int datetime_hhmmssmmm;
int frame_type, freq_range, subcarrier_spacing, cyclic_prefix, symbols_per_slot;
int Nid_cell, rnti;
int rb_size, rb_start, start_symbol_index,nr_of_symbols;
int qam_mod_order,mcs_index,mcs_table,nrOfLayers,transform_precoding;
int dmrs_config_type,ul_dmrs_symb_pos,number_dmrs_symbols;
int dmrs_port,dmrs_nscid,nb_antennas_rx,number_of_bits;
int data_size, data;
} event_trace_msg_data;
void setup_trace_msg_data(event_trace_msg_data *d, void *database)
{
database_event_format f;
int i;
/* Data Collection Trace Message Structure */
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols :
// int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
// Initialize the data structure
d->frame = -1;
d->slot = -1;
d->datetime_yyyymmdd = -1;
d->datetime_hhmmssmmm = -1;
d->frame_type = -1;
d->freq_range = -1;
d->subcarrier_spacing = -1;
d->cyclic_prefix = -1;
d->symbols_per_slot = -1;
d->Nid_cell = -1;
d->rnti = -1;
d->rb_size = -1;
d->rb_start = -1;
d->start_symbol_index = -1;
d->nr_of_symbols = -1;
d->qam_mod_order = -1;
d->mcs_index = -1;
d->mcs_table = -1;
d->nrOfLayers = -1;
d->transform_precoding = -1;
d->dmrs_config_type = -1;
d->ul_dmrs_symb_pos = -1;
d->number_dmrs_symbols = -1;
d->dmrs_port = -1;
d->dmrs_nscid = -1;
d->nb_antennas_rx = -1;
d->number_of_bits = -1;
d->data = -1;
/* this macro looks for a particular element and checks its type */
#define G(var_name, var_type, var) \
if (!strcmp(f.name[i], var_name)) { \
if (strcmp(f.type[i], var_type)) { printf("Error: Trace Message has a bad type for %s\n", var_name); exit(1); } \
var = i; \
continue; \
}
/* ------------------------------*/
/* Create Macro for Data Collection Trace Message */
/* ------------------------------*/
// Data Collection Trace Message Structure
// Example: GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID,
// GNB_PHY_UL_FD_CHAN_EST_DMRS_POS, GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL
// GNB_PHY_UL_PAYLOAD_RX_BITS
// UE_PHY_UL_SCRAMBLED_TX_BITS
// UE_PHY_UL_PAYLOAD_TX_BITS
// Get a template of any message based on Data Collection Trace Messages Structure
int Trace_MSG_ID = event_id_from_name(database, "GNB_PHY_UL_FD_PUSCH_IQ");
f = get_format(database, Trace_MSG_ID);
/* get the elements of the trace
* the value is an index in the event, see below */
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols :
// int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
for (i = 0; i < f.count; i++) {
G("frame", "int", d->frame)
G("slot", "int", d->slot)
G("datetime_yyyymmdd", "int", d->datetime_yyyymmdd)
G("datetime_hhmmssmmm", "int", d->datetime_hhmmssmmm)
G("frame_type", "int", d->frame_type)
G("freq_range", "int", d->freq_range)
G("subcarrier_spacing", "int", d->subcarrier_spacing)
G("cyclic_prefix", "int", d->cyclic_prefix)
G("symbols_per_slot", "int", d->symbols_per_slot)
G("Nid_cell", "int", d->Nid_cell)
G("rnti", "int", d->rnti)
G("rb_size", "int", d->rb_size)
G("rb_start", "int", d->rb_start)
G("start_symbol_index", "int", d->start_symbol_index)
G("nr_of_symbols", "int", d->nr_of_symbols)
G("qam_mod_order", "int", d->qam_mod_order)
G("mcs_index", "int", d->mcs_index)
G("mcs_table", "int", d->mcs_table)
G("nrOfLayers", "int", d->nrOfLayers)
G("transform_precoding", "int", d->transform_precoding)
G("dmrs_config_type", "int", d->dmrs_config_type)
G("ul_dmrs_symb_pos", "int", d->ul_dmrs_symb_pos)
G("number_dmrs_symbols", "int", d->number_dmrs_symbols)
G("dmrs_port", "int", d->dmrs_port)
G("dmrs_nscid", "int", d->dmrs_nscid)
G("nb_antennas_rx", "int", d->nb_antennas_rx)
G("number_of_bits", "int", d->number_of_bits)
G("data", "buffer", d->data)
}
//if (d->frame == -1 || d->slot == -1) goto error;
#undef G
return;
}
// Function to check if a value is in the array
int isValueInArray(int value, int arr[], int size)
{
for (int i = 0; i < size; i++) {
if (arr[i] == value) {
return 1; // Value found
}
}
return 0; // Value not found
}
void reestablish_connection(int *socket, const char *ip, int port, int number_of_events, int *is_on)
{
clear_remote_config();
if (*socket != -1) close(*socket);
/* connect to the nr-softmodem */
*socket = connect_to(ip, port);
if (*socket == -1) {
printf("\n Failed to connect to nr-softmodem. Retrying...\n");
return;
}
printf("\n Connected");
/* activate the traces in the nr-softmodem */
activate_traces(*socket, number_of_events, is_on);
printf("\n Activated Traces in nr-softmodem");
}
int main(int n, char **v)
{
// Define and initialize an array of strings to list
// trace_msgs_support_data_Collection_format
// it is used to parse the requested messages if it is based
// on Data Collection Trace Messages Structure and supported tracer messages indices
const char *traces_iq_support_data_Collection_format[] = {
"GNB_PHY_UL_FD_PUSCH_IQ",
"GNB_PHY_UL_FD_DMRS",
"GNB_PHY_UL_FD_CHAN_EST_DMRS_POS",
"GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL"
};
const char *traces_bits_support_data_Collection_format[] = {
"GNB_PHY_UL_PAYLOAD_RX_BITS",
"UE_PHY_UL_SCRAMBLED_TX_BITS",
"UE_PHY_UL_PAYLOAD_TX_BITS"
};
// extra number of records to simlify Sync between base station and UE synchronization records.
// if you have network delay, you can increase the number of records to capture
int max_sync_offset = 6; // 6 frames ~ 60 ms
// all supported messages
// Calculate the size of the combined array
int n_iq_msgs = sizeof(traces_iq_support_data_Collection_format) / sizeof(traces_iq_support_data_Collection_format[0]);
int n_bits_msgs = sizeof(traces_bits_support_data_Collection_format) / sizeof(traces_bits_support_data_Collection_format[0]);
int n_msgs_based_data_Collection_format = n_iq_msgs + n_bits_msgs;
// Create the combined array
const char *traces_support_data_Collection_format[n_msgs_based_data_Collection_format];
// Copy IQ messages to the combined array
for (int i = 0; i < n_iq_msgs; i++) {
traces_support_data_Collection_format[i] = traces_iq_support_data_Collection_format[i];
}
// Copy Bits messages to the combined array
for (int i = 0; i < n_bits_msgs; i++) {
traces_support_data_Collection_format[i + n_iq_msgs] = traces_bits_support_data_Collection_format[i];
}
uint16_t msg_id = 0;
uint16_t start_frame_number = 0;
uint32_t number_records = 0; // number of records to capture, it is number of slots
// array to store the requested tracer messages indices
int req_tracer_msgs_indices[100] = {0};
// define variables --> to do: add all of them to be class of pointers
char *database_filename = NULL;
void *database;
char *ip = DEFAULT_REMOTE_IP;
char ip_address[16]; // max IP address length is 15 + 1 for null terminator
int port = DEFAULT_REMOTE_PORT;
int *is_on;
int number_of_events;
int i;
int socket = -1;
char trace_time_stamp_str[30];
// data structure for the trace messages based on Data Collection Trace Messages Structure
event_trace_msg_data trace_msg_data;
// initlization variables
unsigned int bufIdx_wr = 0;
unsigned int bufIdx_rd = 0;
uint8_t num_req_tracer_msgs = 0;
// initilaze shared memory
char *addr_wr,*addr_rd;
printf("\n Data Collection Service: Initializing shared memory ...");
printf("\n Directory 1: %s, Directory 2: %s", GETKEYDIR1_gNB, GETKEYDIR2_gNB);
printf("\n Project ID: %d\n", PROJECTID_gNB);
int shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_gNB, PROJECTID_gNB);
int shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_gNB, PROJECTID_gNB);
del_shm(addr_wr, shm_id_wr);
del_shm(addr_rd, shm_id_rd);
shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_gNB, PROJECTID_gNB);
shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_gNB, PROJECTID_gNB);
/* write on a socket fails if the other end is closed and we get SIGPIPE */
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) abort();
/* parse command line options */
// Port number and IP address are given via API and not via command line
for (i = 1; i < n; i++) {
if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) usage();
if (!strcmp(v[i], "-d"))
{ if (i > n-2) usage(); database_filename = v[++i]; continue; }
if (!strcmp(v[i], "-ip")) { if (i > n-2) usage(); ip = v[++i]; continue; }
if (!strcmp(v[i], "-p"))
{ if (i > n-2) usage(); port = atoi(v[++i]); continue; }
usage();
}
if (database_filename == NULL) {
printf("ERROR: Provide a database file (-d)\n");
exit(1);
}
/* load the database T_messages.txt */
database = parse_database(database_filename);
load_config_file(database_filename);
/* an array of int for all the events defined in the database is needed */
number_of_events = number_of_ids(database);
is_on = calloc(number_of_events, sizeof(int));
if (is_on == NULL) abort();
// Set first byte of the shared memory to 0
addr_rd[0] = 0; // important to check if we have new requested messages
// read requested tracer msg indices from memory
printf("\n Data Collection Service: Waiting for messages request ...\n");
// 0: Wait
// 1: config
// 2: record
// 3: stop
// Wait for Action Config
while (1) {
if ((uint8_t)(addr_rd[0]) == 0){
usleep(50); // sleep for 50 us: 0.5 ms slot duration
}
// config state
else if ((uint8_t)(addr_rd[0]) == 1) {
get_time_stamp_usec(trace_time_stamp_str);
printf("\n Received config message. Time Stamp: %s", trace_time_stamp_str);
// get the IP address length in bytes
bufIdx_rd = 1;
uint8_t ip_address_length = addr_rd[bufIdx_rd];
bufIdx_rd +=1; // + 1 byte = IP address length
// read the IP address
for (int i = 0; i < ip_address_length; i++) {
ip_address[i] = addr_rd[bufIdx_rd];
bufIdx_rd +=1;
}
ip = ip_address;
// get the array bytes of the port number : 2 bytes
uint8_t port_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1]};
bufIdx_rd +=2; // + 2 bytes = start frame number
port = combine_bytes(port_number_bytes, 2);
printf("\n Parameters: IP Address length: %d, IP Address: %s, Port Number: %d \n", ip_address_length, ip_address, port);
addr_rd[0] = 0; // reset memory : to wait for record action
break;
}
}
// Establish connection to the nr-softmodem
clear_remote_config();
if (socket != -1) close(socket);
/* connect to the nr-softmodem */
socket = connect_to(ip, port);
printf("\n Connected to nr-softmodem");
// Read Action record or stop
printf("\n Data Collection Service: Waiting for record message ...\n");
while (1) {
// wait for Action record
if ((uint8_t)(addr_rd[0]) == 0){
usleep(50); // sleep for 50 us: 0.5 ms slot duration
}
// quit state
else if ((uint8_t)(addr_rd[0]) == 3) {
printf("\n Received 'stop' command. Exiting...");
printf("\n ");
// Clean up and exit
break;
}
// record state
else if ((uint8_t)(addr_rd[0]) == 2){
// clear remote buffer if there is
clear_remote_config();
get_time_stamp_usec(trace_time_stamp_str);
printf("\n Received record message. Time Stamp: %s", trace_time_stamp_str);
// read number of requested messages
bufIdx_rd = 1;
num_req_tracer_msgs = addr_rd[bufIdx_rd];
bufIdx_rd +=1;
printf("\n Number of requested tracer messages: %d,", num_req_tracer_msgs);
// reset memory : action to wait for next record action
addr_rd[0] = 0;
// read tracer msg IDs - every message ID has been stored in two bytes
for (uint8_t msg_n = 0; msg_n < num_req_tracer_msgs; msg_n++) {
// get the array bytes of the tracer message ID: 2 bytes
uint8_t msg_id_bytes [2]= {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1]};
bufIdx_rd +=2; // + 2 bytes = message ID
msg_id= combine_bytes(msg_id_bytes, 2);
req_tracer_msgs_indices[msg_n] = msg_id;
printf(" msg_id: %d, ", msg_id);
}
// get the array bytes of the number of records: 4 bytes
uint8_t number_records_bytes[4] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1], addr_rd[bufIdx_rd+2], addr_rd[bufIdx_rd+3]};
bufIdx_rd +=4; // + 4 bytes = number of records
number_records = combine_bytes(number_records_bytes, 4);
printf("num_records: %d, ", number_records);
// get the array bytes of the start frame number : 2 bytes
uint8_t start_frame_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1]};
bufIdx_rd +=2; // + 2 bytes = start frame number
start_frame_number = combine_bytes(start_frame_number_bytes, 2);
printf("start_frame: %d\n", start_frame_number);
/* activate the trace in this array */
printf("\n Activate Tracer messages in on_off array: ");
for (i = 0; i < num_req_tracer_msgs; i++){
char *on_off_name= event_name_from_id(database, req_tracer_msgs_indices[i]);
on_off(database, on_off_name, is_on, 1);
printf("%d, %s, ", req_tracer_msgs_indices[i], on_off_name);
//on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1);
}
// get the event IDs for the bit messages
int traces_bits_support_data_Collection_format_idx [n_bits_msgs];
for (int i = 0; i < n_bits_msgs; i++) {
traces_bits_support_data_Collection_format_idx[i] =
event_id_from_name(database, traces_bits_support_data_Collection_format[i]);
}
// all supported messages
int traces_support_data_Collection_format_idx [n_msgs_based_data_Collection_format];
for (int i = 0; i < n_msgs_based_data_Collection_format; i++) {
traces_support_data_Collection_format_idx[i] =
event_id_from_name(database, traces_support_data_Collection_format[i]);
}
// setup data for the trace messages
setup_trace_msg_data(&trace_msg_data, database);
printf("\n Setup Trace Data Done");
// Get the start time
struct timespec start_time = get_current_time();
/* activate the tracee in the nr-softmodem */
activate_traces(socket, number_of_events, is_on);
printf("\n Activated Traces in nr-softmodem");
/* a buffer needed to receive events from the nr-softmodem */
OBUF ebuf = { osize: 0, omaxsize: 0, obuf: NULL };
/* read events */
int nrecord_idx = 0;
bufIdx_wr = 0;
// logic to be sure that we have a complete slot record
bool start_recording = false;
bool got_ref_frame_slot = false;
int ref_slot = 0;
int ref_frame = 0;
int slot_difference = 0;
int frame_difference = 0;
int current_frame = 0, prev_frame = 0, current_slot = 0, prev_slot = 0;
// offset to sync between base station and UE synchronization records or power measurements
int sync_offset_index = 0; // increase the index only if the index of frame changes after getting all records
// since we will use the frame differece to do extra records, we should be sure that the last slot is recorded completely
printf("\n\n Data Collection Service: Start reading messages ...");
struct pollfd event_poll_fd;
event_poll_fd.fd = socket;
event_poll_fd.events = POLLIN;
while (1) {
// stop if number of records is done
if ((nrecord_idx >= number_records) && (sync_offset_index >= max_sync_offset)){
// We added one to the number of records to capture the last record completely if
// we have several messages enabled per slot
break;
}
int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll
if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) {
event e = get_event(socket, &ebuf, database);
if (e.type == -1) {
printf("\n Link broken or unexpected message received. Re-establishing connection...\n");
reestablish_connection(&socket, ip, port, number_of_events, is_on);
continue; // Skip further processing and retry
}
//-------------------------
// GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID, GNB_PHY_UL_FD_CHAN_EST_DMRS_POS,
// UE_PHY_UL_SCRAMBLED_TX_BITS, GNB_PHY_UL_PAYLOAD_RX_BITS, UE_PHY_UL_PAYLOAD_TX_BITS
//-------------------------
// is it a requested message
if (isValueInArray(e.type, req_tracer_msgs_indices, num_req_tracer_msgs)) {
// is it based on Data Collection Trace Messages Structure
if (isValueInArray(e.type, traces_support_data_Collection_format_idx, n_msgs_based_data_Collection_format)) {
// Start recording from the next slot to mitigate capturing partial data
// check if the current frame and slot are different from the previous frame and slot
// Then, increase the record index
if (start_recording == false) {
if (got_ref_frame_slot == false) {
ref_frame = e.e[trace_msg_data.frame].i;
ref_slot = e.e[trace_msg_data.slot].i;
printf ("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type),e.type);
got_ref_frame_slot = true;}
current_frame = e.e[trace_msg_data.frame].i;
current_slot = e.e[trace_msg_data.slot].i;
frame_difference = (current_frame - ref_frame + MAX_FRAME_INDEX + 1) % (MAX_FRAME_INDEX + 1);
slot_difference = (current_slot - ref_slot + MAX_SLOT_INDEX + 1) % (MAX_SLOT_INDEX + 1);
printf("\n First frame.slot: %d.%d, current frame.slot: %d.%d, diff frame.slot: %d.%d",
ref_frame, ref_slot,
current_frame, current_slot,
frame_difference, slot_difference);
if ((ref_frame != current_frame) || (ref_slot != current_slot)) {
start_recording = true;
printf("\n Start recording from frame: %d, slot: %d ", e.e[trace_msg_data.frame].i, e.e[trace_msg_data.slot].i);}
}
// start recording from the next frame to mitigate capturing partial data
if (start_recording == true) {
/* this is how to access the elements of the Data Collection trace messages.
* we use e.e[<element>] and then the correct suffix, here
* it's .i for the integer and .b for the buffer and .bsize for the buffer size
* see in event.h the structure event_arg
*/
unsigned char *buf = e.e[trace_msg_data.data].b;
printf("\n\nRecord number: %d", nrecord_idx);
#ifdef DEBUG_BUFFER
printf("\nBuffer index in bytes: %d", bufIdx_wr);
#endif
// add general message header: message ID,
// T-Tracer Message format
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols :
// int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
memcpy(&addr_wr[bufIdx_wr], &e.type, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.slot].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_yyyymmdd].i, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_hhmmssmmm].i, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame_type].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.freq_range].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.subcarrier_spacing].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.cyclic_prefix].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.symbols_per_slot].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.Nid_cell].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rnti].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_size].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_start].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.start_symbol_index].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nr_of_symbols].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.qam_mod_order].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_index].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_table].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nrOfLayers].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.transform_precoding].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_config_type].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.ul_dmrs_symb_pos].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_dmrs_symbols].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_port].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_nscid].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nb_antennas_rx].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_of_bits].i, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
printf("\nTime Stamp: %d_%d", e.e[trace_msg_data.datetime_yyyymmdd].i, e.e[trace_msg_data.datetime_hhmmssmmm].i);
// add message body: length in bytes + recorded data
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.data].bsize, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
// read data from buffer and convert from unsigned char * array to int 16 using the right endianness
// T-tracer: Little Endian
// For BITS Messages, example: UE_PHY_UL_SCRAMBLED_TX_BITS: data in bytes
if(is_bits_messages(traces_bits_support_data_Collection_format_idx, n_bits_msgs, e.type)) {
for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx+=1) {
//printf("%d, ", buf[byte_idx]);
memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
}
}
else {
for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx+=2) {
//For a little-endian system:
memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx+1], sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
}
}
/*
for (int byte_idx_i = 0; byte_idx_i < e.e[d.nr_ul_fd_dmrs_data].bsize; byte_idx_i+=4) {
int16_t I = buf[byte_idx_i] | (buf[byte_idx_i+1] << 8);
int16_t Q = buf[byte_idx_i+2] | (buf[byte_idx_i+3] << 8);
printf ("idx %d, ", byte_idx_i);
printf ("\n%d", I);
printf ("\n%d", Q);
}
*/
// check if the current frame and slot are different from the previous frame and slot
// Then, increase the record index
current_frame = e.e[trace_msg_data.frame].i;
current_slot = e.e[trace_msg_data.slot].i;
// increase sync offset index if the current frame is different from the previous frame
if ((current_frame != prev_frame) && nrecord_idx >= number_records) {
sync_offset_index++;
}
if (current_frame != prev_frame || current_slot != prev_slot) {
nrecord_idx++;
// Update previous frame and slot numbers
prev_frame = current_frame;
prev_slot = current_slot;
}
#ifdef DEBUG_T_Tracer
printf ("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type),e.type);
printf("frame %d, slot %d, datetime %d_%d\n",
e.e[trace_msg_data.frame].i,
e.e[trace_msg_data.slot].i,
e.e[trace_msg_data.datetime_yyyymmdd].i,
e.e[trace_msg_data.datetime_hhmmssmmm].i);
printf("frame_type %d, freq_range %d, subcarrier_spacing %d, cyclic_prefix %d, symbols_per_slot %d\n",
e.e[trace_msg_data.frame_type].i,
e.e[trace_msg_data.freq_range].i,
e.e[trace_msg_data.subcarrier_spacing].i,
e.e[trace_msg_data.cyclic_prefix].i,
e.e[trace_msg_data.symbols_per_slot].i);
printf("Nid_cell %d, rnti %d, rb_size %d, rb_start %d, start_symbol_index %d, nr_of_symbols %d\n",
e.e[trace_msg_data.Nid_cell].i,
e.e[trace_msg_data.rnti].i,
e.e[trace_msg_data.rb_size].i,
e.e[trace_msg_data.rb_start].i,
e.e[trace_msg_data.start_symbol_index].i,
e.e[trace_msg_data.nr_of_symbols].i);
printf("qam_mod_order %d, mcs_index %d, mcs_table %d, nrOfLayers %d, transform_precoding %d\n",
e.e[trace_msg_data.qam_mod_order].i,
e.e[trace_msg_data.mcs_index].i,
e.e[trace_msg_data.mcs_table].i,
e.e[trace_msg_data.nrOfLayers].i,
e.e[trace_msg_data.transform_precoding].i);
printf("dmrs_config_type %d, ul_dmrs_symb_pos %d, number_dmrs_symbols %d, dmrs_port %d, dmrs_nscid %d\n",
e.e[trace_msg_data.dmrs_config_type].i,
e.e[trace_msg_data.ul_dmrs_symb_pos].i,
e.e[trace_msg_data.number_dmrs_symbols].i,
e.e[trace_msg_data.dmrs_port].i,
e.e[trace_msg_data.dmrs_nscid].i);
printf("nb_antennas_rx %d, number_of_bits %d, data size %d\n",
e.e[trace_msg_data.nb_antennas_rx].i,
e.e[trace_msg_data.number_of_bits].i,
e.e[trace_msg_data.data].bsize);
#endif
} // End of start recording flag
} // end of if statement for the supported messages based on Data Collection Trace Messages Structure
else {
printf("ERROR: Requested Message is not based on Data Collection Trace Messages Structure\n");
printf("ERROR: Requested Message ID: %d\n", e.type);
}
} // end of if statement for the requested messages
} // end while loop of reading events
else {
// No data, just loop and check time
usleep(100); // optional: avoid busy-waiting
}
} // End of while loop to read events
// de-activate the tracee in the nr-softmodem
printf("\n De-activated Tracer message:\n");
for (i = 0; i < num_req_tracer_msgs; i++){
char *on_off_name= event_name_from_id(database, req_tracer_msgs_indices[i]);
on_off(database, on_off_name, is_on, 0);
//printf("\n %d, %s, ", req_tracer_msgs_indices[i], on_off_name);
//on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1);
}
// De-activate the tracee in the nr-softmodem
activate_traces(socket, number_of_events, is_on);
printf("\n De-activated Traces");
// Get the end time
struct timespec end_time = get_current_time();
// Calculate the time difference
double time_diff = calculate_time_difference(start_time, end_time);
printf("Total Time difference: %.2f ms\n", time_diff);
printf("Time difference per record: %.2f ms\n", time_diff/(number_records + max_sync_offset));
// discard stale or previous record data for the first DISCARD_RECORD_DURATION_MS
struct timespec record_start, record_now;
clock_gettime(CLOCK_MONOTONIC, &record_start);
while (1) {
clock_gettime(CLOCK_MONOTONIC, &record_now);
double elapsed_ms = calculate_time_difference(record_start, record_now);
if (elapsed_ms >= DISCARD_RECORD_DURATION_MS) {
break; // Stop after 10ms
}
int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll
if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) {
event e = get_event(socket, &ebuf, database);
printf("%d", e.type);
if (e.type == -1) {
printf("\n Link broken or unexpected message received. Re-establishing connection...\n");
reestablish_connection(&socket, ip, port, number_of_events, is_on);
continue;
}
} else {
// No data, just loop and check time
usleep(100); // optional: avoid busy-waiting
}
} // End of while loop to discard stale records
}
} // End a while loop to check for the "stop" command
// de-activate the tracee in the nr-softmodem
free_database(database);
free(is_on);
close(socket);
return 0;
}

View File

@@ -0,0 +1,954 @@
/*
* 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
*/
/*! \file common/utils/T/tracer/t_tracer_app_ue.c
* \brief T-Tracer UE service to capture tracee Messages from UE, it is used by data recording application
* \author Abdo Gaber
* \date 2025
* \version 1.0
* \company Emerson, NI Test and Measurement
* \email:
* \note
* \warning
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include "database.h"
#include "event.h"
#include "handler.h"
#include "logger/logger.h"
#include "utils.h"
#include "event_selector.h"
#include "configuration.h"
#include "shared_memory_config.h"
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <poll.h>
#define MAX_FRAME_INDEX 1023
#define MAX_SLOT_INDEX 19
#define DEBUG_T_Tracer
#define DEBUG_BUFFER
// Duration to discard recording in milliseconds to mitigate stale or previous record data
#define DISCARD_RECORD_DURATION_MS 10
//#include <linux/time.h>
// Combine bytes in little-endian format
int combine_bytes(const uint8_t *bytes, size_t num_bytes)
{
int result = 0;
for (size_t i = 0; i < num_bytes; ++i) {
result |= (bytes[i] << (i * 8));
}
return result;
}
// Convert an integer to an array of bytes in little-endian format
void int_to_bytes(int num, uint8_t *bytes, size_t num_bytes)
{
for (size_t i = 0; i < num_bytes; ++i) {
bytes[i] = (num >> (i * 8)) & 0xFF;
}
}
// Check if the message is in the list of bits messages
bool is_bits_messages(int traces_bits_support_data_Collection_format_idx[], int n_bits_msgs, int msg_id)
{
for (int i = 0; i < n_bits_msgs; i++) {
if (msg_id == traces_bits_support_data_Collection_format_idx[i]) {
return true;
}
}
return false;
}
// Get the current time
struct timespec get_current_time()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return time;
}
// Function to convert timespec to microseconds
long long timespec_to_microseconds(struct timespec time)
{
return (time.tv_sec * 1000000LL) + (time.tv_nsec / 1000);
}
// Calculate the time difference in milliseconds
double calculate_time_difference(struct timespec start, struct timespec end)
{
double start_ms = start.tv_sec * 1000.0 + start.tv_nsec / 1000000.0;
double end_ms = end.tv_sec * 1000.0 + end.tv_nsec / 1000000.0;
return end_ms - start_ms;
}
// Get Time Stamp in microseconds in YYYYMMDDHHMMSSmmmuuu format
char* get_time_stamp_usec(char time_stamp_str[])
{
// initialization to measure time stamp --This part should be moved to inilization part
time_t my_time;
struct tm * timeinfo;
time (&my_time);
struct timeval tv;
// get time stamp
timeinfo = localtime (&my_time);
gettimeofday(&tv,NULL);
// Add time stamp: yyyy mm dd hh mm ss usec
uint16_t year= timeinfo->tm_year+1900;
uint8_t mon= timeinfo->tm_mon+1;
uint8_t mday= timeinfo->tm_mday;
uint8_t hour= timeinfo->tm_hour;
uint8_t min= timeinfo->tm_min;
uint8_t sec= timeinfo->tm_sec;
uint16_t usec= (tv.tv_usec);
//printf ("Time stamp: %d_%d_%d_%d_%d_%d_%d \n",year,mon,mday,hour,min,sec,usec);
//sprintf(time_stamp_str, "%d_%d_%d_%d_%d_%d_%d",year,mon,mday,hour,min,sec,usec);
sprintf(time_stamp_str, "%04d%02d%02d%02d%02d%02d%06d", year, mon, mday, hour, min, sec, usec);
return time_stamp_str;
}
// Convert timestamp string to integer
int convert_time_stamp_to_int(const char* timestamp)
{
return atoi(timestamp);
}
// Split timestamp string and convert to integer
int split_time_stamp_and_convert_to_int (char time_stamp_str[], int shift, int length)
{
char time_part[length+1]; // Buffer to hold the date part YYYYMMDD or HHMMSSmmm
// Copy the first 8 or 9 characters (YYYYMMDD) to HHMMSSmmm
strncpy(time_part, time_stamp_str + shift, length);
time_part[length] = '\0'; // Null-terminate the string
// Convert timestamp string to integer
return convert_time_stamp_to_int(time_part);
}
void err_exit(char *buf)
{
fprintf(stderr, "%s\n", buf);
exit(1);
}
// create shared memory
int create_shm(char **addrN, const char *shm_path, int projectId)
{
key_t key;
if (-1 != open(shm_path, O_CREAT, 0777)) {
key = ftok(shm_path, projectId);
} else {
err_exit("Error: open shared memory");
}
if ( key < 0 ) {
err_exit("Error: ftok error");
}
int shm_id;
shm_id = shmget(key, SHMSIZE, IPC_CREAT | IPC_EXCL | 0664);
if ( shm_id == -1 ) {
if ( errno == EEXIST ) {
printf("Error: shared memeory already exist\n");
shm_id = shmget(key ,0, 0);
printf("reference shm_id = %d\n", shm_id);
} else {
perror("errno");
err_exit("shmget error");
}
}
char *addr;
// address to attach - attach for read & write
if ( (addr = shmat(shm_id, 0, 0) ) == (void*)-1) {
if (shmctl(shm_id, IPC_RMID, NULL) == -1)
err_exit("Error: shmctl error");
else {
printf("Attach shared memory failed\n");
printf("remove shared memory identifier successful\n");
}
err_exit("shmat error");
}
*addrN = addr;
return shm_id;
}
// delete shared memory
void del_shm(char *addr, int shm_id)
{
if ( shmdt(addr) < 0) err_exit("shmdt error");
if (shmctl(shm_id, IPC_RMID, NULL) == -1)
err_exit("shmctl error");
else {
printf("Remove shared memory identifier successful\n");
}
}
/* this function sends the activated traces to the nr-softmodem */
void activate_traces(int socket, int number_of_events, int *is_on)
{
char t = 1;
if (socket_send(socket, &t, 1) == -1 ||
socket_send(socket, &number_of_events, sizeof(int)) == -1 ||
socket_send(socket, is_on, number_of_events * sizeof(int)) == -1)
abort();
}
void usage(void)
{
printf(
"options:\n"
" -d <database file> this option is mandatory\n"
" -ip <host> connect to given IP address (default %s)\n"
" -p <port> connect to given port (default %d)\n",
DEFAULT_REMOTE_IP,
DEFAULT_REMOTE_PORT
);
exit(1);
}
// class to store the message and the action to enable capture selected data
// 0: do not record message
// 1: record message
struct trace_struct {
char on_off_name[100];
int on_off_action;
};
// struct for trace message based on Data Collection Trace Messages Structure
// you need to define the vararibles of each message
typedef struct {
/* Data Collection Trace Message Structure */
int frame;
int slot;
int datetime_yyyymmdd;
int datetime_hhmmssmmm;
int frame_type, freq_range, subcarrier_spacing, cyclic_prefix, symbols_per_slot;
int Nid_cell, rnti;
int rb_size, rb_start, start_symbol_index,nr_of_symbols;
int qam_mod_order,mcs_index,mcs_table,nrOfLayers,transform_precoding;
int dmrs_config_type,ul_dmrs_symb_pos,number_dmrs_symbols;
int dmrs_port,dmrs_nscid,nb_antennas_tx,number_of_bits;
int data_size, data;
} event_trace_msg_data;
void setup_trace_msg_data(event_trace_msg_data *d, void *database)
{
database_event_format f;
int i;
/* Data Collection Trace Message Structure */
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols :
// int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_tx : int,number_of_bits : buffer,data
// Initialize the data structure
d->frame = -1;
d->slot = -1;
d->datetime_yyyymmdd = -1;
d->datetime_hhmmssmmm = -1;
d->frame_type = -1;
d->freq_range = -1;
d->subcarrier_spacing = -1;
d->cyclic_prefix = -1;
d->symbols_per_slot = -1;
d->Nid_cell = -1;
d->rnti = -1;
d->rb_size = -1;
d->rb_start = -1;
d->start_symbol_index = -1;
d->nr_of_symbols = -1;
d->qam_mod_order = -1;
d->mcs_index = -1;
d->mcs_table = -1;
d->nrOfLayers = -1;
d->transform_precoding = -1;
d->dmrs_config_type = -1;
d->ul_dmrs_symb_pos = -1;
d->number_dmrs_symbols = -1;
d->dmrs_port = -1;
d->dmrs_nscid = -1;
d->nb_antennas_tx = -1;
d->number_of_bits = -1;
d->data = -1;
/* this macro looks for a particular element and checks its type */
#define G(var_name, var_type, var) \
if (!strcmp(f.name[i], var_name)) { \
if (strcmp(f.type[i], var_type)) { printf("Error: Trace Message has a bad type for %s\n", var_name); exit(1); } \
var = i; \
continue; \
}
/* ------------------------------*/
/* Create Macro for Data Collection Trace Message */
/* ------------------------------*/
// Data Collection Trace Message Structure
// Example: GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID,
// GNB_PHY_UL_FD_CHAN_EST_DMRS_POS, GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL
// GNB_PHY_UL_PAYLOAD_RX_BITS
// UE_PHY_UL_SCRAMBLED_TX_BITS
// UE_PHY_UL_PAYLOAD_TX_BITS
// Get a template of any message based on Data Collection Trace Messages Structure
int Trace_MSG_ID = event_id_from_name(database, "GNB_PHY_UL_FD_PUSCH_IQ");
f = get_format(database, Trace_MSG_ID);
/* get the elements of the trace
* the value is an index in the event, see below */
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols :
// int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_tx : int,number_of_bits : buffer,data
for (i = 0; i < f.count; i++) {
G("frame", "int", d->frame)
G("slot", "int", d->slot)
G("datetime_yyyymmdd", "int", d->datetime_yyyymmdd)
G("datetime_hhmmssmmm", "int", d->datetime_hhmmssmmm)
G("frame_type", "int", d->frame_type)
G("freq_range", "int", d->freq_range)
G("subcarrier_spacing", "int", d->subcarrier_spacing)
G("cyclic_prefix", "int", d->cyclic_prefix)
G("symbols_per_slot", "int", d->symbols_per_slot)
G("Nid_cell", "int", d->Nid_cell)
G("rnti", "int", d->rnti)
G("rb_size", "int", d->rb_size)
G("rb_start", "int", d->rb_start)
G("start_symbol_index", "int", d->start_symbol_index)
G("nr_of_symbols", "int", d->nr_of_symbols)
G("qam_mod_order", "int", d->qam_mod_order)
G("mcs_index", "int", d->mcs_index)
G("mcs_table", "int", d->mcs_table)
G("nrOfLayers", "int", d->nrOfLayers)
G("transform_precoding", "int", d->transform_precoding)
G("dmrs_config_type", "int", d->dmrs_config_type)
G("ul_dmrs_symb_pos", "int", d->ul_dmrs_symb_pos)
G("number_dmrs_symbols", "int", d->number_dmrs_symbols)
G("dmrs_port", "int", d->dmrs_port)
G("dmrs_nscid", "int", d->dmrs_nscid)
// bug in T-Tracer Framework, need to be checked that is why we are getting
// the right tx antennas only if we set to rx "nb_antennas_rx"
G("nb_antennas_rx", "int", d->nb_antennas_tx)
G("number_of_bits", "int", d->number_of_bits)
G("data", "buffer", d->data)
}
//if (d->frame == -1 || d->slot == -1) goto error;
#undef G
return;
}
// Function to check if a value is in the array
int isValueInArray(int value, int arr[], int size)
{
for (int i = 0; i < size; i++) {
if (arr[i] == value) {
return 1; // Value found
}
}
return 0; // Value not found
}
void reestablish_connection(int *socket, const char *ip, int port, int number_of_events, int *is_on)
{
clear_remote_config();
if (*socket != -1) close(*socket);
/* connect to the nr-softmodem */
*socket = connect_to(ip, port);
if (*socket == -1) {
printf("\n Failed to connect to nr-softmodem. Retrying...\n");
return;
}
printf("\n Connected");
/* activate the traces in the nr-softmodem */
activate_traces(*socket, number_of_events, is_on);
printf("\n Activated Traces in nr-softmodem");
}
int main(int n, char **v)
{
// Define and initialize an array of strings to list
// trace_msgs_support_data_Collection_format
// it is used to parse the requested messages if it is based
// on Data Collection Trace Messages Structure and supported tracer messages indices
const char *traces_iq_support_data_Collection_format[] = {
"GNB_PHY_UL_FD_PUSCH_IQ",
"GNB_PHY_UL_FD_DMRS",
"GNB_PHY_UL_FD_CHAN_EST_DMRS_POS",
"GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL"
};
const char *traces_bits_support_data_Collection_format[] = {
"GNB_PHY_UL_PAYLOAD_RX_BITS",
"UE_PHY_UL_SCRAMBLED_TX_BITS",
"UE_PHY_UL_PAYLOAD_TX_BITS"
};
// extra number of records to simlify Sync between base station and UE synchronization records.
// if you have network delay, you can increase the number of records to capture
int max_sync_offset = 6; // 6 frames ~ 60 ms
// all supported messages
// Calculate the size of the combined array
int n_iq_msgs = sizeof(traces_iq_support_data_Collection_format) / sizeof(traces_iq_support_data_Collection_format[0]);
int n_bits_msgs = sizeof(traces_bits_support_data_Collection_format) / sizeof(traces_bits_support_data_Collection_format[0]);
int n_msgs_based_data_Collection_format = n_iq_msgs + n_bits_msgs;
// Create the combined array
const char *traces_support_data_Collection_format[n_msgs_based_data_Collection_format];
// Copy IQ messages to the combined array
for (int i = 0; i < n_iq_msgs; i++) {
traces_support_data_Collection_format[i] = traces_iq_support_data_Collection_format[i];
}
// Copy Bits messages to the combined array
for (int i = 0; i < n_bits_msgs; i++) {
traces_support_data_Collection_format[i + n_iq_msgs] = traces_bits_support_data_Collection_format[i];
}
uint16_t msg_id = 0;
uint16_t start_frame_number = 0;
uint32_t number_records = 0; // number of records to capture, it is number of slots
// array to store the requested tracer messages indices
int req_tracer_msgs_indices[100] = {0};
// define variables --> to do: add all of them to be class of pointers
char *database_filename = NULL;
void *database;
char *ip = DEFAULT_REMOTE_IP;
char ip_address[16]; // max IP address length is 15 + 1 for null terminator
int port = DEFAULT_REMOTE_PORT;
int *is_on;
int number_of_events;
int i;
int socket = -1;
char trace_time_stamp_str[30];
// data structure for the trace messages based on Data Collection Trace Messages Structure
event_trace_msg_data trace_msg_data;
// initlization variables
unsigned int bufIdx_wr = 0;
unsigned int bufIdx_rd = 0;
uint8_t num_req_tracer_msgs = 0;
// initilaze shared memory
char *addr_wr,*addr_rd;
printf("\n Data Collection Service: Initializing shared memory ...");
printf("\n Directory 1: %s, Directory 2: %s", GETKEYDIR1_UE, GETKEYDIR2_UE);
printf("\n Project ID: %d\n", PROJECTID_UE);
int shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_UE, PROJECTID_UE);
int shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_UE, PROJECTID_UE);
del_shm(addr_wr, shm_id_wr);
del_shm(addr_rd, shm_id_rd);
shm_id_wr = create_shm(&addr_wr, GETKEYDIR1_UE, PROJECTID_UE);
shm_id_rd = create_shm(&addr_rd, GETKEYDIR2_UE, PROJECTID_UE);
/* write on a socket fails if the other end is closed and we get SIGPIPE */
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) abort();
/* parse command line options */
// Port number and IP address are given via API and not via command line
for (i = 1; i < n; i++) {
if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) usage();
if (!strcmp(v[i], "-d"))
{ if (i > n-2) usage(); database_filename = v[++i]; continue; }
if (!strcmp(v[i], "-ip")) { if (i > n-2) usage(); ip = v[++i]; continue; }
if (!strcmp(v[i], "-p"))
{ if (i > n-2) usage(); port = atoi(v[++i]); continue; }
usage();
}
if (database_filename == NULL) {
printf("ERROR: Provide a database file (-d)\n");
exit(1);
}
/* load the database T_messages.txt */
database = parse_database(database_filename);
load_config_file(database_filename);
/* an array of int for all the events defined in the database is needed */
number_of_events = number_of_ids(database);
is_on = calloc(number_of_events, sizeof(int));
if (is_on == NULL) abort();
// Set first byte of the shared memory to 0
addr_rd[0] = 0; // important to check if we have new requested messages
// read requested tracer msg indices from memory
printf("\n Data Collection Service: Waiting for messages request ...\n");
// 0: Wait
// 1: config
// 2: record
// 3: stop
// Wait for Action Config
while (1) {
if ((uint8_t)(addr_rd[0]) == 0){
usleep(50); // sleep for 50 us: 0.5 ms slot duration
}
// config state
else if ((uint8_t)(addr_rd[0]) == 1) {
get_time_stamp_usec(trace_time_stamp_str);
printf("\n Received config message. Time Stamp: %s", trace_time_stamp_str);
// get the IP address length in bytes
bufIdx_rd = 1;
uint8_t ip_address_length = addr_rd[bufIdx_rd];
bufIdx_rd +=1; // + 1 byte = IP address length
// read the IP address
for (int i = 0; i < ip_address_length; i++) {
ip_address[i] = addr_rd[bufIdx_rd];
bufIdx_rd +=1;
}
ip = ip_address;
// get the array bytes of the port number : 2 bytes
uint8_t port_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1]};
bufIdx_rd +=2; // + 2 bytes = start frame number
port = combine_bytes(port_number_bytes, 2);
printf("\n Parameters: IP Address length: %d, IP Address: %s, Port Number: %d \n", ip_address_length, ip_address, port);
addr_rd[0] = 0; // reset memory : to wait for record action
break;
}
}
// Establish connection to the nr-softmodem
clear_remote_config();
if (socket != -1) close(socket);
/* connect to the nr-softmodem */
socket = connect_to(ip, port);
printf("\n Connected to nr-UEsoftmodem");
// Read Action record or stop
printf("\n Data Collection Service: Waiting for record message ...\n");
while (1) {
// wait for Action record
if ((uint8_t)(addr_rd[0]) == 0){
usleep(50); // sleep for 50 us: 0.5 ms slot duration
}
// quit state
else if ((uint8_t)(addr_rd[0]) == 3) {
printf("\n Received 'stop' command. Exiting...");
printf("\n ");
// Clean up and exit
break;
}
// record state
else if ((uint8_t)(addr_rd[0]) == 2){
// clear remote buffer if there is
clear_remote_config();
get_time_stamp_usec(trace_time_stamp_str);
printf("\n Received record message. Time Stamp: %s", trace_time_stamp_str);
// read number of requested messages
bufIdx_rd = 1;
num_req_tracer_msgs = addr_rd[bufIdx_rd];
bufIdx_rd +=1;
printf("\n Number of requested tracer messages: %d,", num_req_tracer_msgs);
// reset memory : action to wait for next record action
addr_rd[0] = 0;
// read tracer msg IDs - every message ID has been stored in two bytes
for (uint8_t msg_n = 0; msg_n < num_req_tracer_msgs; msg_n++) {
// get the array bytes of the tracer message ID: 2 bytes
uint8_t msg_id_bytes [2]= {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1]};
bufIdx_rd +=2; // + 2 bytes = message ID
msg_id= combine_bytes(msg_id_bytes, 2);
req_tracer_msgs_indices[msg_n] = msg_id;
printf(" msg_id: %d, ", msg_id);
}
// get the array bytes of the number of records: 4 bytes
uint8_t number_records_bytes[4] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1], addr_rd[bufIdx_rd+2], addr_rd[bufIdx_rd+3]};
bufIdx_rd +=4; // + 4 bytes = number of records
number_records = combine_bytes(number_records_bytes, 4);
printf("num_records: %d, ", number_records);
// get the array bytes of the start frame number : 2 bytes
uint8_t start_frame_number_bytes[2] = {addr_rd[bufIdx_rd], addr_rd[bufIdx_rd+1]};
bufIdx_rd +=2; // + 2 bytes = start frame number
start_frame_number = combine_bytes(start_frame_number_bytes, 2);
printf("start_frame: %d\n", start_frame_number);
/* activate the trace in this array */
printf("\n Activate Tracer messages in on_off array: \n");
for (i = 0; i < num_req_tracer_msgs; i++){
char *on_off_name= event_name_from_id(database, req_tracer_msgs_indices[i]);
on_off(database, on_off_name, is_on, 1);
printf("%d, %s, ", req_tracer_msgs_indices[i], on_off_name);
//on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1);
}
// get the event IDs for the bit messages
int traces_bits_support_data_Collection_format_idx [n_bits_msgs];
for (int i = 0; i < n_bits_msgs; i++) {
traces_bits_support_data_Collection_format_idx[i] =
event_id_from_name(database, traces_bits_support_data_Collection_format[i]);
}
// all supported messages
int traces_support_data_Collection_format_idx [n_msgs_based_data_Collection_format];
for (int i = 0; i < n_msgs_based_data_Collection_format; i++) {
traces_support_data_Collection_format_idx[i] =
event_id_from_name(database, traces_support_data_Collection_format[i]);
}
// setup data for the trace messages
setup_trace_msg_data(&trace_msg_data, database);
printf("\n Setup Trace Data Done");
// Get the start time
struct timespec start_time = get_current_time();
/* activate the tracee in the nr-softmodem */
activate_traces(socket, number_of_events, is_on);
printf("\n Activated Traces in nr-UEsoftmodem");
/* a buffer needed to receive events from the nr-softmodem */
OBUF ebuf = { osize: 0, omaxsize: 0, obuf: NULL };
/* read events */
int nrecord_idx = 0;
bufIdx_wr = 0;
// logic to be sure that we have a complete slot record
bool start_recording = false;
bool got_ref_frame_slot = false;
int ref_slot = 0;
int ref_frame = 0;
int slot_difference = 0;
int frame_difference = 0;
int current_frame = 0, prev_frame = 0, current_slot = 0, prev_slot = 0;
// offset to sync between base station and UE synchronization records or power measurements
int sync_offset_index = 0; // increase the index only if the index of frame changes after getting all records
// since we will use the frame differece to do extra records, we should be sure that the last slot is recorded completely
printf("\n\n Data Collection Service: Start reading messages ...");
struct pollfd event_poll_fd;
event_poll_fd.fd = socket;
event_poll_fd.events = POLLIN;
while (1) {
// stop if number of records is done
if ((nrecord_idx >= number_records) && (sync_offset_index >= max_sync_offset)){
// We added one to the number of records to capture the last record completely
// if we have several messages enabled per slot
break;
}
int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll
if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) {
event e = get_event(socket, &ebuf, database);
if (e.type == -1) {
printf("\n Link broken or unexpected message received. Re-establishing connection...\n");
reestablish_connection(&socket, ip, port, number_of_events, is_on);
continue; // Skip further processing and retry
}
//-------------------------
// GNB_PHY_UL_FD_PUSCH_IQ, GNB_PHY_UL_FD_DMRS_ID, GNB_PHY_UL_FD_CHAN_EST_DMRS_POS,
// UE_PHY_UL_SCRAMBLED_TX_BITS, GNB_PHY_UL_PAYLOAD_RX_BITS, UE_PHY_UL_PAYLOAD_TX_BITS
//-------------------------
// is it a requested message
if (isValueInArray(e.type, req_tracer_msgs_indices, num_req_tracer_msgs)) {
// is it based on Data Collection Trace Messages Structure
if (isValueInArray(e.type, traces_support_data_Collection_format_idx, n_msgs_based_data_Collection_format)) {
// Start recording from the next slot to mitigate capturing partial data
// check if the current frame and slot are different from the previous frame and slot
// Then, increase the record index
if (start_recording == false) {
if (got_ref_frame_slot == false) {
ref_frame = e.e[trace_msg_data.frame].i;
ref_slot = e.e[trace_msg_data.slot].i;
printf ("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type),e.type);
got_ref_frame_slot = true;}
current_frame = e.e[trace_msg_data.frame].i;
current_slot = e.e[trace_msg_data.slot].i;
frame_difference = (current_frame - ref_frame + MAX_FRAME_INDEX + 1) % (MAX_FRAME_INDEX + 1);
slot_difference = (current_slot - ref_slot + MAX_SLOT_INDEX + 1) % (MAX_SLOT_INDEX + 1);
printf("\n First frame.slot: %d.%d, current frame.slot: %d.%d, diff frame.slot: %d.%d",
ref_frame, ref_slot,
current_frame, current_slot,
frame_difference, slot_difference);
if ((ref_frame != current_frame) || (ref_slot != current_slot)) {
start_recording = true;
printf("\n Start recording from frame: %d, slot: %d ", e.e[trace_msg_data.frame].i, e.e[trace_msg_data.slot].i);}
}
// start recording from the next frame to mitigate capturing partial data
if (start_recording == true) {
/* this is how to access the elements of the Data Collection trace messages.
* we use e.e[<element>] and then the correct suffix, here
* it's .i for the integer and .b for the buffer and .bsize for the buffer size
* see in event.h the structure event_arg
*/
unsigned char *buf = e.e[trace_msg_data.data].b;
printf("\n\nRecord number: %d", nrecord_idx);
#ifdef DEBUG_BUFFER
printf("\nBuffer index in bytes: %d", bufIdx_wr);
#endif
// add general message header: message ID,
// T-Tracer Message format
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols :
// int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_tx : int,number_of_bits : buffer,data
//printf("\nTX : %d", e.e[trace_msg_data.nb_antennas_tx].i);
memcpy(&addr_wr[bufIdx_wr], &e.type, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.slot].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_yyyymmdd].i, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.datetime_hhmmssmmm].i, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.frame_type].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.freq_range].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.subcarrier_spacing].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.cyclic_prefix].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.symbols_per_slot].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.Nid_cell].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rnti].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_size].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.rb_start].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.start_symbol_index].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nr_of_symbols].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.qam_mod_order].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_index].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.mcs_table].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nrOfLayers].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.transform_precoding].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_config_type].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.ul_dmrs_symb_pos].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_dmrs_symbols].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_port].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.dmrs_nscid].i, sizeof(uint16_t));
bufIdx_wr += sizeof(uint16_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.nb_antennas_tx].i, sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.number_of_bits].i, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
printf("\nTime Stamp: %d_%d", e.e[trace_msg_data.datetime_yyyymmdd].i, e.e[trace_msg_data.datetime_hhmmssmmm].i);
// add message body: length in bytes + recorded data
memcpy(&addr_wr[bufIdx_wr], &e.e[trace_msg_data.data].bsize, sizeof(uint32_t));
bufIdx_wr += sizeof(uint32_t);
// read data from buffer and convert from unsigned char * array to int 16 using the right endianness
// T-tracer: Little Endian
// For BITS Messages, example: UE_PHY_UL_SCRAMBLED_TX_BITS: data in bytes
if(is_bits_messages(traces_bits_support_data_Collection_format_idx, n_bits_msgs, e.type)) {
for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx+=1) {
//printf("%d, ", buf[byte_idx]);
memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
}
}
else {
for (int byte_idx = 0; byte_idx < e.e[trace_msg_data.data].bsize; byte_idx+=2) {
//For a little-endian system:
memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx], sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
memcpy(&addr_wr[bufIdx_wr], &buf[byte_idx+1], sizeof(uint8_t));
bufIdx_wr += sizeof(uint8_t);
}
}
/*
for (int byte_idx_i = 0; byte_idx_i < e.e[d.nr_ul_fd_dmrs_data].bsize; byte_idx_i+=4) {
int16_t I = buf[byte_idx_i] | (buf[byte_idx_i+1] << 8);
int16_t Q = buf[byte_idx_i+2] | (buf[byte_idx_i+3] << 8);
printf ("idx %d, ", byte_idx_i);
printf ("\n%d", I);
printf ("\n%d", Q);
}
*/
// check if the current frame and slot are different from the previous frame and slot
// Then, increase the record index
current_frame = e.e[trace_msg_data.frame].i;
current_slot = e.e[trace_msg_data.slot].i;
// increase sync offset index if the current frame is different from the previous frame
if ((current_frame != prev_frame) && nrecord_idx >= number_records) {
sync_offset_index++;
}
if (current_frame != prev_frame || current_slot != prev_slot) {
nrecord_idx++;
// Update previous frame and slot numbers
prev_frame = current_frame;
prev_slot = current_slot;
}
#ifdef DEBUG_T_Tracer
printf ("\nMessage Info: msg_id %s (%d) \n", event_name_from_id(database, e.type),e.type);
printf("frame %d, slot %d, datetime %d_%d\n",
e.e[trace_msg_data.frame].i,
e.e[trace_msg_data.slot].i,
e.e[trace_msg_data.datetime_yyyymmdd].i,
e.e[trace_msg_data.datetime_hhmmssmmm].i);
printf("frame_type %d, freq_range %d, subcarrier_spacing %d, cyclic_prefix %d, symbols_per_slot %d\n",
e.e[trace_msg_data.frame_type].i,
e.e[trace_msg_data.freq_range].i,
e.e[trace_msg_data.subcarrier_spacing].i,
e.e[trace_msg_data.cyclic_prefix].i,
e.e[trace_msg_data.symbols_per_slot].i);
printf("Nid_cell %d, rnti %d, rb_size %d, rb_start %d, start_symbol_index %d, nr_of_symbols %d\n",
e.e[trace_msg_data.Nid_cell].i,
e.e[trace_msg_data.rnti].i,
e.e[trace_msg_data.rb_size].i,
e.e[trace_msg_data.rb_start].i,
e.e[trace_msg_data.start_symbol_index].i,
e.e[trace_msg_data.nr_of_symbols].i);
printf("qam_mod_order %d, mcs_index %d, mcs_table %d, nrOfLayers %d, transform_precoding %d\n",
e.e[trace_msg_data.qam_mod_order].i,
e.e[trace_msg_data.mcs_index].i,
e.e[trace_msg_data.mcs_table].i,
e.e[trace_msg_data.nrOfLayers].i,
e.e[trace_msg_data.transform_precoding].i);
printf("dmrs_config_type %d, ul_dmrs_symb_pos %d, number_dmrs_symbols %d, dmrs_port %d, dmrs_nscid %d\n",
e.e[trace_msg_data.dmrs_config_type].i,
e.e[trace_msg_data.ul_dmrs_symb_pos].i,
e.e[trace_msg_data.number_dmrs_symbols].i,
e.e[trace_msg_data.dmrs_port].i,
e.e[trace_msg_data.dmrs_nscid].i);
printf("nb_antennas_tx %d, number_of_bits %d, data size %d\n",
e.e[trace_msg_data.nb_antennas_tx].i,
e.e[trace_msg_data.number_of_bits].i,
e.e[trace_msg_data.data].bsize);
#endif
} // End of start recording flag
} // end of if statement for the supported messages based on Data Collection Trace Messages Structure
else {
printf("ERROR: Requested Message is not based on Data Collection Trace Messages Structure\n");
printf("ERROR: Requested Message ID: %d\n", e.type);
}
} // end of if statement for the requested messages
} // end while loop of reading events
else {
// No data, just loop and check time
usleep(100); // optional: avoid busy-waiting
}
} // End of while loop to read messages
// de-activate the tracee in the nr-softmodem
printf("\n De-activated Tracer message:\n");
for (i = 0; i < num_req_tracer_msgs; i++){
char *on_off_name= event_name_from_id(database, req_tracer_msgs_indices[i]);
on_off(database, on_off_name, is_on, 0);
//printf("\n %d, %s, ", req_tracer_msgs_indices[i], on_off_name);
//on_off(database, "GNB_PHY_DL_OUTPUT_SIGNAL", is_on, 1);
}
// De-activate the tracee in the nr-softmodem
activate_traces(socket, number_of_events, is_on);
printf("\n De-activated Traces");
// Get the end time
struct timespec end_time = get_current_time();
// Calculate the time difference
double time_diff = calculate_time_difference(start_time, end_time);
printf("Total Time difference: %.2f ms\n", time_diff);
printf("Time difference per record: %.2f ms\n", time_diff/(number_records+ max_sync_offset));
// discard stale or previous record data for the first DISCARD_RECORD_DURATION_MS
struct timespec record_start, record_now;
clock_gettime(CLOCK_MONOTONIC, &record_start);
while (1) {
clock_gettime(CLOCK_MONOTONIC, &record_now);
double elapsed_ms = calculate_time_difference(record_start, record_now);
if (elapsed_ms >= DISCARD_RECORD_DURATION_MS) {
break; // Stop after 10ms
}
int poll_ret = poll(&event_poll_fd, 1, 1); // 1 ms timeout for poll
if (poll_ret > 0 && (event_poll_fd.revents & POLLIN)) {
event e = get_event(socket, &ebuf, database);
printf("%d", e.type);
if (e.type == -1) {
printf("\n Link broken or unexpected message received. Re-establishing connection...\n");
reestablish_connection(&socket, ip, port, number_of_events, is_on);
continue;
}
} else {
// No data, just loop and check time
usleep(100); // optional: avoid busy-waiting
}
} // End of while loop to discard stale records
}
} // End a while loop to check for the "stop" command
// de-activate the tracee in the nr-softmodem
//free_database(database); //Do on one app, for example on gNB App
free(is_on);
close(socket);
return 0;
}

View File

@@ -0,0 +1,84 @@
{
"id": 0,
"test_config": {
"test_scenario_id": "",
"test_name": "phy_ul_bler_test",
"test_mode": "rf_real_time",
"dut_type": "trad_rx"
},
"environment_emulation": {
"target_link_config": {
"ran_config": {
"uplink": {
"ul_used_signal_BW_MHz": 2.16
}
},
"wireless_channel": {
"downlink": {
"type": "rf_cable"
},
"uplink": {
"type": "statistical_var",
"statistical": {
"var_power_delay_profile": "tdl_d",
"speed_mps": 5.0,
"delay_spread_ns": 50.0,
"snr_db": 20.0,
"predef_channel_profile": null
}
},
"carrierFreqValueList_hz": [
3319680000.0
],
"ul_used_signal_BW_MHz": 2.16
}
}
},
"data_recording_config": {
"data_storage_path": "/home/user/workarea/oai_recorded_data/",
"data_file_format": "SigMF",
"enable_saving_tracer_messages_sigmf": true,
"num_records": 5,
"t_tracer_message_definition_file": "../T/T_messages.txt",
"parameter_map_file": "config/wireless_link_parameter_map.yaml",
"start_frame_number": 5,
"base_station": {
"requested_tracer_messages": [
"GNB_PHY_UL_FD_PUSCH_IQ",
"GNB_PHY_UL_FD_DMRS",
"GNB_PHY_UL_FD_CHAN_EST_DMRS_POS",
"GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL",
"GNB_PHY_UL_PAYLOAD_RX_BITS"
],
"meta_data":{
"num_rx_antennas": 1,
"tx_gain": 48.0,
"rx_gain": 30.0,
"hw_type": "USRP X410",
"hw_subtype": "ZBX",
"seid": "328AB35"
}
},
"user_equipment": {
"requested_tracer_messages": [
"UE_PHY_UL_SCRAMBLED_TX_BITS",
"UE_PHY_UL_PAYLOAD_TX_BITS"
],
"meta_data":{
"num_tx_antennas": 1,
"tx_gain": 48.0,
"rx_gain": 40.0,
"hw_type": "USRP X410",
"hw_subtype": "ZBX",
"seid": "323F75F"
}
},
"common_meta_data": {
"sample_rate": 61440000.0,
"bandwidth": 40000000.0,
"clock_reference": "external"
},
"tracer_service_baseStation_address": "127.0.0.1:2021",
"tracer_service_userEquipment_address": "10.88.136.68:2023"
}
}

View File

@@ -0,0 +1,375 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/config/wireless_link_parameter_map.yaml
# brief Wireless Link Parameter Map Dictionary
# author Abdo Gaber
# date 2024
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
# Wireless Link Parameter Map
# Components:
# Transmitter: list of waveform configuration based on standard
# Parameters pair is given via this parameter map dictionary (Parameter name in Waveform creator vs that in the API ~ SigMF meta-data)
# Having a separate dictionary ease of adoption in case of:
# - Adding new parameters
# - Use different waveform creator that has Different naming scheme
# - Define your own customized standard or new wireless system
# - All changes are limited to parameter map dictionary
# the parameter name of waveform generator has the following style:
# StandardName_WaveformGenerator_parameter, i.e.
# if standard = 5gnr, WaveformGenerator: ni_rfmx_rfws, then the parameter name: 5gnr_ni_rfmx_rfws_parameter
# =====================================================================================
# ======== list of parameter pairs between waveform config and API ~ SigMF Meta-Data ===
# =====================================================================================
# List of waveform generators that generate waveforms
# key:
# standard: "name"
# generator: "link or text ... etc"
# key: Used to create parameter names in wireless_link_parameter_map
# Recommended name for each waveform generator to be given as: standard_WaveformGenerator
# Example: 5g NR waveform created using RFmx with RFWS config file: "5gnr_ni_rfmx_rfws"
# Note: The suffix "_parameter" will be added automatically
# standard name: occur in meta-data
# generator: generator description to occur in meta-data
waveform_generator:
# waveform_source: standard
5gnr_oai: # Generated using OAI
standard: "5gnr"
generator: "OpenAirInterface: https://www.openairinterface.org/"
5gnr_ni_rfmx_rfws: # Generated using NI RFmx Tool
standard: "5gnr"
generator: "NI RFmx Waveform Creator: https://www.ni.com/en-ca/shop/wireless-design-test/application-software-for-wireless-design-test-category/what-is-rfmx.html"
transmitter:
# 5G NR subfield
5gnr:
# frame number
- sigmf_parameter_name: "frame"
description: "5G NR frame number"
5gnr_oai_parameter:
name: "frame"
# slot number
- sigmf_parameter_name: "slot"
description: "5G NR slot number"
5gnr_oai_parameter:
name: "slot"
# bandwidth
- sigmf_parameter_name: "bandwidth"
description: "5G NR bandwidth"
5gnr_ni_rfmx_rfws_parameter:
name: "Bandwidth (Hz)"
5gnr_oai_parameter:
name: "bandwidth"
# Frame structure
- sigmf_parameter_name: "frame_structure"
description: "5G NR duplexing scheme FDD, TDD, user defined"
5gnr_ni_rfmx_rfws_parameter:
name: "Duplex Scheme" # not exact, since it depends if the waveform is for UL or DL
5gnr_oai_parameter:
name: "frame_type"
value_map: # simu value : SigMF value
0: "fdd"
1: "tdd"
# Link direction
- sigmf_parameter_name: "link_direction"
description: "Link direction"
5gnr_ni_rfmx_rfws_parameter:
name: "Link Direction"
# Frequency Range
- sigmf_parameter_name: "frequency_range"
description: "Frequency Range"
5gnr_ni_rfmx_rfws_parameter:
name: "Frequency Range"
value_map: # simu value : SigMF value
"Range 1": "fr1"
"Range 2": "fr2"
5gnr_oai_parameter:
name: "freq_range"
value_map: # simu value : SigMF value
0: "fr1"
1: "fr2"
# Subcarrier spacing in Hz
- sigmf_parameter_name: "subcarrier_spacing"
description: "Subcarrier spacing in Hz"
5gnr_ni_rfmx_rfws_parameter:
name: Subcarrier Spacing (Hz)
5gnr_oai_parameter:
name: subcarrier_spacing
value_map: # simu value : SigMF value
0: 15000
1: 30000
2: 60000
3: 120000
# Sampling rate in Hz
- sigmf_parameter_name: "sample_rate"
description: "Sampling rate in Hz"
5gnr_ni_rfmx_rfws_parameter:
name: "sample_rate_hz"
5gnr_oai_parameter:
name: "sample_rate"
# Cyclic prefix mode
- sigmf_parameter_name: "cp_mode"
description: "Cyclic prefix mode"
5gnr_ni_rfmx_rfws_parameter:
name: "Cyclic Prefix Mode"
5gnr_oai_parameter:
name: "cyclic_prefix"
value_map: # simu value : SigMF value
0: "normal"
1: "extended"
# cell ID
- sigmf_parameter_name: "cell_id"
description: "cell ID"
5gnr_ni_rfmx_rfws_parameter:
name: "Cell ID"
5gnr_oai_parameter:
name: "Nid_cell"
# rnti
- sigmf_parameter_name: "rnti"
description: "RNTI"
5gnr_ni_rfmx_rfws_parameter:
name: "rnti"
5gnr_oai_parameter:
name: "rnti"
# Symbols per slot
- sigmf_parameter_name: "symbols_per_slot"
description: "Symbols per slot"
5gnr_oai_parameter:
name: "symbols_per_slot"
# PUSCH: content
- sigmf_parameter_name: "pusch:content"
description: "PUSCH: content"
# PUSCH: channel code
- sigmf_parameter_name: "pusch:channel_code"
description: "PUSCH: channel code"
# PUSCH: number of PRBs
- sigmf_parameter_name: "pusch:num_prb"
description: "PUSCH Resource Block Size"
5gnr_oai_parameter:
name: "rb_size"
# PUSCH RB Start index
- sigmf_parameter_name: "pusch:start_prb"
description: "PUSCH Resource Block Start"
5gnr_oai_parameter:
name: "rb_start"
# PUSCH MAPPING type
- sigmf_parameter_name: "pusch:mapping_type"
description: "PUSCH Mapping Type"
5gnr_oai_parameter:
name: "mappingType"
value_map: # simu value : SigMF value
"NR_PUSCH_TimeDomainResourceAllocation__mappingType_typeA": "A"
"NR_PUSCH_TimeDomainResourceAllocation__mappingType_typeB": "B"
# PUSCH: OFDM symbol start index within slot
- sigmf_parameter_name: "pusch:start_ofdm_symbol"
description: "PUSCH: OFDM symbol start index within slot"
5gnr_oai_parameter:
name: "start_symbol_index"
# PUSCH: number of OFDM symbols in one slot
- sigmf_parameter_name: "pusch:num_ofdm_symbols"
description: "PUSCH: number of OFDM symbols in one slot"
5gnr_oai_parameter:
name: "nr_of_symbols"
# PUSCH: modulation
- sigmf_parameter_name: "pusch:modulation"
description: "PUSCH: modulation"
5gnr_ni_rfmx_rfws_parameter:
name: "UL Modulation Type"
value_map: # simu value : SigMF value
"8PSK": "8_psk"
"QPSK": "qpsk"
"QAM16": "16_qam"
"QAM64": "64_qam"
"QAM256": "256_qam"
"QAM1024": "1024_qam"
"Standard": ""
5gnr_oai_parameter:
name: "qam_mod_order"
value_map: # simu value : SigMF value
2 : "qpsk"
4 : "16_qam"
6 : "64_qam"
8 : "256_qam"
"Standard": ""
# PUSCH MCS Index
- sigmf_parameter_name: "pusch:mcs"
description: "PUSCH MCS Index"
5gnr_oai_parameter:
name: "mcs_index"
# PUSCH MCS Index Table
- sigmf_parameter_name: "pusch:mcs_table_index"
description: "PUSCH MCS Table Index"
5gnr_oai_parameter:
name: "mcs_table"
# PUSCH: number of transmission layers
- sigmf_parameter_name: "pusch:num_layer"
description: "PUSCH: number of transmission layers"
5gnr_oai_parameter:
name: "nrOfLayers"
# Transform Precoding
- sigmf_parameter_name: "pusch:transform_precoding"
description: "PUSCH Transform Precoding"
5gnr_oai_parameter:
name: "transform_precoding"
value_map: # simu value : SigMF value
0: "enabled"
1: "disabled"
# PUSCH DMRS: content
- sigmf_parameter_name: "pusch_dmrs:content"
description: "PUSCH DMRS: content"
#"none": "none"
#"random_qpsk": "random_qpsk"
#"compliant": "compliant"
# PUSCH DMRS: resource map configuration
- sigmf_parameter_name: "pusch_dmrs:resource_map_config"
description: "PUSCH DMRS: resource map configuration"
5gnr_oai_parameter:
name: "dmrs_config_type"
value_map:
0: "5g nr type 1"
1: "5g nr type 2"
# PUSCH DMRS: OFDM symbol start index within slot
- sigmf_parameter_name: "pusch_dmrs:start_ofdm_symbol"
description: "PUSCH DMRS: OFDM symbol start index within slot"
5gnr_oai_parameter:
# to be derived from this OAI parameter
name: "ul_dmrs_symb_pos"
# PUSCH DMRS: DMRS duration in number of OFDM symbols
- sigmf_parameter_name: "pusch_dmrs:duration_num_ofdm_symbols"
description: "PUSCH DMRS: DMRS duration in number of OFDM symbols"
5gnr_oai_parameter:
# to be derived from this OAI parameter
name: "ul_dmrs_symb_pos"
# PUSCH DMRS: OFDM symbol indices within resource grid
- sigmf_parameter_name: "pusch_dmrs:ofdm_symbol_idx"
description: "PUSCH DMRS: OFDM symbol indices within resource grid"
5gnr_oai_parameter:
# to be derived from this OAI parameter
name: "ul_dmrs_symb_pos"
# PUSCH DMRS: number of additional PUSCH DMRS positions
- sigmf_parameter_name: "pusch_dmrs:num_add_positions"
description: "PUSCH DMRS: number of additional PUSCH DMRS positions"
5gnr_oai_parameter:
# to be derived from this OAI parameter
name: "ul_dmrs_symb_pos"
# PUSCH DMRS: antenna port
- sigmf_parameter_name: "pusch_dmrs:antennna_port"
description: "PUSCH DMRS: antenna port"
5gnr_oai_parameter:
name: "dmrs_port"
# PUSCH DMRS: scrambler initialization
- sigmf_parameter_name: "pusch_dmrs:nscid"
description: "PUSCH DMRS: scrambler initialization"
5gnr_oai_parameter:
name: "dmrs_nscid"
# number of rx antennas
- sigmf_parameter_name: "num_rx_antennas"
description: "Number of Rx antennas"
5gnr_oai_parameter:
name: "nb_antennas_rx"
# number of tx antennas
- sigmf_parameter_name: "num_tx_antennas"
description: "Number of Tx antennas"
5gnr_oai_parameter:
name: "nb_antennas_tx"
channel:
# Signal-to-noise ratio (Es/N0) in dB
- sigmf_parameter_name: "snr_esn0_db"
description: "Signal-to-noise ratio (Es/N0) in dB"
# Signal-to-noise ratio (Eb/N0) in dB
- sigmf_parameter_name: "snr_ebn0_db"
description: "Signal-to-noise ratio (Eb/N0) in dB"
# Channel model
- sigmf_parameter_name: "channel_model"
description: "Fading channel model"
# Number of channel taps
- sigmf_parameter_name: "num_channel_taps"
description: "Number of channel taps"
# Channel attenuation level in dB
- sigmf_parameter_name: "channel_attenuation_db"
description: "Channel attenuation level in dB"
# Nominal delay spread in s
- sigmf_parameter_name: "delay_spread"
description: "Nominal delay spread in s"
# UE speed in m/s
- sigmf_parameter_name: "speed"
description: "UE speed in m/s"
# Carrier frequency in Hz
- sigmf_parameter_name: "carrier_frequency"
description: "Carrier frequency in Hz"
receiver:
# Frequency domain interpolation method for channel estimation
- sigmf_parameter_name: "channel_estimation_freq_interpolation"
description: "Frequency domain interpolation method for channel estimation"
# Time domain interpolation method for channel estimation
- sigmf_parameter_name: "channel_estimation_time_interpolation"
description: "Time domain interpolation method for channel estimation"
# Channel equalization method
- sigmf_parameter_name: "channel_equalization_method"
description: "Channel equalization method"
# Symbol demapping method
- sigmf_parameter_name: "symbol_demapping_method"
description: "Symbol demapping method"
# Manufacturer of the hardware used to receive the signal
- sigmf_parameter_name: "manufacturer"
description: "Manufacturer"
# Unique ID of the emitter, USRP serial number --> usrp_mboard_serial
- sigmf_parameter_name: "seid"
description: "Emitter ID (USRP serial number)"
# For USRP, use “daughterboard ID, a string read out from the device shortened by the hex ID e.g. (0x0085)
- sigmf_parameter_name: "hw_subtype"
description: "Hardware subtype (for USRP: daughterboard ID)"
# RX sync reference source ("internal", "external", "gpsdo")
- sigmf_parameter_name: "clock_reference"
description: "RX clock reference"
required_field: false
# Estimated SNR in dB
- sigmf_parameter_name: "snr_estimate_db"
description: "SNR estimate in dB"
required_field: false
# Result of coarse PSS-based timing estimation (used for synchronization)
- sigmf_parameter_name: "timing_estimate_coarse"
description: "Coarse timing estimate in samples"
required_field: false"
# Result of coarse PSS-based frequency offset estimation (used for synchronization)
- sigmf_parameter_name: "cfo_estimate_coarse"
description: "Coarse frequency estimate in Hz"
required_field: false
# Result of fine CRS-based frequency offset estimation (used for synchronization)
- sigmf_parameter_name: "cfo_estimate_fine"
description: "Fine frequency estimate in Hz"
required_field: false
# Number of transmitted payload bits
- sigmf_parameter_name: "num_payload_bits"
description: "Number of transmitted payload bits"
required_field: false
# Number of bit errors when applying traditional RX processing
- sigmf_parameter_name: "bit_errors_trad_rx"
description: "Bit errors"
required_field: false

View File

@@ -0,0 +1,832 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/data_recording_app_v1.0.py
# brief main application of synchronized real-time data recording
# author Abdo Gaber
# date 2025
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
import sysv_ipc as ipc
import struct
import time
from datetime import datetime
from termcolor import colored
import numpy as np
import json
import concurrent.futures
# from concurrent import futures
import threading
# import related functions
from lib import sigmf_interface
# import library functions
from lib import sync_service
from lib import data_recording_messages_def
from lib import common_utils
from lib import config_interface
DEBUG_WIRELESS_RECORDED_DATA = True
DEBUG_BUFFER_READING = False
# globally applicable metadata
global_info = {
"author": "Abdo Gaber",
"description": "Synchronized Real-Time Data Recording",
"timestamp": 0,
"collection_file_prefix": "data-collection", # collection file name prefix "deap-rx + str(...)"
"collection_file": "", # Reserved to be created in the code: “data-collection_rec-0_TIME-STAMP”
"datetime_offset": "", # datetime offset between current location and UTC/Zulu timezone
# Example: "+01:00" for Berlin, Germany
"save_config_data_recording_app_json": True,
"waveform_generator": "5gnr_oai",
"extensions": {},
}
# Supported OAI Trace messages
# UL receiver messages
# gNB IQ Msgs: "GNB_PHY_UL_FD_PUSCH_IQ", "GNB_PHY_UL_FD_DMRS", "GNB_PHY_UL_FD_CHAN_EST_DMRS_POS",
# "GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL"
# gNB BITS Msgs: "GNB_PHY_UL_PAYLOAD_RX_BITS"
# UE BITS Msgs: "UE_PHY_UL_SCRAMBLED_TX_BITS", "UE_PHY_UL_PAYLOAD_TX_BITS"
supported_oai_tracer_messages = {
# gNB messages
"GNB_PHY_UL_FD_PUSCH_IQ": {
"file_name_prefix": "rx-fd-data",
"scope": "gNB",
"description": "Frequency-domain RX data",
"serialization_scheme": ["subcarriers", "ofdm_symbols"],
},
"GNB_PHY_UL_FD_DMRS": {
"file_name_prefix": "tx-pilots-fd-data",
"scope": "gNB",
"description": "Frequency-domain TX PUSCH DMRS data",
"serialization_scheme": ["subcarriers", "ofdm_symbols"],
},
"GNB_PHY_UL_FD_CHAN_EST_DMRS_POS": {
"file_name_prefix": "raw-ce-fd-data",
"scope": "gNB",
"description": "Frequency-domain raw channel estimates (at DMRS positions)",
"serialization_scheme": ["subcarriers", "ofdm_symbols"],
},
"GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL": {
"file_name_prefix": "raw-inter-ce-fd-data",
"scope": "gNB",
"description": "Interpolcated Frequency-domain raw channel estimates",
"serialization_scheme": ["subcarriers", "ofdm_symbols"],
},
"GNB_PHY_UL_PAYLOAD_RX_BITS": {
"file_name_prefix": "rx-payload-bits",
"scope": "gNB",
"description": "Received PUSCH payload bits",
"serialization_scheme": ["bits", "subcarriers", "ofdm_symbols"],
},
# UE messages
"UE_PHY_UL_SCRAMBLED_TX_BITS": {
"file_name_prefix": "tx-scrambled-bits",
"scope": "UE",
"description": "Transmitted scrambled PUSCH bits",
"serialization_scheme": ["bits", "subcarriers", "ofdm_symbols"],
},
"UE_PHY_UL_PAYLOAD_TX_BITS": {
"file_name_prefix": "tx-payload-bits",
"scope": "UE",
"description": "Transmitted PUSCH payload bits",
"serialization_scheme": ["bits", "subcarriers", "ofdm_symbols"],
},
}
# -------------------------------------------
# System configuration: gNB
project_id_gnb = 2335
read_shm_path_gnb = "/tmp/gnb_app1"
write_shm_path_gnb = "/tmp/gnb_app2"
# System configuration: UE
project_id_ue = 2336
read_shm_path_ue = "/tmp/ue_app1"
write_shm_path_ue = "/tmp/ue_app2"
# initialize shared memory
def attach_shm(shm_path, project_id):
key = ipc.ftok(shm_path, project_id)
shm = ipc.SharedMemory(key, 0, 0)
# I found if we do not attach ourselves
# it will attach as ReadOnly.
shm.attach(0, 0)
return shm
def detach_shm(shm):
try:
shm.detach()
print("Shared memory detached successfully.")
except ipc.ExistentialError:
print("Shared memory segment does not exist.")
def remove_shm(shm):
try:
shm.remove()
print("Shared memory removed successfully.")
except ipc.ExistentialError:
print("Shared memory segment does not exist.")
# check data if avalible in the shared memory
def is_data_available_in_memory(shm, bufIdx, general_message_header_length, timeout=20):
start_time = time.time()
while True:
buf = shm.read(bufIdx + general_message_header_length)
n_bytes = sum(buf)
print("Data Recording App: Waiting for Measurements!")
if n_bytes > 0:
print("There is data in memory, n_bytes: ", n_bytes)
return True
if (time.time() - start_time) > timeout:
break
time.sleep(1)
return False
# Read data from Shared memory based Data Conversion Service message structure
def read_data_from_shm(shm, bufIdx, tracer_msgs_identities):
# print buffer index
if DEBUG_BUFFER_READING:
print("Buffer Index: ", bufIdx)
# get general message header list
general_msg_header_list, general_message_header_length = data_recording_messages_def.get_general_msg_header_list()
buf = shm.read(bufIdx + general_message_header_length)
n_bytes = sum(buf)
if n_bytes == 0:
raise Exception('ERROR: No data available in memory')
msg_id = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("msg_id")])[0]
bufIdx += general_msg_header_list.get("msg_id")
frame = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("frame")])[0]
bufIdx += general_msg_header_list.get("frame")
slot = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("slot")])[0]
bufIdx += general_msg_header_list.get("slot")
# get time stamp: yyyy mm dd hh mm ss msec
nr_trace_time_stamp_yyymmdd = \
struct.unpack('<i', buf[bufIdx:bufIdx+general_msg_header_list.get("datetime_yyyymmdd")])[0]
bufIdx += general_msg_header_list.get("datetime_yyyymmdd")
nr_trace_time_stamp_hhmmssmmm = \
struct.unpack('<i', buf[bufIdx:bufIdx+general_msg_header_list.get("datetime_hhmmssmmm")])[0]
bufIdx += general_msg_header_list.get("datetime_hhmmssmmm")
time_stamp_milli_sec = str(nr_trace_time_stamp_yyymmdd)+"_"+str(nr_trace_time_stamp_hhmmssmmm)
# get frame type
frame_type = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("frame_type")])[0]
bufIdx += general_msg_header_list.get("frame_type")
# get frequency range
freq_range = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("freq_range")])[0]
bufIdx += general_msg_header_list.get("freq_range")
# get subcarrier spacing
subcarrier_spacing = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("subcarrier_spacing")])[0]
bufIdx += general_msg_header_list.get("subcarrier_spacing")
# get cyclic prefix
cyclic_prefix = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("cyclic_prefix")])[0]
bufIdx += general_msg_header_list.get("cyclic_prefix")
# get symbols per slot
symbols_per_slot = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("symbols_per_slot")])[0]
bufIdx += general_msg_header_list.get("symbols_per_slot")
# get Nid cell
Nid_cell = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("Nid_cell")])[0]
bufIdx += general_msg_header_list.get("Nid_cell")
# get rnti
rnti = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("rnti")])[0]
bufIdx += general_msg_header_list.get("rnti")
# get rb size
rb_size = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("rb_size")])[0]
bufIdx += general_msg_header_list.get("rb_size")
# get rb start
rb_start = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("rb_start")])[0]
bufIdx += general_msg_header_list.get("rb_start")
# get start symbol index
start_symbol_index = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("start_symbol_index")])[0]
bufIdx += general_msg_header_list.get("start_symbol_index")
# get number of symbols
nr_of_symbols = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("nr_of_symbols")])[0]
bufIdx += general_msg_header_list.get("nr_of_symbols")
# get qam modulation order
qam_mod_order = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("qam_mod_order")])[0]
bufIdx += general_msg_header_list.get("qam_mod_order")
# get mcs index
mcs_index = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("mcs_index")])[0]
bufIdx += general_msg_header_list.get("mcs_index")
# get mcs table
mcs_table = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("mcs_table")])[0]
bufIdx += general_msg_header_list.get("mcs_table")
# get number of layers
nrOfLayers = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("nrOfLayers")])[0]
bufIdx += general_msg_header_list.get("nrOfLayers")
# get transform precoding
transform_precoding = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("transform_precoding")])[0]
bufIdx += general_msg_header_list.get("transform_precoding")
# get dmrs config type
dmrs_config_type = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("dmrs_config_type")])[0]
bufIdx += general_msg_header_list.get("dmrs_config_type")
# get ul dmrs symb pos
ul_dmrs_symb_pos = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("ul_dmrs_symb_pos")])[0]
bufIdx += general_msg_header_list.get("ul_dmrs_symb_pos")
# get number dmrs symbols
number_dmrs_symbols = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("number_dmrs_symbols")])[0]
bufIdx += general_msg_header_list.get("number_dmrs_symbols")
# get dmrs port
dmrs_port = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("dmrs_port")])[0]
bufIdx += general_msg_header_list.get("dmrs_port")
# get dmrs scid
dmrs_scid = struct.unpack('<H', buf[bufIdx:bufIdx+general_msg_header_list.get("dmrs_scid")])[0]
bufIdx += general_msg_header_list.get("dmrs_scid")
# get nb antennas rx for gNB or nb antennas tx for UE
nb_antennas = struct.unpack('B', buf[bufIdx:bufIdx+general_msg_header_list.get("nb_antennas")])[0]
bufIdx += general_msg_header_list.get("nb_antennas")
# get number of bits
number_of_bits = struct.unpack('<I', buf[bufIdx:bufIdx+general_msg_header_list.get("number_of_bits")])[0]
bufIdx += general_msg_header_list.get("number_of_bits")
# get length of bytes
length_bytes = struct.unpack('<I', buf[bufIdx:bufIdx+general_msg_header_list.get("length_bytes")])[0]
bufIdx += general_msg_header_list.get("length_bytes")
# print all captured data
if DEBUG_WIRELESS_RECORDED_DATA:
print(" ")
print(f"Time stamp: {time_stamp_milli_sec}")
print(f"MSG ID: {msg_id:<5} MSG Name: {tracer_msgs_identities[msg_id]}")
print(f"Frame: {frame:<5} Slot: {slot:<5}")
print(f"Frame Type: {frame_type:<5} Frequency Range: {freq_range:<5}"
f"Subcarrier Spacing: {subcarrier_spacing:<5} Cyclic Prefix: {cyclic_prefix:<5} "
f"Symbols per Slot: {symbols_per_slot:<5}")
print(f"Nid Cell: {Nid_cell:<5} RNTI: {rnti:<5}")
print(f"RB Size: {rb_size:<5} RB Start: {rb_start:<5} Start Symbol Index: {start_symbol_index:<5} "
f"Number of Symbols: {nr_of_symbols:<5}")
print(f"QAM Modulation Order: {qam_mod_order:<5} MCS Index: {mcs_index:<5} "
f"MCS Table: {mcs_table:<5}")
print(f"Number of Layers: {nrOfLayers:<5} Transform Precoding: {transform_precoding:<5}")
print(f"DMRS Config Type: {dmrs_config_type:<5} UL DMRS Symbol Position: {ul_dmrs_symb_pos:<5} "
f"Number of DMRS Symbols: {number_dmrs_symbols:<5}")
print(f"DMRS Port: {dmrs_port:<5} DMRS SCID: {dmrs_scid:<5} "
f"Number of Antennas: {nb_antennas:<5}")
print(f"Number of bits: {number_of_bits:<5} Length of bytes: {length_bytes:<5}")
# raise exception if time stamp is zero, it means that the data is not recorded yet
if nr_trace_time_stamp_yyymmdd == 0 and nr_trace_time_stamp_hhmmssmmm == 0:
raise Exception("ERROR: Time stamp is zero, data is not recorded yet or something wrong, check logs!")
# get recorded data
buf = shm.read(bufIdx + length_bytes)
# bit_msg_index = get_index_of_id(tracer_msgs_identities, "GNB_PHY_UL_PAYLOAD_RX_BITS")
captured_data = {}
# If message is bit message, store data in bytes
# then the field number_of_bits should be not zero
if "_BITS" in tracer_msgs_identities[msg_id]:
# recorded_data = buf[bufIdx:bufIdx + length_bytes]
recorded_data = struct.unpack("<" + int(length_bytes) * 'B', buf[bufIdx:bufIdx + length_bytes])
bufIdx += length_bytes
# convert data in bytes to bits
bits_vector = []
for byte in recorded_data:
bits_vector.extend([int(bit) for bit in format(int(byte), '08b')])
captured_data["sigmf_data_type"] = "ri8_le"
# convert to uint8
captured_data["recorded_data"] = np.asarray(bits_vector).astype(np.uint8)
# recorded_data_formated = recorded_data.astype(np.complex64) # convert to complex64
else:
recorded_data = struct.unpack("<" + int(length_bytes/2) * 'h', buf[bufIdx:bufIdx + length_bytes])
bufIdx += length_bytes
# print("IQ data I/Q: ", recorded_data)
# Convert real data to complext data
# converting list to array
recorded_data = np.asarray(recorded_data)
# recorded_data_complex = recorded_data
recorded_data_complex = common_utils.real_to_complex(recorded_data)
captured_data["sigmf_data_type"] = "cf32_le"
# convert to complex64
captured_data["recorded_data"] = recorded_data_complex.astype(np.complex64)
# print("Recorded Data: ", captured_data["recorded_data"])
# store data in dictonary
captured_data["message_id"] = msg_id
captured_data["message_type"] = tracer_msgs_identities[msg_id]
captured_data["frame"] = frame
captured_data["slot"] = slot
captured_data["time_stamp"] = time_stamp_milli_sec
captured_data["frame_type"] = frame_type
captured_data["freq_range"] = freq_range
captured_data["subcarrier_spacing"] = subcarrier_spacing
captured_data["cyclic_prefix"] = cyclic_prefix
# captured_data["symbols_per_slot"] = symbols_per_slot ... not used
captured_data["Nid_cell"] = Nid_cell
captured_data["rnti"] = rnti
captured_data["rb_size"] = rb_size
captured_data["rb_start"] = rb_start
captured_data["start_symbol_index"] = start_symbol_index
captured_data["nr_of_symbols"] = nr_of_symbols
captured_data["qam_mod_order"] = qam_mod_order
captured_data["mcs_index"] = mcs_index
captured_data["mcs_table"] = mcs_table
captured_data["nrOfLayers"] = nrOfLayers
captured_data["transform_precoding"] = transform_precoding
captured_data["dmrs_config_type"] = dmrs_config_type
captured_data["ul_dmrs_symb_pos"] = ul_dmrs_symb_pos
captured_data["number_dmrs_symbols"] = number_dmrs_symbols
captured_data["dmrs_port"] = dmrs_port
captured_data["dmrs_scid"] = dmrs_scid
captured_data["nb_antennas"] = nb_antennas
captured_data["number_of_bits"] = number_of_bits
return captured_data, bufIdx
# Synchronize data between gNB and UE
def sync_data_conversion_service(
shm_reading_gnb, shm_reading_ue, sync_info, config_meta_data, gnb_args, ue_args):
# Initialize variables
record_idx = 0
prev_frame = -1
prev_slot = -1
ue_bufIdx = 0
gnb_bufIdx = 0
gnb_args.num_requested_tracer_msgs = len(
config_meta_data["data_recording_config"]["base_station"][
"requested_tracer_messages"])
ue_args.num_requested_tracer_msgs = len(
config_meta_data["data_recording_config"]["user_equipment"][
"requested_tracer_messages"])
tracer_msgs_identities = config_meta_data["data_recording_config"][
"tracer_msgs_identities"]
global_info = config_meta_data["data_recording_config"]["global_info"]
# Get UE data based on the sync data
bufIdx = 0
timeout_sync = time.time() + 5 # 5 seconds if no sync data found, stop the process
while True:
# wait for the next record
# To do: check if we need to add exta waiting times between different events in case of
# data streaming via network such as on UE side or gNB side
time.sleep(0.0035) # 2.3 ms = latency of T tracer to capture data from the RAN
ue_bufIdx = bufIdx
captured_data, bufIdx = read_data_from_shm(shm_reading_ue, bufIdx, tracer_msgs_identities)
if (captured_data["frame"] == sync_info["frame"]
and captured_data["slot"] == sync_info["slot"]):
break
if time.time() > timeout_sync:
raise Exception(
"ERROR: Data Recording NO Sync Found, check Tracer Services if they are connected!")
# Get gNB data based on the sync data
bufIdx = 0
while True:
time.sleep(0.0035)
gnb_bufIdx = bufIdx
captured_data, bufIdx = read_data_from_shm(
shm_reading_gnb, bufIdx, tracer_msgs_identities)
if (captured_data["frame"] == sync_info["frame"]
and captured_data["slot"] == sync_info["slot"]):
break
# Read Synchronized data between gNB and UE
while True: # read all records
print("\nRecord number: ", record_idx)
if DEBUG_BUFFER_READING:
print(f"Buffer Index gNB: {gnb_bufIdx}, Buffer Index UE: {ue_bufIdx}")
# wait for the next record
# To do: check if we need to add exta waiting times between different events in case of
# data streaming via network such as on UE side or gNB side
time.sleep(0.0035) # 2.3 ms = latency of T tracer to capture data from the RAN
collected_metafiles = []
# Read data from gNB T-tracer Application
for idx in range(gnb_args.num_requested_tracer_msgs):
time.sleep(0.0015)
if DEBUG_WIRELESS_RECORDED_DATA:
print(f"\nRecord number: {record_idx}, Reading MSG data ", idx)
captured_data, gnb_bufIdx = read_data_from_shm(
shm_reading_gnb, gnb_bufIdx, tracer_msgs_identities)
# drive the collection file time stamp from the first message per record
if idx == 0:
# Get time stamp
time_stamp_ms, time_stamp_ms_file_name = (
sigmf_interface.time_stamp_formating(
captured_data["time_stamp"], global_info["datetime_offset"]))
global_info["collection_file"] = (
global_info["collection_file_prefix"]
+ "-rec-"
+ str(record_idx)
+ "-"
+ str(time_stamp_ms_file_name)
)
global_info["timestamp"] = time_stamp_ms
# Write data into files with the given format
if config_meta_data["data_recording_config"]["enable_saving_tracer_messages_sigmf"]:
collected_metafiles.append(
sigmf_interface.write_recorded_data_to_sigmf(
captured_data, config_meta_data, global_info, record_idx))
# Read data from UE T-tracer Application
for idx in range(ue_args.num_requested_tracer_msgs):
time.sleep(0.0015)
if DEBUG_WIRELESS_RECORDED_DATA:
print(f"\nRecord number: {record_idx}, Reading MSG data ", idx)
captured_data, ue_bufIdx = read_data_from_shm(
shm_reading_ue, ue_bufIdx, tracer_msgs_identities)
# Write data into files with the given format
if config_meta_data["data_recording_config"]["enable_saving_tracer_messages_sigmf"]:
collected_metafiles.append(
sigmf_interface.write_recorded_data_to_sigmf(
captured_data, config_meta_data, global_info, record_idx))
# generate SigMF collection file
if config_meta_data["data_recording_config"]["enable_saving_tracer_messages_sigmf"]:
data_storage_path = config_meta_data["data_recording_config"][
"data_storage_path"]
description = global_info["description"]
sigmf_interface.save_sigmf_collection(
collected_metafiles, global_info, description, data_storage_path)
frame = captured_data["frame"]
slot = captured_data["slot"]
# Check for changes in frame or slot
if frame != prev_frame or slot != prev_slot:
record_idx += 1
# We have reached the end of the data. Break the loop
if record_idx >= config_meta_data["data_recording_config"]["num_records"]:
break
# Update previous frame and slot
prev_frame = frame
prev_slot = slot
# data conversion service
def data_conversion_service(shm_reading, config_meta_data, args, sync_info, do_sync):
# Initialize variables
record_idx = 0
prev_frame = -1
prev_slot = -1
bufIdx = 0
# Read data from T-tracer Application
print("Data Conversion Service: Reading data from T-tracer Application")
print("Requested Tracer Messages: ", args.num_requested_tracer_msgs)
if args.num_requested_tracer_msgs > 0:
num_requested_tracer_msgs = args.num_requested_tracer_msgs
else:
raise Exception("ERROR: No requested tracer messages found!")
tracer_msgs_identities = config_meta_data["data_recording_config"][
"tracer_msgs_identities"]
global_info = config_meta_data["data_recording_config"]["global_info"]
if do_sync:
print(" Find Memory Index of NR MSGs based on Sync info")
while True:
time.sleep(0.0035)
station_bufIdx = bufIdx
captured_data, bufIdx = read_data_from_shm(
shm_reading, bufIdx, tracer_msgs_identities)
print("*Sync Status: NR Captured Data (Frame, slot): (", captured_data["frame"],
", ", captured_data["slot"], "), Sync Info (Frame, slot): (", sync_info["frame"],
", ", sync_info["slot"]," )")
if (captured_data["frame"] == sync_info["frame"]
and captured_data["slot"] == sync_info["slot"]):
print("*sync Pass")
break
print("*sync Fail")
bufIdx = station_bufIdx
# Read data from T-tracer Application
while True: # read all records
print("\nRecord number: ", record_idx)
if DEBUG_BUFFER_READING:
print(f"Buffer Index: {bufIdx}")
# wait for the next record
# To do: check if we need to add exta waiting times between different events in case of
# data streaming via network such as on UE side or gNB side
time.sleep(0.0035) # 2.3 ms = latency of T tracer to capture data from the RAN
collected_metafiles = []
for idx in range(num_requested_tracer_msgs):
time.sleep(0.0015)
if DEBUG_WIRELESS_RECORDED_DATA:
print(f"\nRecord number: {record_idx}, Reading MSG data ", idx)
captured_data, bufIdx = read_data_from_shm(
shm_reading, bufIdx, tracer_msgs_identities)
# derive the collection file time stamp from the first message per record
if idx == 0:
# Get time stamp
time_stamp_ms, time_stamp_ms_file_name = (
sigmf_interface.time_stamp_formating(
captured_data["time_stamp"], global_info["datetime_offset"]))
global_info["collection_file"] = (
global_info["collection_file_prefix"]
+ "-rec-"
+ str(record_idx)
+ "-"
+ str(time_stamp_ms_file_name))
global_info["timestamp"] = time_stamp_ms
# Write data into files with the given format
if config_meta_data["data_recording_config"]["enable_saving_tracer_messages_sigmf"]:
collected_metafiles.append(
sigmf_interface.write_recorded_data_to_sigmf(
captured_data, config_meta_data, global_info, record_idx))
frame = captured_data["frame"]
slot = captured_data["slot"]
# generate SigMF collection file
if config_meta_data["data_recording_config"]["enable_saving_tracer_messages_sigmf"]:
data_storage_path = config_meta_data["data_recording_config"][
"data_storage_path"]
description = global_info["description"]
sigmf_interface.save_sigmf_collection(
collected_metafiles, global_info, description, data_storage_path)
# Check for changes in frame or slot
if frame != prev_frame or slot != prev_slot:
record_idx += 1
# We have reached the end of the data. Break the loop
if record_idx >= config_meta_data["data_recording_config"]["num_records"]:
break
# Update previous frame and slot
prev_frame = frame
prev_slot = slot
# Write Tracer Control Message
def write_shm(shm, args):
# Note: Big Endian >, Little Endian <
# Note: unsigned char B, signed char b, short h, int I, long long q
# Note: float f, double d, string s, char c, bool ?
# 1: config
# 2: record
# 3: quit
print("Write Shared Memory: ", args.action)
if args.action == "config":
# Determine the length of the IP address
ip_length = len(args.bytes_IPaddress) + 1 # String terminator
# Construct the format string dynamically
format_string = f"{ip_length}s"
shm.write(
# Config action
struct.pack("<B", 1) +
struct.pack("<B", ip_length) +
struct.pack(format_string, args.bytes_IPaddress) +
struct.pack("<h", int(args.port))
)
print("T-Tracer Config IP: ", args.bytes_IPaddress, " port: ", args.port)
elif args.action == "record":
shm.write(
# Record action
struct.pack("<B", int(2)) +
struct.pack("<B", args.num_requested_tracer_msgs) +
struct.pack(
"<{}h".format(len(args.req_tracer_msgs_indices)),
*args.req_tracer_msgs_indices,) +
struct.pack("<I", args.num_records) +
struct.pack("<h", args.start_frame_number)
)
print("T-Tracer Record: N Messags: ", args.num_requested_tracer_msgs,
", Msg IDs: ", args.req_tracer_msgs_indices,
", Num records: ", args.num_records,
", Start Frame: ", args.start_frame_number,
)
elif args.action == "quit":
shm.write(struct.pack("<B", int(3))) # Quit action
print("T-Tracer Quit")
else:
print("Unknown action for data recording system!")
# write shared memory task
def write_shm_task(barrier, shm_id, args):
# Wait for threads to be ready
barrier.wait()
# send request to related T-Tracer Application
write_shm(shm_id, args)
if __name__ == "__main__":
# -------------------------------------------
# ------------- Configuration --------------
# ------------------------------------------
# Data Recording Configuration
data_recording_config_file = "config/config_data_recording.json"
# -------------------------------------------
# Configuration
# -------------------------------------------
# First: get the configuration mode either local or remote
# Second: get data recording configuration
# Read and parse the JSON file
with open(data_recording_config_file, "r") as file:
config_meta_data = json.load(file)
# get Configuration parameters
config_meta_data, gnb_args, ue_args = config_interface.get_data_recording_config(config_meta_data)
# check if lists of requested tracer messages IDs are not empty
if not gnb_args.requested_tracer_messages and \
not ue_args.requested_tracer_messages:
raise Exception("ERROR: No requested tracer messages are provided")
# check if gnb_requested_tracer_messages is not empty, attach to the shared memory
if gnb_args.requested_tracer_messages:
# attach to the shared memory
shm_writing_gnb = attach_shm(write_shm_path_gnb, project_id_gnb)
shm_reading_gnb = attach_shm(read_shm_path_gnb, project_id_gnb)
# check if ue_requested_tracer_messages is not empty, attach to the shared memory
if ue_args.requested_tracer_messages:
# attach to the shared memory
shm_writing_ue = attach_shm(write_shm_path_ue, project_id_ue)
shm_reading_ue = attach_shm(read_shm_path_ue, project_id_ue)
# get general message header list
general_msg_header_list, general_message_header_length = \
data_recording_messages_def.get_general_msg_header_list()
# Add supported OAI Tracer Messages
config_meta_data["data_recording_config"]["supported_oai_tracer_messages"] = supported_oai_tracer_messages
# Add global info
config_meta_data["data_recording_config"]["global_info"] = global_info
# -------------------------------------------
# Initialization
# -------------------------------------------
# -------------------------------------------
# send Tracer Control Message request to T-Tracers Apps
# -------------------------------------------
# It consists of the following fields:
# Config action
# IP address length
# IP address
# Port Number
# check if gnb_requested_tracer_messages is not empty, config T-Tracer gNB via shared memory
if gnb_args.requested_tracer_messages:
# Config T-Tracer via shared memory
gnb_args.action = "config"
write_shm(shm_writing_gnb, gnb_args)
# check if ue_requested_tracer_messages is not empty, config T-Tracer UE via shared memory
if ue_args.requested_tracer_messages:
# Config T-Tracer via shared memory
ue_args.action = "config"
write_shm(shm_writing_ue, ue_args)
time.sleep(0.5) # wait for the config to be applied
# -------------------------------------------
# Execution
# -------------------------------------------
# send Tracer Control Message request to T-Tracers Apps
# -------------------------------------------
# It consists of the following fields:
# Record action
# Number of requested Tracer Messages
# Requested Tracer Messages ID 1, …, ID N
# Number of records to be recorded in slots
# Start SFN: Frame Index to start data collection from it, useful for future
# data sync between gNB and UE but not yet used
print("Args:")
if gnb_args.requested_tracer_messages:
gnb_args.action = "record"
print("gnb_args: ", gnb_args)
if ue_args.requested_tracer_messages:
ue_args.action = "record"
print("ue_args: ", ue_args)
start_time = time.time()
print("Send data logging request us:", datetime.now().strftime("%Y%m%d-%H%M%S%f"))
# if requested: gNB and UE Tracer Messages
# gNB + UE Tracer Messages
if gnb_args.requested_tracer_messages and ue_args.requested_tracer_messages:
# Create a barrier to synchronize the threads
barrier = threading.Barrier(2)
with concurrent.futures.ThreadPoolExecutor() as executor:
tracer_ue = executor.submit(write_shm_task, barrier, shm_writing_ue, ue_args)
tracer_gnb = executor.submit(write_shm_task, barrier, shm_writing_gnb, gnb_args)
# Wait for both functions to complete
concurrent.futures.wait([tracer_ue, tracer_gnb])
# gNB Tracer Messages
elif gnb_args.requested_tracer_messages:
write_shm(shm_writing_gnb, gnb_args)
# UE Tracer Messages
elif ue_args.requested_tracer_messages:
write_shm(shm_writing_ue, ue_args)
else:
raise Exception("ERROR: No requested tracer messages IDs are provided")
# -------------------------------------------
# Read data from gNB and UE T-tracer Application
# -------------------------------------------
# Check if data is available in memory
# Initialize variables
bufIdx = 0
timeout = 10 # 10 seconds from now
# If gNB MSGs are requested
if gnb_args.requested_tracer_messages:
# Check if data is available in gNB memory
is_gnb_data_in_memory = is_data_available_in_memory(
shm_reading_gnb, bufIdx, general_message_header_length, timeout)
# Report the status of gNB T-Tracer APP locally
if not is_gnb_data_in_memory:
print("Error: gNB: Check t-Tracer APP of gNB, check IPs and Ports")
print("Error: gNB: If IPs and Ports are correct, re-run the hanging app.")
print("It seems the socket was not closed properly")
raise Exception("ERROR: Time out, check if gNB T-Tracer APP connected to stack")
# If UE MSGs are requested
if ue_args.requested_tracer_messages:
# Check if data is available in UE memory
is_ue_data_in_memory = is_data_available_in_memory(
shm_reading_ue, bufIdx, general_message_header_length, timeout)
# Report the status of UE T-Tracer APP locally
if not is_ue_data_in_memory:
print("Error: UE: Check t-Tracer APP of UE, check IPs and Ports")
print("Error: UE: If IPs and Ports are correct, re-run the hanging app.")
print("It seems the socket was not closed properly")
raise Exception("ERROR: Time out, check if UE T-Tracer APP connected to stack")
# -------------------------------------------
# Sync data between gNB and UE
# -------------------------------------------
# write JSON file
common_utils.write_config_data_recording_app_json(config_meta_data)
sync_info = {}
if gnb_args.requested_tracer_messages and ue_args.requested_tracer_messages:
# Sync data between gNB and UE
sync_info = sync_service.sync_gnb_ue_captured_data(shm_reading_gnb, shm_reading_ue)
print("\n***Sync data between gNB and UE: ", sync_info)
# Read data from gNB and UE T-tracer Applications
sync_data_conversion_service(
shm_reading_gnb, shm_reading_ue, sync_info, config_meta_data, gnb_args, ue_args)
elif gnb_args.requested_tracer_messages:
# Read data from gNB T-tracer Application
data_conversion_service(
shm_reading_gnb, config_meta_data, gnb_args, sync_info, do_sync=False)
elif ue_args.requested_tracer_messages:
# Read data from UE T-tracer Application
data_conversion_service(
shm_reading_ue, config_meta_data, ue_args, sync_info, do_sync=False)
else:
raise Exception("ERROR: No requested tracer messages IDs are provided")
# measure Elapsed time
time_elapsed = time.time() - start_time
time_elapsed_ms = int(time_elapsed * 1000)
print(
"Elapsed time of getting Requested Messages and writing data and meta data files:",
colored(time_elapsed_ms, "yellow"), "ms",)
# Stop T-Tracer Application function
if gnb_args.requested_tracer_messages:
gnb_args.action = "quit"
write_shm(shm_writing_gnb, gnb_args)
# Add Sleep time to ensure that the message sent to the UE T-tracer application is received
# before the shared memory is detached
time.sleep(0.5)
# Clean shared memory
detach_shm(shm_reading_gnb)
detach_shm(shm_writing_gnb)
remove_shm(shm_reading_gnb)
remove_shm(shm_writing_gnb)
if ue_args.requested_tracer_messages:
ue_args.action = "quit"
write_shm(shm_writing_ue, ue_args)
# Add Sleep time to ensure that the message sent to the UE T-tracer application is received
# before the shared memory is detached
time.sleep(0.5)
# Clean shared memory
detach_shm(shm_reading_ue)
detach_shm(shm_writing_ue)
remove_shm(shm_reading_ue)
remove_shm(shm_writing_ue)
print("End of the RF Data Recording API")
pass

View File

@@ -0,0 +1,32 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/lib/__init__.py
# brief for package initialization and code execution
# author Abdo Gaber
# date 2024
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
# This file is important in python to call functions from the lib folder.

View File

@@ -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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/lib/common_utils.py
# brief Data Recording common utilities
# author Abdo Gaber
# date 2025
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
import os
import json
def real_to_complex(real_vector):
# Ensure the length of the real vector is even
if len(real_vector) % 2 != 0:
raise ValueError("The length of the real vector must be even.")
# Split the real vector into real and imaginary parts
real_part = real_vector[::2]
imag_part = real_vector[1::2]
# Combine the real and imaginary parts to form a complex vector
complex_vector = real_part + 1j * imag_part
return complex_vector
def write_config_data_recording_app_json(config_meta_data):
if config_meta_data["data_recording_config"]["global_info"][
"save_config_data_recording_app_json"
]:
try:
json.dumps(config_meta_data)
except (TypeError, ValueError) as e:
print(f"data_recording_config_meta_json is not JSON serializable: {e}")
# Specify the file name
output_file = (
config_meta_data["data_recording_config"]["data_storage_path"]
+ "config_data_recording_app.json"
)
# Ensure the directory exists
os.makedirs(os.path.dirname(output_file), exist_ok=True)
# Write the JSON data to the file
with open(output_file, "w") as file:
try:
json.dump(config_meta_data, file, indent=4)
print(f"JSON file created successfully at {output_file}")
except Exception as e:
print(f"Failed to create JSON file: {e}")

View File

@@ -0,0 +1,153 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/lib/config_interface.py
# brief Data Recording App Configuration Interface
# author Abdo Gaber
# date 2025
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
import yaml
import json
import argparse
# Function to read the main configuration file in JSON format
def read_main_config_file_json(main_config_file: str) -> dict:
"""
Reads main config file.
"""
# read general parameter set from yaml config file
with open(main_config_file, "r") as file:
main_config = json.load(file)
return main_config
# Function to read the main configuration file in YAML format
def read_main_config_file(main_config_file: str) -> dict:
"""
Reads main config file.
"""
# read general parameter set from yaml config file
with open(main_config_file, "r") as file:
main_config = yaml.load(file, Loader=yaml.Loader)
return main_config
# Function to parse the OAI T_messages file and get the index of a given string
def parse_message_file(file_path):
with open(file_path, "r") as file:
content = file.readlines()
# Extract lines that start with 'ID' and remove the 'ID = ' prefix
tracer_msgs_identities = [
line.strip().replace("ID = ", "")
for line in content
if line.strip().startswith("ID")
]
return tracer_msgs_identities
# Function to get the index of a given string in the list of ID lines
def get_index_of_id(tracer_msgs_identities, message_id):
try:
return tracer_msgs_identities.index(message_id)
except ValueError:
return -1 # Return -1 if the string is not found
# Function to get the indices of requested tracer messages
def get_requested_tracer_msgs_indices(requested_tracer_messages, tracer_msgs_identities):
# get requested tracer messages indices
req_tracer_msgs_indices = []
for idx, value in enumerate(requested_tracer_messages):
msg_index = get_index_of_id(tracer_msgs_identities, value)
req_tracer_msgs_indices.append(msg_index)
print("Requested Traces IDs: ", req_tracer_msgs_indices)
return req_tracer_msgs_indices
# Function to get the data recording configuration
def get_data_recording_config(config_meta_data):
parser = argparse.ArgumentParser(description="request messages IDs")
ue_args = parser.parse_args()
gnb_args = parser.parse_args()
# get Tracer Messages IDs from the T-Tracer Messages file
# MSG list order should be similar to the T-Tracer Messages txt file
# Be sure that you are using the same T_messages file that is used in the OAI project
# OAI Path: .../common/utils/T/T_messages.txt
# Parse the T_messages file
tracer_msgs_identities = parse_message_file(
config_meta_data["data_recording_config"]["t_tracer_message_definition_file"])
config_meta_data["data_recording_config"]["tracer_msgs_identities"] = tracer_msgs_identities
# get requested tracer messages indices for gNB
gnb_args.requested_tracer_messages = \
config_meta_data["data_recording_config"]["base_station"]["requested_tracer_messages"]
# get requested tracer messages indices for UE
ue_args.requested_tracer_messages = \
config_meta_data["data_recording_config"]["user_equipment"]["requested_tracer_messages"]
# check if gnb_requested_tracer_messages is not empty
if gnb_args.requested_tracer_messages:
config_meta_data["data_recording_config"]["base_station"][
"req_tracer_msgs_indices"] = get_requested_tracer_msgs_indices(
gnb_args.requested_tracer_messages, tracer_msgs_identities)
# get gNB Trace Messages
gnb_args.num_records = config_meta_data["data_recording_config"]["num_records"]
gnb_args.start_frame_number = config_meta_data["data_recording_config"]["start_frame_number"]
gnb_args.req_tracer_msgs_indices = \
config_meta_data["data_recording_config"]["base_station"]["req_tracer_msgs_indices"]
gnb_args.num_requested_tracer_msgs = len(gnb_args.req_tracer_msgs_indices)
# Split the string into IP and port
gnb_args.IPaddress, gnb_args.port = config_meta_data["data_recording_config"][
"tracer_service_baseStation_address"].split(":")
gnb_args.bytes_IPaddress = bytes(gnb_args.IPaddress, "utf-8")
# check if ue_requested_tracer_messages is not empty
if ue_args.requested_tracer_messages:
config_meta_data["data_recording_config"]["user_equipment"][
"req_tracer_msgs_indices"] = get_requested_tracer_msgs_indices(
ue_args.requested_tracer_messages, tracer_msgs_identities)
# get UE Trace Messages
ue_args.num_records = config_meta_data["data_recording_config"]["num_records"]
ue_args.start_frame_number = config_meta_data["data_recording_config"]["start_frame_number"]
ue_args.req_tracer_msgs_indices = \
config_meta_data["data_recording_config"]["user_equipment"]["req_tracer_msgs_indices"]
ue_args.num_requested_tracer_msgs = len(ue_args.req_tracer_msgs_indices)
ue_args.IPaddress, ue_args.port = config_meta_data["data_recording_config"][
"tracer_service_userEquipment_address"].split(":")
ue_args.bytes_IPaddress = bytes(ue_args.IPaddress, "utf-8")
return config_meta_data, gnb_args, ue_args

View File

@@ -0,0 +1,107 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/lib/data_recording_messages_def.py
# brief defination of captured data recording messages
# author Abdo Gaber
# date 2025
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
# Data Collection Trace Messages - General message structure - number of bytes
def get_general_msg_header_list():
"""
shared memory layout written from the app:
=================================
msg_id (uint8) message type ID
frame (uint16)
slot (uint8)
datetime_yyyymmdd (uint32)
datetime_hhmmssmmm (uint32)
frame_type (uint8)
freq_range (uint8)
subcarrier_spacing (uint8)
cyclic_prefix (uint8)
symbols_per_slot (uint8)
Nid_cell (uint16)
rnti (uint16)
rb_size (uint16)
rb_start (uint16)
start_symbol_index (uint8)
nr_of_symbols (uint8)
qam_mod_order (uint8)
mcs_index (uint8)
mcs_table (uint8)
nrOfLayers (uint8)
transform_precoding (uint8)
dmrs_config_type (uint8)
ul_dmrs_symb_pos (uint16)
number_dmrs_symbols (uint8)
dmrs_port (uint16)
dmrs_scid (uint16)
nb_antennas (uint8)
number_of_bits (uint32)
length_bytes (uint32)
For IQ Data: IQ samples: I0, Q0, I1, Q1, ... I_x, Q_x (int16)
For bit data: bits: b0, b1, b2, ... b_x (uint8)
"""
# Data Collection Trace Messages - General message structure - number of bytes
general_msg_header_list = {
"msg_id": 2,
"frame": 2,
"slot": 1,
"datetime_yyyymmdd": 4,
"datetime_hhmmssmmm": 4,
"frame_type": 1,
"freq_range": 1,
"subcarrier_spacing": 1,
"cyclic_prefix": 1,
"symbols_per_slot": 1,
"Nid_cell": 2,
"rnti": 2,
"rb_size": 2,
"rb_start": 2,
"start_symbol_index": 1,
"nr_of_symbols": 1,
"qam_mod_order": 1,
"mcs_index": 1,
"mcs_table": 1,
"nrOfLayers": 1,
"transform_precoding": 1,
"dmrs_config_type": 1,
"ul_dmrs_symb_pos": 2,
"number_dmrs_symbols": 1,
"dmrs_port": 2,
"dmrs_scid": 2,
"nb_antennas": 1, # for gNB or nb_antennas_tx for UE
"number_of_bits": 4,
"length_bytes": 4,
}
# initial number of bytes to read to get data
general_message_header_length = 0
for key, value in general_msg_header_list.items():
general_message_header_length = general_message_header_length + value
return general_msg_header_list, general_message_header_length

View File

@@ -0,0 +1,581 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/lib/sigmf_interface.py
# brief SigMF Interface to Write data and meta-data to files in SigMF format
# author Abdo Gaber
# date 2025
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
import os
import sigmf
from sigmf import SigMFFile
# from sigmf.utils import get_data_type_str
import numpy as np
from datetime import datetime
import yaml
from sigmf import SigMFCollection
"""
SERIALIZATION_SCHEMES = {
# gnb
"rx-fd-data": ["subcarriers", "ofdm_symbols"],
"tx-pilots-fd-data": ["subcarriers", "ofdm_symbols"],
"raw-ce-fd-data": ["subcarriers", "ofdm_symbols"],
"rx-payload-bits": ["bits", "subcarriers", "ofdm_symbols"],
# ue
"tx-scrambled-bits": ["bits", "subcarriers", "ofdm_symbols"],
"tx-payload-bits": ["bits", "subcarriers", "ofdm_symbols"],
}
"""
STANDARDS = {"5gnr_oai": "5gnr"}
def time_stamp_formating(time_stamp, datetime_offset):
# Parse the input string into a datetime object
time_stamp_ms_obj = datetime.strptime(time_stamp, "%Y%m%d_%H%M%S%f")
# Format the datetime object into the desired output format with milliseconds
time_stamp_ms_iso = (
time_stamp_ms_obj.strftime("%Y_%m_%dT%H:%M:%S.%f")[:-3] + datetime_offset
)
time_stamp_ms = time_stamp_ms_iso
time_stamp_ms_file_name = time_stamp_ms_iso.replace(":", "_").replace(".", "_")
return time_stamp_ms, time_stamp_ms_file_name
def create_serialization_metadata(serialization_scheme, data_source: str,
link_sim_parameters: dict) -> dict:
"""Creates dict that specifies the serialization metadata."""
# retrieve parameter from LinkSimulator config
if data_source == "5gnr_oai":
num_ofdm_symbol = link_sim_parameters["nr_of_symbols"]
num_subcarriers = link_sim_parameters["rb_size"] * 12
num_bits_per_symbol = link_sim_parameters["qam_mod_order"]
else:
raise Exception(
f"Invalid data source string '{data_source}'! Only '5gnr_oai' is valid!"
)
# create array with scheme dimensions
if len(serialization_scheme) == 2:
counts = [num_subcarriers, num_ofdm_symbol]
elif len(serialization_scheme) == 3:
counts = [num_bits_per_symbol, num_subcarriers, num_ofdm_symbol]
else:
raise Exception(
f"Invalid number of elements in serialization scheme: {len(serialization_scheme)}")
# create metadata dict
serialization_dict = {
"serialization:scheme": serialization_scheme,
"serialization:counts": counts,
}
return serialization_dict
def map_metadata_to_sigmf_format(scope, waveform_generator, parameter_map_file, captured_data):
"""
Maps metadata from Waveform creator to API and SigMF format.
The used parameters and the mapping pairs are specified in a separate YAML file
that must be provided as well.
"""
# read waveform parameter map from yaml file
dir_path = os.path.dirname(__file__)
src_path = os.path.split(dir_path)[0]
with open(os.path.join(src_path, parameter_map_file), "r") as file:
parameter_map_dic = yaml.load(file, Loader=yaml.Loader)
# preallocate target dict
sigmf_metadata_dict = {}
# get standard and name of generator
standard_key = parameter_map_dic["waveform_generator"][waveform_generator]
generator = standard_key["generator"]
if scope == "tx":
parameter_map_dic = parameter_map_dic["transmitter"][
STANDARDS[waveform_generator]
]
elif scope == "channel":
parameter_map_dic = parameter_map_dic["channel"]
elif scope == "rx":
parameter_map_dic = parameter_map_dic["receiver"]
else:
raise Exception(
f"Invalid mapping scope '{scope}'! Only 'tx', 'channel' and 'rx' are valid!"
)
# check if standard key is given
if parameter_map_dic is None:
raise Exception(
"Invalid standard key: "
"Name should be corrected or added to wireless_link_parameter_map.yaml, given: "
f"{waveform_generator}"
)
for parameter_pair in parameter_map_dic:
# check if key for chosen simulator even exists
if waveform_generator + "_parameter" in parameter_pair.keys():
# only continue with mapping from file if direct equivalent exists
if parameter_pair[waveform_generator + "_parameter"]["name"]:
# It is not necessary to get all parameters from wireless_link_parameter_map.yaml
# in captured_data since some parameters related to DL or UL only
if (parameter_pair[waveform_generator + "_parameter"]["name"] in captured_data.keys()):
# extract value from waveform config source
value = captured_data[
parameter_pair[waveform_generator + "_parameter"]["name"]]
# additional mapping if parameter values should come from a discrete set of values
if ("value_map" in parameter_pair[waveform_generator + "_parameter"].keys()):
value = parameter_pair[waveform_generator + "_parameter"]["value_map"][value]
# write to target dictionary for SigMF
sigmf_metadata_dict[parameter_pair["sigmf_parameter_name"]] = value
else:
raise Exception(
f"Incomplete specification in field '{waveform_generator}_parameter'!")
# else: # fill_non_explicit_fields
# waveform_config[parameter_pair["sigmf_parameter_name"]] = "none"
if not sigmf_metadata_dict:
raise Exception(
"""ERROR: Check captured meta-data or provided config and meta data """)
# check for non-JSON-serializable data types
def isfloat(NumberString):
try:
float(NumberString)
return True
except ValueError:
return False
for key, value in sigmf_metadata_dict.items():
if isinstance(value, np.integer):
sigmf_metadata_dict[key] = int(value)
elif isinstance(value, (np.float16, np.float32, np.float64)):
sigmf_metadata_dict[key] = np.format_float_positional(value, trim="-")
elif isinstance(value, int):
sigmf_metadata_dict[key] = int(value)
elif isinstance(value, float):
# store value in decimal and not in scientific notation
sigmf_metadata_dict[key] = float(value)
elif isinstance(value, str) and key != "standard":
# convert string to lower case
sigmf_metadata_dict[key] = value.lower()
if isfloat(value):
if value.isdigit():
sigmf_metadata_dict[key] = int(float(value))
elif value.replace(".", "", 1).isdigit() and value.count(".") < 2:
sigmf_metadata_dict[key] = float(value)
return sigmf_metadata_dict, generator
def create_system_components_metadata(waveform_generator, parameter_map_file, captured_data):
"""Creates system components (TX, channel, RX) metadata that will reside in the annotations."""
# map metadata of waveform generator to SigMF format
signal_info, generator = map_metadata_to_sigmf_format(
"tx", waveform_generator, parameter_map_file, captured_data
)
tx_metadata = {
"signal:detail": {
"standard": STANDARDS[waveform_generator],
"generator": generator,
STANDARDS[waveform_generator]: signal_info,
}
}
channel_metadata = {} # will be filled later
rx_metadata = {} # will be filled later
return tx_metadata, channel_metadata, rx_metadata
def write_recorded_data_to_sigmf(captured_data, config_meta_data, global_info, idx):
"""
Compiles and saves provided data and metadata into SigMF file format.
"""
# get meta data from config file
base_station_meta_data = config_meta_data["data_recording_config"]["base_station"][
"meta_data"]
user_equipment_meta_data = config_meta_data["data_recording_config"][
"user_equipment"]["meta_data"]
# Check the receive target path is valid, else create folder
data_storage_path = config_meta_data["data_recording_config"]["data_storage_path"]
if not os.path.isdir(data_storage_path):
print("Create new folder for recorded data: " + str(data_storage_path))
os.makedirs(data_storage_path)
# Write recorded data to file
# Get time stamp
time_stamp_ms, time_stamp_ms_file_name = time_stamp_formating(
captured_data["time_stamp"], global_info["datetime_offset"])
# Map OAI Message Name to SigMF Message Name
file_name_prefix = config_meta_data["data_recording_config"][
"supported_oai_tracer_messages"][captured_data["message_type"]]["file_name_prefix"]
recorded_data_file_name = (
file_name_prefix + "-rec-" + str(idx) + "-" + time_stamp_ms_file_name)
dataset_filename = recorded_data_file_name + ".sigmf-data"
dataset_file_path = os.path.join(data_storage_path, dataset_filename)
print(dataset_file_path)
captured_data["recorded_data"].tofile(dataset_file_path)
# map OAI config data to SigMF metadata
waveform_generator = config_meta_data["data_recording_config"]["global_info"][
"waveform_generator"]
parameter_map_file = config_meta_data["data_recording_config"]["parameter_map_file"]
tx_metadata, channel_metadata, rx_metadata = create_system_components_metadata(
waveform_generator, parameter_map_file, captured_data)
# Add other OAI metadata to SigMF metadata
# ----------------------------------------------------
# Set 1: Parameters needs to be derived from OAI message
# ----------------------------------------------------
# PUSCH DMRS: OFDM symbol start index within slot
get_pusch_dmrs_start_ofdm_symbol = 0
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch_dmrs:ofdm_symbol_idx"] = []
for symbol in range(captured_data["start_symbol_index"],
captured_data["start_symbol_index"] + captured_data["nr_of_symbols"]):
dmrs_symbol_flag = (captured_data["ul_dmrs_symb_pos"] >> symbol) & 0x01
if dmrs_symbol_flag and not get_pusch_dmrs_start_ofdm_symbol:
get_pusch_dmrs_start_ofdm_symbol = 1
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch_dmrs:start_ofdm_symbol"] = symbol
if dmrs_symbol_flag:
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch_dmrs:ofdm_symbol_idx"].append(symbol) # Append symbol to the list
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch_dmrs:duration_num_ofdm_symbols"] = 1
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch_dmrs:num_add_positions"] = (captured_data["number_dmrs_symbols"] - 1)
# If the message is a BIT message, add number of bits to the signal info
# "UE_PHY_UL_SCRAMBLED_TX_BITS", "GNB_PHY_UL_PAYLOAD_RX_BITS", "UE_PHY_UL_PAYLOAD_TX_BITS"
if "_BITS" in captured_data["message_type"]:
# if "BIT" in captured_data["message_type"]:
tx_metadata["signal:detail"][STANDARDS[waveform_generator]]["num_bits"] = (
captured_data["number_of_bits"])
# If the message is captured from UE, it is UPLink message,
# so number of antennas is num of Tx antennas
# number of antennas on Rx side should be read from global_info given by user
if "UE" in captured_data["message_type"]:
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"num_tx_antennas"
] = captured_data["nb_antennas"]
rx_metadata.update(
{"num_rx_antennas": base_station_meta_data["num_rx_antennas"]}
)
# If the message is captured from gNB, it is UPLink message,
# so number of antennas is num of Rx antennas
# number of antennas on Tx side should be read from global_info given by user
else:
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"num_tx_antennas"] = user_equipment_meta_data["num_tx_antennas"]
rx_metadata.update({"num_rx_antennas": captured_data["nb_antennas"]})
# Check if the message type contains "UL"
if "UL" in captured_data["message_type"]:
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"link_direction"] = "uplink"
else:
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"link_direction"] = "downlink"
# ----------------------------------------------------
# Set 2: Parameters that is hardcoded
# ----------------------------------------------------
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch:content"] = "compliant"
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch:mapping_type"] = "B"
tx_metadata["signal:detail"][STANDARDS[waveform_generator]]["num_slots"] = 1
if config_meta_data["test_config"]["test_mode"] == "rf_simulation":
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch:payload_bit_pattern"] = "random"
elif config_meta_data["test_config"]["test_mode"] == "rf_real_time":
tx_metadata["signal:detail"][STANDARDS[waveform_generator]][
"pusch:payload_bit_pattern"] = "zeros"
else:
raise Exception("ERROR: Test mode is not supported in SigMF formatting")
# ----------------------------------------------------
# Read mean parameters
freq = config_meta_data["environment_emulation"]["target_link_config"][
"wireless_channel"]["carrierFreqValueList_hz"][0]
bandwidth = config_meta_data["data_recording_config"]["common_meta_data"][
"bandwidth"]
# used_signal_bandwidth = (
# pow(10, 6) * config_meta_data["environment_emulation"]["target_link_config"]["ran_config"][
# "uplink"]["ul_used_signal_BW_MHz"])
sample_rate = config_meta_data["data_recording_config"]["common_meta_data"][
"sample_rate"]
# get signal emitter info
signal_emitter = {
"manufacturer": "NI",
"seid": user_equipment_meta_data["seid"], # Unique ID of the emitter
"hw": user_equipment_meta_data["hw_type"],
"hw_subtype": user_equipment_meta_data["hw_subtype"],
"frequency": freq,
"sample_rate": sample_rate,
"bandwidth": bandwidth,
"gain_tx": user_equipment_meta_data["tx_gain"],
"clock_reference": config_meta_data["data_recording_config"][
"common_meta_data"
]["clock_reference"],
}
# ----------------------------------------------------
# get channel metadata
# ----------------------------------------------------
# get downlink channel info
# ch_downlink_config = config_meta_data["environment_emulation"]["target_link_config"]["wireless_channel"]["downlink"]
# ch_downlink ={"channel_model": ch_downlink_config["type"]}
# ch_downlink_model = {"channel_model": "AWGN"}
# get uplink channel info
ch_uplink_config = config_meta_data["environment_emulation"]["target_link_config"][
"wireless_channel"
]["uplink"]
# Real Time RF Emulation
if config_meta_data["test_config"]["test_mode"] == "rf_real_time":
channel_metadata.update({"emulation_mode": "ni rf real time"})
if ch_uplink_config["type"] == "statistical_predefined":
channel_metadata.update(
{
"channel_model": ch_uplink_config["statistical"]["predef_channel_profile"],
"snr_esn0_db": ch_uplink_config["statistical"]["snr_db"],
}
)
elif ch_uplink_config["type"] == "statistical_var":
channel_metadata.update(
{
"channel_model": ch_uplink_config["statistical"]["var_power_delay_profile"],
"snr_esn0_db": ch_uplink_config["statistical"]["snr_db"],
"delay_spread": ch_uplink_config["statistical"]["delay_spread_ns"] * pow(10, -9),
"speed": ch_uplink_config["statistical"]["speed_mps"],
}
)
else:
raise Exception("ERROR: channel type is not supported in SigMF formatting")
# RF Simulation
elif config_meta_data["test_config"]["test_mode"] == "rf_simulation":
channel_metadata.update({"emulation_mode": "oai rf simulation"})
channel_metadata.update(
{
"channel_model": ch_uplink_config["statistical"]["predef_channel_profile"],
"snr_esn0_db": ch_uplink_config["statistical"]["snr_db"],
"delay_spread": ch_uplink_config["statistical"]["delay_spread_ns"]
* pow(10, -9), "speed": ch_uplink_config["statistical"]["speed_mps"],
}
)
else:
raise Exception(
"ERROR: Channel Emulation mode is not supported in SigMF formatting")
channel_metadata["carrier_frequency"] = config_meta_data["environment_emulation"][
"target_link_config"]["wireless_channel"]["carrierFreqValueList_hz"][0]
# ----------------------------------------------------
# get rx metadata
# ----------------------------------------------------
rx_metadata.update(
{
"bandwidth": bandwidth,
"gain": base_station_meta_data["rx_gain"],
"manufacturer": "NI",
"seid": base_station_meta_data["seid"],
"hw_subtype": base_station_meta_data["hw_subtype"],
"clock_reference": config_meta_data["data_recording_config"][
"common_meta_data"
]["clock_reference"],
"phy_freq_domain_receiver_type": config_meta_data["test_config"][
"dut_type"
],
}
)
# ----------------------------------------------------
# get test config
# ----------------------------------------------------
"""
test_config = {
"test_name": config_meta_data["test_config"]["test_name"],
"test_mode": config_meta_data["test_config"]["test_mode"],
"dut_type": config_meta_data["test_config"]["dut_type"],
}
"""
# Create sigmf metadata
# ----------------------
# Add global parameters to SigMF metadata
# ----------------------
meta = SigMFFile(
data_file=dataset_file_path, # extension is optional
global_info={
SigMFFile.DATATYPE_KEY: captured_data[
"sigmf_data_type"
], # "cf32_le", # get_data_type_str(rx_data) - 'cf64_le' is not supported yet
SigMFFile.SAMPLE_RATE_KEY: sample_rate,
SigMFFile.NUM_CHANNELS_KEY: 1,
SigMFFile.AUTHOR_KEY: global_info["author"],
SigMFFile.DESCRIPTION_KEY: config_meta_data["data_recording_config"][
"supported_oai_tracer_messages"
][captured_data["message_type"]]["description"],
SigMFFile.RECORDER_KEY: "NI Data Recording Application for OAI",
SigMFFile.LICENSE_KEY: "MIT License",
# Since we are focusing on 5G NR UL, the base station is the receiver
SigMFFile.HW_KEY: base_station_meta_data["hw_type"],
# Disable DATASET key to mitigate the warning when read SIGMF data although it is given in the spec.
# It seems SIGMF still has bug here
SigMFFile.DATASET_KEY: dataset_filename,
SigMFFile.VERSION_KEY: sigmf.__version__,
sigmf.SigMFFile.COLLECTION_KEY: global_info["collection_file"],
},
)
# ----------------------
# Add capture parameters to SigMF metadata
# ----------------------
serialization_scheme = config_meta_data["data_recording_config"][
"supported_oai_tracer_messages"
][captured_data["message_type"]]["serialization_scheme"]
capture_metadata = {
sigmf.SigMFFile.DATETIME_KEY: time_stamp_ms,
SigMFFile.FREQUENCY_KEY: freq,
**create_serialization_metadata(
serialization_scheme, waveform_generator, captured_data
),
}
meta.add_capture(start_index=0, metadata=capture_metadata) # Sample Start
# create annotations dict
system_components_dict = {
"system_components:transmitter": [
{
"transmitter_id": "tx_0",
**tx_metadata,
"signal:emitter": signal_emitter,
}
]
}
system_components_dict["system_components:channel"] = [
{"transmitter_id": "tx_0", **channel_metadata}]
system_components_dict["system_components:receiver"] = rx_metadata
# ----------------------
# Add annotation parameters to SigMF metadata
# ----------------------
meta.add_annotation(
start_index=0, # Sample Start
length=len(captured_data["recorded_data"]), # Sample count
metadata={
# SigMFFile.FLO_KEY: freq - sample_rate/ 2, # args.freq - args.rate / 2,
# SigMFFile.FHI_KEY: freq + sample_rate/ 2, # args.freq + args.rate / 2,
# SigMFFile.LABEL_KEY: label,
# SigMFFile.COMMENT_KEY: general_config["comment"],
"num_transmitters": 1,
**system_components_dict,
},
)
# Write Meta Data to file
dataset_meta_filename = recorded_data_file_name + ".sigmf-meta"
dataset_meta_file_path = os.path.join(data_storage_path, dataset_meta_filename)
meta.tofile(dataset_meta_file_path) # extension is optional
print(dataset_meta_file_path)
return dataset_meta_file_path
def save_sigmf_collection(streams: list, global_info: dict, description: str, storage_path: str):
"""Save metadata and links to SigMF data/metadata files to SigMF collection file."""
# construct path
storage_path = os.path.expanduser(storage_path)
collection_filepath = os.path.join(storage_path, global_info["collection_file"])
# statically configure Collection metadata
collection = SigMFCollection(
streams,
metadata={
SigMFCollection.COLLECTION_KEY: {
SigMFCollection.VERSION_KEY: sigmf.__version__,
SigMFCollection.DESCRIPTION_KEY: description,
SigMFCollection.AUTHOR_KEY: global_info["author"],
# SigMFCollection.LICENSE_KEY: "License",
# SigMFCollection.EXTENSIONS_KEY: global_info["extensions"],
SigMFCollection.STREAMS_KEY: [],
}
},
)
# save collection file
collection.tofile(collection_filepath)
print("")
print(collection_filepath + ".sigmf-collection")
def load_sigmf(filename: str, storage_path: str, scope: str):
"""
Loads metadata and data from SigMF file.
"""
# construct path
storage_path = os.path.expanduser(storage_path)
filepath = os.path.join(storage_path, filename)
# load SigMF file
signal = sigmf.sigmffile.fromfile(filepath)
# get metadata fields
global_metadata = signal.get_global_info()
annotations_metadata = signal.get_annotations()
# we only consider single annotations metadata element per SigMF file
annotation_start_idx = annotations_metadata[0][sigmf.SigMFFile.START_INDEX_KEY]
annotation_length = annotations_metadata[0][sigmf.SigMFFile.LENGTH_INDEX_KEY]
# get capture metadata
capture_metadata = signal.get_capture_info(annotation_start_idx)
# from source code: sigmffile.py
# "autoscale : bool, default True
# If dataset is in a fixed-point representation, scale samples from (min, max) to (-1.0, 1.0)
# raw_components : bool, default False
# If True read and return the sample components (individual I & Q for complex, samples for real)
# with no conversions or interleaved channels.""
# TODO: problem: raw_components flag is not used at all! changes in source code required!
# TODO: data is always converted to float32 after being read
# read actual data
data = signal.read_samples(
annotation_start_idx, annotation_length, autoscale=False, raw_components=True)
return data, global_metadata, annotations_metadata

View File

@@ -0,0 +1,150 @@
#/*
# * 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
# */
# ---------------------------------------------------------------------
# file common/utils/data_recording/lib/sync_service.py
# brief Sync service between captured Data from 5GNR gNB and UE
# author Abdo Gaber
# date 2025
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
import struct
from lib import data_recording_messages_def
DEBUG_POW_MEAS_SYNC = True
# check if first frame ahead:
def is_frame_ahead(frame1, frame2, max_frame=1023):
"""
Check if frame1 is ahead of frame2, considering wrap-around from max_frame to 0.
Args:
frame1 (int): The first frame number.
frame2 (int): The second frame number.
max_frame (int): The maximum frame number before wrap-around. Default is 1023.
Returns:
bool: True if frame1 is ahead of frame2, False otherwise.
"""
# Calculate the difference considering wrap-around
diff = (frame1 - frame2 + (max_frame + 1)) % (max_frame + 1)
# If the difference is less than half the range, frame1 is ahead
return diff < (max_frame + 1) // 2
# find the frame and slot start
def find_frame_slot_start(dataset1_start, dataset2_start):
"""
Function to find the frame and slot start for data sync
Args:
dataset1_start_info: Dictionary containing the start information of dataset1
dataset2_start_info: Dictionary containing the start information of dataset2
Returns:
sync_info: Dictionary containing the sync information
sync_info["frame"] = frame_start
sync_info["slot"] = slot_start
"""
sync_info = {}
if dataset1_start["frame"] == dataset2_start["frame"]:
frame_diff = 0
frame_start = dataset1_start["frame"]
slot_start = max(dataset1_start["slot"], dataset2_start["slot"])
elif is_frame_ahead(dataset1_start["frame"], dataset2_start["frame"]):
frame_start = dataset1_start["frame"]
slot_start = dataset1_start["slot"]
frame_diff = (dataset1_start["frame"]- dataset2_start["frame"] + 1024) % 1024
elif is_frame_ahead(dataset2_start["frame"], dataset1_start["frame"]):
frame_start = dataset2_start["frame"]
slot_start = dataset2_start["slot"]
frame_diff = (dataset2_start["frame"] - dataset1_start["frame"] + 1024) % 1024
# check first if the delta time between the two datasets is larger than expected offset time.
# So, the sync will not be applied
# check during the calculation of the frame the ramp-around from 1023 to 0
if abs(frame_diff) > 6:
raise Exception(
f"Frame difference between dataset1 and dataset2 is too large: {frame_diff}. "
"This may indicate a problem with the data collection or synchronization."
)
# Determine the starting frame and slot for data sync
sync_info["frame"] = frame_start
sync_info["slot"] = slot_start
return sync_info
# Read data from gNB T-tracer Application
def get_frame_slot_start(shm_reading, bufIdx, general_msg_header_list,
general_message_header_length):
buf = shm_reading.read(bufIdx + general_message_header_length)
msg_id = struct.unpack("<H", buf[bufIdx: bufIdx + general_msg_header_list.get("msg_id")])[0]
bufIdx += general_msg_header_list.get("msg_id")
frame = struct.unpack("<H", buf[bufIdx: bufIdx + general_msg_header_list.get("frame")])[0]
bufIdx += general_msg_header_list.get("frame")
slot = struct.unpack("B", buf[bufIdx: bufIdx + general_msg_header_list.get("slot")])[0]
return frame, slot
# Sync data between gNB and UE
def sync_gnb_ue_captured_data(shm_reading_gnb, shm_reading_ue):
"""
Function to get the sync (frame, slot) data between gNB and UE
Args:
shm_reading_gnb: Shared memory for gNB
shm_reading_ue: Shared memory for UE
Returns:
sync_info: Dictionary containing the sync information between gNB and UE
sync_info["frame"] = frame_start
sync_info["slot"] = slot_start
"""
# get general message header list
general_msg_header_list, general_message_header_length = (
data_recording_messages_def.get_general_msg_header_list())
# Read data from gNB T-tracer Application
bufIdx = 0
frame_gnb, slot_gnb = get_frame_slot_start(
shm_reading_gnb, bufIdx, general_msg_header_list, general_message_header_length)
# Read data from UE T-tracer Application
bufIdx = 0
frame_ue, slot_ue = get_frame_slot_start(
shm_reading_ue, bufIdx, general_msg_header_list, general_message_header_length)
dataset1_start = {}
dataset1_start["frame"] = frame_gnb
dataset1_start["slot"] = slot_gnb
dataset2_start = {}
dataset2_start["frame"] = frame_ue
dataset2_start["slot"] = slot_ue
# Sync data between gNB and UE
# We noticed that the maximum difference between the frame number of gNB and UE is 3 frames
# Calculate the frame difference considering the wrap-around from 1023 to 0
sync_info = find_frame_slot_start(dataset1_start, dataset2_start)
print(" gNB Start info: ", dataset1_start)
print(" UE Start info: ", dataset2_start)
print(" gNB and UE Sync info: ", sync_info)
return sync_info

View File

@@ -0,0 +1,150 @@
#/*
# * 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
# */
#---------------------------------------------------------------------
# file common/utils/data_recording/sync_validation_demo.py
# brief simple application to validate sync between payload captured on gNB and UE
# author Abdo Gaber
# date 2025
# version 1.0
# company Emerson, NI Test and Measurement
# email:
# note
# warning
import numpy as np
import os
import glob
import matplotlib.pyplot as plt
from lib import sigmf_interface
from lib import config_interface
import sigmf
def calculate_ber(capture_subdir: str, scope: str):
"""
Iteratively counts all bit errors from data and finally calculates the BER for a given Eb/N0.
"""
print("Start to calculate BER for subdirectory", capture_subdir)
# get all collection files in the specified directory
file_list = glob.glob(os.path.join(capture_subdir, "*.sigmf-collection"))
print("Found Collection files:", len(file_list))
bit_errors_total = 0
num_bits_total = 0
for idx, file in enumerate(file_list):
print("Processing file:", file)
# read SigMF collection file
collection_file = sigmf.sigmffile.fromfile(file)
print(" ")
print("Collection file:", file)
# extract filenames
stream_files = collection_file.get_stream_names()
print("Stream files:", stream_files)
# extract filename from list of streams
tx_bits_filename = next(
filename for filename in stream_files if "tx-payload-bits" in filename
)
rx_bits_ref_rx_real_ce_filename = next(
filename for filename in stream_files if "rx-payload-bits" in filename
)
# extract data and deserialized data (not used here)
tx_bits, _, _ = sigmf_interface.load_sigmf(
tx_bits_filename, capture_subdir, scope="tx-payload-bits"
)
rx_bits, _, rx_annotations = (
sigmf_interface.load_sigmf(
rx_bits_ref_rx_real_ce_filename, capture_subdir, scope="rx-payload-bits"
)
)
# calculate bit errors
bit_diffs = np.logical_xor(tx_bits, rx_bits)
bit_errors = np.count_nonzero(bit_diffs)
# increment total bit/symbol errors
print("Current bit errors:", bit_errors / len(tx_bits))
bit_errors_total += bit_errors
num_bits_total += len(tx_bits)
# plot "tx_bits" and "rx_bits_ref_rx_real_ce" for debugging purposes
# Calculate the differences
differences = np.array(tx_bits) - np.array(rx_bits)
# Plot TX and RX bits using a scatter plot
fig = plt.figure(figsize=(15, 6))
indices = np.arange(len(tx_bits))
plt.scatter(indices, tx_bits, label="TX bits", color="blue", marker="o", s=80)
plt.scatter(indices, rx_bits, label="RX bits", color="red", marker="x", s=80)
# Highlight differences
for i in range(len(differences)):
if differences[i] != 0:
plt.scatter(i, 1.1, color="red", marker="|", s=100)
plt.xlabel("Bit Index")
plt.ylabel("Bit Value")
plt.title(
"TX vs RX Bits, Record "
+ str(idx)
+ ", Number of bits mismatchs: "
+ str(bit_errors),
fontsize=16,
)
plt.legend()
plt.grid(True)
plt.ylim(-0.1, 1.2)
plt.xlim(200, 250)
plt.show(block=False)
# Sleep for a specified amount of time (e.g., 5 seconds)
plt.pause(6)
plt.close(plt.gcf()) # Close the figure
# Keep the script running to display the plots
# input("Press Enter to continue...")
# calculate BER
ber = bit_errors_total / num_bits_total
print("Total bit errors = ", ber)
def calculate_stats(main_config_file: str):
scope = "real-world"
print(f"Starting to generate statistics with data scope: {scope}")
# read main config
main_config = config_interface.read_main_config_file_json(main_config_file)
# get paths for recorded captures
capture_path = main_config["data_recording_config"]["data_storage_path"]
calculate_ber(capture_path, scope)
if __name__ == "__main__":
main_config = "/home/user/workarea/oai_recorded_data/config_data_recording_app.json"
# calculate BER stats
calculate_stats(main_config)

203
doc/data_recording.md Normal file
View File

@@ -0,0 +1,203 @@
# Synchronized Real-Time Data Recording Application
The Data Recording Application runs in parallel to the system components on one of the LINUX servers. It makes use of OAI T-tracer framework. It is able to communicate with the Base station (gNB) and User terminal (UE).
In addition, it synchronizes and combines data traces and control information (metadata) from Base station and User terminal into consistent [Signal Metadata Format (SigMF) format](https://github.com/gnuradio/SigMF) data sets. SigMF is an open-source standard that specifies a way to describe sets of recorded digital signal samples with metadata (data properties and scenario descriptions) provided in [JSON](http://www.json.org/) files. These recorded data sets have a wide range of uses in research and applications.
## Data Recording Application Architecture
Data Recording Application designed to have multiple services in different simulation environments:
- Data Control Service (Python)
- Data collection (T-Tracer) Service (C)
- Data Conversion Service (Python)
Regarding to the APIs between different services, they are such as the following:
- Data Control Service -> Data Collection (TTracer) Service: The API is based on a shared memory.
- Data Collection (TTracer) Service -> 5G NR Stack: T-Tracer Framework, ethernet connection.
- Data Collection (TTracer) Service -> Data Conversion Service: The API is based on a shared memory.
The following figure shows OAI gNB and UE with the Data Recording App system architecture.
<img src="images/data_recording_arch.png" alt="OAI gNB and UE with the Data Recording App system architectur" width="1000">
## Required Packages
The following packages are required:
- `sudo apt-get install -y python3-sysv-ipc`
- `sudo apt-get install libxft-dev`
- `sudo python3 -m pip install sigmf==1.2.1`
- `sudo python3 -m pip install termcolor`
- `sudo python3 -m pip install bitarray`
- `sudo python3 -m pip install pandas`
- `sudo python3 -m pip install numpy==1.23`
## Configuration Files
### Main Data Recording JSON Configuration File
The Data Recording application provides configuration file in [JSON](http://www.json.org/) format. It is stored in [common/utils/data_recording/config/config_data_recording.json](../common/utils/data_recording/config/config_data_recording.json) folder. The main parameters are:
- **data_storage_path**: Path to directory for data storage
- **num_records**: Number of requested data records in slots
- **t_tracer_message_definition_file**: T-Tracer message definition file
- **parameter_map_file**: Parameter mapping dictionary (OAI parameters to standardized metadata). It is located here: [common/utils/data_recording/config/wireless_link_parameter_map.yaml](../common/utils/data_recording/config/wireless_link_parameter_map.yaml)
- **start_frame_number**: It can be used to start the recording from a specific frame, but it is not yet supported.
- **base_station**:
- requested_tracer_messages: Requested base station data traces. The supported messages are:
- GNB_PHY_UL_FD_PUSCH_IQ
- GNB_PHY_UL_FD_DMRS
- GNB_PHY_UL_FD_CHAN_EST_DMRS_POS
- GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL
- GNB_PHY_UL_PAYLOAD_RX_BITS
- meta_data: Additional base station metadata (additional fixed base station parameters that cannot be read from the system yet, but shall be included into the SigMF metadata)
- **user_equipment**
- requested_tracer_messages: Requested user equipment data traces. The supported messages are:
- UE_PHY_UL_SCRAMBLED_TX_BITS
- UE_PHY_UL_PAYLOAD_TX_BITS
- meta_data: Additional user equipment metadata (additional fixed UE parameters that are not read from the system yet, but shall be included into the SigMF metadata)
- **common_meta_data**: Common metadata parameters that are not read from the system yet, but shall be included into the SigMF metadata such as the sampling rate, the bandwidth, and the clock reference of USRP.
- **tracer_service_baseStation_address**: The IP address and port number of base station server. If the Data Recording App and the Data Collection (TTracer) Service are running on the same server of base station, the IP address and port number are : `127.0.0.1:2021`
- **tracer_service_userEquipment_address**: The IP address and port number of UE server.
**Notes**:
- The data can be recorded from base station and UE or from base station only or UE only. To disable the recording from any station, make the list of `requested_tracer_messages` empty such as: `requested_tracer_messages:[]`
The figure below illustrates an example of a JSON Data Recording App configuration file.
<img src="images/data_recording_app_config.png" alt="Example from JSON Data Recording Configuration File" width="500">
### Wireless Link Parameter Map Dictionary
Since every signal recorder has related configuration with different naming scheme, the [common/utils/data_recording/config/wireless_link_parameter_map.yaml](../common/utils/data_recording/config/wireless_link_parameter_map.yaml) is a dictionary to do the parameters pair between the signal configuration and the SigMF metadata (e.g. OAI parameter name vs. SigMF metadata parameter name). It eases of adoption in case of adding new parameters. In case of changing the name of given parameters in OAI and we need to get those parameters in metadata, the required changes need to be done in the parameter map dictionary.
The following figure shows an example of Wireless Link Parameter Map Dictionary. For example, the frequency range is called in standardized SigMF metadata `frequency_range` while it is called in OAI `freq_range` and it is called in NI 5G NR RFmx `frequency range`.
<img src="images/wireless_link_parameter_map_example.png" alt="Example of Wireless Link Parameter Map Dictionary" width="400">
### Global Metadata
There are some metadata parameters that the user may need to change only once. Those parameters have been hard coded in the Data Recording App header.
[common/utils/data_recording/data_recording_app_v1.0.py](../common/utils/data_recording/data_recording_app_v1.0.py). Those are:
- The global metadata such as: author, description, sigmf collection file prefix, datetime_offset, enable saving config Data Recording App in json file with recorded data, and name of signal generator (i.e. 5gnr_oai). The name of signal generator is used for parameter mapping dictionary (OAI parameters to standardized metadata).
- The mapping between supported OAI messages and file_name_prefix, scope, and description.
The following figure shows an example of global metadata.
<img src="images/global metadata_example.png" alt="Example from global meta-data" width="750">
The following figure shows an example of mapping between supported OAI messages by Data recorrding App and file_name_prefix.
<img src="images/mapping_oai_messages_and_file_name_prefix.png" alt="mapping between supported OAI messages and file_name_prefix" width="600">
## How to run Data Recording Application
The data recording application can be hosted on the gNB Server or in another server (for example the data lake server). The T-Tracer framework is a Network socket-based (TCP) communication between OAI 5G RAN stack and external tracer program.
### Step1: Run NR gNB Softmodem with enabling T-Tracer Option
It is worth mentioning that there is no need to set the T-Tracer port number via NR gNB Softmodem command, the default port `2021` is going to be used. If the Data Recording App is running on the same server of gNB, the parameter `tracer_service_baseStation_address` should be `"tracer_service_baseStation_address": "127.0.0.1:2021`. The gNB T-tracer App will read captured data then from port `2021`.
For the test simplicity, user can run the system in PHY-test mode. We will show how to run using PHY-Test mode:
Run NR gNB Softmodem with USRP:
```
sudo ./cmake_targets/ran_build/build/nr-softmodem -O ./targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.band78.sa.fr1.106PRB.1x1.usrpx410_3300MHz.conf --gNBs.[0].min_rxtxtime 6 --usrp-tx-thread-config 1 --phy-test -d --T_stdout 2 --T_nowait
```
Run NR gNB Softmodem in RF Simulation:
```
sudo ./cmake_targets/ran_build/build/nr-softmodem -O ./targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.band78.sa.fr1.106PRB.1x1.usrpx410_3300MHz.conf --gNBs.[0].min_rxtxtime 6 --rfsim --rfsimulator.serveraddr server --phy-test -d --T_stdout 2 --T_nowait
```
Note: User needs to change the name of gNB config file to your gNB config file.
It is worth mentioning that the possible values for T-tracer control options
- --T_stdout option: default 1
- --T_stdout = 0: Disable output on the terminal and only use the T tracer
- --T_stdout = 1: Disable the T tracer and only output on the terminal
- --T_stdout = 2: Both Eeable the T tracer and output on the terminal
- --T_nowait: Starting gNB without waiting for T-Tracer to be connected
- --T_port [port]: Default port 2021
### Step2: Run NR UE Softmodem with enabling T-Tracer Option
User needs to set the T-Tracer port number via NR UE Softmodem command, assume `2023`. The UE T-tracer App will read captured data then from port `2023`. The IP address and port number are configured in JSON file via the parameter: `"tracer_service_userEquipment_address": "192.168.100.3:2023”`, where the IP address `192.168.100.3` is the IP address of the UE server.
In OAI PHY-test mode, after running the NR gNB softmodem, copy from gNB Server the following two RRC files `reconfig.raw` and `rbconfig.raw` to UE Server (assume to `/home/user/workarea/rrc_files/`):
Run OAI Soft UE with USRP:
```
sudo ./cmake_targets/ran_build/build/nr-uesoftmodem --usrp-args "type=x4xx,addr=192.168.10.2,second_addr=192.168.11.2,clock_source=external,time_source=external" --phy-test -O ./targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf --reconfig-file /home/user/workarea/rrc_files/reconfig.raw --rbconfig-file /home/user/workarea/rrc_files/rbconfig.raw --ue-rxgain 40 --ue-txgain 12 --T_stdout 2 --T_nowait --T_port 2023
```
Run OAI Soft UE in RF Simulation, assume the IP address of gNB server is `192.168.100.2`:
```
sudo ./cmake_targets/ran_build/build/nr-uesoftmodem --rfsim --rfsimulator.serveraddr 192.168.100.2 --phy-test -O ./targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf --reconfig-file /home/user/workarea/rrc_files/reconfig.raw --rbconfig-file /home/user/workarea/rrc_files/rbconfig.raw --T_stdout 2 --T_nowait --T_port 2023
```
### Step3: Run Data Recording Application Services
First, compile the Data Collection (T-Tracer) Services:
- Go to: `common/utils/T/tracer`
- Compile gNB and UE Data Collection Services (T-Tracer Apps)
```
make t_tracer_app_gnb
make t_tracer_app_ue
```
Run gNB Data Collection service on one terminal:
```
./t_tracer_app_gnb -d ../T_messages.txt
```
Run UE Data Collection service on another terminal:
```
./t_tracer_app_ue -d ../T_messages.txt
```
Second, run main Data Recording Application:
- Go to: `common/utils/data_recording`
Run the following python script:
```
python3 data_recording_app_v1.0.py
```
The recorded data set will be stored in the configured path, assume `/home/user/workarea/oai_recorded_data/`.
The following figure shows an example of global metadata.
<img src="images/sigmf_dataset.png" alt="Example from SigMF recorded data set" width="600">
## Overview on Collected Data Set
SigMF supports heterogeneous data via SigMF collections. It is used to bundle multiple SigMF files into one recording.
A heterogeneous data set generated for a certain scenario is stored in a SigMF collection which consists of (as it is shown in the figure above)
- One SigMF collection file per each record, e.g.
- data-collection-rec-idx-timestamp.sigmf-collection
- SigMF recordings (.sigmf-meta file + .sigmf-data file). Per each message, there are two files:
- rx-fd-data-rec-idx-timestamp.sigmf-meta
- rx-fd-data-rec-idx-timestamp.sigmf-data
- tx-pilots-fd-data-rec-idx-timestamp.sigmf-meta
- tx-pilots-fd-data-rec-idx-timestamp.sigmf-data
- raw-ce-fd-data-rec-idx-timestamp.sigmf-meta
- raw-ce-fd-data-rec-idx-timestamp.sigmf-data
- tx-scrambled-bits-rec-idx-timestamp.sigmf-meta
- tx-scrambled-bits-rec-idx-timestamp.sigmf-data
The following figure shows an example of SigMF collection file and how it bundles multiple SigMF files of a single record.
<img src="images/sigmf_data_collection.png" alt="Example from data collection" width="700">
Each SigMF metadata has three sections:
- global: The standardized global parameters are presented here to understand and read the binary data.
- captures: The standardized capture parameters provide details about the capture process.
- annotations: It provides details about the recording scenario.
The following figure shows an example of SigMF metadata file, for example for the frequency-domain RX data.
<img src="images/sigmf_metadata.png" alt="Example from SigMF-metadata" width="500">
## Synchronization Validation
For synchronization validation and to show how to read SigMF metadata, a simple script `common/utils/data_recording/sync_validation_demo.py` has been created to validate that, for example: the recorded bits from gNB and UE are in sync.
## Data Recording Application Limitation
- MIMO Support: The data recording app is ready, tested and validated for SISO. For MIMO, it should be enhanced, tested, and validated.
- It supports the uplink gNB and UE messages listed above. If the user would like to record another message for example from DL, the user needs to define the required meta-data. User can use the existing meta-data definition of UL as a template. Then, add those messages to the headers of different Data Recording App services.
- Data serialization in Tx scrambled bits message without considering location of DMRS symbols: Only captured valid data bits is stored. It means the location of DMRS symbols are not considered and filled with zeros and stored as it is in the grid presented on the figure below. For example:
- Number of bits per IQ Symbol = 4
- Number of subcarrier = 72
- Number of OFDM symbols = 13
- So, the valid number of bits in the transport block (slot) is: 3312 bits. If we will fill DMRS locations by zeros, the number of bits is: 3744 bits, but it is not done due to the real-timing issues. For Tx scrambled bits data de-serialization, the user can reconstruct the Tx Scrambled Bits Grid (2D Grid) by using the captured DMRS grid or Channel Estimates Grid as a reference and no need to derive the DMRS symbols locations based on 5G NR config parameters.
<img src="images/data_serialization_tx_scrambled_bit_message.png" alt="Data serialization " width="500">
### To Do List:
- Provide an overview about the different services of the Data Recording App (Data Control Service, Data collection (T-Tracer) Service, Data Conversion Service) and the APIs definition between them.

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -79,6 +79,8 @@ static void nr_pusch_antenna_processing(void *arg)
uint64_t noise_amp2 = *(rdata->noise_amp2);
int nest_count = *(rdata->nest_count);
delay_t *delay = rdata->delay;
c16_t *pusch_ch_est_dmrs_pos_slot_mem = rdata->pusch_ch_est_dmrs_pos_slot_mem;
int dmrs_symbol_start_idx = rdata->dmrs_symbol_start_idx;
const int chest_freq = rdata->chest_freq;
NR_gNB_PUSCH *pusch_vars = rdata->pusch_vars;
@@ -124,6 +126,7 @@ static void nr_pusch_antenna_processing(void *arg)
int re_offset = k0;
LOG_D(PHY, "PUSCH estimation DMRS type 1, Freq-domain interpolation");
int pilot_cnt = 0;
int ch_est_cnt = 0; // To trace channel coefficients
for (int n = 0; n < 3 * nb_rb_pusch; n++) {
// LS estimation
@@ -140,6 +143,15 @@ static void nr_pusch_antenna_processing(void *arg)
for (int k = pilot_cnt << 1; k < (pilot_cnt << 1) + 4; k++) {
ul_ls_est[k] = ch16;
}
//------------------Write channel parameters to Memory for data recording ------------------//
#if T_TRACER
if (T_ACTIVE(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_POS)) {
// Trace channel coefficients
pusch_ch_est_dmrs_pos_slot_mem[dmrs_symbol_start_idx + delta + ch_est_cnt] = ch16; // 0, 2, 4, 6, 8, location of REs
pusch_ch_est_dmrs_pos_slot_mem[dmrs_symbol_start_idx + delta + ch_est_cnt + 2] = ch16; // 0, 2, 4, 6, 8, location of REs
}
#endif
ch_est_cnt += 4;
pilot_cnt += 2;
}
@@ -423,7 +435,9 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB,
unsigned short bwp_start_subcarrier,
nfapi_nr_pusch_pdu_t *pusch_pdu,
int *max_ch,
uint32_t *nvar)
uint32_t *nvar,
c16_t *pusch_dmrs_slot_mem,
c16_t *pusch_ch_est_dmrs_pos_slot_mem)
{
c16_t pilot[3280] __attribute__((aligned(32)));
@@ -481,6 +495,20 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB,
#endif
//------------------Write DMRS to Memory for Data Recording ------------------//
int dmrs_symbol_start_idx = symbol * pusch_pdu->nrOfLayers * nb_rb_pusch * NR_NB_SC_PER_RB + nl * nb_rb_pusch * NR_NB_SC_PER_RB;
#if T_TRACER
if (T_ACTIVE(T_GNB_PHY_UL_FD_DMRS)) {
// used by T-Tracer to trace DMRS slot grid
int dmrs_delta = 0; // intialize it to zero currently, derive it later from above functions
for (int i = 0; i < (6 * nb_rb_pusch); i++) {
// the generated DMRs is a complex conjugate of mod table, so flip the sign of imag. part
pusch_dmrs_slot_mem [dmrs_symbol_start_idx + dmrs_delta + i * 2].r = pilot[i].r; // 0, 2, 4, 6, 8, location of REs
pusch_dmrs_slot_mem [dmrs_symbol_start_idx + dmrs_delta + i * 2].i = -pilot[i].i; // 0, 2, 4, 6, 8, location of REs
}
}
#endif
int nest_count = 0;
uint64_t noise_amp2 = 0;
delay_t *delay = &gNB->ulsch[ul_id].delay;
@@ -532,6 +560,8 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB,
rdata->chest_freq = gNB->chest_freq;
rdata->rxdataF = gNB->common_vars.rxdataF;
rdata->ans = &ans;
rdata->pusch_ch_est_dmrs_pos_slot_mem = pusch_ch_est_dmrs_pos_slot_mem;
rdata->dmrs_symbol_start_idx = dmrs_symbol_start_idx;
// Call the nr_pusch_antenna_processing function
if (job_id == num_jobs - 1) {
// Run the last job inline

View File

@@ -51,7 +51,9 @@ int nr_pusch_channel_estimation(PHY_VARS_gNB *gNB,
unsigned short bwp_start_subcarrier,
nfapi_nr_pusch_pdu_t *pusch_pdu,
int *max_ch,
uint32_t *nvar);
uint32_t *nvar,
c16_t *pusch_dmrs_slot_mem,
c16_t *pusch_ch_est_dmrs_pos_slot_mem);
void dump_nr_I0_stats(FILE *fd,PHY_VARS_gNB *gNB);

View File

@@ -10,9 +10,18 @@
#include "common/utils/nr/nr_common.h"
#include <openair1/PHY/TOOLS/phy_scope_interface.h>
#include "PHY/sse_intrin.h"
#include "T.h"
#include <sys/time.h>
#include "PHY/log_tools.h"
#define INVALID_VALUE 255
static void copy_c16_data_to_slot_memory(
c16_t *src, c16_t *dst_slot, int nb_re_pusch, int symbol)
{
memcpy(&dst_slot[nb_re_pusch * symbol], src, nb_re_pusch * sizeof(c16_t));
}
void nr_idft(int32_t *z, uint32_t Msc_PUSCH)
{
@@ -1021,7 +1030,9 @@ static void inner_rx(PHY_VARS_gNB *gNB,
int length,
int symbol,
int output_shift,
uint32_t nvar)
uint32_t nvar,
c16_t *rxFext_slot,
c16_t *chFext_slot)
{
int nb_layer = rel15_ul->nrOfLayers;
int nb_rx_ant = frame_parms->nb_antennas_rx;
@@ -1032,6 +1043,7 @@ static void inner_rx(PHY_VARS_gNB *gNB,
memset(rxFext, 0, sizeof(c16_t) * nb_rx_ant * buffer_length);
memset(chFext, 0, sizeof(c16_t) * nb_layer * nb_rx_ant* buffer_length);
int nb_re_pusch = NR_NB_SC_PER_RB * rel15_ul->rb_size;
int dmrs_symbol;
if (gNB->chest_time == 0)
dmrs_symbol = dmrs_symbol_flag ? symbol : get_valid_dmrs_idx_for_channel_est(rel15_ul->ul_dmrs_symb_pos, symbol);
@@ -1052,6 +1064,15 @@ static void inner_rx(PHY_VARS_gNB *gNB,
dmrs_symbol_flag,
rel15_ul,
frame_parms);
#if T_TRACER
// Assume assume Tx and Rx = 1
if (T_ACTIVE(T_GNB_PHY_UL_FD_PUSCH_IQ)) {
copy_c16_data_to_slot_memory(rxFext[aarx], rxFext_slot, nb_re_pusch, symbol);
}
if (T_ACTIVE(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL)) {
copy_c16_data_to_slot_memory(chFext[aatx][aarx], chFext_slot, nb_re_pusch, symbol);
}
#endif
}
}
c16_t rho[nb_layer][nb_layer][buffer_length] __attribute__((aligned(32)));
@@ -1159,6 +1180,8 @@ typedef struct puschSymbolProc_s {
uint32_t nvar;
int beam_nb;
task_ans_t *ans;
c16_t *pusch_ch_est_dmrs_interpl_slot_mem;
c16_t *rxFext_slot_mem;
} puschSymbolProc_t;
static void nr_pusch_symbol_processing(void *arg)
@@ -1188,7 +1211,9 @@ static void nr_pusch_symbol_processing(void *arg)
gNB->pusch_vars[ulsch_id].ul_valid_re_per_slot[symbol],
symbol,
gNB->pusch_vars[ulsch_id].log2_maxh,
rdata->nvar);
rdata->nvar,
rdata->rxFext_slot_mem,
rdata->pusch_ch_est_dmrs_interpl_slot_mem);
int nb_re_pusch = gNB->pusch_vars[ulsch_id].ul_valid_re_per_slot[symbol];
// layer de-mapping
@@ -1250,6 +1275,37 @@ int nr_rx_pusch_tp(PHY_VARS_gNB *gNB,
LOG_D(PHY,"pusch %d.%d : bwp_start_subcarrier %d, rb_start %d, first_carrier_offset %d\n", frame,slot,bwp_start_subcarrier, rel15_ul->rb_start, frame_parms->first_carrier_offset);
LOG_D(PHY,"pusch %d.%d : ul_dmrs_symb_pos %x\n",frame,slot,rel15_ul->ul_dmrs_symb_pos);
// Memories to store data for data recording
int buffer_length_slot = rel15_ul->rb_size * NR_NB_SC_PER_RB * 14 ; // 14 OFDM Symbols per slot
int nb_rx_ant = frame_parms->nb_antennas_rx;
int nb_layer = rel15_ul->nrOfLayers;
// Initialize memory for DMRS signals
c16_t pusch_dmrs_slot_mem[nb_layer * buffer_length_slot] __attribute__((aligned(32)));
// Initialize memory for channel estimates based on DMRS positions
c16_t pusch_ch_est_dmrs_pos_slot_mem[buffer_length_slot * nb_layer * nb_rx_ant] __attribute__((aligned(32)));
// memory to store slot grid with channel coefficients based on DMRS positions after interpolation
c16_t pusch_ch_est_dmrs_interpl_slot_mem[buffer_length_slot * nb_layer * nb_rx_ant] __attribute__((aligned(32)));
// memory to store extracted data including PUSCH + DMRS
c16_t rxFext_slot_mem[nb_rx_ant*buffer_length_slot] __attribute__((aligned(32)));
#if T_TRACER
// Initialize memory for DMRS signals
if (T_ACTIVE(T_GNB_PHY_UL_FD_DMRS))
memset(pusch_dmrs_slot_mem, 0, sizeof(c16_t) * nb_layer * buffer_length_slot);
// Initialize memory for channel estimates based on DMRS positions
if (T_ACTIVE(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_POS))
memset(pusch_ch_est_dmrs_pos_slot_mem, 0, sizeof(c16_t) * buffer_length_slot * nb_layer * nb_rx_ant );
// memory to store slot grid with channel coefficients based on DMRS positions after interpolation
if (T_ACTIVE(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL))
memset(pusch_ch_est_dmrs_interpl_slot_mem, 0, sizeof(c16_t) * buffer_length_slot * nb_layer * nb_rx_ant );
// memory to store extracted data including PUSCH + DMRS
if (T_ACTIVE(T_GNB_PHY_UL_FD_PUSCH_IQ))
memset(rxFext_slot_mem, 0, sizeof(c16_t) * buffer_length_slot * nb_rx_ant);
#endif
//----------------------------------------------------------
//------------------- Channel estimation -------------------
//----------------------------------------------------------
@@ -1275,7 +1331,9 @@ int nr_rx_pusch_tp(PHY_VARS_gNB *gNB,
bwp_start_subcarrier,
rel15_ul,
&max_ch,
&nvar_tmp);
&nvar_tmp,
pusch_dmrs_slot_mem,
pusch_ch_est_dmrs_pos_slot_mem);
nvar += nvar_tmp;
}
// measure the SNR from the channel estimation
@@ -1503,6 +1561,8 @@ int nr_rx_pusch_tp(PHY_VARS_gNB *gNB,
rdata->scramblingSequence = scramblingSequence;
rdata->nvar = nvar;
rdata->beam_nb = beam_nb;
rdata->rxFext_slot_mem= rxFext_slot_mem;
rdata->pusch_ch_est_dmrs_interpl_slot_mem = pusch_ch_est_dmrs_interpl_slot_mem;
if (rel15_ul->pdu_bit_map & PUSCH_PDU_BITMAP_PUSCH_PTRS) {
nr_pusch_symbol_processing(rdata);
@@ -1517,6 +1577,189 @@ int nr_rx_pusch_tp(PHY_VARS_gNB *gNB,
}
} // symbol loop
#if T_TRACER
// Get Time Stamp for T-tracer messages
char trace_time_stamp_str[30];
get_time_stamp_usec(trace_time_stamp_str);
// trace_time_stamp_str = 8 bytes timestamp = YYYYMMDD
// + 9 bytes timestamp = HHMMSSMMM
// Not Ready for MIMO
int dmrs_port = get_dmrs_port(0,rel15_ul->dmrs_ports);
if (T_ACTIVE(T_GNB_PHY_UL_FD_DMRS)) {
// Log GNB_PHY_UL_FD_DMRS using T-Tracer if activated
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
T(T_GNB_PHY_UL_FD_DMRS,
T_INT((int)frame),
T_INT((int)slot),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 0, 8)),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 8, 9)),
T_INT((int)frame_parms->frame_type), // Frame type (0 FDD, 1 TDD) frame_structure
T_INT((int)frame_parms->freq_range), // Frequency range (0 FR1, 1 FR2)
T_INT((int)rel15_ul->subcarrier_spacing), // Subcarrier spacing (0 15kHz, 1 30kHz, 2 60kHz)
T_INT((int)rel15_ul->cyclic_prefix), // Normal or extended prefix (0 normal, 1 extended)
T_INT((int)frame_parms->symbols_per_slot), // Number of symbols per slot
T_INT((int)frame_parms->Nid_cell),
T_INT((int)rel15_ul->rnti),
T_INT((int)rel15_ul->rb_size),
T_INT((int)rel15_ul->rb_start),
T_INT((int)rel15_ul->start_symbol_index), //start_ofdm_symbol
T_INT((int)rel15_ul->nr_of_symbols), //num_ofdm_symbols
T_INT((int)rel15_ul->qam_mod_order), //modulation
T_INT((int)rel15_ul->mcs_index), //mcs
T_INT((int)rel15_ul->mcs_table), //mcs_table_index
T_INT((int)rel15_ul->nrOfLayers), // num_layer
T_INT((int)rel15_ul->transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1
T_INT((int)rel15_ul->dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1
T_INT((int)rel15_ul->ul_dmrs_symb_pos), // used to derive the DMRS symbol positions
T_INT((int)number_dmrs_symbols),
//dmrs_start_ofdm_symbol
//dmrs_duration_num_ofdm_symbols
//dmrs_num_add_positions
T_INT((int)dmrs_port), //dmrs_antenna_port
T_INT((int)rel15_ul->scid), //dmrs_nscid
T_INT((int)frame_parms->nb_antennas_rx), // rx antenna
T_INT(0), //number_of_bits
T_BUFFER((c16_t*)(&(pusch_dmrs_slot_mem[0])), rel15_ul->rb_size * NR_NB_SC_PER_RB * rel15_ul->nr_of_symbols * 4));
}
if (T_ACTIVE(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_POS)) {
// Log GNB_PHY_UL_FD_CHAN_EST_DMRS_POS using T-Tracer if activated
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
T(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_POS,
T_INT((int)frame),
T_INT((int)slot),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 0, 8)),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 8, 9)),
T_INT((int)frame_parms->frame_type), // Frame type (0 FDD, 1 TDD) frame_structure
T_INT((int)frame_parms->freq_range), // Frequency range (0 FR1, 1 FR2)
T_INT((int)rel15_ul->subcarrier_spacing), // Subcarrier spacing (0 15kHz, 1 30kHz, 2 60kHz)
T_INT((int)rel15_ul->cyclic_prefix), // Normal or extended prefix (0 normal, 1 extended)
T_INT((int)frame_parms->symbols_per_slot), // Number of symbols per slot
T_INT((int)frame_parms->Nid_cell),
T_INT((int)rel15_ul->rnti),
T_INT((int)rel15_ul->rb_size),
T_INT((int)rel15_ul->rb_start),
T_INT((int)rel15_ul->start_symbol_index), //start_ofdm_symbol
T_INT((int)rel15_ul->nr_of_symbols), //num_ofdm_symbols
T_INT((int)rel15_ul->qam_mod_order), //modulation
T_INT((int)rel15_ul->mcs_index), //mcs
T_INT((int)rel15_ul->mcs_table), //mcs_table_index
T_INT((int)rel15_ul->nrOfLayers), // num_layer
T_INT((int)rel15_ul->transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1
T_INT((int)rel15_ul->dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1
T_INT((int)rel15_ul->ul_dmrs_symb_pos), // used to derive the DMRS symbol positions
T_INT((int)number_dmrs_symbols),
//dmrs_start_ofdm_symbol
//dmrs_duration_num_ofdm_symbols
//dmrs_num_add_positions
T_INT((int)dmrs_port), //dmrs_antenna_port
T_INT((int)rel15_ul->scid), //dmrs_nscid
T_INT((int)frame_parms->nb_antennas_rx), // rx antenna
T_INT(0), //number_of_bits
T_BUFFER((c16_t*)(&(pusch_ch_est_dmrs_pos_slot_mem[0])), rel15_ul->rb_size * NR_NB_SC_PER_RB * rel15_ul->nr_of_symbols * 4));
}
if (T_ACTIVE(T_GNB_PHY_UL_FD_PUSCH_IQ)) {
// Log GNB_PHY_UL_FD_PUSCH_IQ using T-Tracer if activated
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
T(T_GNB_PHY_UL_FD_PUSCH_IQ,
T_INT((int)frame),
T_INT((int)slot),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 0, 8)),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 8, 9)),
T_INT((int)frame_parms->frame_type), // Frame type (0 FDD, 1 TDD) frame_structure
T_INT((int)frame_parms->freq_range), // Frequency range (0 FR1, 1 FR2)
T_INT((int)rel15_ul->subcarrier_spacing), // Subcarrier spacing (0 15kHz, 1 30kHz, 2 60kHz)
T_INT((int)rel15_ul->cyclic_prefix), // Normal or extended prefix (0 normal, 1 extended)
T_INT((int)frame_parms->symbols_per_slot), // Number of symbols per slot
T_INT((int)frame_parms->Nid_cell),
T_INT((int)rel15_ul->rnti),
T_INT((int)rel15_ul->rb_size),
T_INT((int)rel15_ul->rb_start),
T_INT((int)rel15_ul->start_symbol_index), //start_ofdm_symbol
T_INT((int)rel15_ul->nr_of_symbols), //num_ofdm_symbols
T_INT((int)rel15_ul->qam_mod_order), //modulation
T_INT((int)rel15_ul->mcs_index), //mcs
T_INT((int)rel15_ul->mcs_table), //mcs_table_index
T_INT((int)rel15_ul->nrOfLayers), // num_layer
T_INT((int)rel15_ul->transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1
T_INT((int)rel15_ul->dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1
T_INT((int)rel15_ul->ul_dmrs_symb_pos), // used to derive the DMRS symbol positions
T_INT((int)number_dmrs_symbols),
//dmrs_start_ofdm_symbol
//dmrs_duration_num_ofdm_symbols
//dmrs_num_add_positions
T_INT((int)dmrs_port), //dmrs_antenna_port
T_INT((int)rel15_ul->scid), //dmrs_nscid
T_INT((int)frame_parms->nb_antennas_rx), // rx antenna
T_INT(0), //number_of_bits
T_BUFFER((c16_t*)(&(rxFext_slot_mem[0])), rel15_ul->rb_size * NR_NB_SC_PER_RB * rel15_ul->nr_of_symbols * frame_parms->nb_antennas_rx * 4));
}
if (T_ACTIVE(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL)) {
// Log pusch_ch_est_dmrs_interpl_slot_mem using T-Tracer if activated
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
T(T_GNB_PHY_UL_FD_CHAN_EST_DMRS_INTERPL,
T_INT((int)frame),
T_INT((int)slot),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 0, 8)),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 8, 9)),
T_INT((int)frame_parms->frame_type), // Frame type (0 FDD, 1 TDD) frame_structure
T_INT((int)frame_parms->freq_range), // Frequency range (0 FR1, 1 FR2)
T_INT((int)rel15_ul->subcarrier_spacing), // Subcarrier spacing (0 15kHz, 1 30kHz, 2 60kHz)
T_INT((int)rel15_ul->cyclic_prefix), // Normal or extended prefix (0 normal, 1 extended)
T_INT((int)frame_parms->symbols_per_slot), // Number of symbols per slot
T_INT((int)frame_parms->Nid_cell),
T_INT((int)rel15_ul->rnti),
T_INT((int)rel15_ul->rb_size),
T_INT((int)rel15_ul->rb_start),
T_INT((int)rel15_ul->start_symbol_index), //start_ofdm_symbol
T_INT((int)rel15_ul->nr_of_symbols), //num_ofdm_symbols
T_INT((int)rel15_ul->qam_mod_order), //modulation
T_INT((int)rel15_ul->mcs_index), //mcs
T_INT((int)rel15_ul->mcs_table), //mcs_table_index
T_INT((int)rel15_ul->nrOfLayers), // num_layer
T_INT((int)rel15_ul->transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1
T_INT((int)rel15_ul->dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1
T_INT((int)rel15_ul->ul_dmrs_symb_pos), // used to derive the DMRS symbol positions
T_INT((int)number_dmrs_symbols),
//dmrs_start_ofdm_symbol
//dmrs_duration_num_ofdm_symbols
//dmrs_num_add_positions
T_INT((int)dmrs_port), //dmrs_antenna_port
T_INT((int)rel15_ul->scid), //dmrs_nscid
T_INT((int)frame_parms->nb_antennas_rx), // rx antenna
T_INT(0), //number_of_bits
T_BUFFER((c16_t*)pusch_ch_est_dmrs_interpl_slot_mem,
rel15_ul->rb_size * NR_NB_SC_PER_RB * rel15_ul->nr_of_symbols * frame_parms->nb_antennas_rx * rel15_ul->nrOfLayers * 4));
}
#endif
join_task_ans(&ans);
stop_meas(&gNB->rx_pusch_symbol_processing_stats);

View File

@@ -109,7 +109,8 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue,
const uint8_t slot,
unsigned int *G,
int nb_ulsch,
uint8_t *ULSCH_ids);
uint8_t *ULSCH_ids,
uint16_t number_dmrs_symbols);
/*! \brief Perform PUSCH scrambling. TS 38.211 V15.4.0 subclause 6.3.1.1
@param[in] in Pointer to input bits

View File

@@ -32,6 +32,8 @@
#include "PHY/NR_UE_TRANSPORT/nr_transport_ue.h"
#include "executables/nr-uesoftmodem.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "PHY/log_tools.h"
int nr_ulsch_encoding(PHY_VARS_NR_UE *ue,
NR_UE_ULSCH_t *ulsch,
@@ -39,7 +41,8 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue,
const uint8_t slot,
unsigned int *G,
int nb_ulsch,
uint8_t *ULSCH_ids)
uint8_t *ULSCH_ids,
uint16_t number_dmrs_symbols)
{
start_meas_nr_ue_phy(ue, ULSCH_ENCODING_STATS);
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_NR_UE_ULSCH_ENCODING, VCD_FUNCTION_IN);
@@ -108,6 +111,59 @@ int nr_ulsch_encoding(PHY_VARS_NR_UE *ue,
AssertFatal((A / 8) + 3 <= max_payload_bytes, "A %d is too big (A/8+3 = %d > %d)\n", A, (A / 8) + 3, max_payload_bytes);
}
#if T_TRACER
if (T_ACTIVE(T_UE_PHY_UL_PAYLOAD_TX_BITS)) {
// Get Time Stamp for T-tracer messages
char trace_tx_payload_time_stamp_str[30];
get_time_stamp_usec(trace_tx_payload_time_stamp_str);
// trace_time_stamp_str = 8 bytes timestamp = YYYYMMDD
// + 9 bytes timestamp = HHMMSSMMM
// Log UE_PHY_UL_PAYLOAD_TX_BITS using T-Tracer if activated
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid :
// nb_antennas_tx : int,number_of_bits : buffer,data
// Define the subcarrier spacing vector
//int subcarrier_spacing_vect[] = {15000, 30000, 60000, 120000};
NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
int subcarrier_spacing_index = frame_parms->subcarrier_spacing / 15000 -1;
T(T_UE_PHY_UL_PAYLOAD_TX_BITS,
T_INT((int)frame),
T_INT((int)slot),
T_INT((int)split_time_stamp_and_convert_to_int(trace_tx_payload_time_stamp_str, 0, 8)),
T_INT((int)split_time_stamp_and_convert_to_int(trace_tx_payload_time_stamp_str, 8, 9)),
T_INT((int)frame_parms->frame_type), // Frame type (0 FDD, 1 TDD) frame_structure
T_INT((int)frame_parms->freq_range), // Frequency range (0 FR1, 1 FR2)
T_INT((int)subcarrier_spacing_index), // Subcarrier spacing (0 15kHz, 1 30kHz, 2 60kHz)
T_INT((int)ulsch->pusch_pdu.cyclic_prefix), // Normal or extended prefix (0 normal, 1 extended)
T_INT((int)frame_parms->symbols_per_slot), // Number of symbols per slot
T_INT((int)frame_parms->Nid_cell),
T_INT((int)ulsch->pusch_pdu.rnti),
T_INT((int)ulsch->pusch_pdu.rb_size),
T_INT((int)ulsch->pusch_pdu.rb_start),
T_INT((int)ulsch->pusch_pdu.start_symbol_index), //start_ofdm_symbol
T_INT((int)ulsch->pusch_pdu.nr_of_symbols), //num_ofdm_symbols
T_INT((int)ulsch->pusch_pdu.qam_mod_order), //modulation
T_INT((int)ulsch->pusch_pdu.mcs_index), //mcs
T_INT((int)ulsch->pusch_pdu.mcs_table), //mcs_table_index
T_INT((int)ulsch->pusch_pdu.nrOfLayers), // num_layer
T_INT((int)ulsch->pusch_pdu.transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1
T_INT((int)ulsch->pusch_pdu.dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1
T_INT((int)ulsch->pusch_pdu.ul_dmrs_symb_pos), // used to derive the DMRS symbol positions
T_INT((int)number_dmrs_symbols),
//dmrs_start_ofdm_symbol
//dmrs_duration_num_ofdm_symbols
//dmrs_num_add_positions
T_INT((int)get_dmrs_port(0,ulsch->pusch_pdu.dmrs_ports)), //dmrs_antenna_port
T_INT((int)ulsch->pusch_pdu.scid), //dmrs_nscid
T_INT((int)frame_parms->nb_antennas_tx), // number of tx antennas
T_INT((int)A), //number_of_bits
T_BUFFER((uint8_t*)harq_process->payload_AB, A/8));
}
#endif
///////////////////////// b---->| block segmentation |---->c /////////////////////////
harq_process->BG = pusch_pdu->ldpcBaseGraph;

View File

@@ -48,6 +48,7 @@
#include "executables/softmodem-common.h"
#include "PHY/NR_REFSIG/ul_ref_seq_nr.h"
#include <openair2/UTIL/OPT/opt.h>
#include "PHY/log_tools.h"
//#define DEBUG_PUSCH_MAPPING
//#define DEBUG_MAC_PDU
@@ -539,7 +540,7 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE,
/////////////////////////ULSCH coding/////////////////////////
if (nr_ulsch_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids) == -1) {
if (nr_ulsch_encoding(UE, &phy_data->ulsch, frame, slot, G, 1, ULSCH_ids, number_dmrs_symbols) == -1) {
NR_UL_UE_HARQ_t *harq_process_ulsch = &UE->ul_harq_processes[harq_pid];
harq_process_ulsch->ULstatus = SCH_IDLE;
stop_meas_nr_ue_phy(UE, PUSCH_PROC_STATS);
@@ -600,7 +601,62 @@ void nr_ue_ulsch_procedures(PHY_VARS_NR_UE *UE,
rnti,
false,
scrambled_output);
#if T_TRACER
if (T_ACTIVE(T_UE_PHY_UL_SCRAMBLED_TX_BITS)) {
// Get Time Stamp for T-tracer messages
char trace_time_stamp_str[30];
get_time_stamp_usec(trace_time_stamp_str);
// trace_time_stamp_str = 8 bytes timestamp = YYYYMMDD
// + 9 bytes timestamp = HHMMSSMMM
int dmrs_port = get_dmrs_port(0, pusch_pdu->dmrs_ports);
const uint8_t *in_bytes = (const uint8_t *)scrambled_output;
// Log UE_PHY_UL_SCRAMBLED_TX_BITS using T-Tracer if activated
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid :
// nb_antennas_tx : int,number_of_bits : buffer,data
// Define the subcarrier spacing vector
//int subcarrier_spacing_vect[] = {15000, 30000, 60000, 120000};
int subcarrier_spacing_index = frame_parms->subcarrier_spacing / 15000 -1;
T(T_UE_PHY_UL_SCRAMBLED_TX_BITS,
T_INT((int)frame),
T_INT((int)slot),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 0, 8)),
T_INT((int)split_time_stamp_and_convert_to_int(trace_time_stamp_str, 8, 9)),
T_INT((int)frame_parms->frame_type), // Frame type (0 FDD, 1 TDD) frame_structure
T_INT((int)frame_parms->freq_range), // Frequency range (0 FR1, 1 FR2)
T_INT((int)subcarrier_spacing_index), // Subcarrier spacing (0 15kHz, 1 30kHz, 2 60kHz)
T_INT((int)pusch_pdu->cyclic_prefix), // Normal or extended prefix (0 normal, 1 extended)
T_INT((int)frame_parms->symbols_per_slot), // Number of symbols per slot
T_INT((int)frame_parms->Nid_cell),
T_INT((int)pusch_pdu->rnti),
T_INT((int)pusch_pdu->rb_size),
T_INT((int)pusch_pdu->rb_start),
T_INT((int)pusch_pdu->start_symbol_index), //start_ofdm_symbol
T_INT((int)pusch_pdu->nr_of_symbols), //num_ofdm_symbols
T_INT((int)pusch_pdu->qam_mod_order), //modulation
T_INT((int)pusch_pdu->mcs_index), //mcs
T_INT((int)pusch_pdu->mcs_table), //mcs_table_index
T_INT((int)pusch_pdu->nrOfLayers), // num_layer
T_INT((int)pusch_pdu->transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1
T_INT((int)pusch_pdu->dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1
T_INT((int)pusch_pdu->ul_dmrs_symb_pos), // used to derive the DMRS symbol positions
T_INT((int)number_dmrs_symbols),
//dmrs_start_ofdm_symbol
//dmrs_duration_num_ofdm_symbols
//dmrs_num_add_positions
T_INT((int)dmrs_port), //dmrs_antenna_port
T_INT((int)pusch_pdu->scid), //dmrs_nscid
T_INT((int)frame_parms->nb_antennas_tx), // number of tx antennas
T_INT((int)available_bits), //number_of_bits
T_BUFFER((uint8_t*)in_bytes, available_bits /8));
}
#endif
/////////////////////////ULSCH modulation/////////////////////////
int max_num_re = Nl * number_of_symbols * nb_rb * NR_NB_SC_PER_RB;

View File

@@ -595,6 +595,8 @@ typedef struct puschAntennaProc_s {
NR_DL_FRAME_PARMS *frame_parms;
c16_t ***rxdataF;
task_ans_t* ans;
c16_t *pusch_ch_est_dmrs_pos_slot_mem;
int dmrs_symbol_start_idx;
} puschAntennaProc_t;
struct puschAntennaReqId {

80
openair1/PHY/log_tools.c Normal file
View File

@@ -0,0 +1,80 @@
/*
* 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
*/
/*! \file openair1/PHY/log_tools.c
* \brief log tools used by data recording application (to be merged to existing OAI log tools)
* \author Abdo Gaber
* \date 2024
* \version 1.0
* \company Emerson, NI Test and Measurement
* \email:
* \note
* \warning
*/
#include "log_tools.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <string.h>
#include "PHY/TOOLS/tools_defs.h"
// Get Time Stamp in microseconds in YYYYMMDDHHMMSSmmmuuu format
char* get_time_stamp_usec(char time_stamp_str[]){
// initialization to measure time stamp --This part should be moved to inilization part
time_t my_time;
struct tm * timeinfo;
time (&my_time);
struct timeval tv;
// get time stamp
timeinfo = localtime (&my_time);
gettimeofday(&tv,NULL);
// Add time stamp: yyyy mm dd hh mm ss msec
uint16_t year= timeinfo->tm_year+1900;
uint8_t mon= timeinfo->tm_mon+1;
uint8_t mday= timeinfo->tm_mday;
uint8_t hour= timeinfo->tm_hour;
uint8_t min= timeinfo->tm_min;
uint8_t sec= timeinfo->tm_sec;
uint16_t usec= (tv.tv_usec);
//printf ("Time stamp: %d_%d_%d_%d_%d_%d_%d \n",year,mon,mday,hour,min,sec,usec);
//sprintf(time_stamp_str, "%d_%d_%d_%d_%d_%d_%d",year,mon,mday,hour,min,sec,usec);
sprintf(time_stamp_str, "%04d%02d%02d%02d%02d%02d%06d", year, mon, mday, hour, min, sec, usec);
return time_stamp_str;
}
// Function to convert timestamp string to integer
int convert_time_stamp_to_int(const char* timestamp) {
return atoi(timestamp);
}
int split_time_stamp_and_convert_to_int (char time_stamp_str[], int shift, int length){
char time_part[length+1]; // Buffer to hold the date part YYYYMMDD or HHMMSSmmm
// Copy the first 8 or 9 characters (YYYYMMDD) to HHMMSSmmm
strncpy(time_part, time_stamp_str + shift, length);
time_part[length] = '\0'; // Null-terminate the string
// Convert timestamp string to integer
return convert_time_stamp_to_int(time_part);
}

43
openair1/PHY/log_tools.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* 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
*/
/*! \file openair1/PHY/log_tools.h
* \brief log tools used by data recording application (to be merged to existing OAI log tools)
* \author Abdo Gaber
* \date 2024
* \version 1.0
* \company Emerson, NI Test and Measurement
* \email:
* \note
* \warning
*/
#ifndef __PHY_LOG_TOOLS_H__
#define __PHY_LOG_TOOLS_H__
#include <stdint.h>
#include "PHY/TOOLS/tools_defs.h"
char* get_time_stamp_usec(char time_stamp_str[]);
int convert_time_stamp_to_int(const char* timestamp);
int split_time_stamp_and_convert_to_int (char time_stamp_str[], int shift, int length);
#endif /*__PHY_LOG_TOOLS_H__ */

View File

@@ -39,8 +39,10 @@
#include "nfapi/oai_integration/vendor_ext.h"
#include "assertions.h"
#include <time.h>
#include <sys/time.h>
#include <stdint.h>
#include <openair1/PHY/TOOLS/phy_scope_interface.h>
#include "PHY/log_tools.h"
//#define DEBUG_RXDATA
//#define SRS_IND_DEBUG
@@ -402,6 +404,68 @@ static int nr_ulsch_procedures(PHY_VARS_gNB *gNB, int frame_rx, int slot_rx, boo
crc_valid = true;
}
}
#if T_TRACER
if (T_ACTIVE(T_GNB_PHY_UL_PAYLOAD_RX_BITS)) {
// capture Rx Payload via T-Tracer for both CRC valid and invalid cases
// Get Time Stamp for T-tracer messages
char trace_rx_payload_time_stamp_str[30];
get_time_stamp_usec(trace_rx_payload_time_stamp_str);
// trace_rx_payload_time_stamp_str = 8 bytes timestamp = YYYYMMDD
// + 9 bytes timestamp = HHMMSSMMM
// Log GNB_PHY_UL_PAYLOAD_RX_BITS using T-Tracer if activated
// FORMAT = int,frame : int,slot : int,datetime_yyyymmdd : int,datetime_hhmmssmmm :
// int,frame_type : int,freq_range : int,subcarrier_spacing : int,cyclic_prefix : int,symbols_per_slot :
// int,Nid_cell : int,rnti :
// int,rb_size : int,rb_start : int,start_symbol_index : int,nr_of_symbols :
// int,qam_mod_order : int,mcs_index : int,mcs_table : int,nrOfLayers :
// int,transform_precoding : int,dmrs_config_type : int,ul_dmrs_symb_pos : int,number_dmrs_symbols : int,dmrs_port : int,dmrs_nscid :
// int,nb_antennas_rx : int,number_of_bits : buffer,data
NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms;
int dmrs_port = get_dmrs_port(0, pusch_pdu->dmrs_ports);
//int num_bytes = rdata->Kr_bytes - (ulsch_harq->F >> 3) - ((ulsch_harq->C > 1) ? 3 : 0);
//printf("num_bytes %d, len with CRC %d, data len %d\n", num_bytes, lenWithCrc(1, rdata->A), rdata->A);
// calculate the number of dmrs symbols in the slot
int number_dmrs_symbols = 0;
for (int l = pusch_pdu->start_symbol_index; l < pusch_pdu->start_symbol_index + pusch_pdu->nr_of_symbols; l++)
number_dmrs_symbols += ((pusch_pdu->ul_dmrs_symb_pos)>>l) & 0x01;
// Log GNB_PHY_UL_PAYLOAD_RX_BITS using T-Tracer if activated
T(T_GNB_PHY_UL_PAYLOAD_RX_BITS,
T_INT((int)ulsch->frame),
T_INT((int)ulsch->slot),
T_INT((int)split_time_stamp_and_convert_to_int(trace_rx_payload_time_stamp_str, 0, 8)),
T_INT((int)split_time_stamp_and_convert_to_int(trace_rx_payload_time_stamp_str, 8, 9)),
T_INT((int)frame_parms->frame_type), // Frame type (0 FDD, 1 TDD) frame_structure
T_INT((int)frame_parms->freq_range), // Frequency range (0 FR1, 1 FR2)
T_INT((int)pusch_pdu->subcarrier_spacing), // Subcarrier spacing (0 15kHz, 1 30kHz, 2 60kHz)
T_INT((int)pusch_pdu->cyclic_prefix), // Normal or extended prefix (0 normal, 1 extended)
T_INT((int)frame_parms->symbols_per_slot), // Number of symbols per slot
T_INT((int)frame_parms->Nid_cell),
T_INT((int)pusch_pdu->rnti),
T_INT((int)pusch_pdu->rb_size),
T_INT((int)pusch_pdu->rb_start),
T_INT((int)pusch_pdu->start_symbol_index), //start_ofdm_symbol
T_INT((int)pusch_pdu->nr_of_symbols), //num_ofdm_symbols
T_INT((int)pusch_pdu->qam_mod_order), //modulation
T_INT((int)pusch_pdu->mcs_index), //mcs
T_INT((int)pusch_pdu->mcs_table), //mcs_table_index
T_INT((int)pusch_pdu->nrOfLayers), // num_layer
T_INT((int)pusch_pdu->transform_precoding), // transformPrecoder_enabled = 0, transformPrecoder_disabled = 1
T_INT((int)pusch_pdu->dmrs_config_type), // dmrs_resource_map_config: pusch_dmrs_type1 = 0, pusch_dmrs_type2 = 1
T_INT((int)pusch_pdu->ul_dmrs_symb_pos), // used to derive the DMRS symbol positions
T_INT((int)number_dmrs_symbols),
//dmrs_start_ofdm_symbol
//dmrs_duration_num_ofdm_symbols
//dmrs_num_add_positions
T_INT((int)dmrs_port), //dmrs_antenna_port
T_INT((int)pusch_pdu->scid), //dmrs_nscid
T_INT((int)frame_parms->nb_antennas_rx), // rx antenna
T_INT(((ulsch_harq->TBS) << 3)), //number_of_bits
T_BUFFER((uint8_t*)((ulsch_harq->b)), ((ulsch_harq->TBS) << 3)/8)); //data
}
#endif
nfapi_nr_crc_t *crc = &UL_INFO->crc_ind.crc_list[UL_INFO->crc_ind.number_crcs++];
nfapi_nr_rx_data_pdu_t *pdu = &UL_INFO->rx_ind.pdu_list[UL_INFO->rx_ind.number_of_pdus++];

View File

@@ -513,7 +513,7 @@ int main(int argc, char **argv)
if (input_fd == NULL) {
uint8_t ULSCH_ids[] = {0};
nr_ulsch_encoding(UE, ulsch_ue, 0, 0, &G, 1, ULSCH_ids);
nr_ulsch_encoding(UE, ulsch_ue, 0, 0, &G, 1, ULSCH_ids, 0);
}
printf("\n");