update
This commit is contained in:
@@ -227,6 +227,28 @@ void rxAddInput(c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_d
|
||||
double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt];
|
||||
Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI));
|
||||
|
||||
/* random_channel() defines AWGN as a one-tap identity matrix. The generic
|
||||
* convolution below nevertheless scans every TX branch for every RX sample,
|
||||
* turning an 8x8 RFsim link into 64 complex multiplies per sample. Select
|
||||
* the sole non-zero branch directly. This is the exact same channel model;
|
||||
* it only removes multiplication by the known zeros. */
|
||||
if (channelDesc->modelid == AWGN && channelDesc->channel_length == 1
|
||||
&& channelDesc->Doppler_phase_inc == 0.0) {
|
||||
const int txAnt = rxAnt % nbTx;
|
||||
const struct complexd channel = channelDesc->ch[rxAnt + (txAnt * channelDesc->nb_rx)][0];
|
||||
const bool noise_rounds_to_zero = noise_per_sample < 1e-6;
|
||||
for (int i = 0; i < nbSamples; i++) {
|
||||
const struct complex16 tx = input_sig[txAnt][i];
|
||||
const double rx_r = tx.r * channel.r - tx.i * channel.i;
|
||||
const double rx_i = tx.i * channel.r + tx.r * channel.i;
|
||||
after_channel_sig[i].r += rx_r * pathLossLinear
|
||||
+ (noise_rounds_to_zero ? 0.0 : noise_per_sample * gaussZiggurat(0.0, 1.0));
|
||||
after_channel_sig[i].i += rx_i * pathLossLinear
|
||||
+ (noise_rounds_to_zero ? 0.0 : noise_per_sample * gaussZiggurat(0.0, 1.0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nbSamples; i++) {
|
||||
cf_t *out_ptr = after_channel_sig + i;
|
||||
struct complexd rx_tmp = {0};
|
||||
|
||||
+129
-14
@@ -56,6 +56,14 @@ extern int get_currentchannels_type(const char *buf,
|
||||
#define SEND_BUFF_SIZE 100000000 // Socket buffer size
|
||||
#define MAX_BEAMS 64
|
||||
|
||||
// The RF simulator normally transports c16_t samples verbatim. At 106 PRB,
|
||||
// eight TX ports exceed a 10-Gbit/s link before TCP overhead. IQ8 is an
|
||||
// explicitly enabled wire encoding: a block is packed only when every I/Q
|
||||
// component is exactly representable as int8_t, otherwise that block is sent
|
||||
// in the legacy c16_t format. The magic value avoids confusing this with any
|
||||
// future use of the otherwise-unused option fields in samplesBlockHeader_t.
|
||||
static constexpr uint32_t RFSIM_OPTION_IQ8 = 0x49513831U; // "IQ81"
|
||||
|
||||
// Simulator role
|
||||
typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole;
|
||||
|
||||
@@ -81,7 +89,8 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole;
|
||||
#define RFSIM_CONFIG_HELP_OPTIONS \
|
||||
" list of comma separated options to enable rf simulator functionalities. Available options: \n" \
|
||||
" chanmod: enable channel modelisation\n" \
|
||||
" saviq: enable saving written iqs to a file\n"
|
||||
" saviq: enable saving written iqs to a file\n" \
|
||||
" iq8: losslessly pack int8-representable IQ blocks on the wire\n"
|
||||
|
||||
#define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
|
||||
#define simBool PARAMFLAG_BOOL | PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
|
||||
@@ -161,6 +170,7 @@ typedef struct buffer_s {
|
||||
rfsim_packet_t *packet_ptr;
|
||||
size_t payload_sz;
|
||||
size_t remainToTransferBeam;
|
||||
bool packet_iq8;
|
||||
std::queue<rfsim_packet_t *> received_packets;
|
||||
} buffer_t;
|
||||
|
||||
@@ -191,6 +201,12 @@ typedef struct {
|
||||
double rx_freq;
|
||||
double tx_bw;
|
||||
int channelmod;
|
||||
bool iq8;
|
||||
bool iq8_tx_logged;
|
||||
bool iq8_fallback_logged;
|
||||
bool iq8_rx_logged;
|
||||
uint64_t iq8_tx_blocks;
|
||||
uint64_t iq8_raw_blocks;
|
||||
double chan_pathloss;
|
||||
double chan_forgetfact;
|
||||
uint64_t chan_offset;
|
||||
@@ -463,6 +479,49 @@ static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t)
|
||||
}
|
||||
}
|
||||
|
||||
static bool pack_iq8_block(void ***samplesVoid,
|
||||
const std::vector<size_t> &beam_indices,
|
||||
int nbAnt,
|
||||
int nsamps,
|
||||
std::vector<int8_t> &packed,
|
||||
int *max_abs)
|
||||
{
|
||||
const size_t complex_samples = (size_t)beam_indices.size() * nbAnt * nsamps;
|
||||
packed.resize(complex_samples * 2);
|
||||
size_t out = 0;
|
||||
*max_abs = 0;
|
||||
for (size_t beam : beam_indices) {
|
||||
for (int ant = 0; ant < nbAnt; ant++) {
|
||||
const sample_t *in = static_cast<const sample_t *>(samplesVoid[beam][ant]);
|
||||
for (int sample = 0; sample < nsamps; sample++) {
|
||||
const int real = in[sample].r;
|
||||
const int imag = in[sample].i;
|
||||
*max_abs = std::max(*max_abs, std::max(std::abs(real), std::abs(imag)));
|
||||
if (real < INT8_MIN || real > INT8_MAX || imag < INT8_MIN || imag > INT8_MAX) {
|
||||
packed.clear();
|
||||
return false;
|
||||
}
|
||||
packed[out++] = static_cast<int8_t>(real);
|
||||
packed[out++] = static_cast<int8_t>(imag);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void unpack_iq8_block_in_place(char *payload, size_t complex_samples)
|
||||
{
|
||||
int8_t *packed = reinterpret_cast<int8_t *>(payload);
|
||||
sample_t *unpacked = reinterpret_cast<sample_t *>(payload);
|
||||
// Expanding from the end keeps unread packed bytes intact.
|
||||
for (size_t sample = complex_samples; sample-- > 0;) {
|
||||
const int16_t real = packed[2 * sample];
|
||||
const int16_t imag = packed[2 * sample + 1];
|
||||
unpacked[sample].r = real;
|
||||
unpacked[sample].i = imag;
|
||||
}
|
||||
}
|
||||
|
||||
static float get_rx_gain_db(rfsimulator_state_t *rfsimulator, uint rx_beam, uint tx_beam)
|
||||
{
|
||||
if (!rfsimulator->beam_ctrl->enable_beams) {
|
||||
@@ -584,6 +643,10 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator)
|
||||
rfsimulator->rx_freq,
|
||||
rfsimulator->tx_bw);
|
||||
rfsimulator->channelmod = true;
|
||||
} else if (strcmp(rfsimuParam[p].strlistptr[i], "iq8") == 0) {
|
||||
rfsimulator->iq8 = true;
|
||||
LOG_I(HW,
|
||||
"RFsim IQ8 wire packing enabled (exact int8 blocks, automatic c16 fallback)\n");
|
||||
} else {
|
||||
fprintf(stderr, "unknown rfsimulator option: %s\n", rfsimuParam[p].strlistptr[i]);
|
||||
exit(-1);
|
||||
@@ -975,23 +1038,58 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t,
|
||||
mutexlock(t->Sockmutex);
|
||||
LOG_D(HW, "Sending %d samples at time: %ld, nbAnt %d\n", nsamps, timestamp, nbAnt);
|
||||
|
||||
const int num_beams = tx_beams.size();
|
||||
AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n");
|
||||
// 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]; });
|
||||
|
||||
static thread_local std::vector<int8_t> iq8_payload;
|
||||
int max_abs = 0;
|
||||
const bool use_iq8 = t->iq8 && pack_iq8_block(samplesVoid, indices, nbAnt, nsamps, iq8_payload, &max_abs);
|
||||
if (use_iq8) {
|
||||
t->iq8_tx_blocks++;
|
||||
if (!t->iq8_tx_logged) {
|
||||
LOG_I(HW,
|
||||
"RFsim IQ8 first packed TX block: samples=%d antennas=%d beams=%d max_abs=%d wire=%zu/raw=%zu bytes\n",
|
||||
nsamps,
|
||||
nbAnt,
|
||||
num_beams,
|
||||
max_abs,
|
||||
iq8_payload.size(),
|
||||
sampleToByte(nsamps, nbAnt) * (size_t)num_beams);
|
||||
t->iq8_tx_logged = true;
|
||||
}
|
||||
} else if (t->iq8) {
|
||||
t->iq8_raw_blocks++;
|
||||
if (!t->iq8_fallback_logged) {
|
||||
LOG_W(HW,
|
||||
"RFsim IQ8 raw fallback: an IQ component is outside [-128,127] (observed_abs=%d)\n",
|
||||
max_abs);
|
||||
t->iq8_fallback_logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
|
||||
buffer_t *b = &t->buf[i];
|
||||
|
||||
if (b->conn_sock >= 0) {
|
||||
samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beams_to_beam_map(tx_beams)};
|
||||
samplesBlockHeader_t header = {(uint32_t)nsamps,
|
||||
(uint32_t)nbAnt,
|
||||
(uint64_t)timestamp,
|
||||
0,
|
||||
use_iq8 ? RFSIM_OPTION_IQ8 : 0,
|
||||
beams_to_beam_map(tx_beams)};
|
||||
fullwrite(b->conn_sock, &header, sizeof(header), t);
|
||||
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[indices[beam]][a];
|
||||
fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t);
|
||||
if (use_iq8) {
|
||||
fullwrite(b->conn_sock, iq8_payload.data(), iq8_payload.size(), t);
|
||||
} else {
|
||||
for (int beam = 0; beam < num_beams; beam++) {
|
||||
for (int a = 0; a < nbAnt; a++) {
|
||||
sample_t *in = (sample_t *)samplesVoid[indices[beam]][a];
|
||||
fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1106,6 +1204,9 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_
|
||||
{
|
||||
b->headerMode = false; // We got the header
|
||||
AssertFatal(b->th.nbAnt != 0, "Number of antennas not set\n");
|
||||
AssertFatal(b->th.option_flag == 0 || b->th.option_flag == RFSIM_OPTION_IQ8,
|
||||
"Unsupported RFsim sample encoding flag 0x%08x\n",
|
||||
b->th.option_flag);
|
||||
if (b->nbAnt != b->th.nbAnt) {
|
||||
LOG_A(HW, "RFsim: Number of antennas changed from %d to %d\n", b->nbAnt, b->th.nbAnt);
|
||||
b->nbAnt = b->th.nbAnt;
|
||||
@@ -1132,7 +1233,9 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_
|
||||
b->packet_ptr = static_cast<rfsim_packet_t *>(calloc_or_fail(1, payload_sz + sizeof(samplesBlockHeader_t)));
|
||||
b->packet_ptr->header = b->th;
|
||||
b->transferPtr = b->packet_ptr->payload;
|
||||
b->remainToTransfer = payload_sz;
|
||||
b->payload_sz = payload_sz;
|
||||
b->packet_iq8 = b->th.option_flag == RFSIM_OPTION_IQ8;
|
||||
b->remainToTransfer = b->packet_iq8 ? payload_sz / 2 : payload_sz;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1274,6 +1377,17 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time)
|
||||
b->remainToTransfer = sizeof(samplesBlockHeader_t);
|
||||
|
||||
if (!b->trashingPacket) {
|
||||
if (b->packet_iq8) {
|
||||
const size_t complex_samples = b->payload_sz / sizeof(sample_t);
|
||||
unpack_iq8_block_in_place(b->packet_ptr->payload, complex_samples);
|
||||
if (!t->iq8_rx_logged) {
|
||||
LOG_I(HW,
|
||||
"RFsim IQ8 first received block: wire=%zu/raw=%zu bytes, restored exactly to c16\n",
|
||||
b->payload_sz / 2,
|
||||
b->payload_sz);
|
||||
t->iq8_rx_logged = true;
|
||||
}
|
||||
}
|
||||
b->lastReceivedTS = b->th.timestamp + b->th.size;
|
||||
LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS);
|
||||
b->received_packets.emplace(b->packet_ptr);
|
||||
@@ -1281,6 +1395,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time)
|
||||
free(b->packet_ptr);
|
||||
}
|
||||
b->packet_ptr = NULL;
|
||||
b->packet_iq8 = false;
|
||||
b->trashingPacket = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user