Merge remote-tracking branch 'origin/vrtsim-multi-ue' into integration_2026_w11 (!3904)
add multi-ue support to vrtsim
Summary
Extending vrtsim to support multiple UEs with per-UE channel emulation using
CIRDB. Each UE can have different antenna configurations and independent
channel conditions (TDL models, delay spreads, and mobility speeds). This
includes:
- Add num_ues and ue_id parameters for server and client configuration
- Implement antenna offset multiplexing for UL combining and DL distribution
- Support heterogeneous antenna configurations across UEs (e.g., UE0: 1x2,
UE1: 2x2)
- Integrate CIRDB for per-UE channel modeling with independent TDL models,
delay spreads, and speeds
- Replace comma-separated per-UE params with structured ue_config blocks for
cleaner configuration
Usage:
* Start 5G core network
cd openairinterface5g/doc/tutorial_resources/oai-cn5g
docker-compose up -d
* Create network namespaces
sudo ip netns add ue0
sudo ip netns add ue1
* Start gNB (2x2 with multi-UE CIRDB)
sudo ./nr-softmodem -O ../../ci-scripts/conf_files/gnb.sa.band78.106prb.vrtsim.2x2.yaml --device.name vrtsim --vrtsim.role server --vrtsim.num_ues 2 --vrtsim.cirdb 1 --vrtsim.cirdb_file ~/raytracing-channel-emulator/server/external_taps/cir_db.bin --vrtsim.cirdb_yaml ~/raytracing-channel-emulator/server/external_taps/cir_db.yaml --vrtsim.ue_config.[0].antennas "1x2" --vrtsim.ue_config.[0].model_id 0 --vrtsim.ue_config.[0].ds_ns 10.0 --vrtsim.ue_config.[0].speed_mps 1.5 --vrtsim.ue_config.[1].antennas "2x2" --vrtsim.ue_config.[1].model_id 1 --vrtsim.ue_config.[1].ds_ns 30.0 --vrtsim.ue_config.[1].speed_mps 30.0
* Start UE 0 (1x2 antennas, TDL-A, 10ns DS, 1.5 m/s)
sudo ip netns exec ue0 ./nr-uesoftmodem -O ../../ci-scripts/conf_files/nrue.vrtsim.chanmod.yaml -C 3319680000 -r 106 --numerology 1 --ssb 516 --device.name vrtsim --vrtsim.role client --vrtsim.ue_id 0 --band 78 --ue-nb-ant-tx 1 --ue-nb-ant-rx 2 --uicc0.imsi 001010000000001
* Start UE 1 (2x2 antennas, TDL-B, 30ns DS, 30 m/s)
sudo ip netns exec ue1 ./nr-uesoftmodem -O ../../ci-scripts/conf_files/nrue.vrtsim.chanmod.yaml -C 3319680000 -r 106 --numerology 1 --ssb 516 --device.name vrtsim --vrtsim.role client --vrtsim.ue_id 1 --band 78 --ue-nb-ant-tx 2 --ue-nb-ant-rx 2 --uicc0.imsi 001010000000002
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<testCaseList>
|
||||
<htmlTabRef>vrtsim-5gnr-multiue-gh</htmlTabRef>
|
||||
<htmlTabName>Multi-UE gNB vrtsim + CIRDB</htmlTabName>
|
||||
<htmlTabIcon>wrench</htmlTabIcon>
|
||||
|
||||
<testCase>
|
||||
<class>Pull_Local_Registry</class>
|
||||
<desc>Pull Images from Local Registry</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb oai-nr-ue</images>
|
||||
<tag_prefix>arm_</tag_prefix>
|
||||
</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_vrtsim_multiue</yaml_path>
|
||||
<services>mysql oai-amf oai-smf oai-upf oai-ext-dn</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OAI 5G gNB+UE RF sim SA</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_vrtsim_multiue</yaml_path>
|
||||
<services>oai-gnb oai-nr-ue0 oai-nr-ue1</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Attach_UE</class>
|
||||
<desc>Attach OAI UE 1 (Wait for IP)</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Attach_UE</class>
|
||||
<desc>Attach OAI UE 2 (Wait for IP)</desc>
|
||||
<id>rfsim5g_ue2</id>
|
||||
<node>localhost</node>
|
||||
</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 UE 1 (DL/3Mbps/UDP)(30 sec)</desc>
|
||||
<iperf_args>-u -b 3M -t 30 -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 UE 1 (UL/1Mbps/UDP)(30 sec)</desc>
|
||||
<iperf_args>-u -b 1M -t 30</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>Ping</class>
|
||||
<desc>Ping ext-dn from UE 2</desc>
|
||||
<id>rfsim5g_ue2</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 UE 2 from ext-dn</desc>
|
||||
<id>rfsim5g_ext_dn</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ue2</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 UE 2 (DL/3Mbps/UDP)(30 sec)</desc>
|
||||
<iperf_args>-u -b 3M -t 30 -R</iperf_args>
|
||||
<id>rfsim5g_ue2</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 UE 2 (UL/1Mbps/UDP)(30 sec)</desc>
|
||||
<iperf_args>-u -b 1M -t 30</iperf_args>
|
||||
<id>rfsim5g_ue2</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_vrtsim_multiue</yaml_path>
|
||||
<analysis>
|
||||
<services>oai-gnb=EndsWithBye oai-nr-ue0 oai-nr-ue1</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 oai-nr-ue</images>
|
||||
</testCase>
|
||||
|
||||
</testCaseList>
|
||||
@@ -0,0 +1,212 @@
|
||||
services:
|
||||
# CIR Generator service - generates CIRDB files on every CI run
|
||||
cir-generator:
|
||||
image: oaisoftwarealliance/cir-generator:test
|
||||
environment:
|
||||
GENERATOR_ARGS: "--demo"
|
||||
OUTPUT: "--out /opt/output/cir_db.bin"
|
||||
volumes:
|
||||
- cirdb_data:/opt/output
|
||||
|
||||
mysql:
|
||||
container_name: "rfsim5g-mysql"
|
||||
image: mysql:8.0
|
||||
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"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
retries: 30
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.131
|
||||
|
||||
oai-amf:
|
||||
container_name: "rfsim5g-oai-amf"
|
||||
image: oaisoftwarealliance/oai-amf:v2.1.10
|
||||
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.1.10
|
||||
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.1.10
|
||||
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"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
|
||||
oai-gnb:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${GNB_IMG:-oai-gnb}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-gnb
|
||||
cap_drop:
|
||||
- ALL
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role server --vrtsim.num_ues 2 --vrtsim.cirdb 1 --vrtsim.cirdb_file /cirdb/cir_db.bin --vrtsim.cirdb_yaml /cirdb/cir_db.yaml --vrtsim.ue_config.[0].antennas 1x2 --vrtsim.ue_config.[0].model_id 0 --vrtsim.ue_config.[0].ds_ns 10.0 --vrtsim.ue_config.[0].speed_mps 1.5 --vrtsim.ue_config.[1].antennas 2x2 --vrtsim.ue_config.[1].model_id 1 --vrtsim.ue_config.[1].ds_ns 10.0 --vrtsim.ue_config.[1].speed_mps 1.5 --gNBs.[0].min_rxtxtime 8
|
||||
depends_on:
|
||||
cir-generator:
|
||||
condition: service_completed_successfully
|
||||
oai-ext-dn:
|
||||
condition: service_started
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.140
|
||||
volumes:
|
||||
- ../../conf_files/gnb.sa.band78.106prb.vrtsim.2x2.yaml:/opt/oai-gnb/etc/gnb.yaml
|
||||
- cirdb_data:/cirdb:ro
|
||||
- tmp_data:/tmp/
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-softmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
retries: 5
|
||||
ipc: host
|
||||
|
||||
oai-nr-ue0:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E -r 106 --numerology 1 --band 78 -C 3319680000 --ssb 516 --uicc0.imsi 208990100001100 --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role client --vrtsim.ue_id 0 --ue-nb-ant-tx 1 --ue-nb-ant-rx 2
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.150
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../conf_files/nrue.vrtsim.chanmod.yaml:/opt/oai-nr-ue/etc/nr-ue.yaml
|
||||
- tmp_data:/tmp/
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
ipc: host
|
||||
|
||||
oai-nr-ue1:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue2
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -E -r 106 --numerology 1 --band 78 -C 3319680000 --ssb 516 --uicc0.imsi 208990100001101 --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role client --vrtsim.ue_id 1 --ue-nb-ant-tx 2 --ue-nb-ant-rx 2
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.151
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../conf_files/nrue.vrtsim.chanmod.yaml:/opt/oai-nr-ue/etc/nr-ue.yaml
|
||||
- tmp_data:/tmp/
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
ipc: host
|
||||
|
||||
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"
|
||||
|
||||
volumes:
|
||||
tmp_data:
|
||||
cirdb_data:
|
||||
+372
-39
@@ -44,6 +44,7 @@
|
||||
#include <common/utils/load_module_shlib.h>
|
||||
#include <common/utils/telnetsrv/telnetsrv.h>
|
||||
#include <common/config/config_userapi.h>
|
||||
#include <common/openairinterface5g_limits.h>
|
||||
#include "common_lib.h"
|
||||
#include "shm_td_iq_channel.h"
|
||||
#include "SIMULATION/TOOLS/sim.h"
|
||||
@@ -59,6 +60,7 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
||||
|
||||
#define MAX_NUM_ANTENNAS_TX 4
|
||||
#define SAVED_SAMPLES_LEN 256
|
||||
#define MAX_NUM_UES MAX_MOBILES_PER_GNB
|
||||
|
||||
#define ROLE_CLIENT_STRING "client"
|
||||
#define ROLE_SERVER_STRING "server"
|
||||
@@ -90,6 +92,8 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
||||
{"cirdb_model_id", "Preferred TDL model id 0..4", 0, .iptr = &vrtsim_state->cirdb_model_id, .defintval = 0, TYPE_INT, 0}, \
|
||||
{"cirdb_ds_ns", "Desired RMS delay spread in ns", 0, .dblptr = &vrtsim_state->cirdb_ds_ns, .defdblval = 10.0, TYPE_DOUBLE, 0}, \
|
||||
{"cirdb_speed_mps", "Desired speed in m/s", 0, .dblptr = &vrtsim_state->cirdb_speed_mps, .defdblval = 1.5, TYPE_DOUBLE, 0}, \
|
||||
{"num_ues", "Number of UE slots (server only)\n", 0, .iptr = &vrtsim_state->num_ues, .defintval = 1, TYPE_INT, 0}, \
|
||||
{"ue_id", "UE slot index 0..num_ues-1 (client only)\n", 0, .iptr = &vrtsim_state->ue_id, .defintval = 0, TYPE_INT, 0}, \
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -113,6 +117,20 @@ typedef struct tx_timing_s {
|
||||
histogram_t tx_histogram;
|
||||
} tx_timing_t;
|
||||
|
||||
typedef struct {
|
||||
int model_id;
|
||||
double ds_ns;
|
||||
double speed_mps;
|
||||
} cirdb_conf_t;
|
||||
|
||||
typedef struct {
|
||||
int tx_ant;
|
||||
int rx_ant;
|
||||
int tx_offset;
|
||||
int rx_offset;
|
||||
cirdb_conf_t cir_conf;
|
||||
} ue_conf_t;
|
||||
|
||||
typedef struct {
|
||||
int role;
|
||||
char *connection_descriptor;
|
||||
@@ -133,6 +151,8 @@ typedef struct {
|
||||
int tx_num_channels;
|
||||
int rx_num_channels;
|
||||
channel_desc_t *channel_desc;
|
||||
channel_desc_t *channel_desc_per_ue[MAX_NUM_UES];
|
||||
pthread_mutex_t cirdb_mutex;
|
||||
Actor_t *channel_modelling_actors;
|
||||
char *taps_socket;
|
||||
int client_num_rx_antennas;
|
||||
@@ -145,6 +165,18 @@ typedef struct {
|
||||
int cirdb_model_id;
|
||||
double cirdb_ds_ns;
|
||||
double cirdb_speed_mps;
|
||||
// Multi-UE support
|
||||
int num_ues;
|
||||
int ue_id;
|
||||
|
||||
ue_conf_t ue_conf[MAX_NUM_UES];
|
||||
ue_conf_t ue;
|
||||
|
||||
// Totals
|
||||
int total_ul_streams;
|
||||
int total_dl_streams;
|
||||
sample_t *ul_combine_buffer;
|
||||
size_t ul_combine_buffer_len;
|
||||
} vrtsim_state_t;
|
||||
|
||||
static void histogram_add(histogram_t *histogram, double diff)
|
||||
@@ -251,10 +283,11 @@ static void *vrtsim_timing_job(void *arg)
|
||||
}
|
||||
|
||||
typedef struct client_info_s {
|
||||
int server_num_rx_antennas;
|
||||
int client_num_rx_antennas;
|
||||
int num_ues;
|
||||
int gnb_num_tx_ant;
|
||||
int gnb_num_rx_ant;
|
||||
ue_conf_t ues[MAX_NUM_UES];
|
||||
} client_info_t;
|
||||
|
||||
/**
|
||||
* @brief Publishes the client information information to a file for the client to read.
|
||||
*
|
||||
@@ -293,43 +326,174 @@ static client_info_t client_read_info(char *descriptor_file)
|
||||
return client_info;
|
||||
}
|
||||
|
||||
static void parse_ue_config(vrtsim_state_t *vrtsim_state)
|
||||
{
|
||||
AssertFatal(vrtsim_state->num_ues > 0 && vrtsim_state->num_ues <= MAX_NUM_UES,
|
||||
"num_ues=%d out of range (1..%d)\n", vrtsim_state->num_ues, MAX_NUM_UES);
|
||||
|
||||
for (int i = 0; i < vrtsim_state->num_ues; i++) {
|
||||
char prefix[64];
|
||||
snprintf(prefix, sizeof(prefix), "%s.ue_config.[%d]", VRTSIM_SECTION, i);
|
||||
|
||||
char *antennas = NULL;
|
||||
int model_id = vrtsim_state->cirdb_model_id;
|
||||
double ds_ns = vrtsim_state->cirdb_ds_ns;
|
||||
double speed_mps = vrtsim_state->cirdb_speed_mps;
|
||||
|
||||
paramdef_t ue_params[] = {
|
||||
{"antennas", "Antenna config e.g. \"1x2\"", 0, .strptr = &antennas, .defstrval = NULL, TYPE_STRING, 0},
|
||||
{"model_id", "TDL model id 0..4", 0, .iptr = &model_id, .defintval = vrtsim_state->cirdb_model_id, TYPE_INT, 0},
|
||||
{"ds_ns", "Delay spread in ns", 0, .dblptr = &ds_ns, .defdblval = vrtsim_state->cirdb_ds_ns, TYPE_DOUBLE, 0},
|
||||
{"speed_mps", "Speed in m/s", 0, .dblptr = &speed_mps, .defdblval = vrtsim_state->cirdb_speed_mps, TYPE_DOUBLE, 0},
|
||||
};
|
||||
config_get(config_get_if(), ue_params, sizeofArray(ue_params), prefix);
|
||||
|
||||
if (antennas != NULL) {
|
||||
int tx_ant, rx_ant;
|
||||
AssertFatal(sscanf(antennas, "%dx%d", &tx_ant, &rx_ant) == 2,
|
||||
"Invalid antenna format '%s' for UE %d, use e.g. '1x2'\n", antennas, i);
|
||||
AssertFatal(tx_ant > 0 && tx_ant <= MAX_NUM_ANTENNAS_TX,
|
||||
"Invalid TX antenna count %d for UE %d\n", tx_ant, i);
|
||||
AssertFatal(rx_ant > 0 && rx_ant <= MAX_NUM_ANTENNAS_TX,
|
||||
"Invalid RX antenna count %d for UE %d\n", rx_ant, i);
|
||||
vrtsim_state->ue_conf[i].tx_ant = tx_ant;
|
||||
vrtsim_state->ue_conf[i].rx_ant = rx_ant;
|
||||
} else {
|
||||
vrtsim_state->ue_conf[i].tx_ant = 1;
|
||||
vrtsim_state->ue_conf[i].rx_ant = 1;
|
||||
}
|
||||
|
||||
AssertFatal(model_id >= 0 && model_id <= 4,
|
||||
"Invalid model_id %d for UE %d (must be 0-4)\n", model_id, i);
|
||||
AssertFatal(ds_ns > 0,
|
||||
"Invalid ds_ns %.1f for UE %d (must be > 0)\n", ds_ns, i);
|
||||
AssertFatal(speed_mps >= 0,
|
||||
"Invalid speed_mps %.1f for UE %d (must be >= 0)\n", speed_mps, i);
|
||||
|
||||
vrtsim_state->ue_conf[i].cir_conf.model_id = model_id;
|
||||
vrtsim_state->ue_conf[i].cir_conf.ds_ns = ds_ns;
|
||||
vrtsim_state->ue_conf[i].cir_conf.speed_mps = speed_mps;
|
||||
|
||||
LOG_I(HW, "VRTSIM: UE %d - %dx%d antennas, Model %d (TDL-%c), DS %.1fns, Speed %.1fm/s\n",
|
||||
i,
|
||||
vrtsim_state->ue_conf[i].tx_ant,
|
||||
vrtsim_state->ue_conf[i].rx_ant,
|
||||
model_id, 'A' + model_id, ds_ns, speed_mps);
|
||||
}
|
||||
}
|
||||
|
||||
static void compute_ue_antenna_offsets(vrtsim_state_t *vrtsim_state)
|
||||
{
|
||||
int tx_offset = 0;
|
||||
int rx_offset = 0;
|
||||
|
||||
for (int i = 0; i < vrtsim_state->num_ues; i++) {
|
||||
vrtsim_state->ue_conf[i].tx_offset = tx_offset;
|
||||
vrtsim_state->ue_conf[i].rx_offset = rx_offset;
|
||||
|
||||
LOG_I(HW,
|
||||
"VRTSIM: UE %d - TX: %d antennas (offset %d), RX: %d antennas (offset %d)\n",
|
||||
i,
|
||||
vrtsim_state->ue_conf[i].tx_ant,
|
||||
tx_offset,
|
||||
vrtsim_state->ue_conf[i].rx_ant,
|
||||
rx_offset);
|
||||
|
||||
tx_offset += vrtsim_state->ue_conf[i].tx_ant;
|
||||
rx_offset += vrtsim_state->ue_conf[i].rx_ant;
|
||||
}
|
||||
|
||||
vrtsim_state->total_ul_streams = tx_offset;
|
||||
vrtsim_state->total_dl_streams = rx_offset;
|
||||
|
||||
LOG_I(HW, "VRTSIM: Total streams - UL: %d, DL: %d\n", vrtsim_state->total_ul_streams, vrtsim_state->total_dl_streams);
|
||||
}
|
||||
|
||||
static int vrtsim_connect(openair0_device_t *device)
|
||||
{
|
||||
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
|
||||
|
||||
// Setup a shared memory channel
|
||||
if (vrtsim_state->role == ROLE_SERVER) {
|
||||
vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->client_num_rx_antennas;
|
||||
vrtsim_state->channel = shm_td_iq_channel_create(DEFAULT_CHANNEL_NAME,
|
||||
vrtsim_state->peer_info.num_rx_antennas,
|
||||
device->openair0_cfg[0].rx_num_channels);
|
||||
// Exchange peer info
|
||||
client_info_t client_info = {
|
||||
.server_num_rx_antennas = device->openair0_cfg[0].rx_num_channels,
|
||||
.client_num_rx_antennas = vrtsim_state->client_num_rx_antennas,
|
||||
};
|
||||
server_publish_client_info(client_info, vrtsim_state->connection_descriptor);
|
||||
parse_ue_config(vrtsim_state);
|
||||
compute_ue_antenna_offsets(vrtsim_state);
|
||||
|
||||
int ul_streams = (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb)
|
||||
? vrtsim_state->num_ues * device->openair0_cfg[0].rx_num_channels
|
||||
: vrtsim_state->total_ul_streams;
|
||||
vrtsim_state->channel =
|
||||
shm_td_iq_channel_create(DEFAULT_CHANNEL_NAME, ul_streams, vrtsim_state->total_dl_streams);
|
||||
size_t max_nsamps = 7680 * 2;
|
||||
vrtsim_state->ul_combine_buffer = calloc_or_fail(max_nsamps, sizeof(sample_t));
|
||||
vrtsim_state->ul_combine_buffer_len = max_nsamps;
|
||||
// Exchange peer info
|
||||
|
||||
client_info_t client_info = {
|
||||
.num_ues = vrtsim_state->num_ues,
|
||||
.gnb_num_tx_ant = device->openair0_cfg[0].tx_num_channels,
|
||||
.gnb_num_rx_ant = device->openair0_cfg[0].rx_num_channels,
|
||||
};
|
||||
|
||||
for (int i = 0; i < vrtsim_state->num_ues; i++)
|
||||
client_info.ues[i] = vrtsim_state->ue_conf[i];
|
||||
|
||||
server_publish_client_info(client_info, vrtsim_state->connection_descriptor);
|
||||
vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->client_num_rx_antennas;
|
||||
vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
|
||||
vrtsim_state->run_timing_thread = true;
|
||||
threadCreate(&vrtsim_state->timing_thread, vrtsim_timing_job, vrtsim_state, "vrtsim_timing", -1, OAI_PRIORITY_RT_MAX);
|
||||
} else {
|
||||
client_info_t client_info = client_read_info(vrtsim_state->connection_descriptor);
|
||||
AssertFatal(client_info.server_num_rx_antennas > 0, "Server did not publish valid client info, aborting client connection\n");
|
||||
AssertFatal(
|
||||
client_info.client_num_rx_antennas == device->openair0_cfg[0].rx_num_channels,
|
||||
"Server expects different number of RX antennas. %d != %d. Use server command line option --client-num-rx-antennas\n",
|
||||
client_info.client_num_rx_antennas,
|
||||
device->openair0_cfg[0].rx_num_channels);
|
||||
AssertFatal(client_info.num_ues > 0, "Server did not publish valid num_ues\n");
|
||||
AssertFatal(vrtsim_state->ue_id < client_info.num_ues, "ue_id %d >= num_ues %d\n", vrtsim_state->ue_id, client_info.num_ues);
|
||||
|
||||
vrtsim_state->ue = client_info.ues[vrtsim_state->ue_id];
|
||||
|
||||
AssertFatal(vrtsim_state->ue.tx_ant == device->openair0_cfg[0].tx_num_channels,
|
||||
"Server expects UE %d to have %d TX antennas, this UE has %d\n",
|
||||
vrtsim_state->ue_id,
|
||||
vrtsim_state->ue.tx_ant,
|
||||
device->openair0_cfg[0].tx_num_channels);
|
||||
AssertFatal(vrtsim_state->ue.rx_ant == device->openair0_cfg[0].rx_num_channels,
|
||||
"Server expects UE %d to have %d RX antennas, this UE has %d\n",
|
||||
vrtsim_state->ue_id,
|
||||
vrtsim_state->ue.rx_ant,
|
||||
device->openair0_cfg[0].rx_num_channels);
|
||||
|
||||
LOG_I(HW,
|
||||
"VRTSIM: UE %d/%d - %dx%d antennas, UL offset=%d, DL offset=%d\n",
|
||||
vrtsim_state->ue_id,
|
||||
client_info.num_ues,
|
||||
vrtsim_state->ue.tx_ant,
|
||||
vrtsim_state->ue.rx_ant,
|
||||
vrtsim_state->ue.tx_offset,
|
||||
vrtsim_state->ue.rx_offset);
|
||||
if (vrtsim_state->use_cirdb) {
|
||||
vrtsim_state->cirdb_model_id = vrtsim_state->ue.cir_conf.model_id;
|
||||
vrtsim_state->cirdb_ds_ns = vrtsim_state->ue.cir_conf.ds_ns;
|
||||
vrtsim_state->cirdb_speed_mps = vrtsim_state->ue.cir_conf.speed_mps;
|
||||
|
||||
LOG_I(HW,
|
||||
"VRTSIM: UE %d channel - Model %d (TDL-%c), DS %.1fns, Speed %.1fm/s\n",
|
||||
vrtsim_state->ue_id,
|
||||
vrtsim_state->cirdb_model_id,
|
||||
'A' + vrtsim_state->cirdb_model_id,
|
||||
vrtsim_state->cirdb_ds_ns,
|
||||
vrtsim_state->cirdb_speed_mps);
|
||||
}
|
||||
vrtsim_state->channel = shm_td_iq_channel_connect(DEFAULT_CHANNEL_NAME, 10);
|
||||
vrtsim_state->peer_info.num_rx_antennas = client_info.server_num_rx_antennas;
|
||||
vrtsim_state->peer_info.num_rx_antennas = client_info.gnb_num_rx_ant;
|
||||
vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
|
||||
}
|
||||
|
||||
// Handle channel modelling after number of RX antennas are known
|
||||
int num_tx_stats = 1;
|
||||
if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) {
|
||||
vrtsim_state->channel_modelling_actors = calloc_or_fail(vrtsim_state->peer_info.num_rx_antennas, sizeof(Actor_t));
|
||||
for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) {
|
||||
int num_actors = (vrtsim_state->role == ROLE_SERVER && vrtsim_state->num_ues > 1) ? vrtsim_state->total_dl_streams
|
||||
: vrtsim_state->peer_info.num_rx_antennas;
|
||||
|
||||
vrtsim_state->channel_modelling_actors = calloc_or_fail(num_actors, sizeof(Actor_t));
|
||||
for (int i = 0; i < num_actors; i++) {
|
||||
init_actor(&vrtsim_state->channel_modelling_actors[i], "chanmod", -1);
|
||||
}
|
||||
if (vrtsim_state->taps_socket) {
|
||||
@@ -379,16 +543,55 @@ static int vrtsim_connect(openair0_device_t *device)
|
||||
sel.yaml_path ? sel.yaml_path : "(auto)",
|
||||
sel.bin_path ? sel.bin_path : "(auto)");
|
||||
|
||||
cirdb_connect(0,
|
||||
device->openair0_cfg[0].tx_num_channels,
|
||||
vrtsim_state->peer_info.num_rx_antennas,
|
||||
&sel,
|
||||
&vrtsim_state->channel_desc);
|
||||
LOG_A(HW, "VRTSIM: channel taps via CIR DB\n");
|
||||
// Multi-UE server: create per-UE channel descriptors
|
||||
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->num_ues > 1) {
|
||||
for (int u = 0; u < vrtsim_state->num_ues; u++) {
|
||||
cirdb_select_opts_t ue_sel = sel;
|
||||
ue_sel.want_model_id = vrtsim_state->ue_conf[u].cir_conf.model_id;
|
||||
ue_sel.want_ds_ns = vrtsim_state->ue_conf[u].cir_conf.ds_ns;
|
||||
ue_sel.want_speed_mps = vrtsim_state->ue_conf[u].cir_conf.speed_mps;
|
||||
|
||||
AssertFatal(ue_sel.want_model_id >= 0 && ue_sel.want_model_id <= 4,
|
||||
"Invalid CIRDB model_id=%d for UE %d\n",
|
||||
ue_sel.want_model_id,
|
||||
u);
|
||||
|
||||
cirdb_connect(u,
|
||||
device->openair0_cfg[0].tx_num_channels,
|
||||
vrtsim_state->ue_conf[u].rx_ant,
|
||||
&ue_sel,
|
||||
&vrtsim_state->channel_desc_per_ue[u]);
|
||||
|
||||
channel_desc_t *cd = vrtsim_state->channel_desc_per_ue[u];
|
||||
AssertFatal(cd != NULL, "CIRDB failed to create channel_desc for UE %d\n", u);
|
||||
AssertFatal(cd->nb_tx == device->openair0_cfg[0].tx_num_channels,
|
||||
"CIRDB shape mismatch UE%d: nb_tx=%d expected %d\n",
|
||||
u, cd->nb_tx, device->openair0_cfg[0].tx_num_channels);
|
||||
LOG_I(HW, "VRTSIM: UE %d channel_desc=%p ch_ps=%p ch=%p nb_tx=%d nb_rx=%d\n",
|
||||
u, cd, cd->ch_ps, cd->ch, cd->nb_tx, cd->nb_rx);
|
||||
LOG_I(HW, "VRTSIM: UE %d CIRDB - Model %d (TDL-%c), antennas %dx%d, nb_rx=%d\n",
|
||||
u, ue_sel.want_model_id, 'A' + ue_sel.want_model_id,
|
||||
device->openair0_cfg[0].tx_num_channels, vrtsim_state->ue_conf[u].rx_ant, cd->nb_rx);
|
||||
}
|
||||
vrtsim_state->channel_desc = vrtsim_state->channel_desc_per_ue[0];
|
||||
pthread_mutex_init(&vrtsim_state->cirdb_mutex, NULL);
|
||||
LOG_A(HW, "VRTSIM: Multi-UE channel taps via CIR DB\n");
|
||||
} else {
|
||||
cirdb_connect(0,
|
||||
device->openair0_cfg[0].tx_num_channels,
|
||||
vrtsim_state->peer_info.num_rx_antennas,
|
||||
&sel,
|
||||
&vrtsim_state->channel_desc);
|
||||
LOG_A(HW, "VRTSIM: channel taps via CIR DB\n");
|
||||
}
|
||||
} else {
|
||||
load_channel_model(vrtsim_state);
|
||||
}
|
||||
num_tx_stats = vrtsim_state->peer_info.num_rx_antennas;
|
||||
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->num_ues > 1) {
|
||||
vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->total_dl_streams;
|
||||
num_tx_stats = vrtsim_state->total_dl_streams;
|
||||
}
|
||||
}
|
||||
vrtsim_state->tx_timing = calloc_or_fail(num_tx_stats, sizeof(tx_timing_t));
|
||||
for (int i = 0; i < num_tx_stats; i++) {
|
||||
@@ -451,15 +654,69 @@ static void perform_channel_modelling(void *arg)
|
||||
c16_t **input_samples = (c16_t **)channel_modelling_args->samples;
|
||||
|
||||
channel_desc_t *channel_desc = vrtsim_state->channel_desc;
|
||||
int local_aarx = aarx;
|
||||
int global_aarx = aarx;
|
||||
int target_ue = 0;
|
||||
|
||||
if (vrtsim_state->role == ROLE_CLIENT) {
|
||||
global_aarx = vrtsim_state->ue_id * vrtsim_state->peer_info.num_rx_antennas + aarx;
|
||||
}
|
||||
|
||||
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->num_ues > 1) {
|
||||
if (aarx >= vrtsim_state->total_dl_streams) {
|
||||
LOG_E(HW, "VRTSIM: aarx=%d >= total_dl_streams=%d\n", aarx, vrtsim_state->total_dl_streams);
|
||||
return;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (int u = 0; u < vrtsim_state->num_ues; u++) {
|
||||
int offset = vrtsim_state->ue_conf[u].rx_offset;
|
||||
int count = vrtsim_state->ue_conf[u].rx_ant;
|
||||
if (aarx >= offset && aarx < offset + count) {
|
||||
target_ue = u;
|
||||
local_aarx = aarx - offset;
|
||||
channel_desc = vrtsim_state->channel_desc_per_ue[u];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOG_E(HW, "VRTSIM: aarx=%d could not map to any UE (total_dl_streams=%d)\n", aarx, vrtsim_state->total_dl_streams);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel_desc == NULL) {
|
||||
LOG_E(HW, "VRTSIM: channel_desc is NULL for aarx=%d\n", aarx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bounds check: ensure local_aarx is within channel_desc bounds
|
||||
if (local_aarx >= channel_desc->nb_rx) {
|
||||
LOG_E(HW,
|
||||
"VRTSIM: UE%d local_aarx=%d >= nb_rx=%d (global aarx=%d). CIRDB shape mismatch.\n",
|
||||
target_ue,
|
||||
local_aarx,
|
||||
channel_desc->nb_rx,
|
||||
aarx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (vrtsim_state->use_cirdb) {
|
||||
double seconds = (double)channel_modelling_args->timestamp / vrtsim_state->sample_rate;
|
||||
uint64_t elapsed_ns = (uint64_t)(seconds * 1e9 + 0.5);
|
||||
|
||||
// Lock for multi-UE to prevent concurrent CIRDB updates
|
||||
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->num_ues > 1) {
|
||||
pthread_mutex_lock(&vrtsim_state->cirdb_mutex);
|
||||
}
|
||||
|
||||
cirdb_update(elapsed_ns);
|
||||
|
||||
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->num_ues > 1) {
|
||||
pthread_mutex_unlock(&vrtsim_state->cirdb_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
cf_t channel_impulse_response[nb_tx_ant][channel_desc->channel_length];
|
||||
@@ -468,7 +725,7 @@ static void perform_channel_modelling(void *arg)
|
||||
const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0);
|
||||
// Convert channel impulse response to float + apply pathloss
|
||||
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||
const struct complexd *channelModel = channel_desc->ch[aarx + (aatx * channel_desc->nb_rx)];
|
||||
const struct complexd *channelModel = channel_desc->ch[local_aarx + (aatx * channel_desc->nb_rx)];
|
||||
for (int i = 0; i < channel_desc->channel_length; i++) {
|
||||
channel_impulse_response[aatx][i].r = channelModel[i].r * pathloss_linear;
|
||||
channel_impulse_response[aatx][i].i = channelModel[i].i * pathloss_linear;
|
||||
@@ -477,7 +734,7 @@ static void perform_channel_modelling(void *arg)
|
||||
}
|
||||
} else {
|
||||
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||
struct complexf *channelModel = channel_desc->ch_ps[aarx + (aatx * channel_desc->nb_rx)];
|
||||
struct complexf *channelModel = channel_desc->ch_ps[local_aarx + (aatx * channel_desc->nb_rx)];
|
||||
channel_impulse_response_p[aatx] = channelModel;
|
||||
}
|
||||
}
|
||||
@@ -537,7 +794,7 @@ static void perform_channel_modelling(void *arg)
|
||||
channel_modelling_args->timestamp + start_sample,
|
||||
samples_out,
|
||||
num_samples,
|
||||
aarx,
|
||||
global_aarx,
|
||||
channel_modelling_args->flags,
|
||||
aarx);
|
||||
}
|
||||
@@ -623,8 +880,27 @@ static int vrtsim_write(openair0_device_t *device,
|
||||
timestamp -= device->openair0_cfg->command_line_sample_advance;
|
||||
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
|
||||
bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb;
|
||||
return channel_modelling ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags)
|
||||
: vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0);
|
||||
if (channel_modelling) {
|
||||
return vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags);
|
||||
}
|
||||
if (vrtsim_state->role == ROLE_CLIENT) {
|
||||
for (int aatx = 0; aatx < nbAnt; aatx++) {
|
||||
int global_ul_ant = vrtsim_state->ue.tx_offset + aatx;
|
||||
vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[aatx], nsamps, global_ul_ant, flags, 0);
|
||||
}
|
||||
return nsamps;
|
||||
} else {
|
||||
AssertFatal(nbAnt == 1, "Multi-TX gNB not yet supported in multi-UE no-chanmod mode\n");
|
||||
for (int u = 0; u < vrtsim_state->num_ues; u++) {
|
||||
int rx_offset = vrtsim_state->ue_conf[u].rx_offset;
|
||||
int num_rx_ant = vrtsim_state->ue_conf[u].rx_ant;
|
||||
for (int aarx = 0; aarx < num_rx_ant; aarx++) {
|
||||
int global_dl_ant = rx_offset + aarx;
|
||||
vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, global_dl_ant, flags, 0);
|
||||
}
|
||||
}
|
||||
return nsamps;
|
||||
}
|
||||
}
|
||||
|
||||
static int vrtsim_write_beams(openair0_device_t *device,
|
||||
@@ -665,12 +941,62 @@ static int vrtsim_read(openair0_device_t *device, openair0_timestamp_t *ptimesta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, 0, samplesVoid[0]);
|
||||
if (ret == CHANNEL_ERROR_TOO_LATE) {
|
||||
vrtsim_state->rx_samples_late += nsamps;
|
||||
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
|
||||
vrtsim_state->rx_early += 1;
|
||||
if (vrtsim_state->role == ROLE_SERVER) {
|
||||
if (vrtsim_state->num_ues > 1) {
|
||||
/* Multi-UE UL combining */
|
||||
if (nsamps > vrtsim_state->ul_combine_buffer_len) {
|
||||
sample_t *tmp = realloc(vrtsim_state->ul_combine_buffer, nsamps * sizeof(sample_t));
|
||||
AssertFatal(tmp != NULL, "Failed to realloc UL combine buffer\n");
|
||||
vrtsim_state->ul_combine_buffer = tmp;
|
||||
vrtsim_state->ul_combine_buffer_len = nsamps;
|
||||
}
|
||||
for (int aarx = 0; aarx < nbAnt; aarx++)
|
||||
memset(samplesVoid[aarx], 0, nsamps * sizeof(sample_t));
|
||||
for (int u = 0; u < vrtsim_state->num_ues; u++) {
|
||||
for (int aarx = 0; aarx < nbAnt; aarx++) {
|
||||
int stream = u * nbAnt + aarx;
|
||||
int ret = shm_td_iq_channel_rx(vrtsim_state->channel,
|
||||
vrtsim_state->last_received_sample,
|
||||
nsamps,
|
||||
stream,
|
||||
vrtsim_state->ul_combine_buffer);
|
||||
if (ret == CHANNEL_ERROR_TOO_LATE) {
|
||||
vrtsim_state->rx_samples_late += nsamps;
|
||||
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
|
||||
vrtsim_state->rx_early += 1;
|
||||
}
|
||||
int16_t *out = (int16_t *)samplesVoid[aarx];
|
||||
int16_t *in = (int16_t *)vrtsim_state->ul_combine_buffer;
|
||||
for (int i = 0; i < nsamps * 2; i++) {
|
||||
int32_t sum = (int32_t)out[i] + (int32_t)in[i];
|
||||
out[i] = (int16_t)((sum > 32767) ? 32767 : (sum < -32768) ? -32768 : sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Single-UE server UL read */
|
||||
int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, 0, samplesVoid[0]);
|
||||
if (ret == CHANNEL_ERROR_TOO_LATE) {
|
||||
vrtsim_state->rx_samples_late += nsamps;
|
||||
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
|
||||
vrtsim_state->rx_early += 1;
|
||||
}
|
||||
for (int aarx = 1; aarx < nbAnt; aarx++) {
|
||||
if (samplesVoid[aarx] != NULL)
|
||||
memcpy(samplesVoid[aarx], samplesVoid[0], nsamps * sizeof(sample_t));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int aarx = 0; aarx < nbAnt; aarx++) {
|
||||
int global_dl_ant = vrtsim_state->ue.rx_offset + aarx;
|
||||
int ret =
|
||||
shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, global_dl_ant, samplesVoid[aarx]);
|
||||
if (ret == CHANNEL_ERROR_TOO_LATE) {
|
||||
vrtsim_state->rx_samples_late += nsamps;
|
||||
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
|
||||
vrtsim_state->rx_early += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
vrtsim_state->rx_samples_total += nsamps;
|
||||
*ptimestamp = vrtsim_state->last_received_sample;
|
||||
@@ -693,6 +1019,9 @@ static void vrtsim_end(openair0_device_t *device)
|
||||
shutdown_actor(&vrtsim_state->channel_modelling_actors[i]);
|
||||
}
|
||||
free(vrtsim_state->channel_modelling_actors);
|
||||
if (vrtsim_state->use_cirdb && vrtsim_state->role == ROLE_SERVER && vrtsim_state->num_ues > 1) {
|
||||
pthread_mutex_destroy(&vrtsim_state->cirdb_mutex);
|
||||
}
|
||||
for (int i = 1; i < vrtsim_state->peer_info.num_rx_antennas; i++) {
|
||||
histogram_merge(&tx_timing->tx_histogram, &tx_timing[i].tx_histogram);
|
||||
tx_timing->tx_early += tx_timing[i].tx_early;
|
||||
@@ -708,6 +1037,10 @@ static void vrtsim_end(openair0_device_t *device)
|
||||
taps_client_stop();
|
||||
}
|
||||
}
|
||||
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->ul_combine_buffer) {
|
||||
free(vrtsim_state->ul_combine_buffer);
|
||||
vrtsim_state->ul_combine_buffer = NULL;
|
||||
}
|
||||
shm_td_iq_channel_abort(vrtsim_state->channel);
|
||||
sleep(1);
|
||||
shm_td_iq_channel_destroy(vrtsim_state->channel);
|
||||
|
||||
Reference in New Issue
Block a user