Compare commits

...

7 Commits

Author SHA1 Message Date
Sakthivel Velumani
1d823735c6 Merge branch 'ue-trs-support' into 'develop'
UE support for TRS - frequency offset estimation and correction

See merge request oai/openairinterface5g!3574
2026-07-02 07:26:00 +00:00
Robert Schmidt
c4125d1c74 Fix license in files from PR #211
Signed-off-by: Robert Schmidt <robert.schmidt@openairinterface.org>
2026-06-29 16:27:21 +02:00
Sakthivel Velumani
12ef1ccf85 ci: simulate freq drift and verify freq correction
A new test case 5g_zmq_radio_trs is created by copying the folder
5g_zmq_radio_1x1. A gnuradio python script (rotate_zmq.py) generated by
grc is used to read the samples from gNB and apply a freq drift and send
them to a ZMQ sink. The UE reads samples from the ZMQ port on gnuradio.
The gnuradio script is modified to receive message containing freq
offsets from another script (send_freq_offset_zmq.py).

Dockerfile.gnuradio builds an ubuntu 24.04 image and copy both python
scripts.

Modified the docker-compose.yaml to include a new service gnuradio-zmq
that starts by running rotate_zmq.py.

Created container_5g_zmq_ocudu_trs.xml by copying
container_5g_zmq_ocudu_1x1.xml. Included a Custom_Command to run
send_freq_offset_zmq.py and another custom_command to grep the UE stdout
for TRS freq correction.

Added a PUSH socket in zmq_radio.cpp to send carrier frequency to
gnuradio and remove the offset applied so that the test can continue
without a freq offset.

Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-05-21 16:22:18 +00:00
Sakthivel Velumani
bfe0b95df5 Add timing stats for TRS processing
Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-05-15 16:18:48 +00:00
Sakthivel Velumani
d633682773 fix: tracking CSI-RS measurement bitmap
measurement bitmap for CSI-RS is 0 means CSI-RS for tracking. So don't
return and proceed with processing the CSI-RS.

Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-05-15 16:18:47 +00:00
Sakthivel Velumani
ec4544992e feature: Estimate CFO from CSI-RS tracking signal
TRS resources if configured by gnb, is present in two consequtive slots
and two symbols per slot. LS estimates from the first symbol is saved
and in second symbol frequency offset and time offset is estimated. The
estimated FO is saved in UE global structure which is used in the last
TRS slot. If the FO is above a threshold, the radio's center frequency
is adjusted.

Time offset is estimated but not used because time is already tracked
using PBCH DMRS.

Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-05-15 16:18:42 +00:00
Sakthivel Velumani
3d4b18a1b4 Remove unnecessary FO compensation
After detecting PSS, SSS and PBCH there is no need to compensate for FO
in rxbuffer. SIB1 is detected from freshly received samples that are FO
compensated at the radio.

Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-05-12 15:29:41 -04:00
19 changed files with 919 additions and 102 deletions

View File

@@ -0,0 +1,123 @@
<!-- SPDX-License-Identifier: LicenseRef-CSSL-1.0 -->
<testCaseList>
<htmlTabRef>ocudu-5gnr-tdd-trs</htmlTabRef>
<htmlTabName>OCUDU gnb split8 with OAI UE</htmlTabName>
<htmlTabIcon>wrench</htmlTabIcon>
<testCase>
<class>Pull_Local_Registry</class>
<desc>Pull Images from Local Registry</desc>
<node>localhost</node>
<images>oai-gnb-asan oai-nr-ue-asan</images>
</testCase>
<testCase>
<class>Create_Workspace</class>
<desc>Create new Workspace</desc>
<node>localhost</node>
</testCase>
<testCase>
<class>Deploy_Object</class>
<desc>Deploy OAI 5G CoreNetwork</desc>
<node>localhost</node>
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_trs</yaml_path>
<services>mysql oai-amf oai-smf oai-upf oai-ext-dn</services>
</testCase>
<testCase>
<class>Deploy_Object</class>
<desc>Deploy OCUDU Split 8 gNB + GNURadio + OAI UE</desc>
<node>localhost</node>
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_trs</yaml_path>
<services>ocudu-gnb gnuradio-zmq oai-nr-ue</services>
</testCase>
<testCase>
<class>Attach_UE</class>
<desc>Attach OAI UE (Wait for IP)</desc>
<id>rfsim5g_ue</id>
<node>localhost</node>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Simulate frequency drift of 50 Hz per second for 13 seconds</desc>
<node>localhost</node>
<command>docker exec gnuradio-zmq python3 /app/send_freq_offset_zmq.py --num-msg 13</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Verify TRS frequency correction</desc>
<node>localhost</node>
<command>timeout 3 docker logs -f oai-nr-ue 2>&1 | grep -m1 'CFO estimated \([5-6][0-9][0-9]\) from TRS exceeded threshold'</command>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping ext-dn from NR-UE</desc>
<id>rfsim5g_ue</id>
<node>localhost</node>
<svr_id>rfsim5g_ext_dn</svr_id>
<svr_node>localhost</svr_node>
<ping_args>-c 20 -i 0.25</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping NR-UE from ext-dn</desc>
<id>rfsim5g_ext_dn</id>
<node>localhost</node>
<svr_id>rfsim5g_ue</svr_id>
<svr_node>localhost</svr_node>
<ping_args>-c 20 -i 0.25</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Iperf</class>
<desc>Iperf (DL/3Mbps/UDP)(20 sec)</desc>
<iperf_args>-u -b 3M -t 20 -R</iperf_args>
<id>rfsim5g_ue</id>
<node>localhost</node>
<svr_id>rfsim5g_ext_dn</svr_id>
<svr_node>localhost</svr_node>
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
</testCase>
<testCase>
<class>Iperf</class>
<desc>Iperf (UL/3Mbps/UDP)(20 sec)</desc>
<iperf_args>-u -b 3M -t 20</iperf_args>
<id>rfsim5g_ue</id>
<node>localhost</node>
<svr_id>rfsim5g_ext_dn</svr_id>
<svr_node>localhost</svr_node>
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
</testCase>
<testCase>
<class>Undeploy_Object</class>
<always_exec>true</always_exec>
<desc>Undeploy all OAI 5G stack</desc>
<node>localhost</node>
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_trs</yaml_path>
<analysis>
<services>oai-nr-ue</services>
</analysis>
</testCase>
<testCase>
<class>Clean_Test_Server_Images</class>
<always_exec>true</always_exec>
<desc>Clean Test Images on Test Server</desc>
<node>localhost</node>
<images>oai-gnb-asan oai-nr-ue-asan</images>
</testCase>
</testCaseList>

View File

@@ -0,0 +1,11 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
gnuradio \
python3-zmq \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY rotate_zmq.py send_freq_offset_zmq.py .

View File

@@ -0,0 +1,178 @@
# SPDX-License-Identifier: MIT
services:
mysql:
container_name: "rfsim5g-mysql"
image: mysql:9.6
init: true
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"
start_period: 10s
start_interval: 500ms
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.2.1
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.2.1
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.2.1
init: true
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
interface_name: eth0
traffic_net:
ipv4_address: 192.168.72.134
interface_name: eth1
oai-ext-dn:
privileged: true
container_name: rfsim5g-oai-ext-dn
image: oaisoftwarealliance/trf-gen-cn5g:latest
init: true
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"
start_period: 10s
start_interval: 500ms
interval: 10s
timeout: 5s
retries: 5
ocudu-gnb:
image: ghcr.io/bpodrygajlo/ocudu/ocudu-avx2:7cecca5
container_name: ocudu-gnb
cap_drop:
- ALL
cap_add:
- SYS_NICE
depends_on:
- oai-ext-dn
networks:
public_net:
ipv4_address: 192.168.71.140
volumes:
- ./ocudu.yml:/ocudu.yml
healthcheck:
test: /bin/bash -c "pgrep gnb_split_8"
start_period: 10s
start_interval: 500ms
interval: 10s
timeout: 5s
retries: 5
command: ./usr/local/bin/gnb_split_8 -c /ocudu.yml
gnuradio-zmq:
build:
context: .
dockerfile: Dockerfile.gnuradio
cap_drop:
- ALL
cap_add:
- SYS_NICE
depends_on:
- ocudu-gnb
networks:
public_net:
ipv4_address: 192.168.71.141
command:
bash -c "cd /app/ & python3 rotate_zmq.py --zmq-req-src tcp://192.168.71.140:4558 --zmq-req-snk tcp://0.0.0.0:4559"
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
- SYS_NICE
environment:
USE_ADDITIONAL_OPTIONS: -r 51 --numerology 1 --band 78 -C 3489420000 --ssb 0 -E --uicc0.imsi 208990100001100 --uecap_file /opt/oai-nr-ue/etc/uecap.xml --log_config.global_log_options level,nocolor,time --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://0.0.0.0:4556 --zmq.[0].rx_channels tcp://192.168.71.141:4559
depends_on:
- gnuradio-zmq
networks:
public_net:
ipv4_address: 192.168.71.150
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml:/opt/oai-nr-ue/etc/uecap.xml
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
healthcheck:
test: /bin/bash -c "pgrep nr-uesoftmodem"
start_period: 10s
start_interval: 500ms
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"

View File

@@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited
# SPDX-License-Identifier: BSD-3-Clause-Open-MPI
# Refer to https://gitlab.com/ocudu/ocudu/-/raw/dev/LICENSE?ref_type=heads
cu_cp:
amf:
addrs: 192.168.71.132
port: 38412
bind_addrs: 192.168.71.140
supported_tracking_areas:
- tac: 1
plmn_list:
- plmn: "20899"
tai_slice_support_list:
- sst: 1
ru_sdr:
device_driver: zmq
device_args: tx_port=tcp://0.0.0.0:4558,rx_port=tcp://192.168.71.150:4556
srate: 23.04
tx_gain: 25
rx_gain: 25
cell_cfg:
dl_arfcn: 632628
band: 78
channel_bandwidth_MHz: 20
common_scs: 30
plmn: "20899"
tac: 1
pci: 1
tdd_ul_dl_cfg:
dl_ul_tx_period: 5
nof_dl_slots: 3
nof_dl_symbols: 10
nof_ul_slots: 1
nof_ul_symbols: 2
csi:
csi_rs_enabled: false
pucch:
formats: f0_and_f2
nof_cell_csi_res: 0
log:
filename: gnb.log
all_level: debug
pcap:
mac_enable: false
mac_filename: /tmp/gnb_mac.pcap
ngap_enable: false
ngap_filename: /tmp/gnb_ngap.pcap

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: GPL-3.0
#
# GNU Radio Python Flow Graph
# Title: Simple doppler injector
# GNU Radio version: 3.10.9.2
import threading
import struct
import sys
import signal
from argparse import ArgumentParser
import zmq
from gnuradio import analog, blocks, gr
from gnuradio.filter import firdes
from gnuradio.fft import window
from gnuradio import zeromq
class untitled(gr.top_block):
def __init__(self, samp_rate=23040000, rx_freq=0, freq_off=0, zmq_req_src='tcp://127.0.0.1:4445', zmq_req_snk='tcp://127.0.0.1:4446', zmq_pull_addr='tcp://*:5555', zmq_pull_oai_addr='tcp://*5556'):
gr.top_block.__init__(self, "Simple doppler injector", catch_exceptions=True)
##################################################
# Variables
##################################################
self.samp_rate = samp_rate
self.freq_off = freq_off
self.rx_freq = rx_freq
##################################################
# Blocks
##################################################
self.zeromq_req_source_0 = zeromq.req_source(gr.sizeof_gr_complex, 1, zmq_req_src, 100, False, (-1), False)
self.zeromq_rep_sink_0 = zeromq.rep_sink(gr.sizeof_gr_complex, 1, zmq_req_snk, 100, False, (-1), True)
self.blocks_throttle2_0_0 = blocks.throttle(
gr.sizeof_gr_complex*1, samp_rate, True,
0 if "auto" == "auto" else max(
int(float(0.1) * samp_rate) if "auto" == "time" else int(0.1), 1)
)
self.blocks_multiply_xx_0 = blocks.multiply_vcc(1)
self.analog_sig_source_x_0_1_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_off, 1, 0, 0)
##################################################
# Connections
##################################################
self.connect((self.analog_sig_source_x_0_1_0, 0), (self.blocks_multiply_xx_0, 0))
self.connect((self.blocks_multiply_xx_0, 0), (self.zeromq_rep_sink_0, 0))
self.connect((self.blocks_throttle2_0_0, 0), (self.blocks_multiply_xx_0, 1))
self.connect((self.zeromq_req_source_0, 0), (self.blocks_throttle2_0_0, 0))
##################################################
# ZMQ sockets
##################################################
self._zmq_ctx = zmq.Context()
self._zmq_ctx_oai = zmq.Context()
# PULL - receive freq offset updates from Python script
self._pull_sock = self._zmq_ctx.socket(zmq.PULL)
self._pull_sock.setsockopt(zmq.LINGER, 0)
self._pull_sock.bind(zmq_pull_addr)
print(f"[zmq] PULL bound to {zmq_pull_addr}")
# PULL - receive carrier freq updates from OAI
self._pull_sock_oai = self._zmq_ctx_oai.socket(zmq.PULL)
self._pull_sock_oai.setsockopt(zmq.LINGER, 0)
self._pull_sock_oai.bind(zmq_pull_oai_addr)
print(f"[zmq] PULL bound to {zmq_pull_oai_addr}")
self._zmq_thread = threading.Thread(target=self._zmq_listener, daemon=True)
self._zmq_thread_oai = threading.Thread(target=self._zmq_listener_oai, daemon=True)
# ------------------------------------------------------------------
# Getters / setters
# ------------------------------------------------------------------
def get_samp_rate(self):
return self.samp_rate
def get_rx_freq(self):
return self.rx_freq
def set_rx_freq(self, rx_freq):
self.rx_freq = rx_freq
print(f"[rx_freq] -> {self.rx_freq}")
def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.analog_sig_source_x_0_1_0.set_sampling_freq(self.samp_rate)
self.blocks_throttle2_0_0.set_sample_rate(self.samp_rate)
print(f"[samp_rate] -> {self.samp_rate}")
def get_freq_off(self):
return self.freq_off
def set_freq_off(self, freq_off):
self.freq_off = freq_off
self.analog_sig_source_x_0_1_0.set_frequency(self.freq_off)
print(f"[freq_off] -> {self.freq_off}")
# ------------------------------------------------------------------
# ZMQ PULL listener
# ------------------------------------------------------------------
def _zmq_listener(self):
while True:
try:
# Accept either a raw 8-byte double or a UTF-8 string
raw = self._pull_sock.recv()
if len(raw) == 8:
val = struct.unpack('d', raw)[0] # double from C/numpy sender
else:
val = float(raw.decode('utf-8').strip())
self.set_freq_off(self.freq_off + val)
except (ValueError, struct.error) as e:
print(f"[zmq] invalid message: {e}")
except zmq.ZMQError as e:
print(f"[zmq] error: {e}")
break
def _zmq_listener_oai(self):
while True:
try:
# Accept either a raw 8-byte double or a UTF-8 string
raw = self._pull_sock_oai.recv()
if len(raw) == 8:
val = struct.unpack('d', raw)[0] # double from C/numpy sender
else:
val = float(raw.decode('utf-8').strip())
self.set_rx_freq(val)
self.set_freq_off(0)
except (ValueError, struct.error) as e:
print(f"[zmq] invalid message: {e}")
except zmq.ZMQError as e:
print(f"[zmq] error: {e}")
break
def start(self):
self._zmq_thread.start()
self._zmq_thread_oai.start()
super().start()
def main(top_block_cls=untitled, options=None):
parser = ArgumentParser(description="Simple doppler injector")
parser.add_argument(
"--samp-rate", type=int, default=23040000,
help="Sample rate in Hz (default: 23040000)"
)
parser.add_argument(
"--rx-freq", type=int, default=3619200000,
help="Carrier freq in Hz (default: 3619200000)"
)
parser.add_argument(
"--freq-off", type=float, default=0.0,
help="Initial frequency offset in Hz (default: 0.0)"
)
parser.add_argument(
"--zmq-req-src", type=str, default="tcp://127.0.0.1:4445",
help="ZMQ REQ source address (default: tcp://127.0.0.1:4445)"
)
parser.add_argument(
"--zmq-req-snk", type=str, default="tcp://127.0.0.1:4446",
help="ZMQ REQ sink address (default: tcp://127.0.0.1:4446)"
)
parser.add_argument(
"--zmq-pull-addr", type=str, default="tcp://*:5555",
help="ZMQ PULL bind address for incoming freq_off updates (default: tcp://*:5555)"
)
parser.add_argument(
"--zmq-pull-oai-addr", type=str, default="tcp://*:5556",
help="ZMQ PULL bind address for incoming carrier_freq updates from OAI UE (default: tcp://*:5556)"
)
args = parser.parse_args()
tb = top_block_cls(
samp_rate=args.samp_rate,
rx_freq=args.rx_freq,
freq_off=args.freq_off,
zmq_req_src=args.zmq_req_src,
zmq_req_snk=args.zmq_req_snk,
zmq_pull_addr=args.zmq_pull_addr,
zmq_pull_oai_addr=args.zmq_pull_oai_addr,
)
def sig_handler(sig=None, frame=None):
tb.stop()
tb.wait()
sys.exit(0)
signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)
tb.start()
print(f"Flowgraph running: samp_rate={args.samp_rate}, rx_freq={args.rx_freq}")
print(f"PULL (freq_off in): {args.zmq_pull_addr}")
print(f"PULL (carrier_freq in): {args.zmq_pull_oai_addr}")
try:
input('Press Enter to quit: ')
except EOFError:
pass
tb.stop()
tb.wait()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
Send a freq offset to the GNURadio doppler injector every second via ZMQ PUSH.
Usage:
python3 send_freq_offset_zmq.py --carrier-freq 3489420000 --freq-off 50.0 --interval 0.5 --addr tcp://127.0.0.1:5555
"""
import sys
import time
import struct
import signal
import threading
import argparse
import zmq
def main():
parser = argparse.ArgumentParser(description="Send freq offset to GNURadio via ZMQ PUSH")
parser.add_argument(
"--freq-off", type=float, default=50.0,
help="Frequency offset in Hz to send (e.g. 50.0)"
)
parser.add_argument(
"--addr", type=str, default="tcp://127.0.0.1:5555",
help="GNURadio PULL address to send freq_off to (default: tcp://127.0.0.1:5555)"
)
parser.add_argument(
"--interval", type=float, default=1.0,
help="Send interval in seconds (default: 1.0)"
)
parser.add_argument(
"--num-msg", type=int, default=15,
help="Number of freq offsets to send (default: 15)"
)
args = parser.parse_args()
ctx = zmq.Context()
# PUSH - send freq_off to GNURadio PULL
push_sock = ctx.socket(zmq.PUSH)
push_sock.setsockopt(zmq.LINGER, 0)
push_sock.connect(args.addr)
print(f"[zmq] PUSH connected to {args.addr}")
def sig_handler(sig, frame):
print("\nStopping.")
push_sock.close()
ctx.term()
sys.exit(0)
signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)
print(f"Sending freq_off = {args.freq_off} Hz every {args.interval}s - Ctrl+C to stop")
msg_count = 0
while msg_count < args.num_msg:
push_sock.send(struct.pack('d', args.freq_off))
print(f"[sent] freq_offset = {args.freq_off} Hz")
time.sleep(args.interval)
msg_count += 1
print("\nStopping.")
push_sock.close()
ctx.term()
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -1,23 +1,7 @@
/*
* 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
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "common/config/config_userapi.h"
#include "common/utils/system.h"
#include "nr-oru.h"

View File

@@ -1,23 +1,7 @@
/*
* 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
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef __NR_ORU_H__
#define __NR_ORU_H__
#include "openair1/PHY/defs_RU.h"

View File

@@ -215,9 +215,8 @@ 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);
if (get_nrUE_params()->cont_fo_comp) {
UE->freq_offset = freq_offset - UE->dl_Doppler_shift;
} else {
UE->freq_offset = freq_offset - UE->dl_Doppler_shift;
if (!get_nrUE_params()->cont_fo_comp) {
// rerun with new cell parameters and frequency-offset
nrue_ru_set_freq(UE, ul_carrier, dl_carrier, freq_offset);
}
@@ -711,6 +710,18 @@ static inline int get_readBlockSize(uint16_t slot, const NR_DL_FRAME_PARMS *fp)
return rem_samples + next_slot_first_symbol;
}
void trs_freq_correction(PHY_VARS_NR_UE *ue, int cfo)
{
if (abs(cfo) > TRS_CFO_THRESH) {
LOG_A(PHY, "CFO estimated (%d) from TRS exceeded threshold (%d). Adjusting radio CF\n", cfo, TRS_CFO_THRESH);
ue->freq_offset += cfo;
uint64_t dl_carrier;
uint64_t ul_carrier;
nr_get_carrier_frequencies(ue, &dl_carrier, &ul_carrier);
nrue_ru_set_freq(ue, dl_carrier, ul_carrier, ue->freq_offset);
}
}
void *UE_thread(void *arg)
{
//this thread should be over the processing thread to keep in real time

View File

@@ -463,6 +463,7 @@ typedef struct {
uint8_t power_control_offset; // Ratio of PDSCH EPRE to NZP CSI-RSEPRE [3GPP TS 38.214, sec 5.2.2.3.1], Value: 0->23 representing -8 to 15 dB in 1dB steps; 255: L1 is configured with ProfileSSS
uint8_t power_control_offset_ss; // Ratio of NZP CSI-RS EPRE to SSB/PBCH block EPRE [3GPP TS 38.214, sec 5.2.2.3.1], Values: 0: -3dB; 1: 0dB; 2: 3dB; 3: 6dB; 255: L1 is configured with ProfileSSS
uint8_t measurement_bitmap; // bit 0 RSRP, bit 1 RI, bit 2 LI, bit 3 PMI, bit 4 CQI, bit 5 i1
uint8_t last_trs_slot; // indicates to PHY if the slot is end of TRS burst
} fapi_nr_dl_config_csirs_pdu_rel15_t;
typedef enum{vrb_to_prb_mapping_non_interleaved = 0, vrb_to_prb_mapping_interleaved = 1} vrb_to_prb_mapping_t;

View File

@@ -250,6 +250,7 @@ uint32_t calc_power_csirs(const uint16_t *x, const fapi_nr_dl_config_csirs_pdu_r
static int nr_csi_rs_channel_estimation(
const NR_DL_FRAME_PARMS *fp,
const int meas_bitmap,
const fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu,
const nr_csi_info_t *nr_csi_info,
const c16_t **csi_rs_generated_signal,
@@ -301,10 +302,16 @@ static int nr_csi_rs_channel_estimation(
const c16_t *rx_csi_rs_signal = &csi_rs_received_signal[ant_rx][symbol_offset];
c16_t tmp =
c16MulConjShift(tx_csi_rs_signal[k_tx], rx_csi_rs_signal[k_rx], nr_csi_info->csi_rs_generated_signal_bits);
// This is not just the LS estimation for each (k,l), but also the sum of the different contributions
// for the sake of optimizing the memory used.
csi_rs_ls_estimated_channel[ant_rx][port_tx][kinit_tx].r += tmp.r;
csi_rs_ls_estimated_channel[ant_rx][port_tx][kinit_tx].i += tmp.i;
if (csirs_config_pdu->csi_type != 0) {
// This is not just the LS estimation for each (k,l), but also the sum of the different contributions
// for the sake of optimizing the memory used.
csi_rs_ls_estimated_channel[ant_rx][port_tx][kinit_tx].r += tmp.r;
csi_rs_ls_estimated_channel[ant_rx][port_tx][kinit_tx].i += tmp.i;
} else {
// for tracking we want estimates of all sub carriers having CSI-RS
csi_rs_ls_estimated_channel[ant_rx][port_tx][k_tx].r = tmp.r;
csi_rs_ls_estimated_channel[ant_rx][port_tx][k_tx].i = tmp.i;
}
}
}
}
@@ -362,6 +369,10 @@ static int nr_csi_rs_channel_estimation(
}
}
// we need only ls estimates for tracing CSI
if (meas_bitmap < 2)
continue;
/// Power noise estimation
AssertFatal(csirs_config_pdu->nr_of_rbs > 0, " nr_of_rbs needs to be greater than 0\n");
uint16_t noise_real[fp->nb_antennas_rx][csi_mapping->ports][csirs_config_pdu->nr_of_rbs];
@@ -406,9 +417,11 @@ static int nr_csi_rs_channel_estimation(
}
*noise_power /= (fp->nb_antennas_rx * csi_mapping->ports);
*log2_maxh = log2_approx(maxh - 1);
*log2_re = log2_approx(count - 1);
if (meas_bitmap > 1) {
*noise_power /= (fp->nb_antennas_rx * csi_mapping->ports);
*log2_maxh = log2_approx(maxh - 1);
*log2_re = log2_approx(count - 1);
}
#ifdef NR_CSIRS_DEBUG
LOG_I(NR_PHY, "Noise power estimation based on CSI-RS: %i\n", *noise_power);
@@ -774,6 +787,77 @@ static void nr_csi_im_power_estimation(const PHY_VARS_NR_UE *ue,
#endif
}
static double get_cfo(const double phase_diff, const int sym0, const int sym1, const NR_DL_FRAME_PARMS *fp)
{
const double one_fs = 1.0 / (fp->samples_per_frame * 100);
int sample_count = 0;
for (int s = sym0 + 1; s <= sym1; s++) {
const int prefix = (s % (7 * (1 << fp->numerology_index))) ? fp->nb_prefix_samples : fp->nb_prefix_samples0;
sample_count += (fp->ofdm_symbol_size + prefix);
}
const double delta_time = sample_count * one_fs;
const double cfo = phase_diff / (2 * M_PI * delta_time);
return cfo;
}
static void nr_ue_trs_processing(PHY_VARS_NR_UE *ue,
const c16_t res0_est[][1][ue->frame_parms.ofdm_symbol_size],
const c16_t res1_est[][1][ue->frame_parms.ofdm_symbol_size],
const c16_t freq_interp_est[][1][ue->frame_parms.ofdm_symbol_size],
const fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu,
const csi_mapping_parms_t *csi_mapping,
const int sym0,
const int sym1,
int *cfo,
int *time_offset)
{
AssertFatal((sym0 > -1) && (sym1 > -1), "Invalid symbol index for TRS estimation\n");
const NR_DL_FRAME_PARMS *fp = &ue->frame_parms;
for (int ant_rx = 0; ant_rx < fp->nb_antennas_rx; ant_rx++) {
// CFO estimation
double phase_diff = 0.0;
int count = 0;
for (int rb = csirs_config_pdu->start_rb; rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
// for freq density 0.5 checks if even or odd RB
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2)) {
continue;
}
for (int cdm_id = 0; cdm_id < csi_mapping->size; cdm_id++) {
// loop over frequency resource elements within a group
for (int kp = 0; kp <= csi_mapping->kprime; kp++) {
uint16_t kinit = rb * NR_NB_SC_PER_RB;
uint16_t k = kinit + csi_mapping->koverline[cdm_id] + kp;
// loop over time resource elements within a group
for (int lp = 0; lp <= csi_mapping->lprime; lp++) {
const c16_t *res0 = res0_est[ant_rx][0];
const c16_t *res1 = res1_est[ant_rx][0];
double tmp_phase = atan2((double)res1[k].i, (double)res1[k].r) - atan2((double)res0[k].i, (double)res0[k].r);
// wrap around phase
tmp_phase = (tmp_phase > M_PI) ? tmp_phase - 2 * M_PI : tmp_phase;
tmp_phase = (tmp_phase < -M_PI) ? tmp_phase + 2 * M_PI : tmp_phase;
phase_diff += tmp_phase;
count++;
}
}
}
}
phase_diff /= count;
*cfo = (int)get_cfo(phase_diff, sym0, sym1, fp);
if (time_offset) {
// Time offset estimation
__attribute__((aligned(32))) c16_t time_est[fp->ofdm_symbol_size];
delay_t delay = {0};
nr_est_delay(fp->ofdm_symbol_size, freq_interp_est[ant_rx][0], time_est, &delay);
*time_offset = delay.delay_max_pos;
}
// estimate only for first antenna port
break;
}
}
void nr_ue_csi_im_procedures(PHY_VARS_NR_UE *ue,
const c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
const fapi_nr_dl_config_csiim_pdu_rel15_t *csiim_config_pdu)
@@ -796,7 +880,10 @@ void nr_ue_csi_im_procedures(PHY_VARS_NR_UE *ue,
void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
const c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu)
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu,
c16_t trs_estimates[][1][ue->frame_parms.ofdm_symbol_size],
const int res_idx,
const int trs_sym0)
{
#ifdef NR_CSIRS_DEBUG
@@ -816,11 +903,6 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
LOG_I(NR_PHY, "csirs_config_pdu->power_control_offset_ss = %i\n", csirs_config_pdu->power_control_offset_ss);
#endif
if(csirs_config_pdu->csi_type == 0) {
LOG_E(NR_PHY, "Handling of CSI-RS for tracking not handled yet at PHY\n");
return;
}
if(csirs_config_pdu->csi_type == 2) {
LOG_E(NR_PHY, "Handling of ZP CSI-RS not handled yet at PHY\n");
return;
@@ -870,29 +952,23 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
&rsrp_dBm,
rxdataF);
if (csirs_config_pdu->measurement_bitmap == 0) {
LOG_D(NR_PHY, "No CSI-RS measurements configured\n");
return;
}
uint32_t noise_power = 0;
int16_t log2_re = 0;
int16_t log2_maxh = 0;
// if we need to measure only RSRP no need to do channel estimation
if (csirs_config_pdu->measurement_bitmap > 1)
nr_csi_rs_channel_estimation(frame_parms,
csirs_config_pdu,
csi_info,
(const c16_t **)csi_info->csi_rs_generated_signal,
csi_rs_received_signal,
&mapping_parms,
CDM_group_size,
csi_rs_ls_estimated_channel,
csi_rs_estimated_channel_freq,
&log2_re,
&log2_maxh,
&noise_power);
const bool use_trs_buff = (csirs_config_pdu->csi_type == 0 && res_idx == 0);
nr_csi_rs_channel_estimation(frame_parms,
csirs_config_pdu->measurement_bitmap,
csirs_config_pdu,
csi_info,
(const c16_t **)csi_info->csi_rs_generated_signal,
csi_rs_received_signal,
&mapping_parms,
CDM_group_size,
(use_trs_buff) ? trs_estimates : csi_rs_ls_estimated_channel,
csi_rs_estimated_channel_freq,
&log2_re,
&log2_maxh,
&noise_power);
uint8_t rank_indicator = 0;
// bit 1 in bitmap to indicate RI measurment
@@ -926,9 +1002,34 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
nr_csi_rs_cqi_estimation(precoded_sinr_dB, &cqi);
}
int trs_cfo = 0;
const bool do_trs_est = (csirs_config_pdu->csi_type == 0) && (res_idx == 1);
if (do_trs_est) {
start_meas_nr_ue_phy(ue, TRS_PROCESSING);
nr_ue_trs_processing(ue,
trs_estimates,
csi_rs_ls_estimated_channel,
csi_rs_estimated_channel_freq,
csirs_config_pdu,
&mapping_parms,
trs_sym0,
csirs_config_pdu->symb_l0,
&trs_cfo,
NULL); // Time offset not estimated because it is corrected using PBCH DMRS
stop_meas_nr_ue_phy(ue, TRS_PROCESSING);
}
switch (csirs_config_pdu->measurement_bitmap) {
case 1 :
LOG_I(NR_PHY, "[UE %d] RSRP = %i dBm\n", ue->Mod_id, rsrp_dBm);
case 0:
if (do_trs_est)
LOG_I(NR_PHY,
"%d.%d TRS estimated CFO: %d Hz\n",
proc->frame_rx,
proc->nr_slot_rx,
trs_cfo);
break;
case 1:
LOG_I(NR_PHY, "%d.%d [UE %d] RSRP = %i dBm\n", proc->frame_rx, proc->nr_slot_rx, ue->Mod_id, rsrp_dBm);
break;
case 26 :
LOG_I(NR_PHY, "RI = %i i1 = %i.%i.%i, i2 = %i, SINR = %i dB, CQI = %i\n",
@@ -942,6 +1043,10 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
AssertFatal(false, "Not supported measurement configuration\n");
}
if (!ue->cont_fo_comp && do_trs_est && csirs_config_pdu->last_trs_slot) {
trs_freq_correction(ue, trs_cfo);
}
// Send CSI measurements to MAC
if (!ue->if_inst || !ue->if_inst->dl_indication)
return;

View File

@@ -8,13 +8,6 @@
#ifndef __PHY_DEFS_NR_UE__H__
#define __PHY_DEFS_NR_UE__H__
#ifdef __cplusplus
#include <atomic>
#ifndef _Atomic
#define _Atomic(X) std::atomic< X >
#endif
#endif
#include "defs_nr_common.h"
#include "CODING/nrPolar_tools/nr_polar_pbch_defs.h"
#include "PHY/defs_nr_sl_UE.h"
@@ -25,6 +18,16 @@
#include <malloc.h>
#include <string.h>
#include <math.h>
#ifndef __cplusplus
#include <stdatomic.h>
#else
#include <atomic>
#ifndef _Atomic
#define _Atomic(X) std::atomic<X>
#endif
#endif
#include "common_lib.h"
#include "fapi_nr_ue_interface.h"
#include "assertions.h"
@@ -57,6 +60,13 @@
// (0 + 0 * 20) % 512 = 0
#define NUM_PROCESS_SLOT_TX_BARRIERS 512
// CSI for tracking can have up to 2 resources per slot
#define MAX_CSI_RES_SLOT 2
// Number of consequtive slots carrying TRS
#define NUM_TRS_SLOT 2
// Threshold to change radio frequency
#define TRS_CFO_THRESH 500
#include "impl_defs_nr.h"
#include "time_meas.h"
#include "PHY/CODING/coding_defs.h"
@@ -555,7 +565,8 @@ typedef struct nr_phy_data_s {
int n_dlsch_codewords;
// Sidelink Rx action decided by MAC
sl_nr_rx_config_type_enum_t sl_rx_action;
NR_UE_CSI_RS csirs_vars;
int num_csirs;
NR_UE_CSI_RS csirs_vars[MAX_CSI_RES_SLOT];
NR_UE_CSI_IM csiim_vars;
} nr_phy_data_t;

View File

@@ -38,7 +38,8 @@
FN(ULSCH_INTERLEAVING_STATS),\
FN(ULSCH_ENCODING_STATS),\
FN(OFDM_MOD_STATS),\
FN(PRACH_GEN_STATS)
FN(PRACH_GEN_STATS),\
FN(TRS_PROCESSING)
typedef enum {
FOREACH_NR_PHY_CPU_MEAS(NOOP),

View File

@@ -134,7 +134,12 @@ void nr_ue_csi_im_procedures(PHY_VARS_NR_UE *ue,
void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
const c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu);
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu,
c16_t trs_estimates[][1][ue->frame_parms.ofdm_symbol_size],
const int res_idx,
const int trs_sym0);
void trs_freq_correction(PHY_VARS_NR_UE *ue, int cfo);
int psbch_pscch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_data_t *phy_data);
void phy_procedures_nrUE_SL_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_data_tx_t *phy_data, c16_t **txp);

View File

@@ -150,8 +150,15 @@ static void nr_ue_scheduled_response_dl(NR_UE_MAC_INST_t *mac,
phy_data->csiim_vars.active = true;
break;
case FAPI_NR_DL_CONFIG_TYPE_CSI_RS:
phy_data->csirs_vars.csirs_config_pdu = pdu->csirs_config_pdu.csirs_config_rel15;
phy_data->csirs_vars.active = true;
AssertFatal(phy_data->num_csirs < MAX_CSI_RES_SLOT, "CSI resources per slot exceeded limit\n");
const int c = phy_data->num_csirs;
if (phy_data->csirs_vars[c].active) {
AssertFatal(false, "Resource should not be active before its configured\n");
continue;
}
phy_data->csirs_vars[c].csirs_config_pdu = pdu->csirs_config_pdu.csirs_config_rel15;
phy_data->csirs_vars[c].active = true;
phy_data->num_csirs++;
break;
case FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH:
case FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH:

View File

@@ -1178,16 +1178,32 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
}
// do procedures for CSI-RS
if (phy_data->csirs_vars.active == 1) {
for(int symb = 0; symb < ue->frame_parms.symbols_per_slot; symb++) {
if(is_csi_rs_in_symbol(phy_data->csirs_vars.csirs_config_pdu, symb)) {
if (!slot_fep_map[symb]) {
nr_slot_fep(ue, &ue->frame_parms, proc->nr_slot_rx, symb, rxdataF, link_type_dl, 0, ue->common_vars.rxdata);
slot_fep_map[symb] = true;
{
/*
CSI-RS for tracking use only one port.
Number of CSI-RS resources for tracking is always 2 per slot.
Computed estimates from first resource is saved and used while estimating second resource.
*/
c16_t trs_estimates[ue->frame_parms.nb_antennas_rx][1][ue->frame_parms.ofdm_symbol_size];
for (int res = 0; res < MAX_CSI_RES_SLOT; res++) {
if (phy_data->csirs_vars[res].active == 1) {
for (int symb = 0; symb < ue->frame_parms.symbols_per_slot; symb++) {
if (is_csi_rs_in_symbol(phy_data->csirs_vars[res].csirs_config_pdu, symb)) {
if (!slot_fep_map[symb]) {
nr_slot_fep(ue, &ue->frame_parms, proc->nr_slot_rx, symb, rxdataF, link_type_dl, 0, ue->common_vars.rxdata);
slot_fep_map[symb] = true;
}
}
}
nr_ue_csi_rs_procedures(ue,
proc,
rxdataF,
&phy_data->csirs_vars[res].csirs_config_pdu,
trs_estimates,
res,
(res == 1) ? phy_data->csirs_vars[0].csirs_config_pdu.symb_l0 : -1);
}
}
nr_ue_csi_rs_procedures(ue, proc, rxdataF, &phy_data->csirs_vars.csirs_config_pdu);
}
int16_t *llr[2];

View File

@@ -68,3 +68,7 @@ void configure_nr_nfapi_vnf(eth_params_t params)
{
UNUSED(params);
}
void trs_freq_correction(PHY_VARS_NR_UE *ue, int cfo)
{
}

View File

@@ -1114,7 +1114,8 @@ static void schedule_ta_command(fapi_nr_dl_config_request_t *dl_config, NR_UE_MA
static NR_CSI_ResourceConfigId_t find_CSI_resourceconfig(NR_CSI_MeasConfig_t *csi_measconfig,
NR_BWP_Id_t dl_bwp_id,
NR_NZP_CSI_RS_ResourceId_t csi_id)
NR_NZP_CSI_RS_ResourceId_t csi_id,
bool *is_last_res)
{
bool found = false;
for (int csi_list = 0; csi_list < csi_measconfig->csi_ResourceConfigToAddModList->list.count; csi_list++) {
@@ -1139,14 +1140,14 @@ static NR_CSI_ResourceConfigId_t find_CSI_resourceconfig(NR_CSI_MeasConfig_t *cs
AssertFatal(csi_res->nzp_CSI_RS_Resources.list.array[k], "NZP_CSI_RS_ResourceId shoulan't be NULL\n");
if (csi_id == *csi_res->nzp_CSI_RS_Resources.list.array[k]) {
found = true;
*is_last_res = (k == (csi_res->nzp_CSI_RS_Resources.list.count - 1));
break;
}
}
if (found && csi_res->trs_Info)
// CRI-RS for Tracking (not implemented yet)
// in this case we there is no associated CSI report
// therefore to signal this we return a value higher than
// maxNrofCSI-ResourceConfigurations
/* CRI-RS for Tracking. In this case there is no associated CSI report
* therefore to signal this we return a value higher than
* maxNrofCSI-ResourceConfigurations. */
return NR_maxNrofCSI_ResourceConfigurations + 1;
else if (found)
return csires->csi_ResourceConfigId;
@@ -1216,7 +1217,8 @@ static void nr_schedule_csirs_reception(NR_UE_MAC_INST_t *mac, int frame, int sl
csi_period_offset(NULL, nzpcsi->periodicityAndOffset, &period, &offset);
if((frame * mac->frame_structure.numb_slots_frame + slot-offset) % period != 0)
continue;
NR_CSI_ResourceConfigId_t csi_res_id = find_CSI_resourceconfig(csi_measconfig, dl_bwp_id, nzpcsi->nzp_CSI_RS_ResourceId);
bool is_last_res = false;
NR_CSI_ResourceConfigId_t csi_res_id = find_CSI_resourceconfig(csi_measconfig, dl_bwp_id, nzpcsi->nzp_CSI_RS_ResourceId, &is_last_res);
// do not schedule reseption of this CSI-RS if not associated with current BWP
if(csi_res_id < 0)
continue;
@@ -1226,9 +1228,12 @@ static void nr_schedule_csirs_reception(NR_UE_MAC_INST_t *mac, int frame, int sl
csirs_config_pdu->subcarrier_spacing = mu;
csirs_config_pdu->cyclic_prefix = current_DL_BWP->cyclicprefix ? *current_DL_BWP->cyclicprefix : 0;
if (csi_res_id > NR_maxNrofCSI_ResourceConfigurations)
if (csi_res_id > NR_maxNrofCSI_ResourceConfigurations) {
/* According to 38.214 5.1.6.1.1, the number of resources indicate if one
* or two consequtive slots for TRS is used. We indicate to phy the last slot. */
csirs_config_pdu->last_trs_slot = is_last_res;
csirs_config_pdu->csi_type = 0; // TRS
else
} else
csirs_config_pdu->csi_type = 1; // NZP-CSI-RS
csirs_config_pdu->scramb_id = nzpcsi->scramblingID;

View File

@@ -44,11 +44,13 @@
#define ZMQ_SECTION "zmq"
#define ZMQ_TX_CHANNELS "tx_channels"
#define ZMQ_RX_CHANNELS "rx_channels"
#define ZMQ_CTRL_CHANNELS "ctrl_channels"
#define ZMQ_PARAMS_DESC \
{ \
STRINGLISTPARAM(ZMQ_TX_CHANNELS, "list of zmq addresses represeting tx channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
STRINGLISTPARAM(ZMQ_RX_CHANNELS, "list of zmq addresses represeting rx channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
STRINGLISTPARAM(ZMQ_CTRL_CHANNELS, "list of zmq addresses represeting control channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
};
const size_t sample_size = sizeof(cf_t);
@@ -56,6 +58,8 @@ const size_t rx_buffer_size = sample_size * 300000;
typedef struct {
void *context;
void *ctrl_context;
void *ctrl_socket;
zmq_tx_stream tx_stream;
zmq_rx_stream rx_stream;
std::thread poll_thread;
@@ -224,6 +228,10 @@ static void zmq_end(openair0_device_t *device)
if (s->context)
zmq_ctx_destroy(s->context);
if (s->ctrl_socket)
zmq_close(s->ctrl_socket);
if (s->ctrl_context)
zmq_ctx_destroy(s->ctrl_context);
delete s;
}
}
@@ -255,8 +263,18 @@ static int zmq_stop(openair0_device_t *device)
static int zmq_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
return 0;
/* Send raw 8-byte little-endian double - matches struct.unpack('d') in Python */
double freq = openair0_cfg[0].rx_freq[0];
zmq_state_t *s = static_cast<zmq_state_t *>(device->priv);
int rc = zmq_send(s->ctrl_socket, &freq, sizeof(double), 0);
if (rc != sizeof(double)) {
LOG_E(HW, "[ZMQ] send failed: %s\n", zmq_strerror(zmq_errno()));
} else {
LOG_I(HW, "[ZMQ] sent rx freq = %.4f Hz\n", freq);
}
return (rc == sizeof(double)) ? 0 : -1;
}
static int zmq_set_gains(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
return 0;
@@ -325,6 +343,21 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d
zmq_state->rx_stream.tx_stream_ = &zmq_state->tx_stream;
}
// Setup control channel
int num_ctrl_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_CTRL_CHANNELS)->numelt;
if (num_ctrl_channels > 0) {
AssertFatal(num_ctrl_channels == 1, "Only one ZMQ control channel is supported at the moment.\n");
char **ctrl_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_CTRL_CHANNELS)->strlistptr;
zmq_state->ctrl_context = zmq_ctx_new();
zmq_state->ctrl_socket = zmq_socket(zmq_state->ctrl_context, ZMQ_PUSH);
AssertFatal(zmq_state->ctrl_socket != NULL, "zmq_socket(ZMQ_PUSH) for control channel failed");
int linger = 0;
zmq_setsockopt(zmq_state->ctrl_socket, ZMQ_LINGER, &linger, sizeof(linger));
AssertFatal(zmq_connect(zmq_state->ctrl_socket, ctrl_channels[0]) == 0,
"zmq_bind for control channel failed on %s",
ctrl_channels[0]);
}
device->trx_start_func = zmq_start;
device->trx_get_stats_func = zmq_get_stats;
device->trx_reset_stats_func = zmq_reset_stats;