RFsim: Allow arbitrary order of beams.

Allow arbitrary order of beam ids in set_beams2 function. This
allows the application to select any arbitrary order of beam ids.
The underlying transport mechanism still uses beam_map so the beam_ids
are ordered before sending.

Also replaced tx_beam_map/rx_beam_map into beam_map.

Also fixed an issue where --rfsimulator.enable_beams required an argument.
This commit is contained in:
Bartosz Podrygajlo
2025-12-23 10:05:14 +01:00
parent 033aed736e
commit b10617607a
2 changed files with 91 additions and 41 deletions
+16
View File
@@ -623,6 +623,22 @@ struct openair0_device_t {
*/
int (*trx_set_beams)(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp);
/*! \brief Set tx/rx beams
*
* Set the tx/rx beams. This has to be done in advance of the reception in order to
* allow the underlying device to change receiver configuration. The exact time depends
* on the device.
*
* NOTICE: the samples returned from trx_read_func may belong to more than one beam. It is up
* to the application to determine the beam of the received samples.
*
* \param device the hardware to use
* \param beams pointer to array of beam ids
* \param num_beams number of beams
* \return 0 on success
*/
int (*trx_set_beams2)(openair0_device *device, int* beams, int num_beams, openair0_timestamp timestamp);
/*! \brief RRU Configuration callback
* \param idx RU index
* \param arg pointer to capabilities or configuration
+75 -41
View File
@@ -30,6 +30,7 @@
#include "PHY/TOOLS/tools_defs.h"
#include "PHY/defs_common.h"
#include "utils.h"
#include <cstdint>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
@@ -59,6 +60,7 @@ extern "C" {
#include <vector>
#include <sstream>
#include <algorithm>
#include <numeric>
#define PORT 4043 // default TCP port for this simulator
#define sampleToByte(a, b) ((a) * (b) * sizeof(sample_t))
@@ -84,6 +86,7 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole;
" saviq: enable saving written iqs to a file\n"
#define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
#define simBool PARAMFLAG_BOOL | PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
// clang-format off
/*----------------------------------------------------------------------------------------------------------------------------------------------------*/
/* configuration parameters for the rfsimulator device */
@@ -100,10 +103,9 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole;
UINT64PARAM("offset", "<channel offset in samps>\n", simOpt, &rfsimulator->chan_offset, 0L), \
DOUBLEPARAM("prop_delay", "<propagation delay in ms>\n", simOpt, &(rfsimulator->prop_delay_ms), 0.0), \
INTPARAM("wait_timeout", "<wait timeout if no UE connected>\n", simOpt, &rfsimulator->wait_timeout, 1), \
BOOLPARAM("enable_beams", "<enable simplified beam simulation>\n", simOpt, &(beam_ctrl->enable_beams), 0), \
BOOLPARAM("enable_beams", "<enable simplified beam simulation>\n", simBool,&(beam_ctrl->enable_beams), 0), \
INTPARAM("num_concurrent_beams", "<number of concurrent beams supported>\n", simOpt, &beam_ctrl->num_concurrent_beams, 1), \
UINT64PARAM("tx_beam_map", "<initial beam map>\n", simOpt, &beam_ctrl->tx.beam_map, 1), \
UINT64PARAM("rx_beam_map", "<initial beam map>\n", simOpt, &beam_ctrl->rx.beam_map, 1), \
UINT64PARAM("beam_map", "<initial beam map>\n", simOpt, &beam_map, 1), \
STRINGPARAM(RFSIMU_BEAM_GAINS, "<beam gain matrix in toeplitz form>\n", simOpt, NULL, NULL), \
};
// clang-format on
@@ -134,12 +136,12 @@ static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}};
typedef c16_t sample_t; // 2*16 bits complex number
typedef struct beam_switch_command_t {
uint64_t beam_map;
std::vector<int> beams;
openair0_timestamp timestamp;
} beam_switch_command_t;
typedef struct {
uint64_t beam_map;
std::vector<int> beams;
std::queue<beam_switch_command_t> cmd_queue;
std::mutex mutex;
} beam_state_t;
@@ -212,10 +214,10 @@ typedef struct {
* @param nsamps_out output pointer to receive the number of samples until the next beam switch.
* @return The beam map (uint64_t) valid for the given timestamp.
*/
static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timestamp, uint32_t nsamps, uint32_t *nsamps_out)
static std::vector<int> get_beams(beam_state_t *beam_state, openair0_timestamp timestamp, uint32_t nsamps, uint32_t *nsamps_out)
{
std::lock_guard<std::mutex> lock(beam_state->mutex);
uint64_t current_map = beam_state->beam_map;
std::vector<int> current_beams = beam_state->beams;
uint32_t samples_to_next_switch = nsamps;
// Find the latest beam_switch_command_t with timestamp <= requested timestamp
@@ -225,7 +227,7 @@ static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timest
while (!queue_copy.empty()) {
const beam_switch_command_t &cmd = queue_copy.front();
if (cmd.timestamp <= timestamp) {
current_map = cmd.beam_map;
current_beams = cmd.beams;
queue_copy.pop();
} else {
samples_to_next_switch = (cmd.timestamp > timestamp) ? (cmd.timestamp - timestamp) : nsamps;
@@ -240,9 +242,33 @@ static uint64_t get_beam_map(beam_state_t *beam_state, openair0_timestamp timest
*nsamps_out = samples_to_next_switch;
return current_map;
return current_beams;
}
static std::vector<int> beam_map_to_beams(uint64_t beam_map)
{
int num_beams = __builtin_popcountll(beam_map);
AssertFatal(num_beams > 0, "Needs at least one beam\n");
std::vector<int> beam_ids;
for (int i = 0; i < MAX_BEAMS; i++) {
if (beam_map & (1ULL << i)) {
beam_ids.push_back(i);
}
}
return beam_ids;
}
static uint64_t beams_to_beam_map(const std::vector<int> &beam_ids)
{
uint64_t beam_map = 0;
for (size_t i = 0; i < beam_ids.size(); i++) {
beam_map |= (1ULL << beam_ids[i]);
}
return beam_map;
}
/**
* @brief Clears outdated beam switch commands from the queue and updates the current beam map.
*
@@ -259,7 +285,7 @@ static void clear_beam_queue(beam_state_t *beam_state, openair0_timestamp timest
std::lock_guard<std::mutex> lock(beam_state->mutex);
while (!beam_state->cmd_queue.empty()) {
if (beam_state->cmd_queue.front().timestamp <= timestamp) {
beam_state->beam_map = beam_state->cmd_queue.front().beam_map;
beam_state->beams = beam_state->cmd_queue.front().beams;
beam_state->cmd_queue.pop();
} else {
break;
@@ -448,7 +474,19 @@ static int rfsimulator_set_beams(openair0_device *device, uint64_t beam_map, ope
rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl;
std::lock_guard<std::mutex> lock_tx(beam_ctrl->tx.mutex);
std::lock_guard<std::mutex> lock_rx(beam_ctrl->rx.mutex);
beam_switch_command_t command = {.beam_map = beam_map, .timestamp = timestamp};
beam_switch_command_t command = {.beams = beam_map_to_beams(beam_map), .timestamp = timestamp};
beam_ctrl->rx.cmd_queue.emplace(command);
beam_ctrl->tx.cmd_queue.emplace(command);
return 0;
}
static int rfsimulator_set_beams_vector(openair0_device *device, int* beams, int num_beams, openair0_timestamp timestamp)
{
rfsimulator_state_t *s = static_cast<rfsimulator_state_t *>(device->priv);
rfsim_beam_ctrl_t *beam_ctrl = s->beam_ctrl;
std::lock_guard<std::mutex> lock_tx(beam_ctrl->tx.mutex);
std::lock_guard<std::mutex> lock_rx(beam_ctrl->rx.mutex);
beam_switch_command_t command = {.beams = std::vector<int>(beams, beams + num_beams), .timestamp = timestamp};
beam_ctrl->rx.cmd_queue.emplace(command);
beam_ctrl->tx.cmd_queue.emplace(command);
return 0;
@@ -481,6 +519,7 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator)
char *saveF = NULL;
char *modelname = NULL;
rfsim_beam_ctrl_t *beam_ctrl = rfsimulator->beam_ctrl;
uint64_t beam_map = 1;
paramdef_t rfsimu_params[] = RFSIMULATOR_PARAMS_DESC;
int p = config_paramidx_fromname(rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_OPTIONS_PARAMNAME);
int ret = config_get(config_get_if(), rfsimu_params, sizeofArray(rfsimu_params), RFSIMU_SECTION);
@@ -519,6 +558,10 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator)
process_gains(*rfsimu_params[beam_gains_param_index].strptr, beam_ctrl);
}
std::vector<int> initial_beams = beam_map_to_beams(beam_map);
beam_ctrl->rx.beams = initial_beams;
beam_ctrl->tx.beams = initial_beams;
if (strncasecmp(rfsimulator->ip, "enb", 3) == 0 || strncasecmp(rfsimulator->ip, "server", 3) == 0)
rfsimulator->role = SIMU_ROLE_SERVER;
else
@@ -533,8 +576,8 @@ static int rfsimu_set_beam(char *buff, int debug, telnet_printfunc_t prnt, void
uint64_t beam_map = strtoull(buff, NULL, 0);
std::lock_guard<std::mutex> lock_tx(beam_ctrl->tx.mutex);
std::lock_guard<std::mutex> lock_rx(beam_ctrl->rx.mutex);
beam_ctrl->rx.beam_map = beam_map;
beam_ctrl->tx.beam_map = beam_map;
beam_ctrl->rx.beams = beam_map_to_beams(beam_map);
beam_ctrl->tx.beams = beam_map_to_beams(beam_map);
prnt("Beam map set to 0x%lx\n", beam_map);
return CMDSTATUS_FOUND;
}
@@ -854,7 +897,7 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t,
void ***samplesVoid,
int nsamps,
int nbAnt,
uint64_t beam_map,
std::vector<int> tx_beams,
int flags)
{
mutexlock(t->Sockmutex);
@@ -864,15 +907,18 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t,
buffer_t *b = &t->buf[i];
if (b->conn_sock >= 0) {
if (!nbAnt)
LOG_E(HW, "rfsimulator sending 0 tx antennas\n");
samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beam_map};
samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beams_to_beam_map(tx_beams)};
fullwrite(b->conn_sock, &header, sizeof(header), t);
int num_beams = __builtin_popcountll(beam_map);
int num_beams = tx_beams.size();
// Send beams in order of beam index. This is required for beam_map to work correctly on the receiver side.
std::vector<size_t> indices(tx_beams.size());
std::iota(indices.begin(), indices.end(), 0);
std::sort(indices.begin(), indices.end(), [&](size_t i, size_t j) { return tx_beams[i] < tx_beams[j]; });
AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n");
for (int beam = 0; beam < num_beams; beam++) {
for (int a = 0; a < nbAnt; a++) {
sample_t *in = (sample_t *)samplesVoid[beam][a];
sample_t *in = (sample_t *)samplesVoid[indices[beam]][a];
fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t);
}
}
@@ -926,8 +972,8 @@ static int rfsimulator_write_beams(openair0_device *device,
}
while (nsamps > 0) {
uint32_t nsamps_beam_map;
uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps, &nsamps_beam_map);
rfsimulator_write_internal(t, timestamp, samples_ptr, nsamps_beam_map, nbAnt, beam_map, flags);
std::vector<int> beams = get_beams(&t->beam_ctrl->tx, timestamp, nsamps, &nsamps_beam_map);
rfsimulator_write_internal(t, timestamp, samples_ptr, nsamps_beam_map, nbAnt, beams, flags);
for (int beam = 0; beam < num_beams; beam++) {
for (int aatx = 0; aatx < nbAnt; aatx++) {
char *ptr = (char *)samples_ptr[beam][aatx];
@@ -1018,19 +1064,6 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_
return;
}
static std::vector<int> get_beam_ids(uint64_t beam_map)
{
int num_beams = __builtin_popcountll(beam_map);
AssertFatal(num_beams > 0, "Needs at least one beam\n");
std::vector<int> beam_ids;
for (int i = 0; i < MAX_BEAMS; i++) {
if (beam_map & (1ULL << i)) {
beam_ids.push_back(i);
}
}
return beam_ids;
}
/**
* @brief Combines samples from received packets into a single buffer for the rx beam
*
@@ -1070,7 +1103,8 @@ static void combine_received_beams(rfsimulator_state_t *t,
break;
}
std::vector<int> tx_beams = get_beam_ids(pkt->header.beam_map);
// The beams transmitted in are ordered by beam index
std::vector<int> tx_beams = beam_map_to_beams(pkt->header.beam_map);
for (uint beam = 0; beam < tx_beams.size(); beam++) {
float gain_dB = get_rx_gain_db(t, rx_beam_id, tx_beams[beam]);
float gain_linear = powf(10, gain_dB / 20.0);
@@ -1373,20 +1407,19 @@ static int rfsimulator_read_beams(openair0_device *device,
int nsamps_to_process = nsamps;
while (nsamps_to_process > 0) {
uint32_t nsamps_beam_map;
uint64_t beam_map = get_beam_map(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map);
std::vector<int> rx_beam_ids = get_beam_ids(beam_map);
if ((int)rx_beam_ids.size() != num_beams) {
std::vector<int> rx_beams = get_beams(&t->beam_ctrl->tx, timestamp, nsamps_to_process, &nsamps_beam_map);
if ((int)rx_beams.size() != num_beams) {
LOG_D(HW,
"Number of beams does not match application request num_beams %d, beam_map beams %lu\n",
num_beams,
rx_beam_ids.size());
rx_beams.size());
}
for (int beam = 0; beam < num_beams && beam < (int)rx_beam_ids.size(); beam++) {
for (int beam = 0; beam < num_beams && beam < (int)rx_beams.size(); beam++) {
c16_t *samples_beam[nbAnt];
for (int i = 0; i < nbAnt; i++) {
samples_beam[i] = (c16_t *)samplesVoid[beam][i] + timestamp - t->nextRxTstamp;
}
rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beam_ids[beam], beam == 0);
rfsimulator_read_internal(t, samples_beam, timestamp, nsamps_beam_map, nbAnt, rx_beams[beam], beam == 0);
}
timestamp += nsamps_beam_map;
nsamps_to_process -= nsamps_beam_map;
@@ -1526,6 +1559,7 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d
device->priv = rfsimulator;
device->trx_write_init = rfsimulator_write_init;
device->trx_set_beams = rfsimulator_set_beams;
device->trx_set_beams2 = rfsimulator_set_beams_vector;
for (int i = 0; i < MAX_FD_RFSIMU; i++)
rfsimulator->buf[i].conn_sock = -1;