add multi-ue support to vrtsim

- Add num_ues and ue_id parameters for server and client configuration
- Implement ul combining and dl distribution to all ues
- Each ue uses dedicated antenna offsets based on ue_id
- Works with no-chanmod mode only and with uniform antenna dimensions across all ues
This commit is contained in:
Merkebu Girmay
2026-03-11 20:34:11 -07:00
parent 7fa4d018d4
commit fa72a30bf4
+372 -39
View File
@@ -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);