Realtime channel emulation for shm_radio
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
add_library(actor actor.c)
|
add_library(actor actor.c)
|
||||||
target_include_directories(actor PUBLIC ./)
|
# Only include header files due to issues with ODR in threadPool binaries
|
||||||
target_link_libraries(actor PUBLIC thread-pool)
|
target_include_directories(actor PUBLIC ./ ../threadPool/)
|
||||||
if (ENABLE_TESTS)
|
if (ENABLE_TESTS)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ typedef enum {
|
|||||||
#define CHANMODEL_FREE_RSQRT_6 1<<1
|
#define CHANMODEL_FREE_RSQRT_6 1<<1
|
||||||
#define CHANMODEL_FREE_RSQRT_NTAPS 1<<2
|
#define CHANMODEL_FREE_RSQRT_NTAPS 1<<2
|
||||||
#define CHANMODEL_FREE_AMPS 1<<3
|
#define CHANMODEL_FREE_AMPS 1<<3
|
||||||
|
#define SHR3 (jz = jsr, jsr ^= (jsr << 13), jsr ^= (jsr >> 17), jsr ^= (jsr << 5), jz + jsr)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CORR_LEVEL_LOW,
|
CORR_LEVEL_LOW,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
add_library(shm_radio MODULE shm_radio.c)
|
add_library(shm_radio MODULE shm_radio.c noise_device.c)
|
||||||
set_target_properties(shm_radio PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
set_target_properties(shm_radio PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
target_link_libraries(shm_radio PRIVATE shm_td_iq_channel)
|
target_link_libraries(shm_radio PRIVATE SIMU shm_td_iq_channel actor)
|
||||||
add_dependencies(shm_radio generate_T)
|
add_dependencies(shm_radio generate_T)
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
This implements a shared memory realtime and near-realtime radio interface.
|
This implements a shared memory realtime and near-realtime radio interface.
|
||||||
This is performed using `shm_td_iq_channel` which handles the shared memory
|
This is performed using `shm_td_iq_channel` which handles the shared memory
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
# Limitations
|
# Limitations
|
||||||
|
|
||||||
- Only 1gNB to 1nrUE
|
- Only 1gNB to 1nrUE: gNB as a server, UE as a client.
|
||||||
- Only 1rx and 1tx antennas
|
- Server only accepts connections during device bringup. This means that
|
||||||
- No channel modelling
|
restarting the client will not make it reconnect, the server has to be
|
||||||
|
restarted as well.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
@@ -17,3 +18,8 @@ On the UE and gNB: use `device.name shm_radio` command line argument.
|
|||||||
Additionally on gNB use `shm_radio.role server` and optionally
|
Additionally on gNB use `shm_radio.role server` and optionally
|
||||||
`shm_radio.timescale <timescale>` to set the timescale. Timescale 1.0
|
`shm_radio.timescale <timescale>` to set the timescale. Timescale 1.0
|
||||||
is the default and means realtime.
|
is the default and means realtime.
|
||||||
|
|
||||||
|
Channel modelling can be enabled by adding `shm_radio.chanmod 1` to the
|
||||||
|
command line and should work the same as channel modelling in rfsimulator,
|
||||||
|
see rfsimulator [documentation](../rfsimulator/README.md), provided that your
|
||||||
|
CPU is fast enough.
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||||
|
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||||
|
* except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.openairinterface.org/?page_id=698
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*-------------------------------------------------------------------------------
|
||||||
|
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||||
|
* contact@openairinterface.org
|
||||||
|
*/
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "noise_device.h"
|
||||||
|
|
||||||
|
// Use block size equal to cache size
|
||||||
|
#define BLOCK_SIZE (64 / sizeof(float)) // 4 bytes per float
|
||||||
|
// Use a power of 2 for block size length
|
||||||
|
#define NUM_BLOCKS (8192)
|
||||||
|
#define NOISE_VECTOR_LENGTH (BLOCK_SIZE * NUM_BLOCKS)
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
pthread_t noise_device_thread;
|
||||||
|
float noise[NOISE_VECTOR_LENGTH] __attribute__((aligned(64)));
|
||||||
|
float noise_power;
|
||||||
|
bool running;
|
||||||
|
} state;
|
||||||
|
|
||||||
|
static uint32_t jz, jsr = 123456789;
|
||||||
|
static uint32_t random_uint(void)
|
||||||
|
{
|
||||||
|
return SHR3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generate_one_block_noise(int write_block_index, float noise_power)
|
||||||
|
{
|
||||||
|
// Generate noise vector
|
||||||
|
float block[BLOCK_SIZE];
|
||||||
|
for (int i = 0; i < BLOCK_SIZE; i++) {
|
||||||
|
block[i] = noise_power * gaussZiggurat(0, 1);
|
||||||
|
}
|
||||||
|
// Write block to noise vector
|
||||||
|
memcpy(state.noise + write_block_index * BLOCK_SIZE, block, sizeof(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
void *noise_device_thread_function(void *arg)
|
||||||
|
{
|
||||||
|
while (state.running) {
|
||||||
|
// Generate noise vector
|
||||||
|
generate_one_block_noise(random_uint() % NUM_BLOCKS, state.noise_power);
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_noise_device(float noise_power)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NUM_BLOCKS; i++) {
|
||||||
|
generate_one_block_noise(i, noise_power);
|
||||||
|
}
|
||||||
|
int ret = pthread_create(&state.noise_device_thread, NULL, noise_device_thread_function, NULL);
|
||||||
|
AssertFatal(ret == 0, "pthread_create failed: %d, errno %d (%s)\n", ret, errno, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_noise_device(void)
|
||||||
|
{
|
||||||
|
state.running = false;
|
||||||
|
pthread_join(state.noise_device_thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_noise_vector(float *noise_vector, int length)
|
||||||
|
{
|
||||||
|
int start_block = random_uint() % NUM_BLOCKS;
|
||||||
|
while (length > 0) {
|
||||||
|
int copy_size = min(length, (NUM_BLOCKS - start_block) * BLOCK_SIZE);
|
||||||
|
memcpy(noise_vector, &state.noise[start_block * BLOCK_SIZE], copy_size * sizeof(float));
|
||||||
|
start_block = 0;
|
||||||
|
length -= copy_size;
|
||||||
|
noise_vector += copy_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NOISE_DEVICE_H
|
||||||
|
#define NOISE_DEVICE_H
|
||||||
|
|
||||||
|
#include "SIMULATION/TOOLS/sim.h"
|
||||||
|
|
||||||
|
void init_noise_device(float noise_power);
|
||||||
|
void free_noise_device(void);
|
||||||
|
void get_noise_vector(float *noise_vector, int length);
|
||||||
|
|
||||||
|
#endif
|
||||||
+255
-37
@@ -42,10 +42,16 @@
|
|||||||
#include <common/config/config_userapi.h>
|
#include <common/config/config_userapi.h>
|
||||||
#include "common_lib.h"
|
#include "common_lib.h"
|
||||||
#include "shm_td_iq_channel.h"
|
#include "shm_td_iq_channel.h"
|
||||||
|
#include "SIMULATION/TOOLS/sim.h"
|
||||||
|
#include "actor.h"
|
||||||
|
#include "noise_device.h"
|
||||||
|
#include "simde/x86/avx512.h"
|
||||||
|
|
||||||
// Simulator role
|
// Simulator role
|
||||||
typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
||||||
|
|
||||||
|
#define NUM_CHANMOD_THREADS 1
|
||||||
|
|
||||||
#define ROLE_CLIENT_STRING "client"
|
#define ROLE_CLIENT_STRING "client"
|
||||||
#define ROLE_SERVER_STRING "server"
|
#define ROLE_SERVER_STRING "server"
|
||||||
|
|
||||||
@@ -59,6 +65,7 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
|
|||||||
{"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "shm_radio_channel", TYPE_STRING, 0}, \
|
{"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "shm_radio_channel", TYPE_STRING, 0}, \
|
||||||
{"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \
|
{"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \
|
||||||
{"timescale", TIME_SCALE_HLP, 0, .dblptr = &shm_radio_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \
|
{"timescale", TIME_SCALE_HLP, 0, .dblptr = &shm_radio_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \
|
||||||
|
{"chanmod", "Enable channel modelling", 0, .iptr = &shm_radio_state->chanmod, .defintval = 0, TYPE_INT, 0}, \
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@@ -74,6 +81,14 @@ typedef struct peer_info_s {
|
|||||||
int num_rx_antennas;
|
int num_rx_antennas;
|
||||||
} peer_info_t;
|
} peer_info_t;
|
||||||
|
|
||||||
|
typedef struct tx_timing_s {
|
||||||
|
uint64_t tx_samples_late;
|
||||||
|
uint64_t tx_early;
|
||||||
|
uint64_t tx_samples_total;
|
||||||
|
double average_tx_budget;
|
||||||
|
histogram_t tx_histogram;
|
||||||
|
} tx_timing_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int role;
|
int role;
|
||||||
char channel_name[40];
|
char channel_name[40];
|
||||||
@@ -83,15 +98,18 @@ typedef struct {
|
|||||||
bool run_timing_thread;
|
bool run_timing_thread;
|
||||||
double timescale;
|
double timescale;
|
||||||
double sample_rate;
|
double sample_rate;
|
||||||
uint64_t tx_samples_late;
|
|
||||||
uint64_t tx_early;
|
|
||||||
uint64_t tx_samples_total;
|
|
||||||
uint64_t rx_samples_late;
|
uint64_t rx_samples_late;
|
||||||
uint64_t rx_early;
|
uint64_t rx_early;
|
||||||
uint64_t rx_samples_total;
|
uint64_t rx_samples_total;
|
||||||
double average_tx_budget;
|
tx_timing_t *tx_timing;
|
||||||
histogram_t tx_histogram;
|
|
||||||
peer_info_t peer_info;
|
peer_info_t peer_info;
|
||||||
|
int chanmod;
|
||||||
|
double rx_freq;
|
||||||
|
double tx_bw;
|
||||||
|
int tx_num_channels;
|
||||||
|
int rx_num_channels;
|
||||||
|
channel_desc_t *channel_desc;
|
||||||
|
Actor_t *channel_modelling_actors;
|
||||||
} shm_radio_state_t;
|
} shm_radio_state_t;
|
||||||
|
|
||||||
static void histogram_add(histogram_t *histogram, double diff)
|
static void histogram_add(histogram_t *histogram, double diff)
|
||||||
@@ -114,6 +132,27 @@ static void histogram_print(histogram_t *histogram)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void histogram_merge(histogram_t *dest, histogram_t *src)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < sizeofArray(dest->diff); i++) {
|
||||||
|
dest->diff[i] += src->diff[i];
|
||||||
|
}
|
||||||
|
dest->num_samples += src->num_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_channel_model(shm_radio_state_t *shm_radio_state)
|
||||||
|
{
|
||||||
|
load_channellist(shm_radio_state->tx_num_channels,
|
||||||
|
shm_radio_state->peer_info.num_rx_antennas,
|
||||||
|
shm_radio_state->sample_rate,
|
||||||
|
shm_radio_state->rx_freq,
|
||||||
|
shm_radio_state->tx_bw);
|
||||||
|
char *model_name = shm_radio_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model";
|
||||||
|
shm_radio_state->channel_desc = find_channel_desc_fromname(model_name);
|
||||||
|
random_channel(shm_radio_state->channel_desc, 0);
|
||||||
|
AssertFatal(shm_radio_state->channel_desc != NULL, "Could not find channel model %s\n", model_name);
|
||||||
|
}
|
||||||
|
|
||||||
static void shm_radio_readconfig(shm_radio_state_t *shm_radio_state)
|
static void shm_radio_readconfig(shm_radio_state_t *shm_radio_state)
|
||||||
{
|
{
|
||||||
char *channel_name = NULL;
|
char *channel_name = NULL;
|
||||||
@@ -176,16 +215,13 @@ static void *shm_radio_timing_job(void *arg)
|
|||||||
static peer_info_t server_exchange_peer_info(peer_info_t peer_info)
|
static peer_info_t server_exchange_peer_info(peer_info_t peer_info)
|
||||||
{
|
{
|
||||||
int server_fd, client_fd;
|
int server_fd, client_fd;
|
||||||
struct sockaddr_un addr;
|
|
||||||
socklen_t addr_len = sizeof(addr);
|
|
||||||
|
|
||||||
// Create a Unix domain socket
|
// Create a Unix domain socket
|
||||||
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
AssertFatal(server_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
AssertFatal(server_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||||
|
|
||||||
// Bind the socket to a file path
|
// Bind the socket to a file path
|
||||||
memset(&addr, 0, sizeof(addr));
|
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1);
|
strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1);
|
||||||
unlink(addr.sun_path); // Ensure the path does not already exist
|
unlink(addr.sun_path); // Ensure the path does not already exist
|
||||||
int ret = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
|
int ret = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||||
@@ -196,6 +232,7 @@ static peer_info_t server_exchange_peer_info(peer_info_t peer_info)
|
|||||||
AssertFatal(ret == 0, "listen() failed: errno: %d, %s\n", errno, strerror(errno));
|
AssertFatal(ret == 0, "listen() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||||
|
|
||||||
// Accept a connection from a client
|
// Accept a connection from a client
|
||||||
|
socklen_t addr_len = sizeof(addr);
|
||||||
client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len);
|
client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len);
|
||||||
AssertFatal(client_fd >= 0, "accept() failed: errno: %d, %s\n", errno, strerror(errno));
|
AssertFatal(client_fd >= 0, "accept() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||||
|
|
||||||
@@ -229,15 +266,13 @@ static peer_info_t server_exchange_peer_info(peer_info_t peer_info)
|
|||||||
static peer_info_t client_exchange_peer_info(peer_info_t peer_info)
|
static peer_info_t client_exchange_peer_info(peer_info_t peer_info)
|
||||||
{
|
{
|
||||||
int client_fd;
|
int client_fd;
|
||||||
struct sockaddr_un addr;
|
|
||||||
|
|
||||||
// Create a Unix domain socket
|
// Create a Unix domain socket
|
||||||
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
AssertFatal(client_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
AssertFatal(client_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||||
|
|
||||||
// Connect to the server
|
// Connect to the server
|
||||||
memset(&addr, 0, sizeof(addr));
|
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1);
|
strncpy(addr.sun_path, "/tmp/shm_radio_socket", sizeof(addr.sun_path) - 1);
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
@@ -266,9 +301,33 @@ static peer_info_t client_exchange_peer_info(peer_info_t peer_info)
|
|||||||
static int shm_radio_connect(openair0_device *device)
|
static int shm_radio_connect(openair0_device *device)
|
||||||
{
|
{
|
||||||
shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv;
|
shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv;
|
||||||
|
// Exchange peer info
|
||||||
peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels};
|
peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels};
|
||||||
if (shm_radio_state->role == ROLE_SERVER) {
|
if (shm_radio_state->role == ROLE_SERVER) {
|
||||||
shm_radio_state->peer_info = server_exchange_peer_info(peer_info);
|
shm_radio_state->peer_info = server_exchange_peer_info(peer_info);
|
||||||
|
} else {
|
||||||
|
shm_radio_state->peer_info = client_exchange_peer_info(peer_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle channel modelling after number of RX antennas are known
|
||||||
|
int num_tx_stats = 1;
|
||||||
|
if (shm_radio_state->chanmod) {
|
||||||
|
shm_radio_state->channel_modelling_actors = calloc_or_fail(shm_radio_state->peer_info.num_rx_antennas, sizeof(Actor_t));
|
||||||
|
for (int i = 0; i < shm_radio_state->peer_info.num_rx_antennas; i++) {
|
||||||
|
init_actor(&shm_radio_state->channel_modelling_actors[i], "chanmod", -1);
|
||||||
|
}
|
||||||
|
load_channel_model(shm_radio_state);
|
||||||
|
num_tx_stats = shm_radio_state->peer_info.num_rx_antennas;
|
||||||
|
}
|
||||||
|
shm_radio_state->tx_timing = calloc_or_fail(num_tx_stats, sizeof(tx_timing_t));
|
||||||
|
for (int i = 0; i < num_tx_stats; i++) {
|
||||||
|
shm_radio_state->tx_timing[i].tx_histogram.min_samples = 100;
|
||||||
|
// Set the histogram range to 3000uS. Anything above that is not interesting
|
||||||
|
shm_radio_state->tx_timing[i].tx_histogram.range = 3000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a shared memory channel
|
||||||
|
if (shm_radio_state->role == ROLE_SERVER) {
|
||||||
shm_radio_state->channel = shm_td_iq_channel_create(shm_radio_state->channel_name,
|
shm_radio_state->channel = shm_td_iq_channel_create(shm_radio_state->channel_name,
|
||||||
shm_radio_state->peer_info.num_rx_antennas,
|
shm_radio_state->peer_info.num_rx_antennas,
|
||||||
device->openair0_cfg[0].rx_num_channels);
|
device->openair0_cfg[0].rx_num_channels);
|
||||||
@@ -280,12 +339,146 @@ static int shm_radio_connect(openair0_device *device)
|
|||||||
int ret = pthread_create(&shm_radio_state->timing_thread, NULL, shm_radio_timing_job, shm_radio_state);
|
int ret = pthread_create(&shm_radio_state->timing_thread, NULL, shm_radio_timing_job, shm_radio_state);
|
||||||
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
|
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||||
} else {
|
} else {
|
||||||
shm_radio_state->peer_info = client_exchange_peer_info(peer_info);
|
|
||||||
shm_radio_state->channel = shm_td_iq_channel_connect(shm_radio_state->channel_name, 10);
|
shm_radio_state->channel = shm_td_iq_channel_connect(shm_radio_state->channel_name, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int shm_radio_write_internal(shm_radio_state_t *shm_radio_state,
|
||||||
|
openair0_timestamp timestamp,
|
||||||
|
c16_t *samples,
|
||||||
|
int nsamps,
|
||||||
|
int aarx,
|
||||||
|
int flags,
|
||||||
|
int stats_index)
|
||||||
|
{
|
||||||
|
tx_timing_t *tx_timing = &shm_radio_state->tx_timing[stats_index];
|
||||||
|
|
||||||
|
uint64_t sample = shm_td_iq_channel_get_current_sample(shm_radio_state->channel);
|
||||||
|
int64_t diff = timestamp - sample;
|
||||||
|
double budget = diff / (shm_radio_state->sample_rate / 1e6);
|
||||||
|
tx_timing->average_tx_budget = .05 * budget + .95 * tx_timing->average_tx_budget;
|
||||||
|
histogram_add(&tx_timing->tx_histogram, budget);
|
||||||
|
|
||||||
|
int ret = shm_td_iq_channel_tx(shm_radio_state->channel, timestamp, nsamps, aarx, (sample_t *)samples);
|
||||||
|
|
||||||
|
if (ret == CHANNEL_ERROR_TOO_LATE) {
|
||||||
|
tx_timing->tx_samples_late += nsamps;
|
||||||
|
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
|
||||||
|
tx_timing->tx_early += 1;
|
||||||
|
}
|
||||||
|
tx_timing->tx_samples_total += nsamps;
|
||||||
|
|
||||||
|
return nsamps;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
shm_radio_state_t *shm_radio_state;
|
||||||
|
openair0_timestamp timestamp;
|
||||||
|
c16_t *samples[4];
|
||||||
|
int nsamps;
|
||||||
|
int nbAnt;
|
||||||
|
int flags;
|
||||||
|
int aarx;
|
||||||
|
} channel_modelling_args_t;
|
||||||
|
|
||||||
|
static void perform_channel_modelling(void *arg)
|
||||||
|
{
|
||||||
|
channel_modelling_args_t *channel_modelling_args = arg;
|
||||||
|
int nsamps = channel_modelling_args->nsamps;
|
||||||
|
int aarx = channel_modelling_args->aarx;
|
||||||
|
int nb_tx_ant = channel_modelling_args->nbAnt;
|
||||||
|
c16_t **input_samples = (c16_t **)channel_modelling_args->samples;
|
||||||
|
|
||||||
|
int aligned_nsamps = ceil_mod(nsamps, (512 / 8) / sizeof(cf_t));
|
||||||
|
cf_t samples[aligned_nsamps] __attribute__((aligned(64)));
|
||||||
|
// Apply noise from global settings
|
||||||
|
get_noise_vector((float *)samples, nsamps * 2);
|
||||||
|
|
||||||
|
channel_desc_t *channel_desc = channel_modelling_args->shm_radio_state->channel_desc;
|
||||||
|
const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0);
|
||||||
|
|
||||||
|
// Convert channel impulse response to float + apply pathloss
|
||||||
|
cf_t channel_impulse_response[nb_tx_ant][channel_desc->channel_length];
|
||||||
|
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||||
|
const struct complexd *channelModel = channel_desc->ch[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
|
||||||
|
for (int i = 0; i < nsamps; i++) {
|
||||||
|
cf_t *impulse_response = channel_impulse_response[aatx];
|
||||||
|
for (int l = 0; l < channel_desc->channel_length; l++) {
|
||||||
|
int idx = i - l;
|
||||||
|
// TODO: Use AVX512 for this
|
||||||
|
// TODO: What are the previously sent samples (for impulse response)
|
||||||
|
if (idx >= 0) {
|
||||||
|
c16_t tx_input = input_samples[aatx][idx];
|
||||||
|
samples[i].r += tx_input.r * impulse_response[l].r - tx_input.i * impulse_response[l].i;
|
||||||
|
samples[i].i += tx_input.i * impulse_response[l].r + tx_input.r * impulse_response[l].i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to c16_t
|
||||||
|
c16_t samples_out[aligned_nsamps] __attribute__((aligned(64)));
|
||||||
|
#if defined(__AVX512F__)
|
||||||
|
for (int i = 0; i < aligned_nsamps / 8; i++) {
|
||||||
|
simde__m512 *in = (simde__m512 *)&samples[i * 8];
|
||||||
|
simde__m256i *out = (simde__m256i *)&samples_out[i * 8];
|
||||||
|
*out = simde_mm512_cvtsepi32_epi16(simde_mm512_cvtps_epi32(*in));
|
||||||
|
}
|
||||||
|
#elif defined(__AVX2__)
|
||||||
|
for (int i = 0; i < aligned_nsamps / 4; i++) {
|
||||||
|
simde__m256 *in = (simde__m256 *)&samples[i * 4];
|
||||||
|
simde__m128i *out = (simde__m128i *)&samples_out[i * 4];
|
||||||
|
*out = simde_mm256_cvtsepi32_epi16(simde_mm256_cvtps_epi32(*in));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
for (int i = 0; i < nsamps; i++) {
|
||||||
|
samples_out[i].r = lroundf(samples[i].r);
|
||||||
|
samples_out[i].i = lroundf(samples[i].i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
shm_radio_write_internal(channel_modelling_args->shm_radio_state,
|
||||||
|
channel_modelling_args->timestamp,
|
||||||
|
samples_out,
|
||||||
|
channel_modelling_args->nsamps,
|
||||||
|
aarx,
|
||||||
|
channel_modelling_args->flags,
|
||||||
|
aarx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int shm_radio_write_with_chanmod(shm_radio_state_t *shm_radio_state,
|
||||||
|
openair0_timestamp timestamp,
|
||||||
|
void **samplesVoid,
|
||||||
|
int nsamps,
|
||||||
|
int nbAnt,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
for (int aarx = 0; aarx < shm_radio_state->peer_info.num_rx_antennas; aarx++) {
|
||||||
|
notifiedFIFO_elt_t *task = newNotifiedFIFO_elt(sizeof(channel_modelling_args_t), 0, NULL, perform_channel_modelling);
|
||||||
|
channel_modelling_args_t *args = (channel_modelling_args_t *)NotifiedFifoData(task);
|
||||||
|
args->shm_radio_state = shm_radio_state;
|
||||||
|
args->timestamp = timestamp;
|
||||||
|
args->nsamps = nsamps;
|
||||||
|
args->nbAnt = nbAnt;
|
||||||
|
args->flags = flags;
|
||||||
|
args->aarx = aarx;
|
||||||
|
for (int i = 0; i < nbAnt; i++) {
|
||||||
|
args->samples[i] = samplesVoid[i];
|
||||||
|
}
|
||||||
|
pushNotifiedFIFO(&shm_radio_state->channel_modelling_actors[aarx].fifo, task);
|
||||||
|
}
|
||||||
|
return nsamps;
|
||||||
|
}
|
||||||
|
|
||||||
static int shm_radio_write(openair0_device *device,
|
static int shm_radio_write(openair0_device *device,
|
||||||
openair0_timestamp timestamp,
|
openair0_timestamp timestamp,
|
||||||
void **samplesVoid,
|
void **samplesVoid,
|
||||||
@@ -295,20 +488,9 @@ static int shm_radio_write(openair0_device *device,
|
|||||||
{
|
{
|
||||||
timestamp -= device->openair0_cfg->command_line_sample_advance;
|
timestamp -= device->openair0_cfg->command_line_sample_advance;
|
||||||
shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv;
|
shm_radio_state_t *shm_radio_state = (shm_radio_state_t *)device->priv;
|
||||||
uint64_t sample = shm_td_iq_channel_get_current_sample(shm_radio_state->channel);
|
return shm_radio_state->chanmod
|
||||||
int64_t diff = timestamp - sample;
|
? shm_radio_write_with_chanmod(shm_radio_state, timestamp, samplesVoid, nsamps, nbAnt, flags)
|
||||||
double budget = diff / (shm_radio_state->sample_rate / 1e6);
|
: shm_radio_write_internal(shm_radio_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0);
|
||||||
shm_radio_state->average_tx_budget = .05 * budget + .95 * shm_radio_state->average_tx_budget;
|
|
||||||
histogram_add(&shm_radio_state->tx_histogram, budget);
|
|
||||||
|
|
||||||
int ret = shm_td_iq_channel_tx(shm_radio_state->channel, timestamp, nsamps, 0, samplesVoid[0]);
|
|
||||||
if (ret == CHANNEL_ERROR_TOO_LATE) {
|
|
||||||
shm_radio_state->tx_samples_late += nsamps;
|
|
||||||
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
|
|
||||||
shm_radio_state->tx_early += 1;
|
|
||||||
}
|
|
||||||
shm_radio_state->tx_samples_total += nsamps;
|
|
||||||
return nsamps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int shm_radio_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt)
|
static int shm_radio_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt)
|
||||||
@@ -335,17 +517,38 @@ static void shm_radio_end(openair0_device *device)
|
|||||||
int ret = pthread_join(shm_radio_state->timing_thread, NULL);
|
int ret = pthread_join(shm_radio_state->timing_thread, NULL);
|
||||||
AssertFatal(ret == 0, "pthread_join() failed: errno: %d, %s\n", errno, strerror(errno));
|
AssertFatal(ret == 0, "pthread_join() failed: errno: %d, %s\n", errno, strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx_timing_t *tx_timing = shm_radio_state->tx_timing;
|
||||||
|
if (shm_radio_state->chanmod) {
|
||||||
|
for (int i = 0; i < shm_radio_state->peer_info.num_rx_antennas; i++) {
|
||||||
|
shutdown_actor(&shm_radio_state->channel_modelling_actors[i]);
|
||||||
|
}
|
||||||
|
free(shm_radio_state->channel_modelling_actors);
|
||||||
|
for (int i = 1; i < shm_radio_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;
|
||||||
|
tx_timing->tx_samples_late += tx_timing[i].tx_samples_late;
|
||||||
|
tx_timing->average_tx_budget += tx_timing[i].average_tx_budget;
|
||||||
|
tx_timing->tx_samples_total += tx_timing[i].tx_samples_total;
|
||||||
|
}
|
||||||
|
tx_timing->average_tx_budget /= shm_radio_state->peer_info.num_rx_antennas;
|
||||||
|
}
|
||||||
|
// produce 1 second of extra samples so threads can finish
|
||||||
|
shm_td_iq_channel_produce_samples(shm_radio_state->channel, shm_radio_state->sample_rate);
|
||||||
|
sleep(1);
|
||||||
|
shm_td_iq_channel_destroy(shm_radio_state->channel);
|
||||||
|
|
||||||
LOG_I(HW,
|
LOG_I(HW,
|
||||||
"SHM_RADIO: Realtime issues: TX %.2f%%, RX %.2f%%\n",
|
"SHM_RADIO: Realtime issues: TX %.2f%%, RX %.2f%%\n",
|
||||||
shm_radio_state->tx_samples_late / (float)shm_radio_state->tx_samples_total * 100,
|
tx_timing->tx_samples_late / (float)tx_timing->tx_samples_total * 100,
|
||||||
shm_radio_state->rx_samples_late / (float)shm_radio_state->rx_samples_total * 100);
|
shm_radio_state->rx_samples_late / (float)shm_radio_state->rx_samples_total * 100);
|
||||||
LOG_I(HW,
|
LOG_I(HW,
|
||||||
"SHM_RADIO: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n",
|
"SHM_RADIO: Read/write too early (suspected radio implementaton error) TX: %lu, RX: %lu\n",
|
||||||
shm_radio_state->tx_early,
|
tx_timing->tx_early,
|
||||||
shm_radio_state->rx_early);
|
shm_radio_state->rx_early);
|
||||||
LOG_I(HW, "SHM_RADIO: Average TX budget %.3lf uS\n", shm_radio_state->average_tx_budget);
|
LOG_I(HW, "SHM_RADIO: Average TX budget %.3lf uS\n", tx_timing->average_tx_budget);
|
||||||
histogram_print(&shm_radio_state->tx_histogram);
|
histogram_print(&tx_timing->tx_histogram);
|
||||||
shm_td_iq_channel_destroy(shm_radio_state->channel);
|
free(shm_radio_state->tx_timing);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int shm_radio_stub(openair0_device *device)
|
static int shm_radio_stub(openair0_device *device)
|
||||||
@@ -357,9 +560,16 @@ static int shm_radio_stub2(openair0_device *device, openair0_config_t *openair0_
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int shm_radio_set_freq(openair0_device *device, openair0_config_t *openair0_cfg)
|
||||||
|
{
|
||||||
|
shm_radio_state_t *s = device->priv;
|
||||||
|
s->rx_freq = openair0_cfg->rx_freq[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
__attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg)
|
__attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg)
|
||||||
{
|
{
|
||||||
shm_radio_state_t *shm_radio_state = calloc(1, sizeof(shm_radio_state_t));
|
shm_radio_state_t *shm_radio_state = calloc_or_fail(1, sizeof(shm_radio_state_t));
|
||||||
shm_radio_readconfig(shm_radio_state);
|
shm_radio_readconfig(shm_radio_state);
|
||||||
LOG_I(HW,
|
LOG_I(HW,
|
||||||
"Running as %s\n",
|
"Running as %s\n",
|
||||||
@@ -369,7 +579,7 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi
|
|||||||
device->trx_reset_stats_func = shm_radio_stub;
|
device->trx_reset_stats_func = shm_radio_stub;
|
||||||
device->trx_end_func = shm_radio_end;
|
device->trx_end_func = shm_radio_end;
|
||||||
device->trx_stop_func = shm_radio_stub;
|
device->trx_stop_func = shm_radio_stub;
|
||||||
device->trx_set_freq_func = shm_radio_stub2;
|
device->trx_set_freq_func = shm_radio_set_freq;
|
||||||
device->trx_set_gains_func = shm_radio_stub2;
|
device->trx_set_gains_func = shm_radio_stub2;
|
||||||
device->trx_write_func = shm_radio_write;
|
device->trx_write_func = shm_radio_write;
|
||||||
device->trx_read_func = shm_radio_read;
|
device->trx_read_func = shm_radio_read;
|
||||||
@@ -380,8 +590,16 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi
|
|||||||
device->trx_write_init = shm_radio_stub;
|
device->trx_write_init = shm_radio_stub;
|
||||||
shm_radio_state->last_received_sample = 0;
|
shm_radio_state->last_received_sample = 0;
|
||||||
shm_radio_state->sample_rate = openair0_cfg->sample_rate;
|
shm_radio_state->sample_rate = openair0_cfg->sample_rate;
|
||||||
shm_radio_state->tx_histogram.min_samples = 100;
|
shm_radio_state->rx_freq = openair0_cfg->rx_freq[0];
|
||||||
// Set the histogram range to 3000uS. Anything above that is not interesting
|
shm_radio_state->tx_bw = openair0_cfg->tx_bw;
|
||||||
shm_radio_state->tx_histogram.range = 3000.0;
|
shm_radio_state->tx_num_channels = openair0_cfg->tx_num_channels;
|
||||||
|
shm_radio_state->rx_num_channels = openair0_cfg->rx_num_channels;
|
||||||
|
|
||||||
|
if (shm_radio_state->chanmod) {
|
||||||
|
init_channelmod();
|
||||||
|
int noise_power_dBFS = get_noise_power_dBFS();
|
||||||
|
int16_t noise_power = noise_power_dBFS == INVALID_DBFS_VALUE ? 0 : (int16_t)(32767.0 / powf(10.0, .05 * -noise_power_dBFS));
|
||||||
|
init_noise_device(noise_power);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user