This commit is contained in:
2026-09-19 23:28:08 +09:00
parent c50fe1af53
commit 67717ed5c1
43 changed files with 3145 additions and 294 deletions
+22
View File
@@ -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
View File
@@ -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;
}
}