srsLTE/lib/src/phy/phch/prach.c

1069 lines
34 KiB
C

/**
* Copyright 2013-2022 Software Radio Systems Limited
*
* This file is part of srsRAN.
*
* srsRAN is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsRAN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include <assert.h>
#include <math.h>
#include <string.h>
#include "srsran/phy/common/phy_common.h"
#include "srsran/phy/common/phy_common_nr.h"
#include "srsran/phy/phch/prach.h"
#include "srsran/phy/utils/debug.h"
#include "srsran/phy/utils/vector.h"
#include "prach_tables.h"
// PRACH detection threshold is PRACH_DETECT_FACTOR*average
#define PRACH_DETECT_FACTOR 18
#define SUCCESSIVE_CANCELLATION_ITS 4
#define N_SEQS 64 // Number of prach sequences available
#define N_RB_SC 12 // Number of subcarriers per resource block
#define DELTA_F 15000 // Normal subcarrier spacing
#define DELTA_F_RA 1250 // PRACH subcarrier spacing
#define DELTA_F_RA_4 7500 // PRACH subcarrier spacing for format 4
#define PHI 7 // PRACH phi parameter
#define PHI_4 2 // PRACH phi parameter for format 4
#define MAX_ROOTS 838 // Max number of root sequences
//#define PRACH_CANCELLATION_HARD
#define PRACH_AMP 1.0
// Comment following line for disabling complex exponential look-up table
#define PRACH_USE_CEXP_LUT
#ifdef PRACH_USE_CEXP_LUT
// Define read-only complex exponential tables for two possibles size of sequences, common for all possible PRACH
// objects
#define PRACH_N_ZC_LONG_LUT_SIZE (2 * SRSRAN_PRACH_N_ZC_LONG)
static cf_t cexp_table_long[PRACH_N_ZC_LONG_LUT_SIZE] = {};
#define PRACH_N_ZC_SHORT_LUT_SIZE (2 * SRSRAN_PRACH_N_ZC_SHORT)
static cf_t cexp_table_short[PRACH_N_ZC_SHORT_LUT_SIZE] = {};
// Use constructor attribute for writing complex exponential tables
__attribute__((constructor)) static void prach_cexp_init()
{
for (uint32_t i = 0; i < PRACH_N_ZC_LONG_LUT_SIZE; i++) {
cexp_table_long[i] = cexpf(-I * 2.0f * M_PI * (float)i / (float)PRACH_N_ZC_LONG_LUT_SIZE);
}
for (uint32_t i = 0; i < PRACH_N_ZC_SHORT_LUT_SIZE; i++) {
cexp_table_short[i] = cexpf(-I * 2.0f * M_PI * (float)i / (float)PRACH_N_ZC_SHORT_LUT_SIZE);
}
}
#endif // PRACH_USE_CEXP_LUT
// Generate ZC sequence using either look-up tables or conventional cexp function
static void prach_cexp(uint32_t N_zc, uint32_t u, cf_t* root)
{
#ifdef PRACH_USE_CEXP_LUT
// Use long N_zc table (839 length)
if (N_zc == SRSRAN_PRACH_N_ZC_LONG) {
for (int j = 0; j < SRSRAN_PRACH_N_ZC_LONG; j++) {
uint32_t phase_idx = u * j * (j + 1);
root[j] = cexp_table_long[phase_idx % PRACH_N_ZC_LONG_LUT_SIZE];
}
return;
}
// Use short N_zc table (139 length)
if (N_zc == SRSRAN_PRACH_N_ZC_SHORT) {
for (int j = 0; j < SRSRAN_PRACH_N_ZC_SHORT; j++) {
uint32_t phase_idx = u * j * (j + 1);
root[j] = cexp_table_short[phase_idx % PRACH_N_ZC_SHORT_LUT_SIZE];
}
return;
}
#endif // PRACH_USE_CEXP_LUT
// If the N_zc does not match any of the tables, use conventional exponential function
for (int j = 0; j < N_zc; j++) {
double phase = -M_PI * u * j * (j + 1) / N_zc;
root[j] = cexp(phase * I);
}
}
int srsran_prach_set_cell_(srsran_prach_t* p,
uint32_t N_ifft_ul,
srsran_prach_cfg_t* cfg,
srsran_tdd_config_t* tdd_config);
uint32_t srsran_prach_get_preamble_format(uint32_t config_idx)
{
return config_idx / 16;
}
srsran_prach_sfn_t srsran_prach_get_sfn(uint32_t config_idx)
{
if ((config_idx % 16) < 3 || (config_idx % 16) == 15) {
return SRSRAN_PRACH_SFN_EVEN;
} else {
return SRSRAN_PRACH_SFN_ANY;
}
}
/* Returns true if current_tti is a valid opportunity for PRACH transmission and the is an allowed subframe,
* or allowed_subframe == -1
*/
bool srsran_prach_tti_opportunity(srsran_prach_t* p, uint32_t current_tti, int allowed_subframe)
{
if (p == NULL) {
return false;
}
uint32_t config_idx = p->config_idx;
if (p->tdd_config.configured) {
if (p->is_nr) {
return srsran_prach_nr_tti_opportunity_fr1_unpaired(p->config_idx, current_tti);
} else {
return srsran_prach_tti_opportunity_config_tdd(
config_idx, p->tdd_config.sf_config, current_tti, &p->current_prach_idx);
}
} else {
if (p->is_nr) {
return srsran_prach_nr_tti_opportunity_fr1_paired(p->config_idx, current_tti);
} else {
return srsran_prach_tti_opportunity_config_fdd(config_idx, current_tti, allowed_subframe);
}
}
}
bool srsran_prach_tti_opportunity_config_fdd(uint32_t config_idx, uint32_t current_tti, int allowed_subframe)
{
// Get SFN and sf_idx from the PRACH configuration index
srsran_prach_sfn_t prach_sfn = srsran_prach_get_sfn(config_idx);
// This is the only option which provides always an opportunity for PRACH transmission.
if (config_idx == 14) {
return true;
}
if ((prach_sfn == SRSRAN_PRACH_SFN_EVEN && ((current_tti / 10) % 2) == 0) || prach_sfn == SRSRAN_PRACH_SFN_ANY) {
srsran_prach_sf_config_t sf_config;
srsran_prach_sf_config(config_idx, &sf_config);
for (int i = 0; i < sf_config.nof_sf; i++) {
if (((current_tti % 10) == sf_config.sf[i] && allowed_subframe == -1) ||
((current_tti % 10) == sf_config.sf[i] && (current_tti % 10) == allowed_subframe)) {
return true;
}
}
}
return false;
}
bool srsran_prach_in_window_config_fdd(uint32_t config_idx, uint32_t current_tti, int allowed_subframe)
{
if (srsran_prach_tti_opportunity_config_fdd(config_idx, current_tti, allowed_subframe)) {
return true;
}
uint32_t preamble_format = srsran_prach_get_preamble_format(config_idx);
float T_tot = (prach_Tseq[preamble_format] + prach_Tcp[preamble_format]) * SRSRAN_LTE_TS;
uint32_t tti_dur = (uint32_t)ceilf(T_tot * 1000);
for (uint32_t i = 1; i < tti_dur; ++i) {
if (srsran_prach_tti_opportunity_config_fdd(config_idx, current_tti - i, allowed_subframe)) {
return true;
}
}
return false;
}
uint32_t srsran_prach_nof_f_idx_tdd(uint32_t config_idx, uint32_t tdd_ul_dl_config)
{
if (config_idx < 64 && tdd_ul_dl_config < 7) {
return prach_tdd_loc_table[config_idx][tdd_ul_dl_config].nof_elems;
} else {
ERROR("PRACH: Invalid parmeters config_idx=%d, tdd_ul_config=%d", config_idx, tdd_ul_dl_config);
return 0;
}
}
uint32_t srsran_prach_f_id_tdd(uint32_t config_idx, uint32_t tdd_ul_dl_config, uint32_t prach_idx)
{
if (config_idx < 64 && tdd_ul_dl_config < 7) {
return prach_tdd_loc_table[config_idx][tdd_ul_dl_config].elems[prach_idx].f;
} else {
ERROR("PRACH: Invalid parmeters config_idx=%d, tdd_ul_config=%d", config_idx, tdd_ul_dl_config);
return 0;
}
}
uint32_t srsran_prach_f_ra_tdd(uint32_t config_idx,
uint32_t tdd_ul_dl_config,
uint32_t current_tti,
uint32_t prach_idx,
uint32_t prach_offset,
uint32_t n_rb_ul)
{
if (config_idx >= 64 || tdd_ul_dl_config >= 7) {
ERROR("PRACH: Invalid parameters config_idx=%d, tdd_ul_config=%d", config_idx, tdd_ul_dl_config);
return 0;
}
uint32_t f_ra = prach_tdd_loc_table[config_idx][tdd_ul_dl_config].elems[prach_idx].f;
if (config_idx < 48) {
if ((f_ra % 2) == 0) {
return prach_offset + 6 * (f_ra / 2);
} else {
return n_rb_ul - 6 - prach_offset + 6 * (f_ra / 2);
}
} else {
uint32_t N_sp;
if (tdd_ul_dl_config >= 3 && tdd_ul_dl_config <= 5) {
N_sp = 1;
} else {
N_sp = 2;
}
uint32_t t1 = prach_tdd_loc_table[config_idx][tdd_ul_dl_config].elems[prach_idx].t1;
uint32_t sfn = current_tti / 10;
if ((((sfn % 2) * (2 - N_sp) + t1) % 2) == 0) {
return 6 * f_ra;
} else {
return n_rb_ul - 6 * (f_ra + 1);
}
}
}
bool srsran_prach_tti_opportunity_config_tdd(uint32_t config_idx,
uint32_t tdd_ul_dl_config,
uint32_t current_tti,
uint32_t* prach_idx)
{
if (config_idx >= 64 || tdd_ul_dl_config >= 7) {
ERROR("PRACH: Invalid parameters config_idx=%d, tdd_ul_config=%d", config_idx, tdd_ul_dl_config);
return 0;
}
uint32_t nof_elems = prach_tdd_loc_table[config_idx][tdd_ul_dl_config].nof_elems;
// Table 5.7.1-4 allocates in time then in frequency
for (uint32_t i = 0; i < nof_elems; i++) {
uint32_t t0 = prach_tdd_loc_table[config_idx][tdd_ul_dl_config].elems[i].t0;
uint32_t t1 = prach_tdd_loc_table[config_idx][tdd_ul_dl_config].elems[i].t1;
uint32_t t2 = prach_tdd_loc_table[config_idx][tdd_ul_dl_config].elems[i].t2;
uint32_t sfn = current_tti / 10;
uint32_t sf_idx = current_tti % 10;
if (((sfn % 2) && t0 == 2) || (!(sfn % 2) && t0 == 1) || (t0 == 0)) {
if ((sf_idx < 5 && t1 == 0) || (sf_idx >= 5 && t1 == 1)) {
if (config_idx < 48) { // format 0 to 3
if ((sf_idx) % 5 == (t2 + 2)) {
if (prach_idx) {
*prach_idx = i;
}
return true;
}
} else {
// Only UpTs subframes
srsran_tdd_config_t c = {tdd_ul_dl_config, 0, true};
if (srsran_sfidx_tdd_type(c, sf_idx) == SRSRAN_TDD_SF_S) {
if (prach_idx) {
*prach_idx = i;
}
return true;
}
}
}
}
}
return false;
}
void srsran_prach_sf_config(uint32_t config_idx, srsran_prach_sf_config_t* sf_config)
{
memcpy(sf_config, &prach_sf_config[config_idx % 16], sizeof(srsran_prach_sf_config_t));
}
const prach_nr_config_t* srsran_prach_nr_get_cfg_fr1_paired(uint32_t config_idx)
{
if (config_idx < PRACH_NR_CFG_FR1_PAIRED_NOF_CFG) {
return &prach_nr_cfg_fr1_paired[config_idx];
}
ERROR("Invalid configuration index %d", config_idx);
return NULL;
}
bool srsran_prach_nr_tti_opportunity_fr1_paired(uint32_t config_idx, uint32_t current_tti)
{
uint32_t sfn = current_tti / SRSRAN_NOF_SF_X_FRAME;
uint32_t sf_idx = current_tti % SRSRAN_NOF_SF_X_FRAME;
// Get configuration
const prach_nr_config_t* cfg = srsran_prach_nr_get_cfg_fr1_paired(config_idx);
if (cfg == NULL) {
return false;
}
// Protect zero division
if (cfg->x == 0) {
ERROR("Invalid Zero value");
return false;
}
// Check for System Frame Number match
if (sfn % cfg->x != cfg->y) {
return false;
}
// Protect subframe number vector access
if (cfg->nof_subframe_number > PRACH_NR_CFG_MAX_NOF_SF) {
ERROR("Invalid number of subframes (%d)", cfg->nof_subframe_number);
return false;
}
// Check for subframe number match
for (uint32_t i = 0; i < cfg->nof_subframe_number; i++) {
if (cfg->subframe_number[i] == sf_idx) {
return true;
}
}
// If reached here, no opportunity
return false;
}
uint32_t srsran_prach_nr_start_symbol_fr1_paired(uint32_t config_idx)
{
// Get configuration
const prach_nr_config_t* cfg = srsran_prach_nr_get_cfg_fr1_paired(config_idx);
if (cfg == NULL) {
return 0;
}
return cfg->starting_symbol;
}
const prach_nr_config_t* srsran_prach_nr_get_cfg_fr1_unpaired(uint32_t config_idx)
{
if (config_idx < PRACH_NR_CFG_FR1_UNPAIRED_NOF_CFG) {
return &prach_nr_cfg_fr1_unpaired[config_idx];
}
ERROR("Invalid configuration index %d", config_idx);
return NULL;
}
bool srsran_prach_nr_tti_opportunity_fr1_unpaired(uint32_t config_idx, uint32_t current_tti)
{
uint32_t sfn = current_tti / SRSRAN_NOF_SF_X_FRAME;
uint32_t sf_idx = current_tti % SRSRAN_NOF_SF_X_FRAME;
// Get configuration
const prach_nr_config_t* cfg = srsran_prach_nr_get_cfg_fr1_unpaired(config_idx);
if (cfg == NULL) {
return false;
}
// Protect zero division
if (cfg->x == 0) {
ERROR("Invalid Zero value");
return false;
}
// Check for System Frame Number match
if (sfn % cfg->x != cfg->y) {
return false;
}
// Protect subframe number vector access
if (cfg->nof_subframe_number > PRACH_NR_CFG_MAX_NOF_SF) {
ERROR("Invalid number of subframes (%d)", cfg->nof_subframe_number);
return false;
}
// Check for subframe number match
for (uint32_t i = 0; i < cfg->nof_subframe_number; i++) {
if (cfg->subframe_number[i] == sf_idx) {
return true;
}
}
// If reached here, no opportunity
return false;
}
uint32_t srsran_prach_nr_start_symbol(uint32_t config_idx, srsran_duplex_mode_t duplex_mode)
{
const prach_nr_config_t* cfg;
if (duplex_mode == SRSRAN_DUPLEX_MODE_TDD) {
// Get configuration
cfg = srsran_prach_nr_get_cfg_fr1_unpaired(config_idx);
if (cfg == NULL) {
return 0;
}
} else {
// Get configuration
cfg = srsran_prach_nr_get_cfg_fr1_paired(config_idx);
if (cfg == NULL) {
return 0;
}
}
return cfg->starting_symbol;
}
// For debug use only
void print(void* d, uint32_t size, uint32_t len, char* file_str)
{
FILE* f;
f = fopen(file_str, "wb");
fwrite(d, size, len, f);
fclose(f);
}
/// Calculates the FFT of the specified sequence index if not previously done and returns a pointer to the result.
static cf_t* get_precoded_dft(srsran_prach_t* p, uint32_t idx)
{
assert(idx < 64 && "Invalid idx value");
// Generate FFT for this sequence if it does not exist yet.
uint64_t gen_mask = (uint64_t)1 << idx;
if (!(p->dft_gen_bitmap & gen_mask)) {
srsran_dft_run(&p->zc_fft, p->seqs[idx], p->dft_seqs[idx]);
p->dft_gen_bitmap |= gen_mask;
}
return p->dft_seqs[idx];
}
int srsran_prach_gen_seqs(srsran_prach_t* p)
{
uint32_t u = 0;
uint32_t v = 1;
int v_max = 0;
uint32_t p_ = 0;
uint32_t d_u = 0;
uint32_t d_start = 0;
uint32_t N_shift = 0;
int N_neg_shift = 0;
uint32_t N_group = 0;
uint32_t C_v = 0;
cf_t root[839];
// Generate our 64 preamble sequences
for (int i = 0; i < N_SEQS; i++) {
if (v > v_max) {
// Get a new root sequence
if (4 == p->f) {
u = prach_zc_roots_format4[(p->rsi + p->N_roots) % 138];
} else {
u = prach_zc_roots[(p->rsi + p->N_roots) % 838];
}
// Generate actual sequence
prach_cexp(p->N_zc, u, root);
p->root_seqs_idx[p->N_roots++] = i;
// Determine v_max
if (p->hs) {
// High-speed cell
for (p_ = 1; p_ <= p->N_zc; p_++) {
if (((p_ * u) % p->N_zc) == 1)
break;
}
if (p_ < p->N_zc / 2) {
d_u = p_;
} else {
d_u = p->N_zc - p_;
}
if (d_u >= p->N_cs && d_u < p->N_zc / 3) {
N_shift = d_u / p->N_cs;
d_start = 2 * d_u + N_shift * p->N_cs;
N_group = p->N_zc / d_start;
if (p->N_zc > 2 * d_u + N_group * d_start) {
N_neg_shift = (p->N_zc - 2 * d_u - N_group * d_start) / p->N_cs;
} else {
N_neg_shift = 0;
}
} else if (p->N_zc / 3 <= d_u && d_u <= (p->N_zc - p->N_cs) / 2) {
N_shift = (p->N_zc - 2 * d_u) / p->N_cs;
d_start = p->N_zc - 2 * d_u + N_shift * p->N_cs;
N_group = d_u / d_start;
if (d_u > N_group * d_start) {
N_neg_shift = (d_u - N_group * d_start) / p->N_cs;
} else {
N_neg_shift = 0;
}
if (N_neg_shift > N_shift)
N_neg_shift = N_shift;
} else {
N_shift = 0;
}
v_max = N_shift * N_group + N_neg_shift - 1;
if (v_max < 0) {
v_max = 0;
}
} else {
// Normal cell
if (0 == p->N_cs) {
v_max = 0;
} else {
v_max = (p->N_zc / p->N_cs) - 1;
}
}
v = 0;
}
// Shift root and add to set
if (p->hs) {
if (N_shift == 0) {
C_v = 0;
} else {
C_v = d_start * floor(v / N_shift) + (v % N_shift) * p->N_cs;
}
} else {
C_v = v * p->N_cs;
}
// Copy shifted sequence, equivalent to:
// for (int j = 0; j < p->N_zc; j++) {
// p->seqs[i][j] = root[(j + C_v) % p->N_zc];
// }
srsran_vec_cf_copy(p->seqs[i], &root[C_v], p->N_zc - C_v);
srsran_vec_cf_copy(&p->seqs[i][p->N_zc - C_v], root, C_v);
v++;
}
return 0;
}
int srsran_prach_set_cfg(srsran_prach_t* p, srsran_prach_cfg_t* cfg, uint32_t nof_prb)
{
return srsran_prach_set_cell_(p, srsran_symbol_sz(nof_prb), cfg, &cfg->tdd_config);
}
int srsran_prach_init(srsran_prach_t* p, uint32_t max_N_ifft_ul)
{
int ret = SRSRAN_ERROR;
if (p != NULL && max_N_ifft_ul < 2049) {
bzero(p, sizeof(srsran_prach_t));
p->max_N_ifft_ul = max_N_ifft_ul;
// Set up containers
p->prach_bins = srsran_vec_cf_malloc(SRSRAN_PRACH_N_ZC_LONG);
p->corr_spec = srsran_vec_cf_malloc(SRSRAN_PRACH_N_ZC_LONG);
p->corr = srsran_vec_f_malloc(SRSRAN_PRACH_N_ZC_LONG);
p->cross = srsran_vec_cf_malloc(SRSRAN_PRACH_N_ZC_LONG);
p->corr_freq = srsran_vec_cf_malloc(SRSRAN_PRACH_N_ZC_LONG);
// Set up ZC FFTS
if (srsran_dft_plan(&p->zc_fft, SRSRAN_PRACH_N_ZC_LONG, SRSRAN_DFT_FORWARD, SRSRAN_DFT_COMPLEX)) {
return SRSRAN_ERROR;
}
srsran_dft_plan_set_mirror(&p->zc_fft, false);
srsran_dft_plan_set_norm(&p->zc_fft, true);
if (srsran_dft_plan(&p->zc_ifft, SRSRAN_PRACH_N_ZC_LONG, SRSRAN_DFT_BACKWARD, SRSRAN_DFT_COMPLEX)) {
return SRSRAN_ERROR;
}
srsran_dft_plan_set_mirror(&p->zc_ifft, false);
srsran_dft_plan_set_norm(&p->zc_ifft, false);
uint32_t fft_size_alloc = max_N_ifft_ul * DELTA_F / DELTA_F_RA;
p->ifft_in = srsran_vec_cf_malloc(fft_size_alloc);
p->ifft_out = srsran_vec_cf_malloc(fft_size_alloc);
if (srsran_dft_plan(&p->ifft, fft_size_alloc, SRSRAN_DFT_BACKWARD, SRSRAN_DFT_COMPLEX)) {
ERROR("Error creating DFT plan");
return -1;
}
srsran_dft_plan_set_mirror(&p->ifft, true);
srsran_dft_plan_set_norm(&p->ifft, true);
if (srsran_dft_plan(&p->fft, fft_size_alloc, SRSRAN_DFT_FORWARD, SRSRAN_DFT_COMPLEX)) {
ERROR("Error creating DFT plan");
return -1;
}
p->signal_fft = srsran_vec_cf_malloc(fft_size_alloc);
if (!p->signal_fft) {
ERROR("Error allocating memory");
return -1;
}
srsran_dft_plan_set_mirror(&p->fft, true);
srsran_dft_plan_set_norm(&p->fft, true);
ret = SRSRAN_SUCCESS;
} else {
ERROR("Invalid parameters");
}
return ret;
}
int srsran_prach_set_cell_(srsran_prach_t* p,
uint32_t N_ifft_ul,
srsran_prach_cfg_t* cfg,
srsran_tdd_config_t* tdd_config)
{
int ret = SRSRAN_ERROR;
if (p != NULL && N_ifft_ul < 2049 && cfg->config_idx < 64 && cfg->root_seq_idx < MAX_ROOTS) {
if (N_ifft_ul > p->max_N_ifft_ul) {
ERROR("PRACH: Error in set_cell(): N_ifft_ul (%d) must be lower or equal max_N_ifft_ul (%d) in init()",
N_ifft_ul,
p->max_N_ifft_ul);
return -1;
}
uint32_t preamble_format = srsran_prach_get_preamble_format(cfg->config_idx);
p->is_nr = cfg->is_nr;
p->config_idx = cfg->config_idx;
p->f = preamble_format;
p->rsi = cfg->root_seq_idx;
p->hs = cfg->hs_flag;
p->zczc = cfg->zero_corr_zone;
p->detect_factor = PRACH_DETECT_FACTOR;
p->num_ra_preambles = cfg->num_ra_preambles;
p->successive_cancellation = cfg->enable_successive_cancellation;
p->dft_gen_bitmap = 0;
if (p->successive_cancellation && cfg->zero_corr_zone != 0) {
printf("successive cancellation only currently supported with zero_correlation_zone_config of 0 - disabling\n");
p->successive_cancellation = false;
}
p->freq_domain_offset_calc = cfg->enable_freq_domain_offset_calc;
if (tdd_config) {
p->tdd_config = *tdd_config;
}
// Determine N_zc and N_cs
if (4 == preamble_format) {
if (p->zczc < 7) {
p->N_zc = SRSRAN_PRACH_N_ZC_SHORT;
p->N_cs = prach_Ncs_format4[p->zczc];
} else {
ERROR("Invalid zeroCorrelationZoneConfig=%d for format4", p->zczc);
return SRSRAN_ERROR;
}
} else {
p->N_zc = SRSRAN_PRACH_N_ZC_LONG;
if (p->hs) {
if (p->zczc < 15) {
p->N_cs = prach_Ncs_restricted[p->zczc];
} else {
ERROR("Invalid zeroCorrelationZoneConfig=%d for restricted set", p->zczc);
return SRSRAN_ERROR;
}
} else {
if (p->zczc < 16) {
p->N_cs = prach_Ncs_unrestricted[p->zczc];
} else {
ERROR("Invalid zeroCorrelationZoneConfig=%d", p->zczc);
return SRSRAN_ERROR;
}
}
}
// Set up ZC FFTS
if (p->N_zc != SRSRAN_PRACH_N_ZC_LONG) {
if (srsran_dft_replan(&p->zc_fft, p->N_zc)) {
return SRSRAN_ERROR;
}
if (srsran_dft_replan(&p->zc_ifft, p->N_zc)) {
return SRSRAN_ERROR;
}
}
// Generate our 64 sequences
p->N_roots = 0;
srsran_prach_gen_seqs(p);
// Ensure num_ra_preambles is valid, if not assign default value
if (p->num_ra_preambles < 4 || p->num_ra_preambles > p->N_roots) {
p->num_ra_preambles = p->N_roots;
}
// Create our FFT objects and buffers
p->N_ifft_ul = N_ifft_ul;
if (4 == preamble_format) {
p->N_ifft_prach = p->N_ifft_ul * DELTA_F / DELTA_F_RA_4;
} else {
p->N_ifft_prach = p->N_ifft_ul * DELTA_F / DELTA_F_RA;
}
/* The deadzone specifies the number of samples at the end of the correlation window
* that will be considered as belonging to the next preamble
*/
p->deadzone = 0;
/*
if(p->N_cs != 0) {
float samp_rate=15000*p->N_ifft_ul;
p->deadzone = (uint32_t) ceil((float) samp_rate/((float) p->N_zc*subcarrier_spacing));
}*/
if (srsran_dft_replan(&p->ifft, p->N_ifft_prach)) {
ERROR("Error creating DFT plan");
return -1;
}
if (srsran_dft_replan(&p->fft, p->N_ifft_prach)) {
ERROR("Error creating DFT plan");
return -1;
}
p->N_seq = prach_Tseq[p->f] * p->N_ifft_ul / 2048;
p->N_cp = prach_Tcp[p->f] * p->N_ifft_ul / 2048;
p->T_seq = prach_Tseq[p->f] * SRSRAN_LTE_TS;
p->T_tot = (prach_Tseq[p->f] + prach_Tcp[p->f]) * SRSRAN_LTE_TS;
if (p->successive_cancellation) {
for (int i = 0; i < 64; i++) {
if (!p->td_signals[i]) {
p->td_signals[i] = srsran_vec_malloc(sizeof(cf_t) * (p->N_seq + p->N_cp));
}
}
}
ret = SRSRAN_SUCCESS;
} else {
ERROR("Invalid parameters N_ifft_ul=%d; config_idx=%d; root_seq_idx=%d;",
N_ifft_ul,
cfg->config_idx,
cfg->root_seq_idx);
}
return ret;
}
int srsran_prach_gen(srsran_prach_t* p, uint32_t seq_index, uint32_t freq_offset, cf_t* signal)
{
int ret = SRSRAN_ERROR;
if (p != NULL && seq_index < N_SEQS && signal != NULL) {
// Calculate parameters
uint32_t N_rb_ul = srsran_nof_prb(p->N_ifft_ul);
uint32_t k_0 = freq_offset * N_RB_SC - N_rb_ul * N_RB_SC / 2 + p->N_ifft_ul / 2;
uint32_t K = DELTA_F / DELTA_F_RA;
uint32_t begin = PHI + (K * k_0) + (p->is_nr ? 0 : (K / 2));
if (6 + freq_offset > N_rb_ul) {
ERROR("Error no space for PRACH: frequency offset=%d, N_rb_ul=%d", freq_offset, N_rb_ul);
return ret;
}
DEBUG(
"N_zc: %d, N_cp: %d, N_seq: %d, N_ifft_prach=%d begin: %d", p->N_zc, p->N_cp, p->N_seq, p->N_ifft_prach, begin);
// Fill bottom guard frequency domain with zeros
srsran_vec_cf_zero(p->ifft_in, begin);
// Map dft-precoded sequence to ifft bins
srsran_vec_cf_copy(&p->ifft_in[begin], get_precoded_dft(p, seq_index), p->N_zc);
// Fill top guard frequency domain with zeros
srsran_vec_cf_zero(&p->ifft_in[begin + p->N_zc], p->N_ifft_prach - begin - p->N_zc);
// Generate frequency domain signal
srsran_dft_run(&p->ifft, p->ifft_in, p->ifft_out);
// Copy CP into buffer
memcpy(signal, &p->ifft_out[p->N_ifft_prach - p->N_cp], p->N_cp * sizeof(cf_t));
// Copy preamble sequence into buffer
for (int i = 0; i < p->N_seq; i++) {
signal[p->N_cp + i] = p->ifft_out[i % p->N_ifft_prach];
}
if (p->td_signals[seq_index]) {
memcpy(p->td_signals[seq_index], signal, (p->N_seq + p->N_cp) * sizeof(cf_t));
}
ret = SRSRAN_SUCCESS;
}
return ret;
}
void srsran_prach_set_detect_factor(srsran_prach_t* p, float ratio)
{
p->detect_factor = ratio;
}
int srsran_prach_detect(srsran_prach_t* p,
uint32_t freq_offset,
cf_t* signal,
uint32_t sig_len,
uint32_t* indices,
uint32_t* n_indices)
{
return srsran_prach_detect_offset(p, freq_offset, signal, sig_len, indices, NULL, NULL, n_indices);
}
/// this function subtracts the detected prach preamble from the signal so as to allow for lower power prach signals to
/// be detected more easily in the subsequent searches
void srsran_prach_cancellation(srsran_prach_t* p)
{
srsran_vec_cf_zero(p->sub, p->N_zc * 2);
srsran_vec_cf_copy(p->sub, get_precoded_dft(p, p->root_seqs_idx[p->prach_cancel.idx]), p->N_zc);
srsran_vec_prod_ccc(p->sub, p->prach_cancel.phase_array, p->sub, p->N_zc);
#ifdef PRACH_CANCELLATION_HARD
srsran_vec_prod_conj_ccc(p->prach_bins, p->sub, p->corr_spec, p->N_zc);
srsran_dft_run(&p->zc_ifft, p->corr_spec, p->corr_spec);
srsran_vec_abs_square_cf(p->corr_spec, p->corr, p->N_zc);
prach_cancel->factor = sqrt(p->corr[0] / (p->N_zc * p->N_zc));
#endif
srsran_vec_sc_prod_cfc(p->sub, p->prach_cancel.factor, p->sub, p->N_zc);
srsran_vec_sub_ccc(p->prach_bins, p->sub, p->prach_bins, p->N_zc);
}
// this function checks if we have already detected and stored this particular PRACH index and if so, doesnt store it
// again in the detected prachs array
bool srsran_prach_have_stored(int current_idx, uint32_t* indices, uint32_t n_indices)
{
for (int i = 0; i < n_indices; i++) {
if (indices[i] == current_idx) {
return true;
}
}
return false;
}
// set the offset based on the time domain time offset estimation
float srsran_prach_get_offset_secs(srsran_prach_t* p, int n_win)
{
// takes the offset in samples and converts to time in seconds
return (float)p->peak_offsets[n_win] / (float)(DELTA_F_RA * p->N_zc);
}
// calculates the timing offset of the incoming PRACH by calculating the phase in frequency - alternative to time domain
// approach
float srsran_prach_calculate_time_offset_secs(srsran_prach_t* p, cf_t* cross)
{
// calculate the phase of the cross correlation
float freq_domain_phase = cargf(srsran_vec_acc_cc(cross, p->N_zc));
float ratio = (float)(p->N_ifft_ul * DELTA_F) / (float)(SRSRAN_PRACH_N_ZC_LONG * DELTA_F_RA);
// converting from phase to number of samples
float num_samples = roundf((ratio * freq_domain_phase * p->N_zc) / (2 * M_PI));
// converting to time in seconds
return num_samples / ((float)p->N_ifft_ul * DELTA_F);
}
// calculates the aggregate phase offset of the incomming PRACH signal so it can be applied to the reference signal
// before it is subtracted from the input
void srsran_prach_calculate_correction_array(srsran_prach_t* p, cf_t* corr_freq)
{
srsran_vec_arg_deg_cf(corr_freq, 0, p->phase, p->N_zc);
for (int i = 0; i < p->N_zc; i++) {
p->prach_cancel.phase_array[i] = cexpf(_Complex_I * (p->phase[i] / (180.0f / M_PI)));
}
}
// This function carries out the main processing on the incomming PRACH signal
int srsran_prach_process(srsran_prach_t* p,
cf_t* signal,
uint32_t* indices,
float* t_offsets,
float* peak_to_avg,
uint32_t* n_indices,
int cancellation_idx,
uint32_t begin,
uint32_t sig_len)
{
float max_to_cancel = 0;
cancellation_idx = -1;
int max_idx = 0;
srsran_vec_cf_zero(p->cross, p->N_zc);
srsran_vec_cf_zero(p->corr_freq, p->N_zc);
for (int i = 0; i < p->num_ra_preambles; i++) {
cf_t* root_spec = get_precoded_dft(p, p->root_seqs_idx[i]);
srsran_vec_prod_conj_ccc(p->prach_bins, root_spec, p->corr_spec, p->N_zc);
srsran_vec_prod_conj_ccc(p->corr_spec, &p->corr_spec[1], p->cross, p->N_zc - 1);
if (p->successive_cancellation) {
srsran_vec_cf_copy(p->corr_freq, p->corr_spec, p->N_zc);
}
srsran_dft_run(&p->zc_ifft, p->corr_spec, p->corr_spec);
srsran_vec_abs_square_cf(p->corr_spec, p->corr, p->N_zc);
float corr_ave = srsran_vec_acc_ff(p->corr, p->N_zc) / p->N_zc;
uint32_t winsize = 0;
if (p->N_cs != 0) {
winsize = p->N_cs;
} else {
winsize = p->N_zc;
}
uint32_t n_wins = p->N_zc / winsize;
float max_peak = 0;
for (int j = 0; j < n_wins; j++) {
uint32_t start = (p->N_zc - (j * p->N_cs)) % p->N_zc;
uint32_t end = start + winsize;
if (end > p->deadzone) {
end -= p->deadzone;
}
start += p->deadzone;
p->peak_values[j] = 0;
for (int k = start; k < end; k++) {
if (p->corr[k] > p->peak_values[j]) {
p->peak_values[j] = p->corr[k];
p->peak_offsets[j] = k - start;
if (p->peak_values[j] > max_peak) {
max_peak = p->peak_values[j];
max_idx = k;
}
}
}
}
if (max_peak > (p->detect_factor * corr_ave)) {
for (int j = 0; j < n_wins; j++) {
if (p->peak_values[j] > p->detect_factor * corr_ave) {
if (indices) {
if (p->successive_cancellation) {
if (max_peak > max_to_cancel) {
cancellation_idx = (i * n_wins) + j;
max_to_cancel = max_peak;
p->prach_cancel.idx = cancellation_idx;
p->prach_cancel.factor = (sqrt(max_peak / (p->N_zc * p->N_zc)));
srsran_prach_calculate_correction_array(p, p->corr_freq);
}
if (srsran_prach_have_stored(((i * n_wins) + j), indices, *n_indices)) {
break;
}
}
indices[*n_indices] = (i * n_wins) + j;
}
if (peak_to_avg) {
peak_to_avg[*n_indices] = p->peak_values[j] / corr_ave;
}
if (t_offsets) {
// saves the PRACH offset in seconds to t_offsets, time domain or freq domain base calc
t_offsets[*n_indices] = (p->freq_domain_offset_calc)
? (srsran_prach_calculate_time_offset_secs(p, p->cross))
: (srsran_prach_get_offset_secs(p, j));
}
(*n_indices)++;
}
}
}
}
if (cancellation_idx != -1) {
// if a peak has been found, this applies cancellation, if many found, subtracts strongest
srsran_prach_cancellation(p);
} else {
return 1;
}
return 0;
}
int srsran_prach_detect_offset(srsran_prach_t* p,
uint32_t freq_offset,
cf_t* signal,
uint32_t sig_len,
uint32_t* indices,
float* t_offsets,
float* peak_to_avg,
uint32_t* n_indices)
{
int ret = SRSRAN_ERROR;
if (p != NULL && signal != NULL && sig_len > 0 && indices != NULL) {
if (sig_len < p->N_ifft_prach) {
ERROR("srsran_prach_detect: Signal length is %d and should be %d", sig_len, p->N_ifft_prach);
return SRSRAN_ERROR_INVALID_INPUTS;
}
int cancellation_idx = -2;
bzero(&p->prach_cancel, sizeof(srsran_prach_cancellation_t));
// FFT incoming signal
srsran_dft_run(&p->fft, signal, p->signal_fft);
*n_indices = 0;
// Extract bins of interest
uint32_t N_rb_ul = srsran_nof_prb(p->N_ifft_ul);
uint32_t k_0 = freq_offset * N_RB_SC - N_rb_ul * N_RB_SC / 2 + p->N_ifft_ul / 2;
uint32_t K = DELTA_F / DELTA_F_RA;
uint32_t begin = PHI + (K * k_0) + (p->is_nr ? 0 : (K / 2));
memcpy(p->prach_bins, &p->signal_fft[begin], p->N_zc * sizeof(cf_t));
int loops = (p->successive_cancellation) ? SUCCESSIVE_CANCELLATION_ITS : 1;
// if successive cancellation is enabled, we perform the entire search process p->num_ra_preambles times, removing
// the highest power PRACH preamble each time.
for (int l = 0; l < loops; l++) {
if (srsran_prach_process(
p, signal, indices, t_offsets, peak_to_avg, n_indices, cancellation_idx, begin, sig_len)) {
break;
}
}
ret = SRSRAN_SUCCESS;
}
return ret;
}
int srsran_prach_free(srsran_prach_t* p)
{
free(p->prach_bins);
free(p->corr_spec);
free(p->corr);
srsran_dft_plan_free(&p->ifft);
free(p->ifft_in);
free(p->ifft_out);
free(p->cross);
free(p->corr_freq);
srsran_dft_plan_free(&p->fft);
srsran_dft_plan_free(&p->zc_fft);
srsran_dft_plan_free(&p->zc_ifft);
if (p->signal_fft) {
free(p->signal_fft);
}
for (unsigned int i = 0; i < 64; i++) {
free(p->td_signals[i]);
}
bzero(p, sizeof(srsran_prach_t));
return 0;
}
int srsran_prach_print_seqs(srsran_prach_t* p)
{
for (int i = 0; i < N_SEQS; i++) {
FILE* f;
char str[32];
sprintf(str, "prach_seq_%d.bin", i);
f = fopen(str, "wb");
fwrite(p->seqs[i], sizeof(cf_t), p->N_zc, f);
fclose(f);
}
for (int i = 0; i < N_SEQS; i++) {
FILE* f;
char str[32];
sprintf(str, "prach_dft_seq_%d.bin", i);
f = fopen(str, "wb");
fwrite(p->dft_seqs[i], sizeof(cf_t), p->N_zc, f);
fclose(f);
}
for (int i = 0; i < p->N_roots; i++) {
FILE* f;
char str[32];
sprintf(str, "prach_root_seq_%d.bin", i);
f = fopen(str, "wb");
fwrite(p->seqs[p->root_seqs_idx[i]], sizeof(cf_t), p->N_zc, f);
fclose(f);
}
return 0;
}