Merge remote-tracking branch 'origin/ntn_leo_freq_comp' into integration_2025_w15 (!3355)

NR UE: add support for continuous frequency offset compensation (in particular NTN LEO Doppler)

- move function add_noise() to file
  openair1/SIMULATION/TOOLS/phase_noise.c
- refactor (co)sinus calculation out of phase_noise() into get_sin_cos()
  in nr_common.c
- NR UE: continuously estimate the frequency offset using PBCH DMRS
- NR UE: add command line parameter --initial-fo to specify known
  initial frequency offset
- NR UE: add support for continuous DL frequency offset compensation
  --cont-fo-comp
- 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
- NR UE: add PI controller to continuous frequency offset compensation
- rfsimulator: enable and correct NTN LEO doppler simulation
- use SIMD to speed up nr_fo_compensation()
- Extend NTN LEO example in RUNMODEM.md to include continuous Doppler
  compensation
- 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.
- NR UE: remove unused nr_slot_tx_offset, it is unused since commit
  f3e60a14
- NR UE: remove now unnecessary local variable
  update_ntn_system_information, directly use
  UE->ntn_config_message->update instead
This commit is contained in:
Robert Schmidt
2025-04-14 16:59:27 +02:00
27 changed files with 678 additions and 168 deletions
+7
View File
@@ -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 <common/utils/LOG/log.h>
//#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);
@@ -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;
@@ -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++) {
+18
View File
@@ -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;
}
@@ -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
+70
View File
@@ -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 <stdint.h>
#include <math.h>
#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;
}
+7
View File
@@ -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
+5 -4
View File
@@ -460,6 +460,11 @@ 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
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
int timing_advance;
@@ -525,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;
@@ -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
@@ -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),\
@@ -453,3 +453,40 @@ 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;
#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
}
@@ -929,6 +929,17 @@ 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 (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, accumulated frequency offset = %.3f Hz\n", ue->freq_offset, freq_offset, ue->freq_off_acc);
// 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__);
nr_ue_rrc_measurements(ue, proc, rxdataF);
@@ -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],
+30 -36
View File
@@ -23,47 +23,41 @@
#include "sim.h"
static uint16_t LUTSin[ResolSinCos+1];
/* 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++;
}
/* 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);
}
}
}
}
+6 -1
View File
@@ -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;
+38 -32
View File
@@ -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
@@ -379,32 +389,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],
@@ -570,16 +554,38 @@ double N_RB2sampling_rate(uint16_t N_RB);
double N_RB2channel_bandwidth(uint16_t N_RB);
/* 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,