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/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";
};
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;
+ }
+ );
+};
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"
diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md
index b24b4940e5..f2e71390c7 100644
--- a/doc/RUNMODEM.md
+++ b/doc/RUNMODEM.md
@@ -136,7 +136,15 @@ 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. |
+| `--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). |
@@ -349,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
diff --git a/executables/nr-ue.c b/executables/nr-ue.c
index f68b49cd3d..f2716c099e 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
@@ -438,9 +439,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);
@@ -454,7 +459,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;
@@ -553,6 +557,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, "");
}
@@ -868,13 +881,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;
@@ -920,6 +932,8 @@ void *UE_thread(void *arg)
if (usrp_tx_thread == 1)
UE->rfdevice.trx_write_init(&UE->rfdevice);
+ InitSinLUT();
+
notifiedFIFO_t nf;
initNotifiedFIFO(&nf);
@@ -954,13 +968,9 @@ 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;
- 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);
@@ -1085,12 +1095,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;
- 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;
nr_rxtx_thread_data_t curMsg = {0};
curMsg.UE=UE;
@@ -1180,8 +1184,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
@@ -1192,7 +1196,7 @@ 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;
int next_tx_slot_and_frame = absolute_slot + duration_rx_to_tx + 1;
diff --git a/executables/nr-uesoftmodem.c b/executables/nr-uesoftmodem.c
index b898cdf7d2..931ffc3f31 100644
--- a/executables/nr-uesoftmodem.c
+++ b/executables/nr-uesoftmodem.c
@@ -224,6 +224,8 @@ 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;
+ 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 28de33770e..1bc447cb44 100644
--- a/executables/nr-uesoftmodem.h
+++ b/executables/nr-uesoftmodem.h
@@ -14,6 +14,10 @@
#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_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"
#define CONFIG_HLP_NUM_UL_ACTORS "Number of UL actors to use. Set to 0 to use the default value\n"
@@ -66,6 +70,10 @@
{"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}, \
+ {"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}, \
{"num-ul-actors", CONFIG_HLP_NUM_UL_ACTORS, 0, .iptr=&nrUE_params.num_ul_actors, .defintval=0, TYPE_INT, 0}, \
}
@@ -91,6 +99,10 @@ typedef struct {
double time_sync_I;
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;
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/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;
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/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/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/PHY/defs_nr_UE.h b/openair1/PHY/defs_nr_UE.h
index ba280c4f6b..a617784e64 100644
--- a/openair1/PHY/defs_nr_UE.h
+++ b/openair1/PHY/defs_nr_UE.h
@@ -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;
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..700721e69f 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,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
+}
diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c
index 38e53be4ac..d427e9721c 100644
--- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c
+++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c
@@ -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);
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..5bb6df3b5e 100644
--- a/openair1/SIMULATION/TOOLS/phase_noise.c
+++ b/openair1/SIMULATION/TOOLS/phase_noise.c
@@ -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);
+ }
+ }
}
}
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 34cae4ba55..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
@@ -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,
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;