mirror of https://github.com/pvnis/srsRAN_4G.git
Initial UE synchronization for NR
parent
4d13906760
commit
eb7980f2b3
@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \section COPYRIGHT
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 Software Radio Systems Limited
|
||||||
|
*
|
||||||
|
* By using this file, you agree to the terms and conditions set
|
||||||
|
* forth in the LICENSE file which can be found at the top level of
|
||||||
|
* the distribution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SRSRAN_UE_SYNC_NR_H
|
||||||
|
#define SRSRAN_UE_SYNC_NR_H
|
||||||
|
|
||||||
|
#include "srsran/phy/common/timestamp.h"
|
||||||
|
#include "srsran/phy/sync/ssb.h"
|
||||||
|
|
||||||
|
#define SRSRAN_RECV_CALLBACK_TEMPLATE(NAME) int (*NAME)(void*, cf_t**, uint32_t, srsran_timestamp_t*)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes NR UE synchronization object internal states
|
||||||
|
*/
|
||||||
|
typedef enum SRSRAN_API {
|
||||||
|
SRSRAN_UE_SYNC_NR_STATE_IDLE = 0, ///< Initial state, the object has no configuration
|
||||||
|
SRSRAN_UE_SYNC_NR_STATE_FIND, ///< State just after configuring, baseband is not aligned
|
||||||
|
SRSRAN_UE_SYNC_NR_STATE_TRACK ///< Baseband is aligned with subframes
|
||||||
|
} srsran_ue_sync_nr_state_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes a UE sync NR object arguments
|
||||||
|
*/
|
||||||
|
typedef struct SRSRAN_API {
|
||||||
|
// Memory allocation constraints
|
||||||
|
double max_srate_hz; ///< Maximum sampling rate in Hz, set to zero to use default
|
||||||
|
srsran_subcarrier_spacing_t min_scs; ///< Minimum subcarrier spacing
|
||||||
|
uint32_t nof_rx_channels; ///< Number of receive channels, set to 0 for 1
|
||||||
|
|
||||||
|
// Enable/Disable features
|
||||||
|
bool disable_cfo; ///< Set to true for disabling the CFO compensation close loop
|
||||||
|
|
||||||
|
// Signal detection thresholds and averaging coefficients
|
||||||
|
float pbch_dmrs_thr; ///< NR-PBCH DMRS threshold for blind decoding, set to 0 for default
|
||||||
|
float cfo_alpha; ///< Exponential Moving Average (EMA) alpha coefficient for CFO
|
||||||
|
|
||||||
|
// Receive callback
|
||||||
|
void* recv_obj; ///< Receive object
|
||||||
|
SRSRAN_RECV_CALLBACK_TEMPLATE(recv_callback); ///< Receive callback
|
||||||
|
} srsran_ue_sync_nr_args_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes a UE sync NR object configuration
|
||||||
|
*/
|
||||||
|
typedef struct SRSRAN_API {
|
||||||
|
srsran_ssb_cfg_t ssb; ///< SSB configuration
|
||||||
|
uint32_t N_id; ///< Physicall cell identifier
|
||||||
|
} srsran_ue_sync_nr_cfg_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes a UE sync NR object
|
||||||
|
*/
|
||||||
|
typedef struct SRSRAN_API {
|
||||||
|
// State
|
||||||
|
srsran_ue_sync_nr_state_t state; ///< Internal state
|
||||||
|
int32_t next_rf_sample_offset; ///< Next RF sample offset
|
||||||
|
uint32_t ssb_idx; ///< Tracking SSB candidate index
|
||||||
|
uint32_t sf_idx; ///< Current subframe index (0-9)
|
||||||
|
uint32_t sfn; ///< Current system frame number (0-1023)
|
||||||
|
srsran_csi_trs_measurements_t feedback; ///< Feedback measurements
|
||||||
|
|
||||||
|
// Components
|
||||||
|
srsran_ssb_t ssb; ///< SSB internal object
|
||||||
|
cf_t** tmp_buffer; ///< Temporal buffer pointers
|
||||||
|
|
||||||
|
// Initialised arguments
|
||||||
|
uint32_t nof_rx_channels; ///< Number of receive channels
|
||||||
|
bool disable_cfo; ///< Set to true for disabling the CFO compensation close loop
|
||||||
|
float cfo_alpha; ///< Exponential Moving Average (EMA) alpha coefficient for CFO
|
||||||
|
void* recv_obj; ///< Receive object
|
||||||
|
SRSRAN_RECV_CALLBACK_TEMPLATE(recv_callback); ///< Receive callback
|
||||||
|
|
||||||
|
// Current configuration
|
||||||
|
uint32_t N_id; ///< Current physical cell identifier
|
||||||
|
double srate_hz; ///< Current sampling rate in Hz
|
||||||
|
uint32_t sf_sz; ///< Current subframe size
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
float cfo_hz; ///< Current CFO in Hz
|
||||||
|
float avg_delay_us; ///< Current average delay
|
||||||
|
} srsran_ue_sync_nr_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes a UE sync NR zerocopy outcome
|
||||||
|
*/
|
||||||
|
typedef struct SRSRAN_API {
|
||||||
|
bool in_sync; ///< Indicates whether the received baseband is synchronized
|
||||||
|
uint32_t sf_idx; ///< Subframe index
|
||||||
|
uint32_t sfn; ///< System Frame Number
|
||||||
|
srsran_timestamp_t timestamp; ///< Last received timestamp
|
||||||
|
float cfo_hz; ///< Current CFO in Hz
|
||||||
|
float delay_us; ///< Current average delay in microseconds
|
||||||
|
} srsran_ue_sync_nr_outcome_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialises a UE sync NR object
|
||||||
|
* @param q NR UE synchronization object
|
||||||
|
* @param[in] args NR UE synchronization initialization arguments
|
||||||
|
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
|
||||||
|
*/
|
||||||
|
SRSRAN_API int srsran_ue_sync_nr_init(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_args_t* args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deallocate an NR UE synchronization object
|
||||||
|
* @param q NR UE synchronization object
|
||||||
|
*/
|
||||||
|
SRSRAN_API void srsran_ue_sync_nr_free(srsran_ue_sync_nr_t* q);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures a UE sync NR object
|
||||||
|
* @param q NR UE synchronization object
|
||||||
|
* @param[in] cfg NR UE synchronization configuration
|
||||||
|
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
|
||||||
|
*/
|
||||||
|
SRSRAN_API int srsran_ue_sync_nr_set_cfg(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_cfg_t* cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runs the NR UE synchronization object, tries to find and track the configured SSB leaving in buffer the
|
||||||
|
* received baseband subframe
|
||||||
|
* @param q NR UE synchronization object
|
||||||
|
* @param buffer 2D complex buffer
|
||||||
|
* @param outcome zerocopy outcome
|
||||||
|
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
|
||||||
|
*/
|
||||||
|
SRSRAN_API int srsran_ue_sync_nr_zerocopy(srsran_ue_sync_nr_t* q, cf_t** buffer, srsran_ue_sync_nr_outcome_t* outcome);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Feedback Channel State Information from Tracking Reference Signals into a UE synchronization object
|
||||||
|
* @param q NR UE synchronization object
|
||||||
|
* @param measurements CSI-TRS feedback measurement
|
||||||
|
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
|
||||||
|
*/
|
||||||
|
SRSRAN_API int srsran_ue_sync_nr_feedback(srsran_ue_sync_nr_t* q, const srsran_csi_trs_measurements_t* measurements);
|
||||||
|
|
||||||
|
#endif // SRSRAN_UE_SYNC_NR_H
|
@ -0,0 +1,304 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \section COPYRIGHT
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 Software Radio Systems Limited
|
||||||
|
*
|
||||||
|
* By using this file, you agree to the terms and conditions set
|
||||||
|
* forth in the LICENSE file which can be found at the top level of
|
||||||
|
* the distribution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "srsran/common/test_common.h"
|
||||||
|
#include "srsran/phy/channel/ch_awgn.h"
|
||||||
|
#include "srsran/phy/channel/delay.h"
|
||||||
|
#include "srsran/phy/ue/ue_sync_nr.h"
|
||||||
|
#include "srsran/phy/utils/debug.h"
|
||||||
|
#include "srsran/phy/utils/ringbuffer.h"
|
||||||
|
#include "srsran/phy/utils/vector.h"
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// NR parameters
|
||||||
|
static uint32_t pci = 1; // Physical Cell Identifier
|
||||||
|
static uint32_t carrier_nof_prb = 52; // Carrier bandwidth
|
||||||
|
static srsran_subcarrier_spacing_t carrier_scs = srsran_subcarrier_spacing_15kHz;
|
||||||
|
static srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_30kHz;
|
||||||
|
|
||||||
|
// Test and channel parameters
|
||||||
|
static uint32_t nof_sf = 1000; // Number of subframes to test
|
||||||
|
static float cfo_hz = 100.0f; // CFO in Hz
|
||||||
|
static float n0_dB = -10.0f; // Noise floor in dB relative to full-scale
|
||||||
|
static float delay_min_us = 10.0f; // Minimum dynamic delay in microseconds
|
||||||
|
static float delay_max_us = 1000.0f; // Maximum dynamic delay in microseconds
|
||||||
|
static float delay_period_s = 60.0f; // Delay period in seconds
|
||||||
|
|
||||||
|
// Test context
|
||||||
|
static double srate_hz = 0.0f; // Base-band sampling rate
|
||||||
|
static uint32_t sf_len = 0; // Subframe length
|
||||||
|
static cf_t* buffer = NULL; // Base-band buffer
|
||||||
|
static cf_t* buffer2 = NULL; // Base-band buffer
|
||||||
|
|
||||||
|
static void usage(char* prog)
|
||||||
|
{
|
||||||
|
printf("Usage: %s [v]\n", prog);
|
||||||
|
printf("\t-v [set srsran_verbose to debug, default none]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_args(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt(argc, argv, "v")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'v':
|
||||||
|
srsran_verbose++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t sf_idx;
|
||||||
|
uint32_t sfn;
|
||||||
|
srsran_ringbuffer_t ringbuffer;
|
||||||
|
srsran_ssb_t ssb;
|
||||||
|
srsran_timestamp_t timestamp;
|
||||||
|
srsran_channel_awgn_t awgn;
|
||||||
|
srsran_channel_delay_t delay;
|
||||||
|
} test_context_t;
|
||||||
|
|
||||||
|
static void run_channel(test_context_t* ctx)
|
||||||
|
{
|
||||||
|
// Delay
|
||||||
|
srsran_channel_delay_execute(&ctx->delay, buffer, buffer2, sf_len, &ctx->timestamp);
|
||||||
|
|
||||||
|
// CFO
|
||||||
|
srsran_vec_apply_cfo(buffer2, -cfo_hz / srate_hz, buffer, sf_len);
|
||||||
|
|
||||||
|
// AWGN
|
||||||
|
srsran_channel_awgn_run_c(&ctx->awgn, buffer, buffer, sf_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_context_init(test_context_t* ctx)
|
||||||
|
{
|
||||||
|
SRSRAN_MEM_ZERO(ctx, test_context_t, 1);
|
||||||
|
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->sfn = 1;
|
||||||
|
|
||||||
|
if (srsran_ringbuffer_init(&ctx->ringbuffer, (int)(10 * sf_len * sizeof(cf_t))) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_ssb_args_t ssb_args = {};
|
||||||
|
ssb_args.max_srate_hz = srate_hz;
|
||||||
|
ssb_args.min_scs = carrier_scs;
|
||||||
|
ssb_args.enable_encode = true;
|
||||||
|
if (srsran_ssb_init(&ctx->ssb, &ssb_args) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_ssb_cfg_t ssb_cfg = {};
|
||||||
|
ssb_cfg.srate_hz = srate_hz;
|
||||||
|
ssb_cfg.srate_hz = srate_hz;
|
||||||
|
ssb_cfg.center_freq_hz = 3.5e9;
|
||||||
|
ssb_cfg.ssb_freq_hz = 3.5e9 - 960e3;
|
||||||
|
ssb_cfg.scs = ssb_scs;
|
||||||
|
ssb_cfg.pattern = SRSRAN_SSB_PATTERN_C;
|
||||||
|
if (srsran_ssb_set_cfg(&ctx->ssb, &ssb_cfg) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_channel_delay_init(&ctx->delay, delay_min_us, delay_max_us, delay_period_s, 0, (uint32_t)srate_hz) <
|
||||||
|
SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Init");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_channel_awgn_init(&ctx->awgn, 0x0) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Init");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_channel_awgn_set_n0(&ctx->awgn, n0_dB) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Init");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_context_free(test_context_t* ctx)
|
||||||
|
{
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_ringbuffer_free(&ctx->ringbuffer);
|
||||||
|
srsran_ssb_free(&ctx->ssb);
|
||||||
|
srsran_channel_delay_free(&ctx->delay);
|
||||||
|
srsran_channel_awgn_free(&ctx->awgn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int recv_callback(void* ptr, cf_t** rx_buffer, uint32_t nof_samples, srsran_timestamp_t* timestamp)
|
||||||
|
{
|
||||||
|
test_context_t* ctx = (test_context_t*)ptr;
|
||||||
|
|
||||||
|
// Check inputs
|
||||||
|
if (ctx == NULL || rx_buffer == NULL || rx_buffer[0] == NULL) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the number of required bytes
|
||||||
|
int required_nbytes = (int)sizeof(cf_t) * nof_samples;
|
||||||
|
|
||||||
|
// Execute subframe until the ringbuffer has data
|
||||||
|
while (srsran_ringbuffer_status(&ctx->ringbuffer) < required_nbytes) {
|
||||||
|
// Reset buffer
|
||||||
|
srsran_vec_cf_zero(buffer, sf_len);
|
||||||
|
|
||||||
|
if (ctx->sf_idx % (SRSRAN_NOF_SF_X_FRAME / 2) == 0) {
|
||||||
|
// Prepare PBCH message
|
||||||
|
srsran_pbch_msg_nr_t pbch_msg = {};
|
||||||
|
pbch_msg.ssb_idx = 0;
|
||||||
|
pbch_msg.hrf = ctx->sf_idx >= (SRSRAN_NOF_SF_X_FRAME / 2);
|
||||||
|
pbch_msg.sfn_4lsb = ctx->sfn & 0b1111U;
|
||||||
|
|
||||||
|
// Encode SSB
|
||||||
|
if (srsran_ssb_add(&ctx->ssb, pci, &pbch_msg, buffer, buffer) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run channel
|
||||||
|
run_channel(ctx);
|
||||||
|
|
||||||
|
// Write in the ring buffer
|
||||||
|
if (srsran_ringbuffer_write(&ctx->ringbuffer, buffer, (int)sf_len * sizeof(cf_t)) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment subframe index
|
||||||
|
ctx->sf_idx++;
|
||||||
|
|
||||||
|
// Increment SFN if required
|
||||||
|
if (ctx->sf_idx >= SRSRAN_NOF_SF_X_FRAME) {
|
||||||
|
ctx->sfn = (ctx->sfn + 1) % 1024U;
|
||||||
|
ctx->sf_idx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_vec_cf_zero(buffer, sf_len);
|
||||||
|
|
||||||
|
// Read ringbuffer
|
||||||
|
if (srsran_ringbuffer_read(&ctx->ringbuffer, rx_buffer[0], required_nbytes) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup timestamp
|
||||||
|
*timestamp = ctx->timestamp;
|
||||||
|
|
||||||
|
// Advance timestamp
|
||||||
|
srsran_timestamp_add(&ctx->timestamp, 0, (float)(nof_samples / srate_hz));
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_case_1(srsran_ue_sync_nr_t* ue_sync)
|
||||||
|
{
|
||||||
|
for (uint32_t sf_idx = 0; sf_idx < nof_sf; sf_idx++) {
|
||||||
|
srsran_ue_sync_nr_outcome_t outcome = {};
|
||||||
|
TESTASSERT(srsran_ue_sync_nr_zerocopy(ue_sync, &buffer, &outcome) == SRSRAN_SUCCESS);
|
||||||
|
|
||||||
|
// Print outcome
|
||||||
|
INFO("measure - zerocpy in-sync=%s sf_idx=%d sfn=%d timestamp=%f cfo_hz=%+.1f delay_us=%+.3f",
|
||||||
|
outcome.in_sync ? "y" : "n",
|
||||||
|
outcome.sf_idx,
|
||||||
|
outcome.sfn,
|
||||||
|
srsran_timestamp_real(&outcome.timestamp),
|
||||||
|
outcome.cfo_hz,
|
||||||
|
outcome.delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
parse_args(argc, argv);
|
||||||
|
|
||||||
|
srate_hz = (double)SRSRAN_SUBC_SPACING_NR(carrier_scs) * srsran_min_symbol_sz_rb(carrier_nof_prb);
|
||||||
|
sf_len = (uint32_t)ceil(srate_hz / 1000.0);
|
||||||
|
buffer = srsran_vec_cf_malloc(sf_len);
|
||||||
|
buffer2 = srsran_vec_cf_malloc(sf_len);
|
||||||
|
|
||||||
|
test_context_t ctx = {};
|
||||||
|
srsran_ue_sync_nr_t ue_sync = {};
|
||||||
|
|
||||||
|
if (buffer == NULL) {
|
||||||
|
ERROR("Malloc");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer2 == NULL) {
|
||||||
|
ERROR("Malloc");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_ue_sync_nr_args_t ue_sync_args = {};
|
||||||
|
ue_sync_args.max_srate_hz = srate_hz;
|
||||||
|
ue_sync_args.min_scs = carrier_scs;
|
||||||
|
ue_sync_args.recv_obj = &ctx;
|
||||||
|
ue_sync_args.recv_callback = &recv_callback;
|
||||||
|
ue_sync_args.disable_cfo = false;
|
||||||
|
if (srsran_ue_sync_nr_init(&ue_sync, &ue_sync_args) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Init");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_ue_sync_nr_cfg_t ue_sync_cfg = {};
|
||||||
|
ue_sync_cfg.ssb.srate_hz = srate_hz;
|
||||||
|
ue_sync_cfg.ssb.center_freq_hz = 3.5e9;
|
||||||
|
ue_sync_cfg.ssb.ssb_freq_hz = 3.5e9 - 960e3;
|
||||||
|
ue_sync_cfg.ssb.scs = ssb_scs;
|
||||||
|
ue_sync_cfg.ssb.pattern = SRSRAN_SSB_PATTERN_C;
|
||||||
|
ue_sync_cfg.N_id = pci;
|
||||||
|
if (srsran_ue_sync_nr_set_cfg(&ue_sync, &ue_sync_cfg) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Init");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test_context_init(&ctx) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Init");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test_case_1(&ue_sync) != SRSRAN_SUCCESS) {
|
||||||
|
ERROR("test case failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
srsran_ue_sync_nr_free(&ue_sync);
|
||||||
|
|
||||||
|
if (buffer) {
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer2) {
|
||||||
|
free(buffer2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_context_free(&ctx);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
@ -0,0 +1,314 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \section COPYRIGHT
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 Software Radio Systems Limited
|
||||||
|
*
|
||||||
|
* By using this file, you agree to the terms and conditions set
|
||||||
|
* forth in the LICENSE file which can be found at the top level of
|
||||||
|
* the distribution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "srsran/phy/ue/ue_sync_nr.h"
|
||||||
|
#include "srsran/phy/utils/vector.h"
|
||||||
|
|
||||||
|
#define UE_SYNC_NR_DEFAULT_CFO_ALPHA 0.1
|
||||||
|
|
||||||
|
int srsran_ue_sync_nr_init(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_args_t* args)
|
||||||
|
{
|
||||||
|
// Check inputs
|
||||||
|
if (q == NULL || args == NULL) {
|
||||||
|
return SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy arguments
|
||||||
|
q->recv_obj = args->recv_obj;
|
||||||
|
q->recv_callback = args->recv_callback;
|
||||||
|
q->nof_rx_channels = args->nof_rx_channels == 0 ? 1 : args->nof_rx_channels;
|
||||||
|
q->disable_cfo = args->disable_cfo;
|
||||||
|
q->cfo_alpha = isnormal(args->cfo_alpha) ? args->cfo_alpha : UE_SYNC_NR_DEFAULT_CFO_ALPHA;
|
||||||
|
|
||||||
|
// Initialise SSB
|
||||||
|
srsran_ssb_args_t ssb_args = {};
|
||||||
|
ssb_args.max_srate_hz = args->max_srate_hz;
|
||||||
|
ssb_args.min_scs = args->min_scs;
|
||||||
|
ssb_args.enable_search = true;
|
||||||
|
ssb_args.enable_decode = true;
|
||||||
|
ssb_args.pbch_dmrs_thr = args->pbch_dmrs_thr;
|
||||||
|
if (srsran_ssb_init(&q->ssb, &ssb_args) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error SSB init");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate temporal buffer pointers
|
||||||
|
q->tmp_buffer = SRSRAN_MEM_ALLOC(cf_t*, q->nof_rx_channels);
|
||||||
|
if (q->tmp_buffer == NULL) {
|
||||||
|
ERROR("Error alloc");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void srsran_ue_sync_nr_free(srsran_ue_sync_nr_t* q)
|
||||||
|
{
|
||||||
|
// Check inputs
|
||||||
|
if (q == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_ssb_free(&q->ssb);
|
||||||
|
|
||||||
|
if (q->tmp_buffer) {
|
||||||
|
free(q->tmp_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
SRSRAN_MEM_ZERO(q, srsran_ue_sync_nr_t, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int srsran_ue_sync_nr_set_cfg(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_cfg_t* cfg)
|
||||||
|
{
|
||||||
|
// Check inputs
|
||||||
|
if (q == NULL || cfg == NULL) {
|
||||||
|
return SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy parameters
|
||||||
|
q->N_id = cfg->N_id;
|
||||||
|
q->srate_hz = cfg->ssb.srate_hz;
|
||||||
|
|
||||||
|
// Calculate new subframe size
|
||||||
|
q->sf_sz = (uint32_t)round(1e-3 * q->srate_hz);
|
||||||
|
|
||||||
|
// Configure SSB
|
||||||
|
if (srsran_ssb_set_cfg(&q->ssb, &cfg->ssb) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error configuring SSB");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition to find
|
||||||
|
q->state = SRSRAN_UE_SYNC_NR_STATE_FIND;
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ue_sync_nr_reset_feedback(srsran_ue_sync_nr_t* q)
|
||||||
|
{
|
||||||
|
SRSRAN_MEM_ZERO(&q->feedback, srsran_csi_trs_measurements_t, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ue_sync_nr_apply_feedback(srsran_ue_sync_nr_t* q)
|
||||||
|
{
|
||||||
|
// Skip any update if there is no feedback available
|
||||||
|
if (q->feedback.nof_re == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update number of samples
|
||||||
|
q->avg_delay_us = q->feedback.delay_us;
|
||||||
|
q->next_rf_sample_offset = (uint32_t)round((double)q->avg_delay_us * (q->srate_hz * 1e-6));
|
||||||
|
|
||||||
|
// Integrate CFO
|
||||||
|
if (q->disable_cfo) {
|
||||||
|
q->cfo_hz = SRSRAN_VEC_SAFE_EMA(q->feedback.cfo_hz, q->cfo_hz, q->cfo_alpha);
|
||||||
|
} else {
|
||||||
|
q->cfo_hz += q->feedback.cfo_hz * q->cfo_alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset feedback
|
||||||
|
ue_sync_nr_reset_feedback(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ue_sync_nr_run_find(srsran_ue_sync_nr_t* q, cf_t* buffer)
|
||||||
|
{
|
||||||
|
srsran_csi_trs_measurements_t measurements = {};
|
||||||
|
srsran_pbch_msg_nr_t pbch_msg = {};
|
||||||
|
|
||||||
|
// Find SSB, measure PSS/SSS and decode PBCH
|
||||||
|
if (srsran_ssb_find(&q->ssb, buffer, q->N_id, &measurements, &pbch_msg) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error finding SSB");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the PBCH message was NOT decoded, early return
|
||||||
|
if (!pbch_msg.crc) {
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset feedback to prevent any previous erroneous measurement
|
||||||
|
ue_sync_nr_reset_feedback(q);
|
||||||
|
|
||||||
|
// Set feedback measurement
|
||||||
|
srsran_combine_csi_trs_measurements(&q->feedback, &measurements, &q->feedback);
|
||||||
|
|
||||||
|
// Apply feedback
|
||||||
|
ue_sync_nr_apply_feedback(q);
|
||||||
|
|
||||||
|
// Setup context
|
||||||
|
q->ssb_idx = pbch_msg.ssb_idx;
|
||||||
|
q->sf_idx = srsran_ssb_candidate_sf_idx(&q->ssb, pbch_msg.ssb_idx, pbch_msg.hrf);
|
||||||
|
q->sfn = pbch_msg.sfn_4lsb;
|
||||||
|
|
||||||
|
// Transition to track only if the measured delay is below 2.4 microseconds
|
||||||
|
if (measurements.delay_us < 2.4f) {
|
||||||
|
q->state = SRSRAN_UE_SYNC_NR_STATE_TRACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ue_sync_nr_run_track(srsran_ue_sync_nr_t* q, cf_t* buffer)
|
||||||
|
{
|
||||||
|
srsran_csi_trs_measurements_t measurements = {};
|
||||||
|
srsran_pbch_msg_nr_t pbch_msg = {};
|
||||||
|
uint32_t half_frame = q->sf_idx / (SRSRAN_NOF_SF_X_FRAME / 2);
|
||||||
|
|
||||||
|
// Check if the SSB selected candidate index shall be received in this subframe
|
||||||
|
bool is_ssb_opportunity = (q->sf_idx == srsran_ssb_candidate_sf_idx(&q->ssb, q->ssb_idx, half_frame > 0));
|
||||||
|
|
||||||
|
// If
|
||||||
|
if (is_ssb_opportunity) {
|
||||||
|
// Measure PSS/SSS and decode PBCH
|
||||||
|
if (srsran_ssb_track(&q->ssb, buffer, q->N_id, q->ssb_idx, half_frame, &measurements, &pbch_msg) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error finding SSB");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the PBCH message was NOT decoded, transition to track
|
||||||
|
if (!pbch_msg.crc) {
|
||||||
|
q->state = SRSRAN_UE_SYNC_NR_STATE_FIND;
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise feedback measurements and apply
|
||||||
|
srsran_combine_csi_trs_measurements(&q->feedback, &measurements, &q->feedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply accumulated feedback
|
||||||
|
ue_sync_nr_apply_feedback(q);
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ue_sync_nr_recv(srsran_ue_sync_nr_t* q, cf_t** buffer, srsran_timestamp_t* timestamp)
|
||||||
|
{
|
||||||
|
// Verify callback and srate are valid
|
||||||
|
if (q->recv_callback == NULL && !isnormal(q->srate_hz)) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t buffer_offset = 0;
|
||||||
|
uint32_t nof_samples = q->sf_sz;
|
||||||
|
|
||||||
|
if (q->next_rf_sample_offset > 0) {
|
||||||
|
// Discard a number of samples from RF
|
||||||
|
if (q->recv_callback(q->recv_obj, buffer, (uint32_t)q->next_rf_sample_offset, timestamp) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Adjust receive buffer
|
||||||
|
buffer_offset = (uint32_t)(-q->next_rf_sample_offset);
|
||||||
|
nof_samples = (uint32_t)(q->sf_sz + q->next_rf_sample_offset);
|
||||||
|
}
|
||||||
|
q->next_rf_sample_offset = 0;
|
||||||
|
|
||||||
|
// Select buffer offsets
|
||||||
|
for (uint32_t chan = 0; chan < q->nof_rx_channels; chan++) {
|
||||||
|
// Set buffer to NULL if not present
|
||||||
|
if (buffer[chan] == NULL) {
|
||||||
|
q->tmp_buffer[chan] = NULL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise first offset samples to zero
|
||||||
|
if (buffer_offset > 0) {
|
||||||
|
srsran_vec_cf_zero(buffer[chan], buffer_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to sample index
|
||||||
|
q->tmp_buffer[chan] = &buffer[chan][buffer_offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive
|
||||||
|
if (q->recv_callback(q->recv_obj, q->tmp_buffer, nof_samples, timestamp) < SRSRAN_SUCCESS) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compensate CFO
|
||||||
|
for (uint32_t chan = 0; chan < q->nof_rx_channels; chan++) {
|
||||||
|
if (buffer[chan] != 0 && !q->disable_cfo) {
|
||||||
|
srsran_vec_apply_cfo(buffer[chan], q->cfo_hz / q->srate_hz, buffer[chan], (int)q->sf_sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int srsran_ue_sync_nr_zerocopy(srsran_ue_sync_nr_t* q, cf_t** buffer, srsran_ue_sync_nr_outcome_t* outcome)
|
||||||
|
{
|
||||||
|
// Check inputs
|
||||||
|
if (q == NULL || buffer == NULL || outcome == NULL) {
|
||||||
|
return SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify callback is valid
|
||||||
|
if (q->recv_callback == NULL) {
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive
|
||||||
|
if (ue_sync_nr_recv(q, buffer, &outcome->timestamp) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error receiving baseband");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run FSM
|
||||||
|
switch (q->state) {
|
||||||
|
case SRSRAN_UE_SYNC_NR_STATE_IDLE:
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
case SRSRAN_UE_SYNC_NR_STATE_FIND:
|
||||||
|
if (ue_sync_nr_run_find(q, buffer[0]) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error running find");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SRSRAN_UE_SYNC_NR_STATE_TRACK:
|
||||||
|
if (ue_sync_nr_run_track(q, buffer[0]) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error running track");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment subframe counter
|
||||||
|
q->sf_idx++;
|
||||||
|
|
||||||
|
// Increment SFN
|
||||||
|
if (q->sf_idx >= SRSRAN_NOF_SF_X_FRAME) {
|
||||||
|
q->sfn = (q->sfn + 1) % 1024;
|
||||||
|
q->sf_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill outcome
|
||||||
|
outcome->in_sync = (q->state == SRSRAN_UE_SYNC_NR_STATE_TRACK);
|
||||||
|
outcome->sf_idx = q->sf_idx;
|
||||||
|
outcome->sfn = q->sfn;
|
||||||
|
outcome->cfo_hz = q->cfo_hz;
|
||||||
|
outcome->delay_us = q->avg_delay_us;
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int srsran_ue_sync_nr_feedback(srsran_ue_sync_nr_t* q, const srsran_csi_trs_measurements_t* measurements)
|
||||||
|
{
|
||||||
|
if (q == NULL || measurements == NULL) {
|
||||||
|
return SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate feedback proportional to the number of elements provided by the measurement
|
||||||
|
srsran_combine_csi_trs_measurements(&q->feedback, measurements, &q->feedback);
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
Loading…
Reference in New Issue