From 9e4df97d8409173b4c994e6780d94655f9ff65ed Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Wed, 21 Aug 2024 18:08:29 +0200 Subject: [PATCH 01/16] move function add_noise() to file openair1/SIMULATION/TOOLS/phase_noise.c --- openair1/SIMULATION/TOOLS/multipath_channel.c | 25 -------- openair1/SIMULATION/TOOLS/phase_noise.c | 35 +++++++++-- openair1/SIMULATION/TOOLS/sim.h | 62 +++++++++---------- 3 files changed, 60 insertions(+), 62 deletions(-) diff --git a/openair1/SIMULATION/TOOLS/multipath_channel.c b/openair1/SIMULATION/TOOLS/multipath_channel.c index de616bf86c..f17c2166a3 100644 --- a/openair1/SIMULATION/TOOLS/multipath_channel.c +++ b/openair1/SIMULATION/TOOLS/multipath_channel.c @@ -143,31 +143,6 @@ void __attribute__ ((no_sanitize_address)) multipath_channel(channel_desc_t *des #else -void add_noise(c16_t **rxdata, - const double **r_re, - const double **r_im, - const double sigma, - const int length, - const int slot_offset, - const double ts, - const int delay, - const uint16_t pdu_bit_map, - const uint16_t ptrs_bit_map, - const uint8_t nb_antennas_rx) -{ - for (int i = 0; i < length; i++) { - for (int ap = 0; ap < nb_antennas_rx; ap++) { - c16_t *rxd = &rxdata[ap][slot_offset + i + delay]; - rxd->r = r_re[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); // convert to fixed point - rxd->i = r_im[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); - /* Add phase noise if enabled */ - if (pdu_bit_map & ptrs_bit_map) { - phase_noise(ts, &rxdata[ap][slot_offset + i + delay].r, &rxdata[ap][slot_offset + i + delay].i); - } - } - } -} - void __attribute__ ((no_sanitize_address)) multipath_channel(channel_desc_t *desc, double *tx_sig_re[NB_ANTENNAS_TX], double *tx_sig_im[NB_ANTENNAS_TX], diff --git a/openair1/SIMULATION/TOOLS/phase_noise.c b/openair1/SIMULATION/TOOLS/phase_noise.c index 8c2b36dfec..fa406e7ff9 100644 --- a/openair1/SIMULATION/TOOLS/phase_noise.c +++ b/openair1/SIMULATION/TOOLS/phase_noise.c @@ -22,8 +22,14 @@ #include #include "sim.h" - +#define ResolSinCos 100 static uint16_t LUTSin[ResolSinCos+1]; + +/* Initialisation function for SIN table values */ +void InitSinLUT( void ) { + for ( int i=0; i<(ResolSinCos+1); i++ ) { + LUTSin[i] = sin((double)(M_PI*i)/(2*ResolSinCos)) * (1<<14); //Format: Q14 + /* linear phase noise model */ void phase_noise(double ts, int16_t * InRe, int16_t * InIm) { @@ -61,9 +67,28 @@ void phase_noise(double ts, int16_t * InRe, int16_t * InIm) InIm[0]= (int16_t)(y>>14); i++; } -/* Initialisation function for SIN table values */ -void InitSinLUT( void ) { - for ( int i=0; i<(ResolSinCos+1); i++ ) { - LUTSin[i] = sin((double)(M_PI*i)/(2*ResolSinCos)) * (1<<14); //Format: Q14 + +void add_noise(c16_t **rxdata, + const double **r_re, + const double **r_im, + const double sigma, + const int length, + const int slot_offset, + const double ts, + const int delay, + const uint16_t pdu_bit_map, + const uint16_t ptrs_bit_map, + const uint8_t nb_antennas_rx) +{ + for (int i = 0; i < length; i++) { + for (int ap = 0; ap < nb_antennas_rx; ap++) { + c16_t *rxd = &rxdata[ap][slot_offset + i + delay]; + rxd->r = r_re[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); // convert to fixed point + rxd->i = r_im[ap][i] + sqrt(sigma / 2) * gaussZiggurat(0.0, 1.0); + /* Add phase noise if enabled */ + if (pdu_bit_map & ptrs_bit_map) { + phase_noise(ts, &rxdata[ap][slot_offset + i + delay].r, &rxdata[ap][slot_offset + i + delay].i); + } + } } } diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index 34cae4ba55..ffd5bef8ed 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -379,32 +379,6 @@ void get_cexp_doppler(struct complexd *cexp_doppler, channel_desc_t *chan_desc, */ int random_channel(channel_desc_t *desc, uint8_t abstraction_flag); -/** -\brief Add AWGN noise and phase noise if enabled -\param rxdata output data with noise -\param r_re real part of input data without noise -\param r_im imaginary part of input data without noise -\param sigma noise power -\param length number of samples to apply the noise -\param slot_offset slot offset to start applying the noise -\param ts sampling time -\param delay introduce delay in terms of number of samples -\param pdu_bit_map bitmap indicating presence of optional PDUs -\param ptrs_bit_map -\param nb_antennas_rx number of receive antennas -*/ -void add_noise(c16_t **rxdata, - const double **r_re, - const double **r_im, - const double sigma, - const int length, - const int slot_offset, - const double ts, - const int delay, - const uint16_t pdu_bit_map, - const uint16_t ptrs_bit_map, - const uint8_t nb_antennas_rx); - /**\fn void multipath_channel(channel_desc_t *desc, double tx_sig_re[NB_ANTENNAS_TX], double tx_sig_im[NB_ANTENANS_TX], @@ -569,17 +543,41 @@ int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_ double N_RB2sampling_rate(uint16_t N_RB); double N_RB2channel_bandwidth(uint16_t N_RB); +void InitSinLUT(void); + /* Linear phase noise model */ -/*! +/** \brief This function produce phase noise and add to input signal - \param ts Sampling time + \param ts Sampling time \param *Re *Im Real and Imag part of the signal */ -//look-up table for the sine (cosine) function -#define ResolSinCos 100 -void InitSinLUT( void ); -void phase_noise(double ts, int16_t * InRe, int16_t * InIm); +void phase_noise(double ts, int16_t *InRe, int16_t *InIm); +/** +\brief Add AWGN noise and phase noise if enabled +\param rxdata output data with noise +\param r_re real part of input data without noise +\param r_im imaginary part of input data without noise +\param sigma noise power +\param length number of samples to apply the noise +\param slot_offset slot offset to start applying the noise +\param ts sampling time +\param delay introduce delay in terms of number of samples +\param pdu_bit_map bitmap indicating presence of optional PDUs +\param ptrs_bit_map +\param nb_antennas_rx number of receive antennas +*/ +void add_noise(c16_t **rxdata, + const double **r_re, + const double **r_im, + const double sigma, + const int length, + const int slot_offset, + const double ts, + const int delay, + const uint16_t pdu_bit_map, + const uint16_t ptrs_bit_map, + const uint8_t nb_antennas_rx); void do_DL_sig(sim_t *sim, uint16_t subframe, From dd827c7bd8082b673466ae22eaed5a912af7ed58 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Fri, 23 Aug 2024 10:01:37 +0200 Subject: [PATCH 02/16] refactor (co)sinus calculation out of phase_noise() into openair1/PHY/TOOLS/get_sin_cos.c --- CMakeLists.txt | 1 + openair1/PHY/TOOLS/get_sin_cos.c | 70 +++++++++++++++++++++++++ openair1/PHY/TOOLS/tools_defs.h | 7 +++ openair1/SIMULATION/TOOLS/phase_noise.c | 45 +++------------- openair1/SIMULATION/TOOLS/sim.h | 2 - 5 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 openair1/PHY/TOOLS/get_sin_cos.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b00ca296cd..25bc56b79a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -823,6 +823,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/TOOLS/get_sin_cos.c ) set(PHY_SRC diff --git a/openair1/PHY/TOOLS/get_sin_cos.c b/openair1/PHY/TOOLS/get_sin_cos.c new file mode 100644 index 0000000000..1795b36554 --- /dev/null +++ b/openair1/PHY/TOOLS/get_sin_cos.c @@ -0,0 +1,70 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include "tools_defs.h" + +#define LUT_SIN_RESOL 128 +// look-up table for the sine (cosine) function +static uint16_t LUTSin[LUT_SIN_RESOL + 1]; + +void InitSinLUT(void) +{ + for (int i = 0; i < (LUT_SIN_RESOL + 1); i++) { + LUTSin[i] = round(sin((M_PI * i) / (2 * LUT_SIN_RESOL)) * (1 << 14)); // Format: Q14 + } +} + +// fast calculation of e^{i*2*pi*phase} +// ret.r == cosinus << 14 +// ret.i == sinus << 14 +c16_t get_sin_cos(double phase) +{ + int index; + if (phase < 0) { + index = (int)(-phase * 4 * LUT_SIN_RESOL + 0.5) % (4 * LUT_SIN_RESOL); + index = 4 * LUT_SIN_RESOL - index; + } else { + index = (int)(phase * 4 * LUT_SIN_RESOL + 0.5) % (4 * LUT_SIN_RESOL); + } + + c16_t ret; + if (index < 2 * LUT_SIN_RESOL) { // check for 1st and 2nd Quadrant + if (index < LUT_SIN_RESOL) { // 1st Quadrant + ret.r = LUTSin[LUT_SIN_RESOL - index]; + ret.i = LUTSin[index]; + } else { // 2nd Quadrant + ret.r = -LUTSin[index - LUT_SIN_RESOL]; + ret.i = LUTSin[2 * LUT_SIN_RESOL - index]; + } + } else { // 3rd and 4th Quadrant + if (index < 3 * LUT_SIN_RESOL) { // 3rd Quadrant + ret.r = -LUTSin[3 * LUT_SIN_RESOL - index]; + ret.i = -LUTSin[index - 2 * LUT_SIN_RESOL]; + } else { // 4th Quadrant + ret.r = LUTSin[index - 3 * LUT_SIN_RESOL]; + ret.i = -LUTSin[4 * LUT_SIN_RESOL - index]; + } + } + + return ret; +} diff --git a/openair1/PHY/TOOLS/tools_defs.h b/openair1/PHY/TOOLS/tools_defs.h index 93b07a60de..34d7b73fc7 100644 --- a/openair1/PHY/TOOLS/tools_defs.h +++ b/openair1/PHY/TOOLS/tools_defs.h @@ -792,6 +792,13 @@ double interp(double x, double *xs, double *ys, int count); void simde_mm128_separate_real_imag_parts(simde__m128i *out_re, simde__m128i *out_im, simde__m128i in0, simde__m128i in1); void simde_mm256_separate_real_imag_parts(simde__m256i *out_re, simde__m256i *out_im, simde__m256i in0, simde__m256i in1); +void InitSinLUT(void); + +// fast calculation of e^{i*2*pi*phase} +// ret.r == cosinus << 14 +// ret.i == sinus << 14 +c16_t get_sin_cos(double phase); + #ifdef __cplusplus } #endif diff --git a/openair1/SIMULATION/TOOLS/phase_noise.c b/openair1/SIMULATION/TOOLS/phase_noise.c index fa406e7ff9..5bb6df3b5e 100644 --- a/openair1/SIMULATION/TOOLS/phase_noise.c +++ b/openair1/SIMULATION/TOOLS/phase_noise.c @@ -22,49 +22,18 @@ #include #include "sim.h" -#define ResolSinCos 100 -static uint16_t LUTSin[ResolSinCos+1]; - -/* Initialisation function for SIN table values */ -void InitSinLUT( void ) { - for ( int i=0; i<(ResolSinCos+1); i++ ) { - LUTSin[i] = sin((double)(M_PI*i)/(2*ResolSinCos)) * (1<<14); //Format: Q14 /* linear phase noise model */ -void phase_noise(double ts, int16_t * InRe, int16_t * InIm) +void phase_noise(double ts, int16_t *InRe, int16_t *InIm) { static uint64_t i=0; - int32_t x=0 ,y=0; double fd = 300;//0.01*30000 - int16_t SinValue = 0, CosValue= 0; - double IdxDouble = (double)(i*fd * ts * ResolSinCos * 4); - int16_t IdxModulo = ((int32_t)(IdxDouble>0 ? IdxDouble+0.5 : IdxDouble-0.5)) % (ResolSinCos*4); - IdxModulo = IdxModulo<0 ? IdxModulo+ResolSinCos*4 : IdxModulo; - - if(IdxModulo<2*ResolSinCos) {//< 2 check for 1st and 2nd - if(IdxModulo < ResolSinCos) {// 1st Quadrant - SinValue = LUTSin[IdxModulo]; - CosValue = LUTSin[ResolSinCos-IdxModulo]; - } - else {// 2nd Quadrant - SinValue = LUTSin[2*ResolSinCos-IdxModulo]; - CosValue = -LUTSin[IdxModulo-ResolSinCos]; - } - } - else {// 3rd and 4th Quadrant - if(IdxModulo < 3*ResolSinCos) {// 3rd Quadrant - SinValue = -LUTSin[IdxModulo-2*ResolSinCos]; - CosValue = -LUTSin[3*ResolSinCos-IdxModulo]; - } - else {//4th Quadrant - SinValue = -LUTSin[4*ResolSinCos-IdxModulo]; - CosValue = LUTSin[IdxModulo-3*ResolSinCos]; - } - } - x = ( ((int32_t)InRe[0] * CosValue) - ((int32_t)InIm[0] * SinValue )); - y = ( ((int32_t)InIm[0] * CosValue) + ((int32_t)InRe[0] * SinValue )); - InRe[0]= (int16_t)(x>>14); - InIm[0]= (int16_t)(y>>14); + double phase = (double)(i * fd * ts); + c16_t rot = get_sin_cos(phase); + c16_t val = {.r = *InRe, .i = *InIm}; + val = c16mulShift(val, rot, 14); + *InRe = val.r; + *InIm = val.i; i++; } diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index ffd5bef8ed..28194dcc25 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -543,8 +543,6 @@ int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_ double N_RB2sampling_rate(uint16_t N_RB); double N_RB2channel_bandwidth(uint16_t N_RB); -void InitSinLUT(void); - /* Linear phase noise model */ /** \brief This function produce phase noise and add to input signal From d16507055f620017dbfa3f9f0a14c1b18117eac9 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 10 Sep 2024 17:13:55 +0200 Subject: [PATCH 03/16] NR UE: continuously estimate the frequency offset using PBCH DMRS --- openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c | 18 ++++++++++++++++++ .../NR_UE_TRANSPORT/nr_transport_proto_ue.h | 4 ++++ openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 5 +++++ 3 files changed, 27 insertions(+) diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c index 1de1996126..a570f0a433 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_pbch.c @@ -525,3 +525,21 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue, TracyCZoneEnd(ctx); return 0; } + +double nr_ue_pbch_freq_offset(const NR_DL_FRAME_PARMS *frame_parms, + int estimateSz, + const c16_t dl_ch_estimates[][estimateSz]) +{ + const int i_ssb = frame_parms->ssb_index; + const int symbol_offset = nr_get_ssb_start_symbol(frame_parms, i_ssb) % frame_parms->symbols_per_slot; + const c16_t *dl_ch_est_symb1 = &dl_ch_estimates[0][(symbol_offset + 1) * frame_parms->ofdm_symbol_size]; + const c16_t *dl_ch_est_symb3 = &dl_ch_estimates[0][(symbol_offset + 3) * frame_parms->ofdm_symbol_size]; + const int nb_re = 240; + const c32_t dot_prod_res = dot_product(dl_ch_est_symb1, dl_ch_est_symb3, nb_re, 8); + const double res_phase = atan2(dot_prod_res.i, dot_prod_res.r); + const int samples_per_symbol = frame_parms->ofdm_symbol_size + frame_parms->nb_prefix_samples; + const double t_ofdm = samples_per_symbol / (frame_parms->samples_per_subframe * 1000.0); // symbol duration in sec + const double freq_offset = res_phase / (2 * M_PI * (3 - 1) * t_ofdm); + + return freq_offset; +} diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h index dc075753ef..bf68a69492 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h @@ -204,6 +204,10 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue, int rxdataFSize, const struct complex16 rxdataF[][rxdataFSize]); +double nr_ue_pbch_freq_offset(const NR_DL_FRAME_PARMS *frame_parms, + int estimateSz, + const c16_t dl_ch_estimates[][estimateSz]); + #ifndef modOrder #define modOrder(I_MCS,I_TBS) ((I_MCS-I_TBS)*2+2) // Find modulation order from I_TBS and I_MCS #endif diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index 37593bb9a7..54788de4d1 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -929,6 +929,11 @@ int pbch_pdcch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_ sampleShift = nr_adjust_synch_ue(fp, ue, gNB_id, fp->ofdm_symbol_size, dl_ch_estimates_time, frame_rx, nr_slot_rx, 16384); } + + if (/*ue->continuous_freq_correction &&*/ pbchSuccess == 0) { + double freq_offset = nr_ue_pbch_freq_offset(fp, estimateSz, dl_ch_estimates); + LOG_I(PHY,"detected frequency offset = %.3f Hz\n", freq_offset); + } } LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__); nr_ue_rrc_measurements(ue, proc, rxdataF); From 616568a79de278ccb712b061d0062b663c152e1c Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 12:46:56 +0100 Subject: [PATCH 04/16] NR UE: add command line parameter --initial-fo to specify known initial frequency offset --- doc/RUNMODEM.md | 1 + executables/nr-uesoftmodem.c | 1 + executables/nr-uesoftmodem.h | 3 +++ openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c | 6 +++++- openair1/PHY/defs_nr_UE.h | 2 ++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index b24b4940e5..56359071a8 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -137,6 +137,7 @@ Here are some useful command line options for the NR UE: |--------------------------|---------------------------------------------------------------------------------------------------------------| | `--ue-scan-carrier` | Scan for cells in current bandwidth. This option can be used if the SSB position of the gNB is unknown. If multiple cells are detected, the UE will try to connect to the first cell. By default, this option is disabled and the UE attempts to only decode SSB given by `--ssb`. | | `--ue-fo-compensation` | Enables the frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | +| `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | | `--time-source` | Sets the time source (internal or external). | diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index c0142ce896..9f8b8d8563 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -224,6 +224,7 @@ void set_options(int CC_id, PHY_VARS_NR_UE *UE){ UE->chest_freq = nrUE_params.chest_freq; UE->chest_time = nrUE_params.chest_time; UE->no_timing_correction = nrUE_params.no_timing_correction; + UE->initial_fo = nrUE_params.initial_fo; LOG_I(PHY,"Set UE_fo_compensation %d, UE_scan_carrier %d, UE_no_timing_correction %d \n, chest-freq %d, chest-time %d\n", UE->UE_fo_compensation, UE->UE_scan_carrier, UE->no_timing_correction, UE->chest_freq, UE->chest_time); diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index f7e3b42a72..93741a3424 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -14,6 +14,7 @@ #define CONFIG_HLP_TIME_SYNC_I "coefficient for Integrating part of time sync PI controller\n" #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" +#define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" /***************************************************************************************************************************************/ @@ -65,6 +66,7 @@ {"time-sync-I", CONFIG_HLP_TIME_SYNC_I, 0, .dblptr=&(nrUE_params.time_sync_I), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ + {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } // clang-format on @@ -89,6 +91,7 @@ typedef struct { double time_sync_I; double ntn_init_time_drift; int autonomous_ta; + double initial_fo; int agc; char *usrp_args; char *tx_subdev; diff --git a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c index 3143333a3c..dc634584f6 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c +++ b/openair1/PHY/NR_UE_TRANSPORT/nr_initial_sync.c @@ -199,6 +199,9 @@ void nr_scan_ssb(void *arg) // initial sync performed on two successive frames, if pbch passes on first frame, no need to process second frame // only one frame is used for simulation tools for (int frame_id = 0; frame_id < ssbInfo->nFrames && !ssbInfo->syncRes.cell_detected; frame_id++) { + if (ssbInfo->freqOffset) + compensate_freq_offset(rxdata, fp, ssbInfo->freqOffset, frame_id); + /* process pss search on received buffer */ ssbInfo->syncRes.frame_id = frame_id; int nid2; @@ -268,7 +271,7 @@ void nr_scan_ssb(void *arg) phase_tdd_ncp, ssbInfo->syncRes.rx_offset); #endif - ssbInfo->freqOffset = freq_offset_pss + freq_offset_sss; + ssbInfo->freqOffset += freq_offset_pss + freq_offset_sss; if (ssbInfo->syncRes.cell_detected) { // we got sss channel ssbInfo->syncRes.cell_detected = nr_pbch_detection(ssbInfo->proc, @@ -319,6 +322,7 @@ nr_initial_sync_t nr_initial_sync(UE_nr_rxtx_proc_t *proc, .syncRes.cell_detected = false, .nFrames = n_frames, .foFlag = ue->UE_fo_compensation, + .freqOffset = ue->initial_fo, .targetNidCell = ue->target_Nid_cell}; ssbInfo->rxdata = malloc16_clear(fp->nb_antennas_rx * sizeof(c16_t *)); for (int ant = 0; ant < fp->nb_antennas_rx; ant++) { diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 2cb2b3d8e6..02fe0d6989 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -460,6 +460,8 @@ typedef struct PHY_VARS_NR_UE_s { int64_t max_pos_iir; /// Timing offset IIR filter int max_pos_acc; /// Timing offset accumuluated error for PI filter + double initial_fo; /// initial frequency offset provided by the user + /// Timing Advance updates variables /// Timing advance update computed from the TA command signalled from gNB int timing_advance; From aac4642910dfa3d81997cc040331829157151f92 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:05:59 +0100 Subject: [PATCH 05/16] NR UE: add support for continuous DL frequency offset compensation (--cont-fo-comp) --- executables/nr-ue.c | 13 +++++++++---- executables/nr-uesoftmodem.c | 1 + executables/nr-uesoftmodem.h | 3 +++ openair1/PHY/MODULATION/slot_fep_nr.c | 7 +++++++ openair1/PHY/defs_nr_UE.h | 2 ++ openair1/PHY/nr_phy_common/inc/nr_phy_common.h | 2 ++ openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h | 1 + openair1/PHY/nr_phy_common/src/nr_phy_common.c | 13 +++++++++++++ openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 6 ++++-- 9 files changed, 42 insertions(+), 6 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d34e710cec..35977941e3 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -436,9 +436,13 @@ static void UE_synch(void *arg) { ((ret.rx_offset << 1) / fp->samples_per_subframe * fp->slots_per_subframe) + round((float)((ret.rx_offset << 1) % fp->samples_per_subframe) / fp->samples_per_slot0); - // rerun with new cell parameters and frequency-offset - // todo: the freq_offset computed on DL shall be scaled before being applied to UL - nr_rf_card_config_freq(cfg0, ul_carrier, dl_carrier, freq_offset); + if (get_nrUE_params()->cont_fo_comp) { + UE->freq_offset = freq_offset; + } else { + // rerun with new cell parameters and frequency-offset + nr_rf_card_config_freq(cfg0, ul_carrier, dl_carrier, freq_offset); + UE->rfdevice.trx_set_freq_func(&UE->rfdevice, cfg0); + } if (get_nrUE_params()->agc) { nr_ue_adjust_rx_gain(UE, cfg0, UE->adjust_rxgain); @@ -452,7 +456,6 @@ static void UE_synch(void *arg) { cfg0->rx_freq[0], cfg0->tx_freq[0]); - UE->rfdevice.trx_set_freq_func(&UE->rfdevice, cfg0); UE->is_synchronized = 1; } else { int gain_change = 0; @@ -913,6 +916,8 @@ void *UE_thread(void *arg) if (usrp_tx_thread == 1) UE->rfdevice.trx_write_init(&UE->rfdevice); + InitSinLUT(); + notifiedFIFO_t nf; initNotifiedFIFO(&nf); diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c index 9f8b8d8563..485fcb8943 100644 --- a/executables/nr-uesoftmodem.c +++ b/executables/nr-uesoftmodem.c @@ -225,6 +225,7 @@ void set_options(int CC_id, PHY_VARS_NR_UE *UE){ UE->chest_time = nrUE_params.chest_time; UE->no_timing_correction = nrUE_params.no_timing_correction; UE->initial_fo = nrUE_params.initial_fo; + UE->cont_fo_comp = nrUE_params.cont_fo_comp; LOG_I(PHY,"Set UE_fo_compensation %d, UE_scan_carrier %d, UE_no_timing_correction %d \n, chest-freq %d, chest-time %d\n", UE->UE_fo_compensation, UE->UE_scan_carrier, UE->no_timing_correction, UE->chest_freq, UE->chest_time); diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index 93741a3424..5210db5362 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -15,6 +15,7 @@ #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" #define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" +#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset estimation and compensation\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" /***************************************************************************************************************************************/ @@ -67,6 +68,7 @@ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ + {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, PARAMFLAG_BOOL, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } // clang-format on @@ -92,6 +94,7 @@ typedef struct { double ntn_init_time_drift; int autonomous_ta; double initial_fo; + int cont_fo_comp; int agc; char *usrp_args; char *tx_subdev; diff --git a/openair1/PHY/MODULATION/slot_fep_nr.c b/openair1/PHY/MODULATION/slot_fep_nr.c index bd61d100f8..201101a39a 100644 --- a/openair1/PHY/MODULATION/slot_fep_nr.c +++ b/openair1/PHY/MODULATION/slot_fep_nr.c @@ -25,6 +25,7 @@ #include "nr_modulation.h" #include "PHY/LTE_ESTIMATION/lte_estimation.h" #include "PHY/NR_UE_ESTIMATION/nr_estimation.h" +#include "PHY/nr_phy_common/inc/nr_phy_common.h" #include //#define DEBUG_FEP @@ -108,6 +109,12 @@ int nr_slot_fep(PHY_VARS_NR_UE *ue, rxdata_ptr = (int16_t *)tmp_dft_in; } + if (ue && ue->cont_fo_comp) { + start_meas_nr_ue_phy(ue, RX_FO_COMPENSATION_STATS); + nr_fo_compensation(ue->freq_offset, frame_parms->samples_per_subframe, rx_offset, (c16_t *)rxdata_ptr, frame_parms->ofdm_symbol_size); + stop_meas_nr_ue_phy(ue, RX_FO_COMPENSATION_STATS); + } + if (ue) start_meas_nr_ue_phy(ue, RX_DFT_STATS); diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 02fe0d6989..1b0bfe9f89 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -461,6 +461,8 @@ typedef struct PHY_VARS_NR_UE_s { int max_pos_acc; /// Timing offset accumuluated error for PI filter double initial_fo; /// initial frequency offset provided by the user + int cont_fo_comp; /// flag enabling the continuous frequency offset estimation and compensation + double freq_offset; /// currently compensated frequency offset /// Timing Advance updates variables /// Timing advance update computed from the TA command signalled from gNB diff --git a/openair1/PHY/nr_phy_common/inc/nr_phy_common.h b/openair1/PHY/nr_phy_common/inc/nr_phy_common.h index 9ae785d8ff..225388cb0d 100644 --- a/openair1/PHY/nr_phy_common/inc/nr_phy_common.h +++ b/openair1/PHY/nr_phy_common/inc/nr_phy_common.h @@ -80,4 +80,6 @@ void nr_256qam_llr(int32_t *rxdataF_comp, int32_t *ch_mag, int32_t *ch_mag2, int void freq2time(uint16_t ofdm_symbol_size, int16_t *freq_signal, int16_t *time_signal); void nr_est_delay(int ofdm_symbol_size, const c16_t *ls_est, c16_t *ch_estimates_time, delay_t *delay); unsigned int nr_get_tx_amp(int power_dBm, int power_max_dBm, int total_nb_rb, int nb_rb); +void nr_fo_compensation(double fo_Hz, int samples_per_ms, int sample_offset, c16_t *rxdata_ptr, int size); + #endif diff --git a/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h b/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h index 09e66e8adf..9f91274bae 100644 --- a/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h +++ b/openair1/PHY/nr_phy_common/inc/nr_ue_phy_meas.h @@ -28,6 +28,7 @@ #define FOREACH_NR_PHY_CPU_MEAS(FN) \ FN(RX_PDSCH_STATS),\ FN(DLSCH_RX_PDCCH_STATS),\ + FN(RX_FO_COMPENSATION_STATS),\ FN(RX_DFT_STATS),\ FN(DLSCH_CHANNEL_ESTIMATION_STATS),\ FN(DLSCH_DECODING_STATS),\ diff --git a/openair1/PHY/nr_phy_common/src/nr_phy_common.c b/openair1/PHY/nr_phy_common/src/nr_phy_common.c index e1580ca9f0..c363037850 100644 --- a/openair1/PHY/nr_phy_common/src/nr_phy_common.c +++ b/openair1/PHY/nr_phy_common/src/nr_phy_common.c @@ -453,3 +453,16 @@ unsigned int nr_get_tx_amp(int power_dBm, int power_max_dBm, int total_nb_rb, in } return (0); } + +void nr_fo_compensation(double fo_Hz, int samples_per_ms, int sample_offset, c16_t *rxdata_ptr, int size) +{ + const double phase_inc = -fo_Hz / (samples_per_ms * 1000); + double phase = sample_offset * phase_inc; + phase -= (int)phase; + for (int i = 0; i < size; i++) { + c16_t rot = get_sin_cos(phase); + *rxdata_ptr = c16mulShift(*rxdata_ptr, rot, 14); + rxdata_ptr++; + phase += phase_inc; + } +} diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index 54788de4d1..c7bfbb0f3b 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -930,9 +930,11 @@ int pbch_pdcch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_ nr_adjust_synch_ue(fp, ue, gNB_id, fp->ofdm_symbol_size, dl_ch_estimates_time, frame_rx, nr_slot_rx, 16384); } - if (/*ue->continuous_freq_correction &&*/ pbchSuccess == 0) { + if (get_nrUE_params()->cont_fo_comp && pbchSuccess == 0) { double freq_offset = nr_ue_pbch_freq_offset(fp, estimateSz, dl_ch_estimates); - LOG_I(PHY,"detected frequency offset = %.3f Hz\n", freq_offset); + LOG_D(PHY,"compensated frequency offset = %.3f Hz, detected residual frequency offset = %.3f Hz\n", ue->freq_offset, freq_offset); + + ue->freq_offset += freq_offset; } } LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__); From 84712f5f923ff5db5645a716c3c8e36f85e3407f Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:21:58 +0100 Subject: [PATCH 06/16] NR UE: add support for continuous UL frequency offset pre-compensation Depending on the main source of frequency offset, pre-compensate the UL frequency offset (FO) accordingly. Therefore modify the flag `--cont-fo-comp` to accept an integer value, where - `1` specifies, that the main FO contribution comes from the accuracy of the local oscillator (LO) - `2` specifies, that the main FO contribution comes from Doppler shift --- doc/RUNMODEM.md | 3 ++- executables/nr-ue.c | 10 ++++++++++ executables/nr-uesoftmodem.h | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index 56359071a8..fc35146e45 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -136,7 +136,8 @@ Here are some useful command line options for the NR UE: | Parameter | Description | |--------------------------|---------------------------------------------------------------------------------------------------------------| | `--ue-scan-carrier` | Scan for cells in current bandwidth. This option can be used if the SSB position of the gNB is unknown. If multiple cells are detected, the UE will try to connect to the first cell. By default, this option is disabled and the UE attempts to only decode SSB given by `--ssb`. | -| `--ue-fo-compensation` | Enables the frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | +| `--ue-fo-compensation` | Enables the initial frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | +| `--cont-fo-comp` | Enables the continuous frequency offset (FO) estimation and compensation. Parameter value `1` specifies that the main FO contribution comes from the local oscillator's (LO) accuracy. Parameter value `2` specifies that the main FO contribution comes from Doppler shift. | | `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | diff --git a/executables/nr-ue.c b/executables/nr-ue.c index 35977941e3..d172255c2f 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -41,6 +41,7 @@ #include "instrumentation.h" #include "common/utils/threadPool/notified_fifo.h" #include "position_interface.h" +#include "nr_phy_common.h" /* * NR SLOT PROCESSING SEQUENCE @@ -554,6 +555,15 @@ static void RU_write(nr_rxtx_thread_data_t *rxtxD, bool sl_tx_action) writeBlockSize -= dummyBlockSize; } + // pre-compensate UL frequency offset + if (flags != TX_BURST_INVALID && get_nrUE_params()->cont_fo_comp) { + double ul_freq_offset = -UE->freq_offset * ((double)fp->ul_CarrierFreq / (double)fp->dl_CarrierFreq); + if (get_nrUE_params()->cont_fo_comp == 2) // different from LO frequency error compensation, Doppler UL pre-compensation has to be negative + ul_freq_offset = -ul_freq_offset; + for (int i = 0; i < fp->nb_antennas_tx; i++) + nr_fo_compensation(ul_freq_offset, fp->samples_per_subframe, writeTimestamp, txp[i], writeBlockSize); + } + int tmp = openair0_write_reorder(&UE->rfdevice, writeTimestamp, txp, writeBlockSize, fp->nb_antennas_tx, flags); AssertFatal(tmp == writeBlockSize, ""); } diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index 5210db5362..cc134eaecd 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -15,7 +15,7 @@ #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" #define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" -#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset estimation and compensation\n" +#define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset (FO) estimation and compensation and specify main FO source (1 = local oscillator, 2 = Doppler shift)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" /***************************************************************************************************************************************/ @@ -68,7 +68,7 @@ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ - {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, PARAMFLAG_BOOL, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ + {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, 0, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } // clang-format on From 18469302d879688a0889706dbce0a57ac7befd8b Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:29:29 +0100 Subject: [PATCH 07/16] NR UE: add PI controller to continuous frequency offset compensation --- doc/RUNMODEM.md | 2 ++ executables/nr-uesoftmodem.h | 6 ++++++ openair1/PHY/defs_nr_UE.h | 1 + openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 8 ++++++-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index fc35146e45..eb47a8f753 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -139,6 +139,8 @@ Here are some useful command line options for the NR UE: | `--ue-fo-compensation` | Enables the initial frequency offset compensation at the UE. Useful when running over the air and/or without an external clock/time source. | | `--cont-fo-comp` | Enables the continuous frequency offset (FO) estimation and compensation. Parameter value `1` specifies that the main FO contribution comes from the local oscillator's (LO) accuracy. Parameter value `2` specifies that the main FO contribution comes from Doppler shift. | | `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | +| `--freq-sync-P` | Sets the coefficient for the Proportional part of the PI-controller for the continuous frequency offset compensation. Default value 0.01. | +| `--freq-sync-I` | Sets the coefficient for the Integrating part of the PI-controller for the continuous frequency offset compensation. Default value 0.001. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | | `--time-source` | Sets the time source (internal or external). | diff --git a/executables/nr-uesoftmodem.h b/executables/nr-uesoftmodem.h index cc134eaecd..2b2387915a 100644 --- a/executables/nr-uesoftmodem.h +++ b/executables/nr-uesoftmodem.h @@ -15,6 +15,8 @@ #define CONFIG_HLP_NTN_INIT_TIME_DRIFT "Initial NTN DL time drift (feeder link and service link), given in µs/s\n" #define CONFIG_HLP_AUTONOMOUS_TA "Autonomously update TA based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite)\n" #define CONFIG_HLP_INITIAL_FO "Initially compensated DL frequency offset (e.g. known Doppler shift in NTN LEO scenario)\n" +#define CONFIG_HLP_FREQ_SYNC_P "coefficient for Proportional part of continuous frequency offset compensation PI controller\n" +#define CONFIG_HLP_FREQ_SYNC_I "coefficient for Integrating part of continuous frequency offset compensation PI controller\n" #define CONFIG_HLP_CONT_FO_COMP "Enable continuous frequency offset (FO) estimation and compensation and specify main FO source (1 = local oscillator, 2 = Doppler shift)\n" #define CONFIG_HLP_AGC "Rx Gain control used for UE\n" @@ -68,6 +70,8 @@ {"ntn-initial-time-drift", CONFIG_HLP_NTN_INIT_TIME_DRIFT, 0, .dblptr=&(nrUE_params.ntn_init_time_drift), .defdblval=0.0, TYPE_DOUBLE, 0}, \ {"autonomous-ta", CONFIG_HLP_AUTONOMOUS_TA, PARAMFLAG_BOOL, .iptr=&(nrUE_params.autonomous_ta), .defintval=0, TYPE_INT, 0}, \ {"initial-fo", CONFIG_HLP_INITIAL_FO, 0, .dblptr=&(nrUE_params.initial_fo), .defdblval=0.0, TYPE_DOUBLE, 0}, \ + {"freq-sync-P", CONFIG_HLP_FREQ_SYNC_P, 0, .dblptr=&(nrUE_params.freq_sync_P), .defdblval=0.01, TYPE_DOUBLE, 0}, \ + {"freq-sync-I", CONFIG_HLP_FREQ_SYNC_I, 0, .dblptr=&(nrUE_params.freq_sync_I), .defdblval=0.001, TYPE_DOUBLE, 0}, \ {"cont-fo-comp", CONFIG_HLP_CONT_FO_COMP, 0, .iptr=&(nrUE_params.cont_fo_comp), .defintval=0, TYPE_INT, 0}, \ {"agc", CONFIG_HLP_AGC, PARAMFLAG_BOOL, .iptr=&(nrUE_params.agc), .defintval=0, TYPE_INT, 0}, \ } @@ -94,6 +98,8 @@ typedef struct { double ntn_init_time_drift; int autonomous_ta; double initial_fo; + double freq_sync_P; + double freq_sync_I; int cont_fo_comp; int agc; char *usrp_args; diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index 1b0bfe9f89..b1f75e8bb4 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -463,6 +463,7 @@ typedef struct PHY_VARS_NR_UE_s { double initial_fo; /// initial frequency offset provided by the user int cont_fo_comp; /// flag enabling the continuous frequency offset estimation and compensation double freq_offset; /// currently compensated frequency offset + double freq_off_acc; /// accumulated frequency error (for PI controller) /// Timing Advance updates variables /// Timing advance update computed from the TA command signalled from gNB diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index c7bfbb0f3b..936adb6d6c 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -932,9 +932,13 @@ int pbch_pdcch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_ if (get_nrUE_params()->cont_fo_comp && pbchSuccess == 0) { double freq_offset = nr_ue_pbch_freq_offset(fp, estimateSz, dl_ch_estimates); - LOG_D(PHY,"compensated frequency offset = %.3f Hz, detected residual frequency offset = %.3f Hz\n", ue->freq_offset, freq_offset); + LOG_D(PHY,"compensated frequency offset = %.3f Hz, detected residual frequency offset = %.3f Hz, accumulated frequency offset = %.3f Hz\n", ue->freq_offset, freq_offset, ue->freq_off_acc); - ue->freq_offset += freq_offset; + // PI controller + const double PID_P = get_nrUE_params()->freq_sync_P; + const double PID_I = get_nrUE_params()->freq_sync_I; + ue->freq_offset += freq_offset * PID_P + ue->freq_off_acc * PID_I; + ue->freq_off_acc += freq_offset; } } LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__); From d6585f37502080de9fed069ccef2c0f4d9935ee4 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Tue, 14 Jan 2025 13:31:21 +0100 Subject: [PATCH 08/16] rfsimulator: enable and correct NTN LEO doppler simulation --- openair1/SIMULATION/TOOLS/random_channel.c | 7 +- openair1/SIMULATION/TOOLS/sim.h | 10 ++ radio/rfsimulator/apply_channelmod.c | 110 ++++++++++++++------- radio/rfsimulator/simulator.c | 2 + 4 files changed, 93 insertions(+), 36 deletions(-) diff --git a/openair1/SIMULATION/TOOLS/random_channel.c b/openair1/SIMULATION/TOOLS/random_channel.c index b9ae9e3fff..d13cfd8921 100644 --- a/openair1/SIMULATION/TOOLS/random_channel.c +++ b/openair1/SIMULATION/TOOLS/random_channel.c @@ -1663,7 +1663,7 @@ channel_desc_t *new_channel_desc_scm(uint8_t nb_tx, maxDoppler = 0; chan_desc->sat_height = 600e3; chan_desc->enable_dynamic_delay = true; - chan_desc->enable_dynamic_Doppler = false; // TODO: requires UE to support continuous Doppler estimation, compensation and pre-compensation + chan_desc->enable_dynamic_Doppler = true; fill_channel_desc(chan_desc,nb_tx, nb_rx, nb_taps, @@ -1762,6 +1762,11 @@ void set_channeldesc_name(channel_desc_t *cdesc,char *modelname) { cdesc->model_name=strdup(modelname); } +void set_channeldesc_direction(channel_desc_t *cdesc, bool is_uplink) +{ + cdesc->is_uplink = is_uplink; +} + #ifdef DEBUG_CH_POWER double accumulated_ch_power = 0; int ch_power_count = 0; diff --git a/openair1/SIMULATION/TOOLS/sim.h b/openair1/SIMULATION/TOOLS/sim.h index 28194dcc25..98b4fb26c6 100644 --- a/openair1/SIMULATION/TOOLS/sim.h +++ b/openair1/SIMULATION/TOOLS/sim.h @@ -139,6 +139,8 @@ typedef struct { float Doppler_phase_inc; /// current Doppler phase of each RX antenna (for continuous phase from one block to the next) float *Doppler_phase_cur; + /// flag indicating if channel direction is UL or DL + bool is_uplink; } channel_desc_t; typedef struct { @@ -358,6 +360,7 @@ void free_channel_desc_scm(channel_desc_t *ch); \param module_id identifies the channel model. should be define as a macro in simu.h */ void set_channeldesc_owner(channel_desc_t *cdesc, channelmod_moduleid_t module_id); + /** \brief This function set a model name to a model descriptor, can be later used to identify a allocated channel model \param cdesc points to the model descriptor @@ -365,6 +368,13 @@ void set_channeldesc_owner(channel_desc_t *cdesc, channelmod_moduleid_t module_i */ void set_channeldesc_name(channel_desc_t *cdesc,char *modelname); +/** +\brief This function set a channel model direction to either uplink or downlink +\param cdesc points to the model descriptor +\param is_uplink indicates if this channel is applied in uplink (not downlink) direction +*/ +void set_channeldesc_direction(channel_desc_t *cdesc, bool is_uplink); + /** \fn void get_cexp_doppler(struct complexd *cexp_doppler, channel_desc_t *chan_desc, const uint32_t length) \brief This routine generates the complex exponential to apply the Doppler shift \param cexp_doppler Output with the complex exponential of Doppler shift diff --git a/radio/rfsimulator/apply_channelmod.c b/radio/rfsimulator/apply_channelmod.c index 60d3afbe83..efeb03b25c 100644 --- a/radio/rfsimulator/apply_channelmod.c +++ b/radio/rfsimulator/apply_channelmod.c @@ -50,6 +50,8 @@ void rxAddInput(const c16_t *input_sig, uint32_t CirSize, bool add_noise) { + static uint64_t last_TS = 0; + if ((channelDesc->sat_height > 0) && (channelDesc->enable_dynamic_delay || channelDesc->enable_dynamic_Doppler)) { // model for transparent satellite on circular orbit /* assumptions: - The Earth is spherical, the ground station is static, and that the Earth does not rotate. @@ -82,42 +84,76 @@ void rxAddInput(const c16_t *input_sig, const double pos_ue_y = 0; const double pos_ue_z = radius_earth; - const double dir_sat_ue_x = pos_ue_x - pos_sat_x; - const double dir_sat_ue_y = pos_ue_y - pos_sat_y; - const double dir_sat_ue_z = pos_ue_z - pos_sat_z; - - const double dist_sat_ue = sqrt(dir_sat_ue_x * dir_sat_ue_x + dir_sat_ue_y * dir_sat_ue_y + dir_sat_ue_z * dir_sat_ue_z); - const double vel_sat_ue = (vel_sat_x * dir_sat_ue_x + vel_sat_y * dir_sat_ue_y + vel_sat_z * dir_sat_ue_z) / dist_sat_ue; - - double dist_gnb_sat = 0; - double vel_gnb_sat = 0; - if (channelDesc->modelid == SAT_LEO_TRANS) { - const double pos_gnb_x = 0; - const double pos_gnb_y = 0; - const double pos_gnb_z = radius_earth; - - const double dir_gnb_sat_x = pos_sat_x - pos_gnb_x; - const double dir_gnb_sat_y = pos_sat_y - pos_gnb_y; - const double dir_gnb_sat_z = pos_sat_z - pos_gnb_z; - - dist_gnb_sat = sqrt(dir_gnb_sat_x * dir_gnb_sat_x + dir_gnb_sat_y * dir_gnb_sat_y + dir_gnb_sat_z * dir_gnb_sat_z); - vel_gnb_sat = (vel_sat_x * dir_gnb_sat_x + vel_sat_y * dir_gnb_sat_y + vel_sat_z * dir_gnb_sat_z) / dist_gnb_sat; - } - const double c = 299792458; // m/s - const double prop_delay = (dist_gnb_sat + dist_sat_ue) / c; - if (channelDesc->enable_dynamic_delay) - channelDesc->channel_offset = prop_delay * channelDesc->sampling_rate; - const double f_Doppler_shift_sat_ue = (vel_sat_ue / (c - vel_sat_ue)) * channelDesc->center_freq; - const double f_Doppler_shift_gnb_sat = (-vel_gnb_sat / c) * channelDesc->center_freq; - if (channelDesc->enable_dynamic_Doppler) - channelDesc->Doppler_phase_inc = 2 * M_PI * (f_Doppler_shift_gnb_sat + f_Doppler_shift_sat_ue) / channelDesc->sampling_rate; + if (channelDesc->is_uplink) { + const double dir_ue_sat_x = pos_sat_x - pos_ue_x; + const double dir_ue_sat_y = pos_sat_y - pos_ue_y; + const double dir_ue_sat_z = pos_sat_z - pos_ue_z; - static uint64_t last_TS = 0; - if(TS - last_TS >= channelDesc->sampling_rate) { - last_TS = TS; - LOG_I(HW, "Satellite orbit: time %f s, Doppler: gNB->SAT %f kHz, SAT->UE %f kHz, Delay %f ms\n", t, f_Doppler_shift_gnb_sat / 1000, f_Doppler_shift_sat_ue / 1000, prop_delay * 1000); + const double dist_ue_sat = sqrt(dir_ue_sat_x * dir_ue_sat_x + dir_ue_sat_y * dir_ue_sat_y + dir_ue_sat_z * dir_ue_sat_z); + const double vel_ue_sat = (vel_sat_x * dir_ue_sat_x + vel_sat_y * dir_ue_sat_y + vel_sat_z * dir_ue_sat_z) / dist_ue_sat; + + double dist_sat_gnb = 0; + if (channelDesc->modelid == SAT_LEO_TRANS) { + const double pos_gnb_x = 0; + const double pos_gnb_y = 0; + const double pos_gnb_z = radius_earth; + + const double dir_sat_gnb_x = pos_gnb_x - pos_sat_x; + const double dir_sat_gnb_y = pos_gnb_y - pos_sat_y; + const double dir_sat_gnb_z = pos_gnb_z - pos_sat_z; + + dist_sat_gnb = sqrt(dir_sat_gnb_x * dir_sat_gnb_x + dir_sat_gnb_y * dir_sat_gnb_y + dir_sat_gnb_z * dir_sat_gnb_z); + } + + const double prop_delay = (dist_ue_sat + dist_sat_gnb) / c; + if (channelDesc->enable_dynamic_delay) + channelDesc->channel_offset = prop_delay * channelDesc->sampling_rate; + + const double f_Doppler_shift_ue_sat = (-vel_ue_sat / c) * channelDesc->center_freq; + if (channelDesc->enable_dynamic_Doppler) + channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_ue_sat / channelDesc->sampling_rate; + + if(TS - last_TS >= channelDesc->sampling_rate) { + last_TS = TS; + LOG_D(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_D(HW, "Uplink delay %f ms, Doppler shift UE->SAT %f kHz\n", prop_delay * 1000, f_Doppler_shift_ue_sat / 1000); + } + } else { + const double dir_sat_ue_x = pos_ue_x - pos_sat_x; + const double dir_sat_ue_y = pos_ue_y - pos_sat_y; + const double dir_sat_ue_z = pos_ue_z - pos_sat_z; + + const double dist_sat_ue = sqrt(dir_sat_ue_x * dir_sat_ue_x + dir_sat_ue_y * dir_sat_ue_y + dir_sat_ue_z * dir_sat_ue_z); + const double vel_sat_ue = (vel_sat_x * dir_sat_ue_x + vel_sat_y * dir_sat_ue_y + vel_sat_z * dir_sat_ue_z) / dist_sat_ue; + + double dist_gnb_sat = 0; + if (channelDesc->modelid == SAT_LEO_TRANS) { + const double pos_gnb_x = 0; + const double pos_gnb_y = 0; + const double pos_gnb_z = radius_earth; + + const double dir_gnb_sat_x = pos_sat_x - pos_gnb_x; + const double dir_gnb_sat_y = pos_sat_y - pos_gnb_y; + const double dir_gnb_sat_z = pos_sat_z - pos_gnb_z; + + dist_gnb_sat = sqrt(dir_gnb_sat_x * dir_gnb_sat_x + dir_gnb_sat_y * dir_gnb_sat_y + dir_gnb_sat_z * dir_gnb_sat_z); + } + + const double prop_delay = (dist_gnb_sat + dist_sat_ue) / c; + if (channelDesc->enable_dynamic_delay) + channelDesc->channel_offset = prop_delay * channelDesc->sampling_rate; + + const double f_Doppler_shift_sat_ue = (vel_sat_ue / (c - vel_sat_ue)) * channelDesc->center_freq; + if (channelDesc->enable_dynamic_Doppler) + channelDesc->Doppler_phase_inc = 2 * M_PI * f_Doppler_shift_sat_ue / channelDesc->sampling_rate; + + if(TS - last_TS >= channelDesc->sampling_rate) { + last_TS = TS; + LOG_D(HW, "Satellite orbit: time %f s, Position = (%f, %f, %f), Velocity = (%f, %f, %f)\n", t, pos_sat_x, pos_sat_y, pos_sat_z, vel_sat_x, vel_sat_y, vel_sat_z); + LOG_D(HW, "Downlink delay %f ms, Doppler shift SAT->UE %f kHz\n", prop_delay * 1000, f_Doppler_shift_sat_ue / 1000); + } } } @@ -131,6 +167,8 @@ void rxAddInput(const c16_t *input_sig, const double noise_per_sample = add_noise ? pow(10,channelDesc->noise_power_dB/10.0) * 256 : 0; const uint64_t dd = channelDesc->channel_offset; const int nbTx=channelDesc->nb_tx; + double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt]; + Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI)); for (int i=0; iDoppler_phase_cur[rxAnt] * I); + double complex out = in * cexp(Doppler_phase_cur * I); rx_tmp.r = creal(out); rx_tmp.i = cimag(out); - channelDesc->Doppler_phase_cur[rxAnt] += channelDesc->Doppler_phase_inc; + Doppler_phase_cur += channelDesc->Doppler_phase_inc; } out_ptr->r += rx_tmp.r * pathLossLinear + noise_per_sample * gaussZiggurat(0.0, 1.0); @@ -172,6 +210,8 @@ void rxAddInput(const c16_t *input_sig, out_ptr++; } + channelDesc->Doppler_phase_cur[rxAnt] = Doppler_phase_cur; + if ( (TS*nbTx)%CirSize+nbSamples <= CirSize ) // Cast to a wrong type for compatibility ! LOG_D(HW,"Input power %f, output power: %f, channel path loss %f, noise coeff: %f \n", diff --git a/radio/rfsimulator/simulator.c b/radio/rfsimulator/simulator.c index ca01a1768c..8070bf837e 100644 --- a/radio/rfsimulator/simulator.c +++ b/radio/rfsimulator/simulator.c @@ -274,6 +274,7 @@ static int allocCirBuf(rfsimulator_state_t *bridge, int sock) } set_channeldesc_owner(ptr->channel_model, RFSIMU_MODULEID); + set_channeldesc_direction(ptr->channel_model, bridge->role == SIMU_ROLE_SERVER); random_channel(ptr->channel_model,false); LOG_I(HW, "Random channel %s in rfsimulator activated\n", modelname); } @@ -467,6 +468,7 @@ static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, t->chan_pathloss, 0); // noise_power set_channeldesc_owner(newmodel, RFSIMU_MODULEID); + set_channeldesc_direction(newmodel, t->role == SIMU_ROLE_SERVER); set_channeldesc_name(newmodel,modelname); random_channel(newmodel,false); channel_desc_t *oldmodel=b->channel_model; From 29ed144764f5f1248ee61b239581816422df517a Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Fri, 17 Jan 2025 09:02:52 +0000 Subject: [PATCH 09/16] use SIMD to speed up nr_fo_compensation() --- .../PHY/nr_phy_common/src/nr_phy_common.c | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/openair1/PHY/nr_phy_common/src/nr_phy_common.c b/openair1/PHY/nr_phy_common/src/nr_phy_common.c index c363037850..700721e69f 100644 --- a/openair1/PHY/nr_phy_common/src/nr_phy_common.c +++ b/openair1/PHY/nr_phy_common/src/nr_phy_common.c @@ -459,10 +459,34 @@ void nr_fo_compensation(double fo_Hz, int samples_per_ms, int sample_offset, c16 const double phase_inc = -fo_Hz / (samples_per_ms * 1000); double phase = sample_offset * phase_inc; phase -= (int)phase; - for (int i = 0; i < size; i++) { - c16_t rot = get_sin_cos(phase); - *rxdata_ptr = c16mulShift(*rxdata_ptr, rot, 14); - rxdata_ptr++; +#if 1 + // The bottleneck is the calculation of the complex rotation values using get_sin_cos(). + // This code path does not compute these values for the complete OFDM symbol, but only for a smaller CHUNK size. + // After applying the rotation to a CHUNK size of the output, these rotation values are efficiently rotated further by `rot_vec`. + // Unfortunately, this propagates small errors from one chunk to the next. + // Therefore, there is a tradeoff between speed (better with small CHUNK sizes) and accuracy (better with large CHUNK sizes). +#define CHUNK 128 + c16_t rot[CHUNK] __attribute__((aligned(32))); + for (int i = 0; i < CHUNK; i++) { + rot[i] = get_sin_cos(phase); phase += phase_inc; } + const c16_t rot_vec = get_sin_cos(CHUNK * phase_inc); + while (size > CHUNK) { + mult_complex_vectors(rxdata_ptr, rot, rxdata_ptr, CHUNK, 14); + rxdata_ptr += CHUNK; + rotate_cpx_vector(rot, &rot_vec, rot, CHUNK, 14); + size -= CHUNK; + } + mult_complex_vectors(rxdata_ptr, rot, rxdata_ptr, size, 14); +#else + // This code path computes the complex rotation values for the complete OFDM symbol using get_sin_cos(). + // This is more accurate, but also slower than the code path above. + c16_t rot[size] __attribute__((aligned(32))); + for (int i = 0; i < size; i++) { + rot[i] = get_sin_cos(phase); + phase += phase_inc; + } + mult_complex_vectors(rxdata_ptr, rot, rxdata_ptr, size, 14); +#endif } From 4e3a7ad56e500e4a5a62f91cf1b59ccafd1afcda Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Mon, 17 Feb 2025 13:18:26 +0000 Subject: [PATCH 10/16] Extend NTN LEO example in RUNMODEM.md to include continuous Doppler compensation --- doc/RUNMODEM.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index eb47a8f753..f2e71390c7 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -141,6 +141,10 @@ Here are some useful command line options for the NR UE: | `--initial-fo` | Sets the known initial frequency offset. Useful especially with large Doppler frequency, e.g. LEO satellite. | | `--freq-sync-P` | Sets the coefficient for the Proportional part of the PI-controller for the continuous frequency offset compensation. Default value 0.01. | | `--freq-sync-I` | Sets the coefficient for the Integrating part of the PI-controller for the continuous frequency offset compensation. Default value 0.001. | +| `--ntn-initial-time-drift` | Sets the initial NTN DL time drift (feeder link and service link), given in µs/s. | +| `--autonomous-ta` | Enables the autonomous TA update, based on DL drift (useful if main contribution to DL drift is movement, e.g. LEO satellite). | +| `--time-sync-P` | Sets the coefficient for the Proportional part of the PI-controller for the time synchronization. Default value 0.5. | +| `--time-sync-I` | Sets the coefficient for the Integrating part of the PI-controller for the time synchronization. Default value 0.0. | | `--usrp-args` | Equivalent to the `sdr_addrs` field in the gNB config file. Used to identify the USRP and set some basic parameters (like the clock source). | | `--clock-source` | Sets the clock source (internal or external). | | `--time-source` | Sets the time source (internal or external). | @@ -353,10 +357,16 @@ For LEO satellite scenarios, the parameter `--ntn-initial-time-drift` must be pr This parameter provides the drift rate of the complete DL timing (incl. feeder link and service link) in µs/s. Also, to perform an autonomous TA update based on the DL drift, the boolean parameter `--autonomous-ta` should be added in case of a LEO satellite scenario. +For LEO satellite scenario we assume the LO to be very accurate and the main FO contribution comes from Doppler shift. +Therefore, we use the command line parameter `--cont-fo-comp 2` to continuously compensate the DL Doppler and pre-compensate the UL Doppler. +The initial Doppler frequency offset must be provided via command line with the parameter `--initial-fo`. + +For other information on optional NR UE command line options, please refer [here](#optional-nr-ue-command-line-options). + So an example NR UE command for FDD, 5MHz BW, 15 kHz SCS, transparent LEO satellite 5G NR NTN is this: ``` cd cmake_targets -sudo ./ran_build/build/nr-uesoftmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 --rfsim --rfsimulator.prop_delay 20 --rfsimulator.options chanmod --time-sync-I 0.1 --ntn-initial-time-drift -46 --autonomous-ta +sudo ./ran_build/build/nr-uesoftmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.conf --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 --rfsim --rfsimulator.prop_delay 20 --rfsimulator.options chanmod --time-sync-I 0.1 --ntn-initial-time-drift -46 --autonomous-ta --initial-fo 57340 --cont-fo-comp 2 ``` # Specific OAI modes From a9132a2f797554f990771cc4ed487e6e384dcada Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 27 Mar 2025 09:22:22 +0000 Subject: [PATCH 11/16] NR UE: in nr_adjust_synch_ue() extend the peak search to +/- cyclic prefix size esp. for LEO NTN scenarios the timing drifts during initial sync, which is mostly compensated with --ntn-initial-time-drift. But there might be a residual time offset, and by extending the peak search window we make this more tolerant. --- openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c b/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c index 5b91a068ed..7bcd8264f8 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_adjust_synch_ue.c @@ -47,7 +47,7 @@ int nr_adjust_synch_ue(NR_DL_FRAME_PARMS *frame_parms, VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_UE_ADJUST_SYNCH, VCD_FUNCTION_IN); // search for maximum position within the cyclic prefix - for (int i = -frame_parms->nb_prefix_samples/2; i < frame_parms->nb_prefix_samples/2; i++) { + for (int i = -frame_parms->nb_prefix_samples; i < frame_parms->nb_prefix_samples; i++) { int temp = 0; int j = (i < 0) ? (i + frame_parms->ofdm_symbol_size) : i; From 997790f6562df1fba617209d5c65816803ede3ee Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 3 Apr 2025 07:31:24 +0000 Subject: [PATCH 12/16] NR UE: remove unused nr_slot_tx_offset `nr_slot_tx_offset` is unused since commit f3e60a141747ac12e2c9058e35e64fc5315d2911 --- executables/nr-ue.c | 4 ---- openair1/PHY/defs_nr_UE.h | 4 ---- 2 files changed, 8 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d172255c2f..d9266a3664 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -964,11 +964,9 @@ void *UE_thread(void *arg) int ntn_koffset = 0; int duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX; - int nr_slot_tx_offset = 0; bool update_ntn_system_information = false; while (!oai_exit) { - nr_slot_tx_offset = 0; if (syncRunning) { notifiedFIFO_elt_t *res = pollNotifiedFIFO(&nf); @@ -1096,7 +1094,6 @@ void *UE_thread(void *arg) if (UE->ntn_config_message->update) { UE->ntn_config_message->update = false; update_ntn_system_information = true; - nr_slot_tx_offset = UE->ntn_config_message->ntn_config_params.cell_specific_k_offset << fp->numerology_index; } int slot_nr = absolute_slot % nb_slot_frame; @@ -1200,7 +1197,6 @@ void *UE_thread(void *arg) curMsgTx->writeBlockSize = writeBlockSize; curMsgTx->proc.timestamp_tx = writeTimestamp; curMsgTx->UE = UE; - curMsgTx->proc.nr_slot_tx_offset = nr_slot_tx_offset; int slot = curMsgTx->proc.nr_slot_tx; int slot_and_frame = slot + curMsgTx->proc.frame_tx * UE->frame_parms.slots_per_frame; diff --git a/openair1/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h index b1f75e8bb4..f9349f7cd0 100644 --- a/openair1/PHY/defs_nr_UE.h +++ b/openair1/PHY/defs_nr_UE.h @@ -530,10 +530,6 @@ typedef struct { int gNB_id; /// NR slot index within frame_tx [0 .. slots_per_frame - 1] to act upon for transmission int nr_slot_tx; - /// NR slot index tx offset to resume - /// in case of NTN, tx_offset can be changed dynamically via SIB19 - /// we need to notify the right tx thread slot based on TX offset change - int nr_slot_tx_offset; int rx_slot_type; /// NR slot index within frame_rx [0 .. slots_per_frame - 1] to act upon for transmission int nr_slot_rx; From 6e06c817f435b3ebe3d0be04973044d2d086c0f1 Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 3 Apr 2025 07:52:38 +0000 Subject: [PATCH 13/16] NR UE: remove now unnecessary local variable update_ntn_system_information directly use `UE->ntn_config_message->update` instead --- executables/nr-ue.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index d9266a3664..ff6da29a0e 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -874,13 +874,12 @@ static inline int get_readBlockSize(uint16_t slot, NR_DL_FRAME_PARMS *fp) { static inline void apply_ntn_config(PHY_VARS_NR_UE *UE, NR_DL_FRAME_PARMS *fp, int slot_nr, - bool *update_ntn_system_information, int *duration_rx_to_tx, int *timing_advance, int *ntn_koffset) { - if (*update_ntn_system_information) { - *update_ntn_system_information = false; + if (UE->ntn_config_message->update) { + UE->ntn_config_message->update = false; double total_ta_ms = UE->ntn_config_message->ntn_config_params.ntn_total_time_advance_ms; UE->timing_advance = fp->samples_per_subframe * total_ta_ms; @@ -962,9 +961,7 @@ void *UE_thread(void *arg) double ntn_init_time_drift = get_nrUE_params()->ntn_init_time_drift; int ntn_koffset = 0; - int duration_rx_to_tx = NR_UE_CAPABILITY_SLOT_RX_TO_TX; - bool update_ntn_system_information = false; while (!oai_exit) { if (syncRunning) { @@ -1091,11 +1088,6 @@ void *UE_thread(void *arg) absolute_slot++; TracyCFrameMark; - if (UE->ntn_config_message->update) { - UE->ntn_config_message->update = false; - update_ntn_system_information = true; - } - int slot_nr = absolute_slot % nb_slot_frame; nr_rxtx_thread_data_t curMsg = {0}; curMsg.UE=UE; @@ -1185,8 +1177,8 @@ void *UE_thread(void *arg) shiftForNextFrame = ret; pushNotifiedFIFO(&UE->dl_actors[curMsg.proc.nr_slot_rx % NUM_DL_ACTORS].fifo, newRx); - // apply new duration next run to avoid thread dead lock - apply_ntn_config(UE, fp, slot_nr, &update_ntn_system_information, &duration_rx_to_tx, &timing_advance, &ntn_koffset); + // apply new NTN timing information + apply_ntn_config(UE, fp, slot_nr, &duration_rx_to_tx, &timing_advance, &ntn_koffset); // Start TX slot processing here. It runs in parallel with RX slot processing // in current code, DURATION_RX_TO_TX constant is the limit to get UL data to encode from a RX slot From 67482f5d85d5f18eb909bc8404150710895649be Mon Sep 17 00:00:00 2001 From: Thomas Schlichter Date: Thu, 10 Apr 2025 14:34:39 +0000 Subject: [PATCH 14/16] CI: add NR UE conf file including NTN LEO channel model --- ci-scripts/conf_files/nrue.uicc.ntn-leo.conf | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 ci-scripts/conf_files/nrue.uicc.ntn-leo.conf diff --git a/ci-scripts/conf_files/nrue.uicc.ntn-leo.conf b/ci-scripts/conf_files/nrue.uicc.ntn-leo.conf new file mode 100644 index 0000000000..d8ad297180 --- /dev/null +++ b/ci-scripts/conf_files/nrue.uicc.ntn-leo.conf @@ -0,0 +1,35 @@ +uicc0 = { + imsi = "208990100001100"; + key = "fec86ba6eb707ed08905757b1bb44b8f"; + opc= "C42449363BBAD02B66D16BC975D77CC1"; + dnn= "oai"; + nssai_sst=1; +} + +position0 = { + x = 0.0; + y = 0.0; + z = 6377900.0; +} + +thread-pool = "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + +#/* configuration for channel modelisation */ +#/* To be included in main config file when */ +#/* channel modelisation is used (rfsimulator with chanmod options enabled) */ +channelmod = { + max_chan = 10; + modellist = "modellist_rfsimu_1"; + modellist_rfsimu_1 = ( + { # DL, modify on UE side + model_name = "rfsimu_channel_enB0" + type = "SAT_LEO_TRANS"; + noise_power_dB = -100; + }, + { # UL, modify on gNB side + model_name = "rfsimu_channel_ue0" + type = "SAT_LEO_TRANS"; + noise_power_dB = -100; + } + ); +}; From 890a6c702e82bb15ba40af5e86d53f70dcff5643 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 14 Apr 2025 10:42:10 +0200 Subject: [PATCH 15/16] Remove unnecessary fields in NTN-LEO gNB config --- .../gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf index 7a47d90975..729bb4d6f0 100644 --- a/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf +++ b/ci-scripts/conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf @@ -235,15 +235,6 @@ RUs = ( } ); -THREAD_STRUCT = ( - { - #three config for level of parallelism "PARALLEL_SINGLE_THREAD", "PARALLEL_RU_L1_SPLIT", or "PARALLEL_RU_L1_TRX_SPLIT" - parallel_config = "PARALLEL_RU_L1_TRX_SPLIT"; - #two option for worker "WORKER_DISABLE" or "WORKER_ENABLE" - worker_config = "WORKER_DISABLE"; - } -); - rfsimulator : { serveraddr = "server"; @@ -296,6 +287,4 @@ log_config : rlc_log_level ="info"; pdcp_log_level ="info"; rrc_log_level ="info"; - ngap_log_level ="debug"; - f1ap_log_level ="debug"; }; From 75b237fb90459f77361845ed4b4df81fe37ab434 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 14 Apr 2025 10:45:40 +0200 Subject: [PATCH 16/16] Add 5G RFsim NTN LEO CI Test --- .../xml_files/container_5g_rfsim_ntn_leo.xml | 118 ++++++++++++++ .../docker-compose.yaml | 151 ++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml create mode 100644 ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml diff --git a/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml b/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml new file mode 100644 index 0000000000..2846fc016e --- /dev/null +++ b/ci-scripts/xml_files/container_5g_rfsim_ntn_leo.xml @@ -0,0 +1,118 @@ + + + rfsim-5gnr-ntn-Leo + Monolithic SA NTN LEO gNB + wrench + + 111111 + 100001 + 800813 + 000001 + 000002 + 000003 + 020001 + 020002 + 100001 + 222222 + + + + + Pull_Local_Registry + Pull Images from Local Registry + 0 + oai-gnb-asan oai-nr-ue-asan + + + + Create_Workspace + Create new Workspace + 0 + 0 + + + Deploy_Object + Deploy OAI 5G CoreNetwork + ci-scripts/yaml_files/5g_rfsimulator_ntn_leo + mysql oai-amf oai-smf oai-upf oai-ext-dn + 0 + 0 + + + + Deploy_Object + Deploy OAI 5G gNB+nrUE RF sim SA + ci-scripts/yaml_files/5g_rfsimulator_ntn_leo + oai-gnb oai-nr-ue + 0 + 0 + + + + Attach_UE + Attach OAI UE (Wait for IP) + rfsim5g_ue + cacofonix + + + + Ping + Ping ext-dn from NR-UE + rfsim5g_ue + cacofonix + rfsim5g_ext_dn + cacofonix + -c 20 -i 0.25 + 5 + + + + Ping + Ping NR-UE from ext-dn + rfsim5g_ext_dn + cacofonix + rfsim5g_ue + cacofonix + -c 20 -i 0.25 + 5 + + + + Undeploy_Object + true + Undeploy all OAI 5G stack + ci-scripts/yaml_files/5g_rfsimulator_ntn_leo + 0 + 0 + + + + Clean_Test_Server_Images + true + Clean Test Images on Test Server + 0 + oai-gnb-asan oai-nr-ue-asan + + + diff --git a/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml new file mode 100644 index 0000000000..0cb382710e --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator_ntn_leo/docker-compose.yaml @@ -0,0 +1,151 @@ +services: + mysql: + container_name: "rfsim5g-mysql" + image: mysql:8.0 + volumes: + - ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql + - ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh + environment: + - TZ=Europe/Paris + - MYSQL_DATABASE=oai_db + - MYSQL_USER=test + - MYSQL_PASSWORD=test + - MYSQL_ROOT_PASSWORD=linux + healthcheck: + test: /bin/bash -c "/tmp/mysql-healthcheck.sh" + interval: 10s + timeout: 5s + retries: 30 + networks: + public_net: + ipv4_address: 192.168.71.131 + oai-amf: + container_name: "rfsim5g-oai-amf" + image: oaisoftwarealliance/oai-amf:v2.0.0 + environment: + - TZ=Europe/paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml + depends_on: + - mysql + networks: + public_net: + ipv4_address: 192.168.71.132 + oai-smf: + container_name: "rfsim5g-oai-smf" + image: oaisoftwarealliance/oai-smf:v2.0.0 + environment: + - TZ=Europe/Paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml + depends_on: + - oai-amf + networks: + public_net: + ipv4_address: 192.168.71.133 + oai-upf: + container_name: "rfsim5g-oai-upf" + image: oaisoftwarealliance/oai-upf:v2.0.0 + environment: + - TZ=Europe/Paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml + depends_on: + - oai-smf + cap_add: + - NET_ADMIN + - SYS_ADMIN + cap_drop: + - ALL + privileged: true + networks: + public_net: + ipv4_address: 192.168.71.134 + traffic_net: + ipv4_address: 192.168.72.134 + oai-ext-dn: + privileged: true + container_name: rfsim5g-oai-ext-dn + image: oaisoftwarealliance/trf-gen-cn5g:focal + entrypoint: /bin/bash -c \ + "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\ + "ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity" + depends_on: + - oai-upf + networks: + traffic_net: + ipv4_address: 192.168.72.135 + healthcheck: + test: /bin/bash -c "ping -c 2 192.168.72.134" + interval: 10s + timeout: 5s + retries: 5 + oai-gnb: + image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} + container_name: rfsim5g-oai-gnb + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --rfsim --log_config.global_log_options level,nocolor,time + ASAN_OPTIONS: detect_leaks=0 + depends_on: + - oai-ext-dn + networks: + public_net: + ipv4_address: 192.168.71.140 + volumes: + - ../../conf_files/gnb.sa.band254.u0.25prb.rfsim.ntn-leo.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-softmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + container_name: rfsim5g-oai-nr-ue + cap_drop: + - ALL + cap_add: + - NET_ADMIN # for interface bringup + - NET_RAW # for ping + environment: + USE_ADDITIONAL_OPTIONS: + --band 254 -C 2488400000 --CO -873500000 -r 25 --numerology 0 --ssb 60 + --rfsim --rfsimulator.prop_delay 20 --rfsimulator.options chanmod + --rfsimulator.serveraddr 192.168.71.140 + --time-sync-I 0.1 --ntn-initial-time-drift -46 + --autonomous-ta --initial-fo 57340 --cont-fo-comp 2 + --log_config.global_log_options level,nocolor,time + depends_on: + - oai-gnb + networks: + public_net: + ipv4_address: 192.168.71.150 + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - ../../conf_files/nrue.uicc.ntn-leo.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + +networks: + public_net: + driver: bridge + name: rfsim5g-oai-public-net + ipam: + config: + - subnet: 192.168.71.128/26 + driver_opts: + com.docker.network.bridge.name: "rfsim5g-public" + traffic_net: + driver: bridge + name: rfsim5g-oai-traffic-net + ipam: + config: + - subnet: 192.168.72.128/26 + driver_opts: + com.docker.network.bridge.name: "rfsim5g-traffic"