diff --git a/lib/src/phy/phch/test/CMakeLists.txt b/lib/src/phy/phch/test/CMakeLists.txt index cd65b1ddb..8ce5df6dd 100644 --- a/lib/src/phy/phch/test/CMakeLists.txt +++ b/lib/src/phy/phch/test/CMakeLists.txt @@ -629,6 +629,10 @@ if(RF_FOUND) target_link_libraries(prach_test_usrp srsran_rf srsran_phy pthread) endif(RF_FOUND) +add_executable(prach_nr_test_perf EXCLUDE_FROM_ALL prach_nr_test_perf.c) +target_link_libraries(prach_nr_test_perf srsran_phy) +# this is just for performance evaluation, not for unit testing + ######################################################################## # NR ######################################################################## diff --git a/lib/src/phy/phch/test/prach_nr_test_perf.c b/lib/src/phy/phch/test/prach_nr_test_perf.c new file mode 100644 index 000000000..2339d8a2c --- /dev/null +++ b/lib/src/phy/phch/test/prach_nr_test_perf.c @@ -0,0 +1,245 @@ +/** + * \copyright Copyright 2013-2021 Software Radio Systems Limited + * + * \copyright 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. + * + */ + +/** + * \file prach_nr_test_perf.c + * \brief Performance test for PRACH NR. + * + * This program simulates several PRACH preamble transmissions (so far, burst format 0 only) + * to estimate the probability of detection and of false alarm. The probability of detection + * is the conditional probability of detecting the preamble when the preamble is present. + * An error consists in detecting no preambles, detecting only preambles different from the + * reference one, or detecting the correct preamble with a timing error beyond tolerance. + * The probability of false alarm is the probability of detecting any preamble when input + * is only noise. + * + * The simulation setup can be controlled by means of the following arguments. + * - -N num: sets the number of experiments to \c num. + * - -n num: sets the total number of UL PRBs to \c num. + * - -f num: sets the preamble format to \c num (for now, format 0 only). + * - -s val: sets the nominal SNR to \c val dB. + * - -v : activates verbose output. + * + * Example: + * \code{.cpp} + * prach_nr_test_perf -n 52 -s -14.6 + * \endcode + * + * \todo Restricted preamble formats not implemented yet. Fading channel and SIMO. + */ + +#include +#include +#include +#include +#include + +#include "srsran/srsran.h" + +#define MAX_LEN 70176 + +static uint32_t nof_prb = 52; +static uint32_t config_idx = 0; +static int nof_runs = 100; +static float snr_dB = -14.5F; +static bool is_verbose = false; + +static void usage(char* prog) +{ + printf("Usage: %s\n", prog); + printf("\t-N Number of experiments [Default %d]\n", nof_runs); + printf("\t-n Uplink number of PRB [Default %d]\n", nof_prb); + printf("\t-f Preamble format [Default %d]\n", config_idx); + printf("\t-s SNR in dB [Default %.2f]\n", snr_dB); + printf("\t-v Activate verbose output [Default %s]\n", is_verbose ? "true" : "false"); +} + +static void parse_args(int argc, char** argv) +{ + int opt = 0; + while ((opt = getopt(argc, argv, "N:n:f:s:v")) != -1) { + switch (opt) { + case 'N': + nof_runs = (int)strtol(optarg, NULL, 10); + break; + case 'n': + nof_prb = (uint32_t)strtol(optarg, NULL, 10); + break; + case 'f': + config_idx = (uint32_t)strtol(optarg, NULL, 10); + break; + case 's': + snr_dB = strtof(optarg, NULL); + break; + case 'v': + is_verbose = true; + break; + default: + usage(argv[0]); + exit(-1); + } + } +} + +int main(int argc, char** argv) +{ + parse_args(argc, argv); + if (config_idx != 0) { + ERROR("Preamble format not yet implemented"); + return SRSRAN_ERROR; + } + srsran_prach_t prach; + + const int fft_size = srsran_symbol_sz(nof_prb); + const float main_scs_kHz = 15; // UL subcarrier spacing (i.e., Delta f) + const float sampling_time_us = 1000.0F / (main_scs_kHz * (float)fft_size); + const int slot_length = 15 * fft_size; // number of samples in a slot + + if (srsran_prach_init(&prach, fft_size)) { + ERROR("Initializing PRACH"); + srsran_prach_free(&prach); + return SRSRAN_ERROR; + } + + cf_t preamble[MAX_LEN]; + memset(preamble, 0, sizeof(cf_t) * MAX_LEN); + + srsran_prach_cfg_t prach_cfg; + ZERO_OBJECT(prach_cfg); + + // Setup according to TS38.104 Section 8.4 + prach_cfg.is_nr = true; + prach_cfg.config_idx = 0; // preamble format 0 + prach_cfg.hs_flag = false; // no high speed + prach_cfg.freq_offset = 0; + prach_cfg.root_seq_idx = 22; // logical (root sequence) index i + prach_cfg.zero_corr_zone = 1; // zero correlation zone -> implies Ncs = 13 + prach_cfg.num_ra_preambles = 0; // use default + const uint32_t seq_index = 32; // sequence index "v" + const float prach_scs_kHz = 1.25F; // PRACH subcarrier spacing (i.e., Delta f^RA) + const float max_time_error_us = 1.04F; // time error tolerance + const int nof_offset_steps = 10; + + if (srsran_prach_set_cfg(&prach, &prach_cfg, nof_prb)) { + ERROR("Error initiating PRACH object"); + srsran_prach_free(&prach); + return SRSRAN_ERROR; + } + + if (srsran_prach_gen(&prach, seq_index, 0, preamble) < SRSRAN_SUCCESS) { + ERROR("Generating PRACH preamble"); + srsran_prach_free(&prach); + return SRSRAN_ERROR; + } + + const uint32_t preamble_length = prach.N_seq; + const uint32_t ZC_length = prach.N_zc; // Zadoff-Chu sequence length (i.e., L_RA) + const float base_time_offset_us = (float)prach.N_cs * 1000 / (2.0F * (float)ZC_length * prach_scs_kHz); + + float prach_pwr = srsran_vec_avg_power_cf(preamble, preamble_length); + srsran_vec_sc_prod_cfc(preamble, 1.0F / sqrtf(prach_pwr), preamble, preamble_length); + + int vector_length = 2 * slot_length; + cf_t symbols[vector_length]; + cf_t noise_vec[vector_length]; + + uint32_t indices[64] = {0}; + float offset_est[64] = {0}; + uint32_t n_indices = 0; + + float time_offset_us = 0; + int offset_samples = 0; + float noise_var = srsran_convert_dB_to_power(-snr_dB); + int ok_detection = 0; + int missed_detection = 0; + int false_detection_signal_tmp = 0; + int false_detection_signal = 0; + int false_detection_noise = 0; + int offset_est_error = 0; + + int step = SRSRAN_MAX(nof_runs / 100, 1); + for (int i_run = 0; i_run < nof_runs; i_run++) { + // show we are doing something + if (i_run % (20 * step) == 0) { + printf("\n"); + } + if (i_run % step == 0) { + printf("*"); + fflush(stdout); + } + + srsran_vec_cf_zero(noise_vec, vector_length); + srsran_ch_awgn_c(noise_vec, noise_vec, noise_var, vector_length); + if (is_verbose) { + prach_pwr = srsran_vec_avg_power_cf(preamble, preamble_length); + float noise_pwr = srsran_vec_avg_power_cf(noise_vec, vector_length); + printf(" Tx power: %.3f\n", prach_pwr); + printf(" Noise power: %.3f\n", noise_pwr); + printf(" Target/measured SNR: %.3f / %.3f dB\n", snr_dB, srsran_convert_power_to_dB(prach_pwr / noise_pwr)); + } + for (int i = 0; i < nof_offset_steps; i++) { + time_offset_us = base_time_offset_us + (float)i * 0.1F; + offset_samples = (int)roundf(time_offset_us / sampling_time_us); + srsran_vec_cf_copy(symbols, noise_vec, vector_length); + for (int j = 0; j < preamble_length; j++) { + symbols[j + offset_samples] += preamble[j]; + } + + srsran_prach_detect_offset(&prach, 0, &symbols[prach.N_cp], slot_length, indices, offset_est, NULL, &n_indices); + false_detection_signal_tmp = 0; + for (int j = 0; j < n_indices; j++) { + if (indices[j] != seq_index) { + false_detection_signal_tmp++; + } else if (fabsf(offset_est[j] * 1.0e6F - time_offset_us) > max_time_error_us) { + offset_est_error++; + } else { + ok_detection++; + } + } + false_detection_signal += (n_indices > 1 || false_detection_signal_tmp == 1); + // Missed detection if no preamble was detected or no detected preamble is the right one + missed_detection += (n_indices == 0 || n_indices == false_detection_signal_tmp); + } + + srsran_prach_detect_offset(&prach, 0, &noise_vec[prach.N_cp], slot_length, indices, offset_est, NULL, &n_indices); + false_detection_noise += (n_indices > 0); + } + int total_runs = nof_offset_steps * nof_runs; + if (missed_detection + ok_detection + offset_est_error != total_runs) { + srsran_prach_free(&prach); + ERROR("Counting detection errors"); + return SRSRAN_ERROR; + } + + printf("\n\nPRACH performance test: format 0, %d PRB, AWGN channel, SNR=%.1f dB\n", nof_prb, snr_dB); + printf("\nMissed detection probability: %.3e (%d out of %d)\n", + (float)missed_detection / (float)total_runs, + missed_detection, + total_runs); + printf("Probability of timing error: %.3e (%d out of %d)\n", + (float)offset_est_error / (float)total_runs, + offset_est_error, + total_runs); + printf("Probability of OK detection: %.3e (%d out of %d)\n", + (float)ok_detection / (float)total_runs, + ok_detection, + total_runs); + printf("\nProbability of false detection with preamble: %.3e (%d out of %d)\n", + (float)false_detection_signal / (float)total_runs, + false_detection_signal, + total_runs); + printf("Probability of false detection without preamble: %.3e (%d out of %d)\n", + (float)false_detection_noise / (float)nof_runs, + false_detection_noise, + nof_runs); + + srsran_prach_free(&prach); + + printf("Done\n"); +}