From e76e31e6525201f70aabdc02e767a5c869cb542c Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Tue, 7 Jan 2020 21:05:57 +0100 Subject: [PATCH] adding NB-IoT DL shared channel, UE DL object and PHY examples --- lib/examples/CMakeLists.txt | 16 + lib/examples/npdsch_enodeb.c | 730 ++++++++++++ lib/examples/npdsch_ue.c | 999 ++++++++++++++++ lib/examples/test/CMakeLists.txt | 70 ++ lib/examples/test/iqtests.cmake | 28 + lib/include/srslte/phy/phch/npdsch.h | 160 +++ lib/include/srslte/phy/phch/npdsch_cfg.h | 42 + lib/include/srslte/phy/ue/ue_dl_nbiot.h | 190 +++ lib/src/phy/phch/npdsch.c | 797 +++++++++++++ lib/src/phy/phch/test/CMakeLists.txt | 68 ++ lib/src/phy/phch/test/dci_nbiot_test.c | 119 ++ .../phy/phch/test/npdsch_npdcch_file_test.c | 331 ++++++ lib/src/phy/phch/test/npdsch_test.c | 489 ++++++++ lib/src/phy/ue/test/CMakeLists.txt | 4 + lib/src/phy/ue/test/ue_dl_nbiot_test.c | 160 +++ lib/src/phy/ue/ue_dl_nbiot.c | 1054 +++++++++++++++++ 16 files changed, 5257 insertions(+) create mode 100644 lib/examples/npdsch_enodeb.c create mode 100644 lib/examples/npdsch_ue.c create mode 100644 lib/examples/test/CMakeLists.txt create mode 100644 lib/examples/test/iqtests.cmake create mode 100644 lib/include/srslte/phy/phch/npdsch.h create mode 100644 lib/include/srslte/phy/phch/npdsch_cfg.h create mode 100644 lib/include/srslte/phy/ue/ue_dl_nbiot.h create mode 100644 lib/src/phy/phch/npdsch.c create mode 100644 lib/src/phy/phch/test/dci_nbiot_test.c create mode 100644 lib/src/phy/phch/test/npdsch_npdcch_file_test.c create mode 100644 lib/src/phy/phch/test/npdsch_test.c create mode 100644 lib/src/phy/ue/test/ue_dl_nbiot_test.c create mode 100644 lib/src/phy/ue/ue_dl_nbiot.c diff --git a/lib/examples/CMakeLists.txt b/lib/examples/CMakeLists.txt index 337e0e63a..ed7fe8313 100644 --- a/lib/examples/CMakeLists.txt +++ b/lib/examples/CMakeLists.txt @@ -35,6 +35,12 @@ if(RF_FOUND) add_executable(pdsch_enodeb pdsch_enodeb.c) target_link_libraries(pdsch_enodeb srslte_phy srslte_common srslte_rf pthread) + + add_executable(npdsch_enodeb npdsch_enodeb.c) + target_link_libraries(npdsch_enodeb srslte_phy srslte_rf pthread) + + add_executable(npdsch_ue npdsch_ue.c) + target_link_libraries(npdsch_ue srslte_common srslte_phy srslte_rf pthread) else(RF_FOUND) add_definitions(-DDISABLE_RF) @@ -43,11 +49,18 @@ else(RF_FOUND) add_executable(pdsch_enodeb pdsch_enodeb.c) target_link_libraries(pdsch_enodeb srslte_common srslte_phy pthread) + + add_executable(npdsch_enodeb npdsch_enodeb.c) + target_link_libraries(npdsch_enodeb srslte_phy pthread) + + add_executable(npdsch_ue npdsch_ue.c) + target_link_libraries(npdsch_ue srslte_common srslte_phy pthread) endif(RF_FOUND) if(SRSGUI_FOUND) include_directories(${SRSGUI_INCLUDE_DIRS}) target_link_libraries(pdsch_ue ${SRSGUI_LIBRARIES}) + target_link_libraries(npdsch_ue ${SRSGUI_LIBRARIES}) add_definitions(-DENABLE_GUI) endif(SRSGUI_FOUND) @@ -92,3 +105,6 @@ if(RF_FOUND) else(RF_FOUND) message(STATUS " examples will NOT BE INSTALLED.") endif(RF_FOUND) + +# Add eNB/UE IQ test +add_subdirectory(test) diff --git a/lib/examples/npdsch_enodeb.c b/lib/examples/npdsch_enodeb.c new file mode 100644 index 000000000..643125660 --- /dev/null +++ b/lib/examples/npdsch_enodeb.c @@ -0,0 +1,730 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "srslte/phy/ch_estimation/chest_dl_nbiot.h" +#include "srslte/phy/channel/ch_awgn.h" +#include "srslte/phy/io/filesink.h" +#include "srslte/phy/io/filesource.h" +#include "srslte/phy/sync/npss.h" +#include "srslte/phy/sync/nsss.h" +#include "srslte/phy/ue/ue_dl_nbiot.h" +#include "srslte/phy/utils/bit.h" +#include "srslte/phy/utils/random.h" + +#define UE_CRNTI 0x1234 + +#define HAVE_NPDSCH 1 +#define NPDCCH_SF_IDX 1 + +const uint8_t dummy_sib1_payload[] = {0x43, 0x4d, 0xd0, 0x92, 0x22, 0x06, 0x04, 0x30, 0x28, 0x6e, 0x87, 0xd0, 0x4b, + 0x13, 0x90, 0xb4, 0x12, 0xa1, 0x02, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +#ifndef DISABLE_RF +#include "srslte/phy/rf/rf.h" +srslte_rf_t rf; +#else +#warning Compiling npdsch_ue with no RF support +#endif + +char* output_file_name = NULL; + +srslte_nbiot_cell_t cell = { + .base = {.nof_ports = 1, .nof_prb = SRSLTE_NBIOT_DEFAULT_NUM_PRB_BASECELL, .cp = SRSLTE_CP_NORM, .id = 0}, + .nbiot_prb = SRSLTE_NBIOT_DEFAULT_PRB_OFFSET, + .n_id_ncell = 0, + .nof_ports = 1, + .mode = SRSLTE_NBIOT_MODE_STANDALONE}; + +uint32_t i_tbs_val = 1, last_i_tbs_val = 1; +int nof_frames = -1; +uint32_t i_sf_val = 0; +uint32_t i_rep_val = 0; + +char* rf_args = ""; +float rf_amp = 0.8, rf_gain = 70.0, rf_freq = 0; +float snr = -100.0; + +bool null_file_sink = false; +srslte_random_t* random_gen; +srslte_filesink_t fsink; +srslte_ofdm_t ifft; +srslte_npss_synch_t npss_sync; +srslte_nsss_synch_t nsss_sync; +srslte_npbch_t npbch; +srslte_npdcch_t npdcch; +srslte_npdsch_t npdsch; +srslte_npdsch_cfg_t sib1_npdsch_cfg; +srslte_npdsch_cfg_t npdsch_cfg; +srslte_nbiot_ue_dl_t ue_dl; +srslte_softbuffer_tx_t softbuffer; +srslte_ra_nbiot_dl_dci_t ra_dl; +srslte_ra_nbiot_dl_dci_t ra_dl_sib1; +srslte_chest_dl_nbiot_t est; +srslte_mib_nb_t mib_nb; +uint32_t sched_info_tag = 0; // according to Table 16.4.1.3-3 in 36.213, 0 means 4 NPDSCH repetitions with TBS 208 + +cf_t *sf_buffer = NULL, *output_buffer = NULL; +int sf_n_re = 0, sf_n_samples = 0; + +void usage(char* prog) +{ + printf("Usage: %s [agmiftleosncvrpu]\n", prog); +#ifndef DISABLE_RF + printf("\t-a RF args [Default %s]\n", rf_args); + printf("\t-e RF amplitude [Default %.2f]\n", rf_amp); + printf("\t-g RF TX gain [Default %.2f dB]\n", rf_gain); + printf("\t-f RF TX frequency [Default %.1f MHz]\n", rf_freq / 1000000); +#else + printf("\t RF is disabled.\n"); +#endif + printf("\t-o output_file [Default use RF board]\n"); + printf("\t-s SNR-10 (only if output to file) [Default %f]\n", snr); + printf("\t-t Value of schedulingInfoSIB1-NB-r13 [Default %d]\n", sched_info_tag); + printf("\t-m Value of i_tbs (translates to MCS) [Default %d]\n", i_tbs_val); + printf("\t-i Value of i_sf [Default %d]\n", i_sf_val); + printf("\t-r Value of i_rep [Default %d]\n", i_rep_val); + printf("\t-n number of frames [Default %d]\n", nof_frames); + printf("\t-l n_id_ncell [Default %d]\n", cell.n_id_ncell); + printf("\t-p NB-IoT PRB id [Default %d]\n", cell.nbiot_prb); + printf("\t-v [set srslte_verbose to debug, default none]\n"); +} + +void parse_args(int argc, char** argv) +{ + int opt; + while ((opt = getopt(argc, argv, "aglfmiosncrtvpu")) != -1) { + switch (opt) { + case 'a': + rf_args = argv[optind]; + break; + case 'g': + rf_gain = strtof(argv[optind], NULL); + break; + case 'e': + rf_amp = strtof(argv[optind], NULL); + break; + case 'f': + rf_freq = strtof(argv[optind], NULL); + break; + case 'o': + output_file_name = argv[optind]; + break; + case 's': + snr = strtof(argv[optind], NULL); + break; + case 't': + sched_info_tag = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'm': + i_tbs_val = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'i': + i_sf_val = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'r': + i_rep_val = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'n': + nof_frames = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'l': + cell.n_id_ncell = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'p': + cell.nbiot_prb = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'v': + srslte_verbose++; + break; + default: + usage(argv[0]); + exit(-1); + } + } + + if (!output_file_name && rf_freq == 0) { + usage(argv[0]); + printf("\nError! Either RF frequency or output filename need to be specified.\n"); + exit(-1); + } + +#ifdef DISABLE_RF + if (!output_file_name) { + usage(argv[0]); + exit(-1); + } +#endif +} + +void base_init() +{ + // init memory + sf_buffer = srslte_vec_malloc(sizeof(cf_t) * sf_n_re); + if (!sf_buffer) { + perror("malloc"); + exit(-1); + } + output_buffer = srslte_vec_malloc(sizeof(cf_t) * sf_n_samples); + if (!output_buffer) { + perror("malloc"); + exit(-1); + } + // open file or USRP + if (output_file_name) { + if (strcmp(output_file_name, "NULL")) { + if (srslte_filesink_init(&fsink, output_file_name, SRSLTE_COMPLEX_FLOAT_BIN)) { + fprintf(stderr, "Error opening file %s\n", output_file_name); + exit(-1); + } + null_file_sink = false; + } else { + null_file_sink = true; + } + } else { +#ifndef DISABLE_RF + printf("Opening RF device...\n"); + if (srslte_rf_open(&rf, rf_args)) { + fprintf(stderr, "Error opening rf\n"); + exit(-1); + } +#else + printf("Error RF not available. Select an output file\n"); + exit(-1); +#endif + } + + if (srslte_ofdm_tx_init(&ifft, SRSLTE_CP_NORM, sf_buffer, output_buffer, cell.base.nof_prb)) { + fprintf(stderr, "Error creating iFFT object\n"); + exit(-1); + } + srslte_ofdm_set_normalize(&ifft, true); + srslte_ofdm_set_freq_shift(&ifft, -SRSLTE_NBIOT_FREQ_SHIFT_FACTOR); + + if (srslte_npss_synch_init(&npss_sync, sf_n_samples, srslte_symbol_sz(cell.base.nof_prb))) { + fprintf(stderr, "Error initializing NPSS object\n"); + exit(-1); + } + + if (srslte_nsss_synch_init(&nsss_sync, sf_n_samples, srslte_symbol_sz(cell.base.nof_prb))) { + fprintf(stderr, "Error initializing NSSS object\n"); + exit(-1); + } + + if (srslte_npbch_init(&npbch)) { + fprintf(stderr, "Error creating NPBCH object\n"); + exit(-1); + } + if (srslte_npbch_set_cell(&npbch, cell)) { + fprintf(stderr, "Error setting cell in NPBCH object\n"); + exit(-1); + } + + if (srslte_npdcch_init(&npdcch)) { + fprintf(stderr, "Error creating NPDCCH object\n"); + exit(-1); + } + + if (srslte_npdcch_set_cell(&npdcch, cell)) { + fprintf(stderr, "Configuring cell in NPDCCH\n"); + exit(-1); + } + + if (srslte_npdsch_init(&npdsch)) { + fprintf(stderr, "Error creating NPDSCH object\n"); + exit(-1); + } + + if (srslte_npdsch_set_cell(&npdsch, cell)) { + fprintf(stderr, "Configuring cell in NPDSCH\n"); + exit(-1); + } + srslte_npdsch_set_rnti(&npdsch, UE_CRNTI); + + if (srslte_softbuffer_tx_init(&softbuffer, cell.base.nof_prb)) { + fprintf(stderr, "Error initiating soft buffer\n"); + exit(-1); + } + + random_gen = srslte_random_init(time(NULL)); +} + +void base_free() +{ + srslte_random_free(random_gen); + srslte_softbuffer_tx_free(&softbuffer); + srslte_npdsch_free(&npdsch); + srslte_npdcch_free(&npdcch); + srslte_npbch_free(&npbch); + srslte_chest_dl_nbiot_free(&est); + srslte_npss_synch_free(&npss_sync); + srslte_nsss_synch_free(&nsss_sync); + srslte_ofdm_tx_free(&ifft); + + if (sf_buffer) { + free(sf_buffer); + } + if (output_buffer) { + free(output_buffer); + } + if (output_file_name) { + if (!null_file_sink) { + srslte_filesink_free(&fsink); + } + } else { +#ifndef DISABLE_RF + srslte_rf_close(&rf); +#endif + } +} + +bool go_exit = false; +#ifndef DISABLE_RF +void sig_int_handler(int signo) +{ + printf("SIGINT received. Exiting...\n"); + if (signo == SIGINT) { + go_exit = true; + } +} +#endif + +int update_radl() +{ + bzero(&ra_dl_sib1, sizeof(srslte_ra_nbiot_dl_dci_t)); + + // NB-IoT specific fields + ra_dl_sib1.alloc.has_sib1 = true; + ra_dl_sib1.alloc.sched_info_sib1 = mib_nb.sched_info_sib1; + ra_dl_sib1.mcs_idx = 1; + + bzero(&ra_dl, sizeof(srslte_ra_nbiot_dl_dci_t)); + ra_dl.mcs_idx = i_tbs_val; + + // NB-IoT specific fields + ra_dl.format = 1; // FormatN1 DCI + ra_dl.alloc.has_sib1 = false; + ra_dl.alloc.is_ra = false; + ra_dl.alloc.i_delay = 0; + ra_dl.alloc.i_sf = i_sf_val; + ra_dl.alloc.i_rep = i_rep_val; + ra_dl.alloc.harq_ack = 1; + ra_dl.alloc.i_n_start = 0; + + srslte_nbiot_dl_dci_fprint(stdout, &ra_dl); + srslte_ra_nbiot_dl_grant_t dummy_grant; + srslte_ra_nbits_t dummy_nbits; + +#define DUMMY_SFIDX 1 +#define DUMMY_SFN 0 + srslte_ra_nbiot_dl_dci_to_grant(&ra_dl, &dummy_grant, DUMMY_SFN, DUMMY_SFIDX, DUMMY_R_MAX, false, cell.mode); + srslte_ra_nbiot_dl_grant_to_nbits(&dummy_grant, cell, 0, &dummy_nbits); + srslte_ra_nbiot_dl_grant_fprint(stdout, &dummy_grant); + printf("Type new MCS index and press Enter: "); + fflush(stdout); + + return SRSLTE_SUCCESS; +} + +/* Read new MCS from stdin */ +int update_control() +{ + char input[128]; + + fd_set set; + FD_ZERO(&set); + FD_SET(0, &set); + + struct timeval to; + to.tv_sec = 0; + to.tv_usec = 0; + + int n = select(1, &set, NULL, NULL, &to); + if (n == 1) { + // stdin ready + if (fgets(input, sizeof(input), stdin)) { + last_i_tbs_val = i_tbs_val; + i_tbs_val = atoi(input); + bzero(input, sizeof(input)); + if (update_radl()) { + printf("Trying with last known MCS index\n"); + i_tbs_val = last_i_tbs_val; + return update_radl(); + } + } + return 0; + } else if (n < 0) { + // error + perror("select"); + return -1; + } else { + return 0; + } +} + +#define DATA_BUFF_SZ 1024 * 128 +uint8_t data[8 * DATA_BUFF_SZ] = {}; +uint8_t data2[DATA_BUFF_SZ] = {}; +uint8_t data_tmp[DATA_BUFF_SZ] = {}; + +int main(int argc, char** argv) +{ + int nf = 0, sf_idx = 0; + cf_t npss_signal[SRSLTE_NPSS_TOT_LEN]; + cf_t nsss_signal[SRSLTE_NSSS_LEN * SRSLTE_NSSS_NUM_SEQ]; // for subframe 9 + + uint8_t bch_payload[SRSLTE_MIB_NB_LEN]; + uint8_t sib1_nb_payload[SRSLTE_NPDSCH_MAX_TBS]; + + srslte_dci_msg_t dci_msg; + srslte_dci_location_t locations[SRSLTE_NOF_SF_X_FRAME][30]; + + uint32_t sfn = 0; + uint32_t hfn = 0; // Hyper frame number is incremented when sfn wraps and is also 10bit wide + +#ifdef DISABLE_RF + if (argc < 3) { + usage(argv[0]); + exit(-1); + } +#endif + + parse_args(argc, argv); + + // adjust SNR input to allow for negative values + if (output_file_name && snr != -100.0) { + snr -= 10.0; + printf("Target SNR: %.2fdB\n", snr); + } + + sf_n_re = 2 * SRSLTE_CP_NORM_NSYMB * cell.base.nof_prb * SRSLTE_NRE; + sf_n_samples = 2 * SRSLTE_SLOT_LEN(srslte_symbol_sz(cell.base.nof_prb)); + + /* this *must* be called after setting slot_len_* */ + base_init(); + + // buffer for outputting signal in RE representation (using sf_n_re) + cf_t* sf_re_symbols[SRSLTE_MAX_PORTS] = {NULL, NULL, NULL, NULL}; + sf_re_symbols[0] = sf_buffer; + + // buffer for outputting actual IQ samples (using sf_n_samples) + cf_t* sf_symbols[SRSLTE_MAX_PORTS] = {NULL, NULL, NULL, NULL}; + sf_symbols[0] = output_buffer; + + // construct MIB-NB + mib_nb.sched_info_sib1 = sched_info_tag; + mib_nb.sys_info_tag = 0; + mib_nb.ac_barring = false; + mib_nb.mode = SRSLTE_NBIOT_MODE_STANDALONE; + + // Initialize UE DL + if (srslte_nbiot_ue_dl_init(&ue_dl, sf_symbols, SRSLTE_NBIOT_MAX_PRB, SRSLTE_NBIOT_NUM_RX_ANTENNAS)) { + fprintf(stderr, "Error initiating UE downlink processing module\n"); + exit(-1); + } + + if (srslte_nbiot_ue_dl_set_cell(&ue_dl, cell)) { + fprintf(stderr, "Setting cell in UE DL\n"); + return -1; + } + + srslte_nbiot_ue_dl_set_mib(&ue_dl, mib_nb); + + /* Generate NPSS/NSSS signals */ + srslte_npss_generate(npss_signal); + srslte_nsss_generate(nsss_signal, cell.n_id_ncell); + +#ifdef NPSS_DUMP + srslte_filesink_t debug_fsink; + char fname[] = "npss.bin"; + if (srslte_filesink_init(&debug_fsink, fname, SRSLTE_COMPLEX_FLOAT_BIN)) { + fprintf(stderr, "Error opening file %s\n", fname); + exit(-1); + } + srslte_filesink_write(&debug_fsink, npss_signal, SRSLTE_NPSS_LEN * 11); + srslte_filesink_free(&debug_fsink); +#endif + + /* Generate CRS+NRS signals */ + if (srslte_chest_dl_nbiot_init(&est, SRSLTE_NBIOT_MAX_PRB)) { + fprintf(stderr, "Error initializing equalizer\n"); + exit(-1); + } + if (srslte_chest_dl_nbiot_set_cell(&est, cell) != SRSLTE_SUCCESS) { + fprintf(stderr, "Error setting channel estimator's cell configuration\n"); + return -1; + } + +#ifndef DISABLE_RF + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGINT); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + signal(SIGINT, sig_int_handler); + + if (!output_file_name) { + + int srate = srslte_sampling_freq_hz(cell.base.nof_prb); + if (srate != -1) { + printf("Setting sampling rate %.2f MHz\n", (float)srate / 1000000); + float srate_rf = srslte_rf_set_tx_srate(&rf, (double)srate); + if (srate_rf != srate) { + fprintf(stderr, "Could not set sampling rate\n"); + exit(-1); + } + } else { + fprintf(stderr, "Invalid number of PRB %d\n", cell.base.nof_prb); + exit(-1); + } + printf("Set TX gain: %.1f dB\n", srslte_rf_set_tx_gain(&rf, rf_gain)); + printf("Set TX freq: %.2f MHz\n", srslte_rf_set_tx_freq(&rf, 0, rf_freq) / 1000000); + } +#endif + + if (update_radl(sf_idx)) { + exit(-1); + } + + /* Initiate valid DCI locations */ + for (int i = 0; i < SRSLTE_NOF_SF_X_FRAME; i++) { + locations[i][0].L = 1; // Agg-level 2, i.e. both NCEEs used + locations[i][0].ncce = 0; + } + + nf = 0; + + bool send_data = false; + bool npdsch_active = false; + srslte_softbuffer_tx_reset(&softbuffer); + bzero(&sib1_npdsch_cfg, sizeof(srslte_npdsch_cfg_t)); + bzero(&npdsch_cfg, sizeof(srslte_npdsch_cfg_t)); + +#ifndef DISABLE_RF + bool start_of_burst = true; +#endif + + while ((nf < nof_frames || nof_frames == -1) && !go_exit) { + for (sf_idx = 0; sf_idx < SRSLTE_NOF_SF_X_FRAME && (nf < nof_frames || nof_frames == -1); sf_idx++) { + srslte_vec_cf_zero(sf_buffer, sf_n_re); + + // Transmit NPBCH in subframe 0 + if (sf_idx == 0) { + if ((sfn % SRSLTE_NPBCH_NUM_FRAMES) == 0) { + srslte_npbch_mib_pack(hfn, sfn, mib_nb, bch_payload); + } + srslte_npbch_put_subframe(&npbch, bch_payload, sf_re_symbols, sfn); + if (SRSLTE_VERBOSE_ISDEBUG()) { + printf("MIB payload: "); + srslte_vec_fprint_hex(stdout, bch_payload, SRSLTE_MIB_NB_LEN); + } + } + + // Transmit NPSS, NSSS and NRS + if (sf_idx == 5) { + // NPSS at subframe 5 + srslte_npss_put_subframe(&npss_sync, npss_signal, sf_buffer, cell.base.nof_prb, cell.nbiot_prb); + } else if ((sfn % 2 == 0) && sf_idx == 9) { + // NSSS in every even numbered frame at subframe 9 + srslte_nsss_put_subframe(&nsss_sync, nsss_signal, sf_buffer, sfn, cell.base.nof_prb, cell.nbiot_prb); + } else { + // NRS in all other subframes (using CSR signal intentionally) + // DEBUG("%d.%d: Putting %d NRS pilots\n", sfn, sf_idx, SRSLTE_REFSIGNAL_NUM_SF(1, cell.nof_ports)); + srslte_refsignal_nrs_put_sf(cell, 0, est.nrs_signal.pilots[0][sf_idx], sf_buffer); + } + +#if HAVE_NPDSCH + // only transmit in subframes not used for NPBCH, NPSS or NSSS and only use subframe 4 when there is no SIB1 + // transmission + if (sf_idx != 0 && sf_idx != 5 && (!(sf_idx == 9 && sfn % 2 == 0)) && + !srslte_nbiot_ue_dl_is_sib1_sf(&ue_dl, sfn, sf_idx)) { + send_data = true; + } else { + send_data = false; + } + + // SIB1-NB content + if (hfn % 4 == 0 && sfn == 0 && sf_idx == 0) { + // copy captured SIB1 + memcpy(sib1_nb_payload, dummy_sib1_payload, sizeof(dummy_sib1_payload)); + + // overwrite Hyper Frame Number (HFN), 8 MSB + uint8_t unpacked_hfn[4 * 8]; + srslte_bit_unpack_vector(sib1_nb_payload, unpacked_hfn, 4 * 8); + uint8_t* tmp = unpacked_hfn; + tmp += 12; + srslte_bit_unpack(hfn >> 2, &tmp, 8); + uint8_t packed_hfn[4]; + srslte_bit_pack_vector(unpacked_hfn, packed_hfn, 32); + memcpy(sib1_nb_payload, packed_hfn, sizeof(packed_hfn)); + + if (SRSLTE_VERBOSE_ISDEBUG()) { + printf("SIB1-NB payload: "); + srslte_vec_fprint_byte(stdout, sib1_nb_payload, sizeof(dummy_sib1_payload)); + } + } + + if (srslte_nbiot_ue_dl_is_sib1_sf(&ue_dl, sfn, sf_idx)) { + INFO("%d.%d: Transmitting SIB1-NB.\n", sfn, sf_idx); + assert(send_data == false); + + // configure DL grant for SIB1-NB transmission + if (sib1_npdsch_cfg.sf_idx == 0) { + srslte_ra_nbiot_dl_grant_t grant; + srslte_ra_nbiot_dl_dci_to_grant( + &ra_dl_sib1, &grant, sfn, sf_idx, DUMMY_R_MAX, true, cell.mode); + if (srslte_npdsch_cfg(&sib1_npdsch_cfg, cell, &grant, sf_idx)) { + fprintf(stderr, "Error configuring NPDSCH\n"); + exit(-1); + } + } + + // Encode SIB1 content + if (srslte_npdsch_encode_rnti( + &npdsch, &sib1_npdsch_cfg, &softbuffer, sib1_nb_payload, SRSLTE_SIRNTI, sf_re_symbols)) { + fprintf(stderr, "Error encoding NPDSCH\n"); + exit(-1); + } + + if (sib1_npdsch_cfg.sf_idx == sib1_npdsch_cfg.grant.nof_sf) { + bzero(&sib1_npdsch_cfg, sizeof(srslte_npdsch_cfg_t)); + } + } + + // Update DL resource allocation from control port + if (update_control(sf_idx)) { + fprintf(stderr, "Error updating parameters from control port\n"); + } + + if (send_data) { + // always transmit NPDCCH on fixed positions if no transmission is going on + if (sf_idx == NPDCCH_SF_IDX && !npdsch_active) { + // Encode NPDCCH + INFO("Putting DCI to location: n=%d, L=%d\n", locations[sf_idx][0].ncce, locations[sf_idx][0].L); + srslte_dci_msg_pack_npdsch(&ra_dl, SRSLTE_DCI_FORMATN1, &dci_msg, false); + if (srslte_npdcch_encode(&npdcch, &dci_msg, locations[sf_idx][0], UE_CRNTI, sf_re_symbols, sf_idx)) { + fprintf(stderr, "Error encoding DCI message\n"); + exit(-1); + } + + // Configure NPDSCH accordingly + srslte_ra_nbiot_dl_grant_t grant; + srslte_ra_nbiot_dl_dci_to_grant(&ra_dl, &grant, sfn, sf_idx, DUMMY_R_MAX, false, cell.mode); + if (srslte_npdsch_cfg(&npdsch_cfg, cell, &grant, sf_idx)) { + fprintf(stderr, "Error configuring NPDSCH\n"); + exit(-1); + } + } + + // catch start of "user" NPDSCH + if (!npdsch_active && (sf_idx == npdsch_cfg.grant.start_sfidx && sfn == npdsch_cfg.grant.start_sfn)) { + // generate data only in first sf + INFO("%d.%d: Generating %d random bits\n", sfn, sf_idx, npdsch_cfg.grant.mcs[0].tbs); + for (int i = 0; i < npdsch_cfg.grant.mcs[0].tbs / 8; i++) { + data[i] = srslte_random_uniform_int_dist(random_gen, 0, 255); + } + if (SRSLTE_VERBOSE_ISDEBUG()) { + printf("Tx payload: "); + srslte_vec_fprint_b(stdout, data, npdsch_cfg.grant.mcs[0].tbs / 8); + } + npdsch_active = true; + } + + if (npdsch_active) { + DEBUG("Current sf_idx=%d, Encoding npdsch.sf_idx=%d start=%d, nof=%d\n", + sf_idx, + npdsch_cfg.sf_idx, + npdsch_cfg.grant.start_sfidx, + npdsch_cfg.grant.nof_sf); + // Encode NPDSCH + if (srslte_npdsch_encode(&npdsch, &npdsch_cfg, &softbuffer, data, sf_re_symbols)) { + fprintf(stderr, "Error encoding NPDSCH\n"); + exit(-1); + } + if (npdsch_cfg.num_sf == npdsch_cfg.grant.nof_sf * npdsch_cfg.grant.nof_rep) { + INFO("Deactive current NPDSCH\n"); + npdsch_active = false; + } + } + } +#endif + + /* Transform to OFDM symbols */ + srslte_ofdm_tx_sf(&ifft); + + if (output_file_name && snr != -100.0) { + // compute average energy per symbol + float abs[sf_n_samples]; + srslte_vec_abs_square_cf(output_buffer, abs, sf_n_samples); + float abs_avg = 0; + for (int i = 0; i < sf_n_samples; i++) { + abs_avg += abs[i]; + } + abs_avg /= sf_n_samples; + + // find the noise spectral density + float snr_lin = powf(10.0f, snr / 10); + float n0 = abs_avg / snr_lin; + float nstd = sqrtf(n0 / 2); + + // add some noise to the signal + srslte_ch_awgn_c(output_buffer, output_buffer, nstd, sf_n_samples); + } + + /* send to file or usrp */ + if (output_file_name) { + // write to file + if (!null_file_sink) { + srslte_filesink_write(&fsink, output_buffer, sf_n_samples); + } + usleep(1000); + } else { +#ifndef DISABLE_RF + // FIXME: output scaling needed? + srslte_rf_send2(&rf, output_buffer, sf_n_samples, true, start_of_burst, false); + start_of_burst = false; +#endif + } + } + nf++; + sfn++; + if (sfn >= 1024) { + sfn = 0; + hfn = (hfn + 1) % 1024; + printf("NB-IoT HFN: %d\n", hfn); + } + } + + base_free(); + + printf("Done\n"); + + return SRSLTE_SUCCESS; +} diff --git a/lib/examples/npdsch_ue.c b/lib/examples/npdsch_ue.c new file mode 100644 index 000000000..3e559d440 --- /dev/null +++ b/lib/examples/npdsch_ue.c @@ -0,0 +1,999 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "srslte/phy/ch_estimation/chest_dl_nbiot.h" +#include "srslte/phy/channel/ch_awgn.h" +#include "srslte/phy/io/filesink.h" +#include "srslte/phy/io/filesource.h" +#include "srslte/phy/ue/ue_dl_nbiot.h" +#include "srslte/phy/ue/ue_mib_nbiot.h" +#include "srslte/phy/ue/ue_sync_nbiot.h" +#include "srslte/phy/utils/bit.h" + +#undef ENABLE_AGC_DEFAULT + +#ifndef DISABLE_RF +#include "srslte/phy/rf/rf.h" +#include "srslte/phy/rf/rf_utils.h" + +#define ENABLE_MANUAL_NSSS_SEARCH 0 +#define HAVE_PCAP 1 + +#if HAVE_PCAP +#include "srslte/common/pcap.h" +#endif + +cell_search_cfg_t cell_detect_config = {.max_frames_pbch = SRSLTE_DEFAULT_MAX_FRAMES_NPBCH, + .max_frames_pss = SRSLTE_DEFAULT_MAX_FRAMES_NPSS, + .nof_valid_pss_frames = SRSLTE_DEFAULT_NOF_VALID_NPSS_FRAMES, + .init_agc = 0, + .force_tdd = false}; + +#else +#warning Compiling npdsch_ue with no RF support +#endif + +#ifdef ENABLE_GUI +#include "srsgui/srsgui.h" +void init_plots(); +pthread_t plot_thread; +sem_t plot_sem; +uint32_t plot_sf_idx = 0; +bool plot_track = true; +#endif // ENABLE_GUI + +#define PRINT_CHANGE_SCHEDULIGN +#define NPSS_FIND_PLOT_WIDTH 80 +//#define CORRECT_SAMPLE_OFFSET + +/********************************************************************** + * Program arguments processing + ***********************************************************************/ +typedef struct { + int nof_subframes; + bool disable_plots; + bool disable_plots_except_constellation; + bool disable_cfo; + uint32_t time_offset; + int n_id_ncell; + bool is_r14; + bool decode_sib2; + int sib2_periodicity; + int sib2_radio_frame_offset; + int sib2_repetition_pattern; + int sib2_tb; + int sib2_window_length; + uint16_t rnti; + char* input_file_name; + int file_offset_time; + float file_offset_freq; + uint32_t file_nof_prb; + uint32_t file_nof_ports; + uint32_t file_cell_id; + char* rf_args; + double rf_freq; + float rf_gain; +} prog_args_t; + +void args_default(prog_args_t* args) +{ + args->disable_plots = false; + args->disable_plots_except_constellation = false; + args->nof_subframes = -1; + args->rnti = SRSLTE_SIRNTI; + args->n_id_ncell = SRSLTE_CELL_ID_UNKNOWN; + args->is_r14 = false; + args->decode_sib2 = false; + args->sib2_periodicity = 512; + args->sib2_radio_frame_offset = 0; + args->sib2_repetition_pattern = 1; + args->sib2_tb = 328; + args->sib2_window_length = 960; + args->input_file_name = NULL; + args->disable_cfo = false; + args->time_offset = 0; + args->file_nof_prb = 1; + args->file_nof_ports = 0; + args->file_cell_id = 0; + args->file_offset_time = 0; + args->file_offset_freq = 0; + args->rf_args = ""; + args->rf_freq = -1.0; +#ifdef ENABLE_AGC_DEFAULT + args->rf_gain = -1.0; +#else + args->rf_gain = 70.0; +#endif +} + +void usage(prog_args_t* args, char* prog) +{ + printf("Usage: %s [agpRPoOildtDnrBuHvqwzxc] -f rx_frequency (in Hz) | -i input_file\n", prog); +#ifndef DISABLE_RF + printf("\t-a RF args [Default %s]\n", args->rf_args); +#ifdef ENABLE_AGC_DEFAULT + printf("\t-g RF fix RX gain [Default AGC]\n"); +#else + printf("\t-g Set RX gain [Default %.1f dB]\n", args->rf_gain); +#endif +#else + printf("\t RF is disabled.\n"); +#endif + printf("\t-i input_file [Default use RF board]\n"); + printf("\t-o offset frequency correction (in Hz) for input file [Default %.1f Hz]\n", args->file_offset_freq); + printf("\t-O offset samples for input file [Default %d]\n", args->file_offset_time); + printf("\t-p nof_prb for input file [Default %d]\n", args->file_nof_prb); + printf("\t-P nof_ports for input file [Default %d]\n", args->file_nof_ports); + printf("\t-r RNTI in Hex [Default 0x%x]\n", args->rnti); + printf("\t-l n_id_ncell [Default %d]\n", args->n_id_ncell); + printf("\t-R Is R14 cell [Default %s]\n", args->is_r14 ? "Yes" : "No"); + printf("\t-B Decode SIB2 [Default %s]\n", args->decode_sib2 ? "Yes" : "No"); + printf("\t-q SIB2 periodicity [Default %d]\n", args->sib2_periodicity); + printf("\t-w SIB2 radio frame offset [Default %d]\n", args->sib2_radio_frame_offset); + printf("\t-z SIB2 repetions pattern [Default %d]\n", args->sib2_repetition_pattern); + printf("\t-x SIB2 TB size [Default %d]\n", args->sib2_tb); + printf("\t-c SIB2 window length [Default %d]\n", args->sib2_window_length); + printf("\t-C Disable CFO correction [Default %s]\n", args->disable_cfo ? "Disabled" : "Enabled"); + printf("\t-t Add time offset [Default %d]\n", args->time_offset); +#ifdef ENABLE_GUI + printf("\t-d disable plots [Default enabled]\n"); + printf("\t-D disable all but constellation plots [Default enabled]\n"); +#else + printf("\t plots are disabled. Graphics library not available\n"); +#endif // ENABLE_GUI + printf("\t-n nof_subframes [Default %d]\n", args->nof_subframes); + printf("\t-v [set srslte_verbose to debug, default none]\n"); +} + +void parse_args(prog_args_t* args, int argc, char** argv) +{ + int opt; + args_default(args); + while ((opt = getopt(argc, argv, "aogRBlipHPOCtdDnvrfqwzxc")) != -1) { + switch (opt) { + case 'i': + args->input_file_name = argv[optind]; + break; + case 'p': + args->file_nof_prb = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'P': + args->file_nof_ports = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'o': + args->file_offset_freq = strtof(argv[optind], NULL); + break; + case 'O': + args->file_offset_time = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'a': + args->rf_args = argv[optind]; + break; + case 'g': + args->rf_gain = strtof(argv[optind], NULL); + break; + case 'C': + args->disable_cfo = true; + break; + case 'B': + args->decode_sib2 = true; + break; + case 'q': + args->sib2_periodicity = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'w': + args->sib2_radio_frame_offset = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'z': + args->sib2_repetition_pattern = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'x': + args->sib2_tb = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'c': + args->sib2_window_length = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 't': + args->time_offset = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'f': + args->rf_freq = strtod(argv[optind], NULL); + break; + case 'n': + args->nof_subframes = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'r': + args->rnti = strtol(argv[optind], NULL, 16); + break; + case 'l': + args->n_id_ncell = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'R': + args->is_r14 = true; + break; + case 'd': + args->disable_plots = true; + break; + case 'D': + args->disable_plots_except_constellation = true; + break; + case 'v': + srslte_verbose++; + break; + default: + usage(args, argv[0]); + exit(-1); + } + } + if (args->rf_freq < 0 && args->input_file_name == NULL) { + usage(args, argv[0]); + exit(-1); + } +} +/**********************************************************************/ + +static uint8_t data[20000]; // Byte buffer for rx'ed transport blocks + +bool go_exit = false; +void sig_int_handler(int signo) +{ + printf("SIGINT received. Exiting...\n"); + if (signo == SIGINT) { + go_exit = true; + } +} + +#if HAVE_PCAP +void pcap_pack_and_write(FILE* pcap_file, + uint8_t* pdu, + uint32_t pdu_len_bytes, + uint32_t reTX, + bool crc_ok, + uint32_t tti, + uint16_t crnti, + uint8_t direction, + uint8_t rnti_type) +{ + MAC_Context_Info_t context = {.radioType = FDD_RADIO, + .direction = direction, + .rntiType = rnti_type, + .rnti = crnti, + .ueid = 1, + .isRetx = reTX, + .crcStatusOK = crc_ok, + .sysFrameNumber = tti / 10, + .subFrameNumber = tti % 10, + .nbiotMode = 1}; + if (pdu) { + LTE_PCAP_MAC_WritePDU(pcap_file, &context, pdu, pdu_len_bytes); + } +} +#endif + +#ifndef DISABLE_RF +int srslte_rf_recv_wrapper(void* h, void* data, uint32_t nsamples, srslte_timestamp_t* t) +{ + DEBUG(" ---- Receive %d samples ---- \n", nsamples); + return srslte_rf_recv_with_time(h, data, nsamples, true, &t->full_secs, &t->frac_secs); +} + +void srslte_rf_set_rx_gain_th_wrapper_(void* h, float f) +{ + srslte_rf_set_rx_gain_th((srslte_rf_t*)h, f); +} + +#endif + +extern float mean_exec_time; + +enum receiver_state { DECODE_MIB, DECODE_SIB, DECODE_NPDSCH } state; + +srslte_nbiot_ue_dl_t ue_dl; +srslte_nbiot_ue_sync_t ue_sync; +prog_args_t prog_args; + +bool have_sib1 = false; +bool have_sib2 = false; + +uint32_t sfn = 0; // system frame number +uint32_t hfn = 0; // Hyper frame number + +#define RSRP_TABLE_MAX_IDX 1024 +float rsrp_table[RSRP_TABLE_MAX_IDX]; +uint32_t rsrp_table_index = 0; +uint32_t rsrp_num_plot = RSRP_TABLE_MAX_IDX; + +int main(int argc, char** argv) +{ + int ret; + srslte_nbiot_cell_t cell = {}; + int64_t sf_cnt; + srslte_ue_mib_nbiot_t ue_mib; +#ifndef DISABLE_RF + srslte_rf_t rf; +#endif + uint32_t nof_trials = 0; + int n; + uint8_t bch_payload[SRSLTE_MIB_NB_LEN] = {}; + int sfn_offset; + float cfo = 0; + + parse_args(&prog_args, argc, argv); + +#if HAVE_PCAP + FILE* pcap_file = LTE_PCAP_Open(MAC_LTE_DLT, "/tmp/npdsch.pcap"); +#endif + + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGINT); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + signal(SIGINT, sig_int_handler); + + cell.base.nof_prb = SRSLTE_NBIOT_DEFAULT_NUM_PRB_BASECELL; + cell.nbiot_prb = SRSLTE_NBIOT_DEFAULT_PRB_OFFSET; + cell.n_id_ncell = prog_args.n_id_ncell; + cell.is_r14 = prog_args.is_r14; + +#ifndef DISABLE_RF + if (!prog_args.input_file_name) { + + printf("Opening RF device...\n"); + if (srslte_rf_open(&rf, prog_args.rf_args)) { + fprintf(stderr, "Error opening rf\n"); + exit(-1); + } + /* Set receiver gain */ + if (prog_args.rf_gain > 0) { + printf("Set RX gain: %.1f dB\n", srslte_rf_set_rx_gain(&rf, prog_args.rf_gain)); + } else { + printf("Starting AGC thread...\n"); + if (srslte_rf_start_gain_thread(&rf, false)) { + fprintf(stderr, "Error opening rf\n"); + exit(-1); + } + srslte_rf_set_rx_gain(&rf, 50); + cell_detect_config.init_agc = 50; + } + + // set transceiver frequency + printf("Set RX freq: %.6f MHz\n", srslte_rf_set_rx_freq(&rf, 0, prog_args.rf_freq) / 1000000); + + // set sampling frequency + int srate = srslte_sampling_freq_hz(cell.base.nof_prb); + if (srate != -1) { + printf("Setting sampling rate %.2f MHz\n", (float)srate / 1000000); + float srate_rf = srslte_rf_set_rx_srate(&rf, (double)srate); + if (srate_rf != srate) { + fprintf(stderr, "Could not set sampling rate\n"); + exit(-1); + } + } else { + fprintf(stderr, "Invalid number of PRB %d\n", cell.base.nof_prb); + exit(-1); + } + + INFO("Stopping RF and flushing buffer...\r"); + srslte_rf_stop_rx_stream(&rf); + +#if ENABLE_MANUAL_NSSS_SEARCH + // determine n_id_ncell + if (prog_args.n_id_ncell == SRSLTE_CELL_ID_UNKNOWN) { + srslte_nsss_synch_t nsss; + float nsss_peak_value; + int input_len = srate * 10 / 1000 * 2; // capture two full frames to make sure we have one NSSS + + cf_t* buffer = malloc(sizeof(cf_t) * input_len * 2); + if (!buffer) { + perror("malloc"); + exit(-1); + } + + if (srslte_nsss_synch_init(&nsss, cell, input_len, srate / 15000)) { + fprintf(stderr, "Error initializing NSSS object\n"); + exit(-1); + } + + srslte_rf_start_rx_stream(&rf); + n = srslte_rf_recv(&rf, buffer, input_len, 1); + if (n != input_len) { + fprintf(stderr, "Error receiving samples\n"); + exit(-1); + } + srslte_rf_stop_rx_stream(&rf); + + // trying to find NSSS + printf("Detecting NSSS signal .. "); + fflush(stdout); + uint32_t sfn_partial; + srslte_nsss_sync_find(&nsss, buffer, &nsss_peak_value, (int*)&cell.n_id_ncell, &sfn_partial); + printf("done!"); + srslte_nsss_synch_free(&nsss); + free(buffer); + } else { + cell.n_id_ncell = prog_args.n_id_ncell; + } + printf("\nSetting n_id_ncell to %d.\n", cell.n_id_ncell); +#else + if (cell.n_id_ncell == SRSLTE_CELL_ID_UNKNOWN) { + uint32_t ntrial = 0; + do { + ret = rf_cell_search_nbiot(&rf, &cell_detect_config, &cell, &cfo); + if (ret != SRSLTE_SUCCESS) { + printf("Cell not found after %d trials. Trying again (Press Ctrl+C to exit)\n", ntrial++); + } + } while (ret != SRSLTE_SUCCESS && !go_exit); + } +#endif + + if (go_exit) { + exit(0); + } + } +#endif + + /* If reading from file, go straight to PDSCH decoding. Otherwise, decode MIB first */ + if (prog_args.input_file_name) { + // set file specific params + cell.base.nof_ports = prog_args.file_nof_ports; + cell.nof_ports = prog_args.file_nof_ports; + + if (srslte_ue_sync_nbiot_init_file( + &ue_sync, cell, prog_args.input_file_name, prog_args.file_offset_time, prog_args.file_offset_freq)) { + fprintf(stderr, "Error initiating ue_sync\n"); + exit(-1); + } + } else { +#ifndef DISABLE_RF + if (srslte_ue_sync_nbiot_init(&ue_sync, cell, srslte_rf_recv_wrapper, (void*)&rf)) { + fprintf(stderr, "Error initiating ue_sync\n"); + exit(-1); + } + // reduce AGC period to every 10th frame + srslte_ue_sync_nbiot_set_agc_period(&ue_sync, 10); +#endif + } + + // Allocate memory to fit a full frame (needed for time re-alignment) + cf_t* buff_ptrs[SRSLTE_MAX_PORTS] = {NULL}; + buff_ptrs[0] = srslte_vec_malloc(sizeof(cf_t) * SRSLTE_SF_LEN_PRB_NBIOT * 10); + + if (srslte_ue_mib_nbiot_init(&ue_mib, buff_ptrs, SRSLTE_NBIOT_MAX_PRB)) { + fprintf(stderr, "Error initaiting UE MIB decoder\n"); + exit(-1); + } + if (srslte_ue_mib_nbiot_set_cell(&ue_mib, cell) != SRSLTE_SUCCESS) { + fprintf(stderr, "Error setting cell configuration in UE MIB decoder\n"); + exit(-1); + } + + // configure SIB2-NB parameters + srslte_nbiot_si_params_t sib2_params; + sib2_params.n = 1; + sib2_params.si_periodicity = prog_args.sib2_periodicity; + sib2_params.si_radio_frame_offset = prog_args.sib2_radio_frame_offset; + sib2_params.si_repetition_pattern = prog_args.sib2_repetition_pattern; + sib2_params.si_tb = prog_args.sib2_tb; + sib2_params.si_window_length = prog_args.sib2_window_length; + + /* Initialize subframe counter */ + sf_cnt = 0; + +#ifdef ENABLE_GUI + if (!prog_args.disable_plots) { + init_plots(cell); + } +#endif // ENABLE_GUI + +#ifndef DISABLE_RF + if (!prog_args.input_file_name) { + srslte_rf_start_rx_stream(&rf, false); + } +#endif + + // Variables for measurements + uint32_t nframes = 0; + float rsrp = 0.0, rsrq = 0.0, noise = 0.0; + bzero(&rsrp_table, sizeof(float) * RSRP_TABLE_MAX_IDX); + +#ifndef DISABLE_RF + if (prog_args.rf_gain < 0) { + srslte_ue_sync_nbiot_start_agc(&ue_sync, srslte_rf_set_rx_gain_th_wrapper_, cell_detect_config.init_agc); + } +#endif +#ifdef PRINT_CHANGE_SCHEDULIGN + srslte_ra_nbiot_dl_dci_t old_dl_dci; + bzero(&old_dl_dci, sizeof(srslte_ra_nbiot_dl_dci_t)); +#endif + + ue_sync.correct_cfo = !prog_args.disable_cfo; + + // Set initial CFO for ue_sync + srslte_ue_sync_nbiot_set_cfo(&ue_sync, cfo); + + srslte_npbch_decode_reset(&ue_mib.npbch); + + INFO("\nEntering main loop...\n\n"); + while (!go_exit && (sf_cnt < prog_args.nof_subframes || prog_args.nof_subframes == -1)) { + + ret = srslte_ue_sync_nbiot_zerocopy_multi(&ue_sync, buff_ptrs); + if (ret < 0) { + fprintf(stderr, "Error calling srslte_nbiot_ue_sync_zerocopy_multi()\n"); + break; + } + +#ifdef CORRECT_SAMPLE_OFFSET + float sample_offset = + (float)srslte_ue_sync_get_last_sample_offset(&ue_sync) + srslte_ue_sync_get_sfo(&ue_sync) / 1000; + srslte_ue_dl_set_sample_offset(&ue_dl, sample_offset); +#endif + + // srslte_ue_sync_nbiot_zerocopy_multi() returns 1 if successfully read 1 aligned subframe + if (ret == 1) { + switch (state) { + case DECODE_MIB: + if (srslte_ue_sync_nbiot_get_sfidx(&ue_sync) == 0) { + n = srslte_ue_mib_nbiot_decode(&ue_mib, buff_ptrs[0], bch_payload, &cell.nof_ports, &sfn_offset); + if (n < 0) { + fprintf(stderr, "Error decoding UE MIB\n"); + exit(-1); + } else if (n == SRSLTE_UE_MIB_FOUND) { + printf("MIB received (CFO: %+6.2f kHz)\n", srslte_ue_sync_nbiot_get_cfo(&ue_sync) / 1000); + srslte_mib_nb_t mib; + srslte_npbch_mib_unpack(bch_payload, &mib); + + // update SFN and set deployment mode + sfn = (mib.sfn + sfn_offset) % 1024; + cell.mode = mib.mode; + + // set number of ports of base cell to that of NB-IoT cell (FIXME: read eutra-NumCRS-Ports-r13) + cell.base.nof_ports = cell.nof_ports; + + if (cell.mode == SRSLTE_NBIOT_MODE_INBAND_SAME_PCI) { + cell.base.id = cell.n_id_ncell; + } + + if (SRSLTE_VERBOSE_ISINFO()) { + srslte_mib_nb_printf(stdout, cell, &mib); + } + + // Initialize DL + if (srslte_nbiot_ue_dl_init(&ue_dl, buff_ptrs, SRSLTE_NBIOT_MAX_PRB, SRSLTE_NBIOT_NUM_RX_ANTENNAS)) { + fprintf(stderr, "Error initiating UE downlink processing module\n"); + exit(-1); + } + + if (srslte_nbiot_ue_dl_set_cell(&ue_dl, cell)) { + fprintf(stderr, "Configuring cell in UE DL\n"); + exit(-1); + } + + // Configure downlink receiver with the MIB params and the RNTI we use + srslte_nbiot_ue_dl_set_mib(&ue_dl, mib); + srslte_nbiot_ue_dl_set_rnti(&ue_dl, prog_args.rnti); + +#if HAVE_PCAP + // write to PCAP + srslte_bit_pack_vector(bch_payload, data, SRSLTE_MIB_NB_CRC_LEN); + pcap_pack_and_write( + pcap_file, data, SRSLTE_MIB_NB_CRC_LEN, 0, true, sfn * 10, 0, DIRECTION_DOWNLINK, NO_RNTI); +#endif + // activate SIB1 decoding + srslte_nbiot_ue_dl_decode_sib1(&ue_dl, sfn); + state = DECODE_SIB; + } + } + break; + + case DECODE_SIB: + if (!have_sib1) { + int dec_ret = srslte_nbiot_ue_dl_decode_npdsch(&ue_dl, + &buff_ptrs[0][prog_args.time_offset], + data, + sfn, + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + SRSLTE_SIRNTI); + if (dec_ret == SRSLTE_SUCCESS) { + printf("SIB1 received.\n"); + have_sib1 = true; +#if HAVE_PCAP + pcap_pack_and_write(pcap_file, + data, + ue_dl.npdsch_cfg.grant.mcs[0].tbs / 8, + 0, + true, + sfn * 10 + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + SRSLTE_SIRNTI, + DIRECTION_DOWNLINK, + SI_RNTI); +#endif + + // active SIB2 decoding if set + if (prog_args.decode_sib2) { + srslte_nbiot_ue_dl_decode_sib(&ue_dl, hfn, sfn, SRSLTE_NBIOT_SI_TYPE_SIB2, sib2_params); + } else { + have_sib2 = true; + } + // if SIB1 was decoded in this subframe, skip processing it further + break; + } else if (dec_ret == SRSLTE_ERROR) { + // reactivate SIB1 grant + if (srslte_nbiot_ue_dl_has_grant(&ue_dl) == false) { + srslte_nbiot_ue_dl_decode_sib1(&ue_dl, sfn); + } + } + } else if (!have_sib2 && + !srslte_nbiot_ue_dl_is_sib1_sf(&ue_dl, sfn, srslte_ue_sync_nbiot_get_sfidx(&ue_sync))) { + // SIB2 is transmitted over multiple subframes, so this needs to be called more than once .. + int dec_ret = srslte_nbiot_ue_dl_decode_npdsch(&ue_dl, + &buff_ptrs[0][prog_args.time_offset], + data, + sfn, + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + SRSLTE_SIRNTI); + if (dec_ret == SRSLTE_SUCCESS) { + printf("SIB2 received.\n"); + have_sib2 = true; +#if HAVE_PCAP + pcap_pack_and_write(pcap_file, + data, + ue_dl.npdsch_cfg.grant.mcs[0].tbs / 8, + 0, + true, + sfn * 10 + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + SRSLTE_SIRNTI, + DIRECTION_DOWNLINK, + SI_RNTI); +#endif + } else { + // reactivate SIB2 grant + if (srslte_nbiot_ue_dl_has_grant(&ue_dl) == false) { + srslte_nbiot_ue_dl_decode_sib(&ue_dl, hfn, sfn, SRSLTE_NBIOT_SI_TYPE_SIB2, sib2_params); + } + } + } + + if (have_sib1 && have_sib2) { + if (prog_args.rnti == SRSLTE_SIRNTI) + srslte_nbiot_ue_dl_decode_sib1(&ue_dl, sfn); + state = DECODE_NPDSCH; + } + break; + case DECODE_NPDSCH: + if (prog_args.rnti != SRSLTE_SIRNTI) { + if (srslte_nbiot_ue_dl_has_grant(&ue_dl)) { + // attempt to decode NPDSCH + n = srslte_nbiot_ue_dl_decode_npdsch(&ue_dl, + &buff_ptrs[0][prog_args.time_offset], + data, + sfn, + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + prog_args.rnti); + if (n == SRSLTE_SUCCESS) { + INFO("NPDSCH decoded ok.\n"); + } + } else { + // decode NPDCCH + srslte_dci_msg_t dci_msg; + n = srslte_nbiot_ue_dl_decode_npdcch(&ue_dl, + &buff_ptrs[0][prog_args.time_offset], + sfn, + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + prog_args.rnti, + &dci_msg); + if (n == SRSLTE_NBIOT_UE_DL_FOUND_DCI) { + INFO("DCI found for rnti=%d\n", prog_args.rnti); + // convert DCI to grant + srslte_ra_nbiot_dl_dci_t dci_unpacked; + srslte_ra_nbiot_dl_grant_t grant; + if (srslte_nbiot_dci_msg_to_dl_grant(&dci_msg, + prog_args.rnti, + &dci_unpacked, + &grant, + sfn, + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + 64 /* fixme: remove */, + cell.mode)) { + fprintf(stderr, "Error unpacking DCI\n"); + return SRSLTE_ERROR; + } + // activate grant + srslte_nbiot_ue_dl_set_grant(&ue_dl, &grant); + } + } + } else { + // decode SIB1 continously + n = srslte_nbiot_ue_dl_decode_npdsch(&ue_dl, + &buff_ptrs[0][prog_args.time_offset], + data, + sfn, + srslte_ue_sync_nbiot_get_sfidx(&ue_sync), + prog_args.rnti); + // reactivate SIB1 grant + if (srslte_nbiot_ue_dl_has_grant(&ue_dl) == false) { + srslte_nbiot_ue_dl_decode_sib1(&ue_dl, sfn); + } + } + + nof_trials++; + + rsrq = SRSLTE_VEC_EMA(srslte_chest_dl_nbiot_get_rsrq(&ue_dl.chest), rsrq, 0.1); + rsrp = SRSLTE_VEC_EMA(srslte_chest_dl_nbiot_get_rsrp(&ue_dl.chest), rsrp, 0.05); + noise = SRSLTE_VEC_EMA(srslte_chest_dl_nbiot_get_noise_estimate(&ue_dl.chest), noise, 0.05); + nframes++; + if (isnan(rsrq)) { + rsrq = 0; + } + if (isnan(noise)) { + noise = 0; + } + if (isnan(rsrp)) { + rsrp = 0; + } + + // Plot and Printf + if (srslte_ue_sync_nbiot_get_sfidx(&ue_sync) == 5) { + float gain = prog_args.rf_gain; + if (gain < 0) { + gain = 10 * log10(srslte_agc_get_gain(&ue_sync.agc)); + } + printf("CFO: %+6.2f kHz, RSRP: %4.1f dBm " + "SNR: %4.1f dB, RSRQ: %4.1f dB, " + "NPDCCH detected: %d, NPDSCH-BLER: %5.2f%% (%d of total %d), NPDSCH-Rate: %5.2f kbit/s\r", + srslte_ue_sync_nbiot_get_cfo(&ue_sync) / 1000, + 10 * log10(rsrp), + 10 * log10(rsrp / noise), + 10 * log10(rsrq), + ue_dl.nof_detected, + (float)100 * ue_dl.pkt_errors / ue_dl.pkts_total, + ue_dl.pkt_errors, + ue_dl.pkts_total, + (ue_dl.bits_total / ((sfn * 10 + srslte_ue_sync_nbiot_get_sfidx(&ue_sync)) / 1000.0)) / 1000.0); + } + break; + } + if (srslte_ue_sync_nbiot_get_sfidx(&ue_sync) == 9) { + sfn++; + if (sfn == 1024) { + sfn = 0; + printf("\n"); + + // don't reset counter when reading from file to maintain complete stats + if (!prog_args.input_file_name) { + ue_dl.pkt_errors = 0; + ue_dl.pkts_total = 0; + ue_dl.nof_detected = 0; + ue_dl.bits_total = 0; + nof_trials = 0; + } + } + } + +#ifdef ENABLE_GUI + if (!prog_args.disable_plots) { + if ((sfn % 4) == 0) { + plot_sf_idx = srslte_ue_sync_nbiot_get_sfidx(&ue_sync); + plot_track = true; + sem_post(&plot_sem); + } + } +#endif // ENABLE_GUI + } else if (ret == 0) { + state = DECODE_MIB; + printf("Finding PSS... Peak: %8.1f, FrameCnt: %d, State: %d\r", + srslte_sync_nbiot_get_peak_value(&ue_sync.sfind), + ue_sync.frame_total_cnt, + ue_sync.state); +#ifdef ENABLE_GUI + if (!prog_args.disable_plots) { + plot_sf_idx = srslte_ue_sync_nbiot_get_sfidx(&ue_sync); + plot_track = false; + sem_post(&plot_sem); + } +#endif // ENABLE_GUI + } + + sf_cnt++; + } // Main loop + + // print statistics + if (prog_args.input_file_name) { + printf("pkt_total=%d\n", ue_dl.pkts_total); + printf("pkt_ok=%d\n", ue_dl.pkts_total - ue_dl.pkt_errors); + printf("pkt_errors=%d\n", ue_dl.pkt_errors); + printf("bler=%.2f\n", ue_dl.pkts_total ? (float)100 * ue_dl.pkt_errors / ue_dl.pkts_total : 0); + printf("rate=%.2f\n", ((ue_dl.bits_total / ((sf_cnt) / 1000.0)) / 1000.0)); + printf("dci_detected=%d\n", ue_dl.nof_detected); + } + +#ifdef ENABLE_GUI + if (!prog_args.disable_plots) { + if (!pthread_kill(plot_thread, 0)) { + pthread_kill(plot_thread, SIGHUP); + pthread_join(plot_thread, NULL); + } + } +#endif // ENABLE_GUI + + srslte_nbiot_ue_dl_free(&ue_dl); + srslte_ue_sync_nbiot_free(&ue_sync); + +#if HAVE_PCAP + printf("Saving PCAP file\n"); + LTE_PCAP_Close(pcap_file); +#endif + +#ifndef DISABLE_RF + if (!prog_args.input_file_name) { + srslte_ue_mib_nbiot_free(&ue_mib); + srslte_rf_close(&rf); + for (int i = 0; i < SRSLTE_MAX_PORTS; i++) { + if (buff_ptrs[i] != NULL) + free(buff_ptrs[i]); + } + } +#endif + + printf("\nBye\n"); + return SRSLTE_SUCCESS; +} + +/********************************************************************** + * Plotting Functions + ***********************************************************************/ +#ifdef ENABLE_GUI + +plot_real_t p_sync, pce, rsrp_plot; +plot_scatter_t constellation_plot; + +float tmp_plot[110 * 15 * 2048]; +float tmp_plot2[110 * 15 * 2048]; +float tmp_plot3[110 * 15 * 2048]; + +void* plot_thread_run(void* arg) +{ + int i; + uint32_t nof_re = SRSLTE_SF_LEN_RE(ue_dl.cell.base.nof_prb, ue_dl.cell.base.cp); + float rsrp_lin = 0; + + sdrgui_init(); + + plot_scatter_init(&constellation_plot); + plot_scatter_setTitle(&constellation_plot, "NPDSCH/NPDCCH - Equalized Symbols"); + plot_scatter_setXAxisScale(&constellation_plot, -4, 4); + plot_scatter_setYAxisScale(&constellation_plot, -4, 4); + + plot_scatter_addToWindowGrid(&constellation_plot, (char*)"pdsch_ue", 0, 0); + + if (!prog_args.disable_plots_except_constellation) { + plot_real_init(&pce); + plot_real_setTitle(&pce, "Channel Response - Magnitude"); + plot_real_setLabels(&pce, "Index", "dB"); + plot_real_setYAxisScale(&pce, -40, 40); + + plot_real_init(&p_sync); + plot_real_setTitle(&p_sync, "NPSS Cross-Corr abs value"); + plot_real_setYAxisScale(&p_sync, 0, 1); + + plot_real_init(&rsrp_plot); + plot_real_setTitle(&rsrp_plot, "RSRP"); + plot_real_setLabels(&rsrp_plot, "subframe index", "dBm"); + plot_real_setYAxisScale(&rsrp_plot, 20, 50); + + plot_real_addToWindowGrid(&pce, (char*)"pdsch_ue", 0, 1); + plot_real_addToWindowGrid(&rsrp_plot, (char*)"pdsch_ue", 1, 0); + plot_real_addToWindowGrid(&p_sync, (char*)"pdsch_ue", 1, 1); + } + + while (1) { + sem_wait(&plot_sem); + + if (!prog_args.disable_plots_except_constellation) { + for (i = 0; i < nof_re; i++) { + tmp_plot[i] = 20 * log10f(cabsf(ue_dl.sf_symbols[i])); + if (isinf(tmp_plot[i])) { + tmp_plot[i] = -80; + } + } + int numpoints = SRSLTE_NRE * 2; + bzero(tmp_plot2, sizeof(float) * numpoints); + int g = (numpoints - SRSLTE_NRE) / 2; + for (i = 0; i < 12 * ue_dl.cell.base.nof_prb; i++) { + tmp_plot2[g + i] = 20 * log10(cabsf(ue_dl.ce[0][i])); + if (isinf(tmp_plot2[g + i])) { + tmp_plot2[g + i] = -80; + } + } + plot_real_setNewData(&pce, tmp_plot2, numpoints); + + if (!prog_args.input_file_name) { + if (plot_track) { + srslte_npss_synch_t* pss_obj = &ue_sync.strack.npss; + int max = srslte_vec_max_fi(pss_obj->conv_output_avg, pss_obj->frame_size + pss_obj->fft_size - 1); + srslte_vec_sc_prod_fff(pss_obj->conv_output_avg, + 1 / pss_obj->conv_output_avg[max], + tmp_plot2, + pss_obj->frame_size + pss_obj->fft_size - 1); + plot_real_setNewData(&p_sync, &tmp_plot2[max - NPSS_FIND_PLOT_WIDTH / 2], NPSS_FIND_PLOT_WIDTH); + } else { + int len = SRSLTE_NPSS_CORR_FILTER_LEN + ue_sync.sfind.npss.frame_size - 1; + int max = srslte_vec_max_fi(ue_sync.sfind.npss.conv_output_avg, len); + srslte_vec_sc_prod_fff( + ue_sync.sfind.npss.conv_output_avg, 1 / ue_sync.sfind.npss.conv_output_avg[max], tmp_plot2, len); + plot_real_setNewData(&p_sync, tmp_plot2, len); + } + } + + // get current RSRP estimate + rsrp_lin = SRSLTE_VEC_EMA(srslte_chest_dl_nbiot_get_rsrp(&ue_dl.chest), rsrp_lin, 0.05); + rsrp_table[rsrp_table_index++] = 10 * log10(rsrp_lin); + if (rsrp_table_index == rsrp_num_plot) { + rsrp_table_index = 0; + } + plot_real_setNewData(&rsrp_plot, rsrp_table, rsrp_num_plot); + } + + // check if NPDSCH or NPDCCH has been received + if (ue_dl.npdsch_cfg.nbits.nof_re) { + // plot NPDSCH + plot_scatter_setNewData(&constellation_plot, ue_dl.npdsch.d, ue_dl.npdsch_cfg.nbits.nof_re); + + } else if (ue_dl.npdcch.num_decoded_symbols) { + // plot NPDCCH + plot_scatter_setNewData(&constellation_plot, ue_dl.npdcch.d, ue_dl.npdcch.num_decoded_symbols); + } + } + + return NULL; +} + +void init_plots() +{ + if (sem_init(&plot_sem, 0, 0)) { + perror("sem_init"); + exit(-1); + } + + pthread_attr_t attr; + struct sched_param param; + param.sched_priority = 0; + pthread_attr_init(&attr); + pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + pthread_attr_setschedparam(&attr, ¶m); + if (pthread_create(&plot_thread, NULL, plot_thread_run, NULL)) { + perror("pthread_create"); + exit(-1); + } +} + +#endif // ENABLE_GUI diff --git a/lib/examples/test/CMakeLists.txt b/lib/examples/test/CMakeLists.txt new file mode 100644 index 000000000..15bdb219f --- /dev/null +++ b/lib/examples/test/CMakeLists.txt @@ -0,0 +1,70 @@ +# +# Copyright 2013-2020 Software Radio Systems Limited +# +# This file is part of srsLTE +# +# srsLTE 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. +# +# srsLTE 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/. +# + +######################################################################## +# NPDSCH_ENODEB/UE TESTS +######################################################################## + +SET(CTEST_OUTPUT_ON_FAILURE TRUE) + +# Run eNB and generate DL signal +# - we generate 512 DL frames +# - schedulingInfoSIB1-NB-r13 is 2, so 16 SIB repetitions are used +# - each frame contains a NPDCCH and NPDSCH with TBS=24 for RNTI 0x1234 +set(ARG "-o;/tmp/test.bin;-n;512;-t;2") +add_test(NAME enb1 + COMMAND ${CMAKE_COMMAND} -DCMD=$ "-DARG=${ARG}" -V -P ${CMAKE_CURRENT_SOURCE_DIR}/iqtests.cmake) + +# Run npdsch_npdcch file tests + +# Try to decode SIB (-k) +set(ARG "-i;/tmp/test.bin;-m;512;-t;2;-w;0;-k") +add_test(NAME npdsch_npdcch_file1 + COMMAND ${CMAKE_COMMAND} -DCMD=$ "-DARG=${ARG}" -V -P ${CMAKE_CURRENT_SOURCE_DIR}/iqtests.cmake) +# Specify test, SIB1 should be decoded once +set_tests_properties (npdsch_npdcch_file1 PROPERTIES PASS_REGULAR_EXPRESSION "pkt_ok=1") + + +# Try to decode NPDCCH+NPDSCH for user +set(ARG "-i;/tmp/test.bin;-m;512;-t;2;-w;0;-r;0x1234") +add_test(NAME npdsch_npdcch_file2 + COMMAND ${CMAKE_COMMAND} -DCMD=$ "-DARG=${ARG}" -V -P ${CMAKE_CURRENT_SOURCE_DIR}/iqtests.cmake) +# Specify test +set_property(TEST npdsch_npdcch_file2 PROPERTY PASS_REGULAR_EXPRESSION "pkt_ok=512") + +# Run eNB and generate DL signal +# - we generate 10 DL frames +# - schedulingInfoSIB1-NB-r13 is 0, so 4 SIB repetitions are used +# - i_sf is 1, so two subframes +# - i_rep is 2, so four repetitions +# - i_mcs is 4, so TBS of 120 +# - each NPDSCH lasts over 8 subframes in total, starting in sf_idx, +# so in total, two frames are needed for NPDCCH+NPDSCH, hence, 10/2=5 frames +# should be received +set(ARG "-o;/tmp/test.bin;-n;10;-t;0;-i;1;-m;4;-r;2") +add_test(NAME enb2 + COMMAND ${CMAKE_COMMAND} -DCMD=$ "-DARG=${ARG}" -V -P ${CMAKE_CURRENT_SOURCE_DIR}/iqtests.cmake) + +# Try to decode NPDCCH+NPDSCH for user +set(ARG "-i;/tmp/test.bin;-m;512;-w;0;-r;0x1234") +add_test(NAME npdsch_npdcch_file3 + COMMAND ${CMAKE_COMMAND} -DCMD=$ "-DARG=${ARG}" -V -P ${CMAKE_CURRENT_SOURCE_DIR}/iqtests.cmake) +# Specify test +set_property(TEST npdsch_npdcch_file3 PROPERTY PASS_REGULAR_EXPRESSION "pkt_ok=5") diff --git a/lib/examples/test/iqtests.cmake b/lib/examples/test/iqtests.cmake new file mode 100644 index 000000000..b148bc135 --- /dev/null +++ b/lib/examples/test/iqtests.cmake @@ -0,0 +1,28 @@ +# +# Copyright 2013-2020 Software Radio Systems Limited +# +# This file is part of srsLTE +# +# srsLTE 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. +# +# srsLTE 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/. +# + +message("CMD: " ${CMD}) +message("ARG: " ${ARG}) +execute_process(COMMAND ${CMD} ${ARG} + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ) +message("RESULT: " ${result}) +message("OUTPUT: " ${output}) diff --git a/lib/include/srslte/phy/phch/npdsch.h b/lib/include/srslte/phy/phch/npdsch.h new file mode 100644 index 000000000..eeb37398b --- /dev/null +++ b/lib/include/srslte/phy/phch/npdsch.h @@ -0,0 +1,160 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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/. + * + */ + +#ifndef SRSLTE_NPDSCH_H +#define SRSLTE_NPDSCH_H + +#include "srslte/config.h" +#include "srslte/phy/common/phy_common.h" +#include "srslte/phy/fec/convcoder.h" +#include "srslte/phy/fec/crc.h" +#include "srslte/phy/mimo/layermap.h" +#include "srslte/phy/mimo/precoding.h" +#include "srslte/phy/modem/demod_soft.h" +#include "srslte/phy/modem/mod.h" +#include "srslte/phy/phch/dci.h" +#include "srslte/phy/phch/npdsch_cfg.h" +#include "srslte/phy/phch/regs.h" +#include "srslte/phy/phch/sch.h" +#include "srslte/phy/scrambling/scrambling.h" + +#define SRSLTE_NPDSCH_MAX_RE \ + (SRSLTE_CP_NORM_SF_NSYMB * SRSLTE_NRE - 8) ///< Full PRB minus 8 RE for NRS (one antenna port) +#define SRSLTE_NPDSCH_MAX_TBS 680 ///< Max TBS in Rel13 NB-IoT +#define SRSLTE_NPDSCH_CRC_LEN (24) +#define SRSLTE_NPDSCH_MAX_TBS_CRC (SRSLTE_NPDSCH_MAX_TBS + SRSLTE_NPDSCH_CRC_LEN) +#define SRSLTE_NPDSCH_MAX_TBS_ENC (3 * SRSLTE_NPDSCH_MAX_TBS_CRC) +#define SRSLTE_NPDSCH_MAX_NOF_SF 10 +#define SRSLTE_NPDSCH_NUM_SEQ (2 * SRSLTE_NOF_SF_X_FRAME) ///< for even and odd numbered SFNs + +/* @brief Narrowband Physical Downlink shared channel (NPDSCH) + * + * Reference: 3GPP TS 36.211 version 13.2.0 Release 13 Sec. 10.2.3 + */ +typedef struct SRSLTE_API { + srslte_nbiot_cell_t cell; + uint32_t max_re; + bool rnti_is_set; + uint16_t rnti; + + // buffers + uint8_t data[SRSLTE_NPDSCH_MAX_TBS_CRC]; + uint8_t data_enc[SRSLTE_NPDSCH_MAX_TBS_ENC]; + float rm_f[SRSLTE_NPDSCH_MAX_TBS_ENC]; + cf_t* ce[SRSLTE_MAX_PORTS]; + cf_t* symbols[SRSLTE_MAX_PORTS]; + cf_t* sib_symbols[SRSLTE_MAX_PORTS]; // extra buffer for SIB1 symbols as they may be interleaved with other NPDSCH + cf_t* tx_syms[SRSLTE_MAX_PORTS]; // pointer to either symbols or sib1_symbols + cf_t* x[SRSLTE_MAX_PORTS]; + cf_t* d; + + float* llr; + uint8_t* temp; + uint8_t* rm_b; + + // tx & rx objects + srslte_modem_table_t mod; + srslte_viterbi_t decoder; + srslte_sequence_t seq[SRSLTE_NPDSCH_NUM_SEQ]; + srslte_crc_t crc; + srslte_convcoder_t encoder; +} srslte_npdsch_t; + +typedef struct { + uint16_t hyper_sfn; + // TODO: add all other fields +} srslte_sys_info_block_type_1_nb_t; + +SRSLTE_API int srslte_npdsch_init(srslte_npdsch_t* q); + +SRSLTE_API void srslte_npdsch_free(srslte_npdsch_t* q); + +SRSLTE_API int srslte_npdsch_set_cell(srslte_npdsch_t* q, srslte_nbiot_cell_t cell); + +SRSLTE_API int srslte_npdsch_set_rnti(srslte_npdsch_t* q, uint16_t rnti); + +SRSLTE_API int srslte_npdsch_cfg(srslte_npdsch_cfg_t* cfg, + srslte_nbiot_cell_t cell, + srslte_ra_nbiot_dl_grant_t* grant, + uint32_t sf_idx); + +SRSLTE_API int srslte_npdsch_encode(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_tx_t* softbuffer, + uint8_t* data, + cf_t* sf_symbols[SRSLTE_MAX_PORTS]); + +SRSLTE_API int srslte_npdsch_encode_rnti_idx(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_tx_t* softbuffer, + uint8_t* data, + uint32_t rnti_idx, + cf_t* sf_symbols[SRSLTE_MAX_PORTS]); + +SRSLTE_API int srslte_npdsch_encode_rnti(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_tx_t* softbuffer, + uint8_t* data, + uint16_t rnti, + cf_t* sf_symbols[SRSLTE_MAX_PORTS]); + +SRSLTE_API int srslte_npdsch_encode_seq(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_tx_t* softbuffer, + uint8_t* data, + srslte_sequence_t* seq, + cf_t* sf_symbols[SRSLTE_MAX_PORTS]); + +SRSLTE_API int srslte_npdsch_decode(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_rx_t* softbuffer, + cf_t* sf_symbols, + cf_t* ce[SRSLTE_MAX_PORTS], + float noise_estimate, + uint32_t sfn, + uint8_t* data); + +SRSLTE_API int srslte_npdsch_decode_rnti(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_rx_t* softbuffer, + cf_t* sf_symbols, + cf_t* ce[SRSLTE_MAX_PORTS], + float noise_estimate, + uint16_t rnti, + uint32_t sfn, + uint8_t* data, + uint32_t rep_counter); + +SRSLTE_API int +srslte_npdsch_rm_and_decode(srslte_npdsch_t* q, srslte_npdsch_cfg_t* cfg, float* softbits, uint8_t* data); + +SRSLTE_API int +srslte_npdsch_cp(srslte_npdsch_t* q, cf_t* input, cf_t* output, srslte_ra_nbiot_dl_grant_t* grant, bool put); + +SRSLTE_API float srslte_npdsch_average_noi(srslte_npdsch_t* q); + +SRSLTE_API uint32_t srslte_npdsch_last_noi(srslte_npdsch_t* q); + +SRSLTE_API void srslte_npdsch_sib1_pack(srslte_cell_t* cell, srslte_sys_info_block_type_1_nb_t* sib, uint8_t* payload); + +SRSLTE_API void srslte_npdsch_sib1_unpack(uint8_t* const msg, srslte_sys_info_block_type_1_nb_t* sib); + +#endif // SRSLTE_NPDSCH_H diff --git a/lib/include/srslte/phy/phch/npdsch_cfg.h b/lib/include/srslte/phy/phch/npdsch_cfg.h new file mode 100644 index 000000000..85e850781 --- /dev/null +++ b/lib/include/srslte/phy/phch/npdsch_cfg.h @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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/. + * + */ + +#ifndef SRSLTE_NPDSCH_CFG_H +#define SRSLTE_NPDSCH_CFG_H + +#include "srslte/phy/phch/ra_nbiot.h" + +/* + * @brief Narrowband Physical downlink shared channel configuration + * + * Reference: 3GPP TS 36.211 version 13.2.0 Release 13 Sec. 10.2.3 + */ +typedef struct SRSLTE_API { + srslte_ra_nbiot_dl_grant_t grant; + srslte_ra_nbits_t nbits; + bool is_encoded; + bool has_bcch; // Whether this NPDSCH is carrying the BCCH + uint32_t sf_idx; // The current idx within the entire NPDSCH + uint32_t rep_idx; // The current repetion within this NPDSCH + uint32_t num_sf; // Total number of subframes tx'ed in this NPDSCH +} srslte_npdsch_cfg_t; + +#endif // SRSLTE_NPDSCH_CFG_H diff --git a/lib/include/srslte/phy/ue/ue_dl_nbiot.h b/lib/include/srslte/phy/ue/ue_dl_nbiot.h new file mode 100644 index 000000000..90a5e7beb --- /dev/null +++ b/lib/include/srslte/phy/ue/ue_dl_nbiot.h @@ -0,0 +1,190 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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/. + * + */ + +#ifndef SRSLTE_UE_DL_NBIOT_H +#define SRSLTE_UE_DL_NBIOT_H + +#include + +#include "srslte/phy/ch_estimation/chest_dl_nbiot.h" +#include "srslte/phy/common/phy_common.h" +#include "srslte/phy/dft/ofdm.h" + +#include "srslte/phy/phch/dci_nbiot.h" +#include "srslte/phy/phch/npdcch.h" +#include "srslte/phy/phch/npdsch.h" +#include "srslte/phy/phch/npdsch_cfg.h" +#include "srslte/phy/phch/ra_nbiot.h" + +#include "srslte/phy/sync/cfo.h" + +#include "srslte/phy/utils/debug.h" +#include "srslte/phy/utils/vector.h" + +#include "srslte/config.h" + +#define SRSLTE_NBIOT_EXPECT_MORE_SF -3 // returned when expecting more subframes for successful decoding +#define SRSLTE_NBIOT_UE_DL_FOUND_DCI -4 // returned when DCI for given RNTI was found +#define SRSLTE_NBIOT_UE_DL_SKIP_SF -5 + +/* + * @brief Narrowband UE downlink object. + * + * This module is a frontend to all the downlink data and control + * channel processing modules. + */ +typedef struct SRSLTE_API { + srslte_npdcch_t npdcch; + srslte_npdsch_t npdsch; + srslte_ofdm_t fft; + srslte_chest_dl_nbiot_t chest; + + srslte_cfo_t sfo_correct; + + srslte_softbuffer_rx_t softbuffer; + srslte_nbiot_cell_t cell; + srslte_mib_nb_t mib; + bool mib_set; + + cf_t* sf_symbols; // this buffer holds the symbols of the current subframe + cf_t* sf_buffer; // this buffer holds multiple subframes + cf_t* ce[SRSLTE_MAX_PORTS]; + cf_t* ce_buffer[SRSLTE_MAX_PORTS]; + float* llr; // Buffer to hold soft-bits for later combining repetitions + + uint32_t pkt_errors; + uint32_t pkts_total; + uint32_t pkts_ok; + uint32_t nof_detected; + uint32_t bits_total; + + // DL configuration for "normal" transmissions + bool has_dl_grant; + srslte_npdsch_cfg_t npdsch_cfg; + + // DL configuration for SIB1 transmissions + uint32_t sib1_sfn[4 * SIB1_NB_MAX_REP]; // there are 4 SIB1 TTIs in each hyper frame + srslte_nbiot_si_params_t si_params[SRSLTE_NBIOT_SI_TYPE_NITEMS]; + bool si_tti[10240]; // We trade memory consumption for speed + + uint16_t current_rnti; + uint32_t last_n_cce; + srslte_dci_location_t last_location; + + float sample_offset; +} srslte_nbiot_ue_dl_t; + +// This function shall be called just after the initial synchronization +SRSLTE_API int srslte_nbiot_ue_dl_init(srslte_nbiot_ue_dl_t* q, + cf_t* in_buffer[SRSLTE_MAX_PORTS], + uint32_t max_prb, + uint32_t nof_rx_antennas); + +SRSLTE_API void srslte_nbiot_ue_dl_free(srslte_nbiot_ue_dl_t* q); + +SRSLTE_API int srslte_nbiot_ue_dl_set_cell(srslte_nbiot_ue_dl_t* q, srslte_nbiot_cell_t cell); + +SRSLTE_API int srslte_nbiot_ue_dl_decode_fft_estimate(srslte_nbiot_ue_dl_t* q, uint32_t sf_idx, bool is_dl_sf); + +SRSLTE_API int srslte_nbiot_ue_dl_decode_estimate(srslte_nbiot_ue_dl_t* q, uint32_t sf_idx); + +SRSLTE_API int +srslte_nbiot_ue_dl_cfg_grant(srslte_nbiot_ue_dl_t* q, srslte_ra_nbiot_dl_grant_t* grant, uint32_t sf_idx); + +SRSLTE_API int +srslte_nbiot_ue_dl_find_dl_dci(srslte_nbiot_ue_dl_t* q, uint32_t sf_idx, uint16_t rnti, srslte_dci_msg_t* dci_msg); + +int srslte_nbiot_ue_dl_find_dl_dci_type_siprarnti(srslte_nbiot_ue_dl_t* q, uint16_t rnti, srslte_dci_msg_t* dci_msg); + +int srslte_nbiot_ue_dl_find_dl_dci_type_crnti(srslte_nbiot_ue_dl_t* q, + uint32_t sf_idx, + uint16_t rnti, + srslte_dci_msg_t* dci_msg); + +SRSLTE_API int +srslte_nbiot_ue_dl_find_ul_dci(srslte_nbiot_ue_dl_t* q, uint32_t tti, uint32_t rnti, srslte_dci_msg_t* dci_msg); + +SRSLTE_API uint32_t srslte_nbiot_ue_dl_get_ncce(srslte_nbiot_ue_dl_t* q); + +SRSLTE_API void srslte_nbiot_ue_dl_set_sample_offset(srslte_nbiot_ue_dl_t* q, float sample_offset); + +SRSLTE_API int +srslte_nbiot_ue_dl_decode(srslte_nbiot_ue_dl_t* q, cf_t* input, uint8_t* data, uint32_t sfn, uint32_t sf_idx); + +SRSLTE_API int srslte_nbiot_ue_dl_decode_npdcch(srslte_nbiot_ue_dl_t* q, + cf_t* input, + uint32_t sfn, + uint32_t sf_idx, + uint16_t rnti, + srslte_dci_msg_t* dci_msg); + +SRSLTE_API int srslte_nbiot_ue_dl_decode_npdsch(srslte_nbiot_ue_dl_t* q, + cf_t* input, + uint8_t* data, + uint32_t sfn, + uint32_t sf_idx, + uint16_t rnti); + +int srslte_nbiot_ue_dl_decode_npdsch_bcch(srslte_nbiot_ue_dl_t* q, uint8_t* data, uint32_t tti); + +int srslte_nbiot_ue_dl_decode_npdsch_no_bcch(srslte_nbiot_ue_dl_t* q, uint8_t* data, uint32_t tti, uint16_t rnti); + +void srslte_nbiot_ue_dl_tb_decoded(srslte_nbiot_ue_dl_t* q, uint8_t* data); + +SRSLTE_API void srslte_nbiot_ue_dl_reset(srslte_nbiot_ue_dl_t* q); + +SRSLTE_API void srslte_nbiot_ue_dl_set_rnti(srslte_nbiot_ue_dl_t* q, uint16_t rnti); + +SRSLTE_API void srslte_nbiot_ue_dl_set_mib(srslte_nbiot_ue_dl_t* q, srslte_mib_nb_t mib); + +SRSLTE_API void srslte_nbiot_ue_dl_decode_sib1(srslte_nbiot_ue_dl_t* q, uint32_t current_sfn); + +SRSLTE_API void +srslte_nbiot_ue_dl_get_sib1_grant(srslte_nbiot_ue_dl_t* q, uint32_t sfn, srslte_ra_nbiot_dl_grant_t* grant); + +SRSLTE_API void srslte_nbiot_ue_dl_decode_sib(srslte_nbiot_ue_dl_t* q, + uint32_t hfn, + uint32_t sfn, + srslte_nbiot_si_type_t type, + srslte_nbiot_si_params_t params); + +SRSLTE_API void srslte_nbiot_ue_dl_get_next_si_sfn(uint32_t current_hfn, + uint32_t current_sfn, + srslte_nbiot_si_params_t params, + uint32_t* si_hfn, + uint32_t* si_sfn); + +SRSLTE_API void +srslte_nbiot_ue_dl_set_si_params(srslte_nbiot_ue_dl_t* q, srslte_nbiot_si_type_t type, srslte_nbiot_si_params_t params); + +SRSLTE_API bool srslte_nbiot_ue_dl_is_sib1_sf(srslte_nbiot_ue_dl_t* q, uint32_t sfn, uint32_t sf_idx); + +SRSLTE_API void srslte_nbiot_ue_dl_set_grant(srslte_nbiot_ue_dl_t* q, srslte_ra_nbiot_dl_grant_t* grant); + +SRSLTE_API void srslte_nbiot_ue_dl_flush_grant(srslte_nbiot_ue_dl_t* q); + +SRSLTE_API bool srslte_nbiot_ue_dl_has_grant(srslte_nbiot_ue_dl_t* q); + +SRSLTE_API void srslte_nbiot_ue_dl_check_grant(srslte_nbiot_ue_dl_t* q, srslte_ra_nbiot_dl_grant_t* grant); + +SRSLTE_API void srslte_nbiot_ue_dl_save_signal(srslte_nbiot_ue_dl_t* q, cf_t* input, uint32_t tti, uint32_t sf_idx); + +#endif // SRSLTE_UE_DL_NBIOT_H diff --git a/lib/src/phy/phch/npdsch.c b/lib/src/phy/phch/npdsch.c new file mode 100644 index 000000000..3dc274beb --- /dev/null +++ b/lib/src/phy/phch/npdsch.c @@ -0,0 +1,797 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 +#include +#include +#include +#include +#include +#include + +#include "prb_dl.h" +#include "srslte/phy/common/phy_common.h" +#include "srslte/phy/fec/rm_conv.h" +#include "srslte/phy/phch/npdsch.h" +#include "srslte/phy/utils/bit.h" +#include "srslte/phy/utils/debug.h" +#include "srslte/phy/utils/vector.h" + +#define CURRENT_SFLEN_RE SRSLTE_SF_LEN_RE(q->cell.base.nof_prb, q->cell.base.cp) + +#define DUMP_SIGNALS 0 +#define RE_EXT_DEBUG 0 + +int srslte_npdsch_cp(srslte_npdsch_t* q, cf_t* input, cf_t* output, srslte_ra_nbiot_dl_grant_t* grant, bool put) +{ + uint32_t l, nof_lte_refs, nof_nbiot_refs; + cf_t * in_ptr = input, *out_ptr = output; + +#if RE_EXT_DEBUG + int num_extracted = 0; +#endif + + // sanity check + if (q == NULL || input == NULL || output == NULL || grant == NULL) { + return 0; + } + + if (put) { + out_ptr += (grant->l_start * q->cell.base.nof_prb * SRSLTE_NRE) + q->cell.nbiot_prb * SRSLTE_NRE; + } else { + in_ptr += (grant->l_start * q->cell.base.nof_prb * SRSLTE_NRE) + q->cell.nbiot_prb * SRSLTE_NRE; + } + + if (q->cell.nof_ports == 1) { + nof_nbiot_refs = 2; + } else { + nof_nbiot_refs = 4; + } + + if (q->cell.base.nof_ports == 1) { + nof_lte_refs = 2; + } else { + nof_lte_refs = 4; + } + + bool skip_crs = false; + if (q->cell.mode == SRSLTE_NBIOT_MODE_INBAND_SAME_PCI || q->cell.mode == SRSLTE_NBIOT_MODE_INBAND_DIFFERENT_PCI) { + skip_crs = true; + } + + if (q->cell.mode == SRSLTE_NBIOT_MODE_INBAND_SAME_PCI && q->cell.n_id_ncell != q->cell.base.id) { + fprintf(stderr, + "Cell IDs must match in operation mode inband same PCI (%d != %d)\n", + q->cell.n_id_ncell, + q->cell.base.id); + return 0; + } + + // start mapping at specified OFDM symbol + for (l = grant->l_start; l < SRSLTE_CP_NORM_SF_NSYMB; l++) { + uint32_t delta = (q->cell.base.nof_prb - 1) * SRSLTE_NRE; // the number of REs skipped in each OFDM symbol + uint32_t offset = 0; // the number of REs left out before start of the REF signal RE + if (l == 5 || l == 6 || l == 12 || l == 13) { + // always skip NRS + if (nof_nbiot_refs == 2) { + if (l == 5 || l == 12) { + offset = q->cell.n_id_ncell % 6; + delta = q->cell.n_id_ncell % 6 == 5 ? 1 : 0; + } else { + offset = (q->cell.n_id_ncell + 3) % 6; + delta = (q->cell.n_id_ncell + 3) % 6 == 5 ? 1 : 0; + } + } else if (nof_nbiot_refs == 4) { + offset = q->cell.n_id_ncell % 3; + delta = (q->cell.n_id_ncell + ((q->cell.n_id_ncell >= 5) ? 0 : 3)) % 6 == 5 ? 1 : 0; + } else { + fprintf(stderr, "Error %d NB-IoT reference symbols not supported.\n", nof_nbiot_refs); + return SRSLTE_ERROR; + } + prb_cp_ref(&in_ptr, &out_ptr, offset, nof_nbiot_refs, nof_nbiot_refs, put); + } else if ((l == 0 || l == 4 || l == 7 || l == 11) && skip_crs) { + // skip LTE's CRS (TODO: use base cell ID?) + if (nof_lte_refs == 2) { + if (l == 0 || l == 7) { + offset = q->cell.base.id % 6; + delta = (q->cell.base.id + 3) % 6 == 2 ? 1 : 0; + } else if (l == 4 || l == 11) { + offset = (q->cell.base.id + 3) % 6; + delta = (q->cell.base.id + ((q->cell.base.id <= 5) ? 3 : 0)) % 6 == 5 ? 1 : 0; + } + } else { + offset = q->cell.base.id % 3; + delta = q->cell.base.id % 3 == 2 ? 1 : 0; + } + prb_cp_ref(&in_ptr, &out_ptr, offset, nof_lte_refs, nof_lte_refs, put); + } else { + // occupy entire symbol + prb_cp(&in_ptr, &out_ptr, 1); + } + + if (put) { + out_ptr += delta; + } else { + in_ptr += delta; + } + +#if RE_EXT_DEBUG + printf("\nl=%d, delta=%d offset=%d\n", l, delta, offset); + uint32_t num_extracted_this_sym = abs((int)(output - out_ptr)) - num_extracted; + printf(" - extracted total of %d RE after symbol %d (this symbol=%d)\n", + abs((int)(output - out_ptr)), + l, + num_extracted_this_sym); + srslte_vec_fprint_c(stdout, &output[num_extracted], num_extracted_this_sym); + num_extracted = abs((int)(output - out_ptr)); +#endif + } + + int r; + if (put) { + r = abs((int)(input - in_ptr)); + } else { + r = abs((int)(output - out_ptr)); + } + + return r; +} + +/** + * Puts NPDSCH in the subframe + * + * Returns the number of symbols written to sf_symbols + * + * 36.211 10.3 section 6.3.5 + */ +int srslte_npdsch_put(srslte_npdsch_t* q, cf_t* symbols, cf_t* sf_symbols, srslte_ra_nbiot_dl_grant_t* grant) +{ + return srslte_npdsch_cp(q, symbols, sf_symbols, grant, true); +} + +/** + * Extracts NPDSCH from the subframe + * + * Returns the number of symbols read + * + * 36.211 10.3 section 6.3.5 + */ +int srslte_npdsch_get(srslte_npdsch_t* q, cf_t* sf_symbols, cf_t* symbols, srslte_ra_nbiot_dl_grant_t* grant) +{ + return srslte_npdsch_cp(q, sf_symbols, symbols, grant, false); +} + +/** Initializes the NPDSCH transmitter and receiver */ +int srslte_npdsch_init(srslte_npdsch_t* q) +{ + int ret = SRSLTE_ERROR_INVALID_INPUTS; + + if (q != NULL) { + bzero(q, sizeof(srslte_npdsch_t)); + ret = SRSLTE_ERROR; + + q->max_re = SRSLTE_NPDSCH_MAX_RE * SRSLTE_NPDSCH_MAX_NOF_SF; + q->rnti_is_set = false; + + INFO("Init NPDSCH: max_re's: %d\n", q->max_re); + + if (srslte_modem_table_lte(&q->mod, SRSLTE_MOD_QPSK)) { + goto clean; + } + + int poly[3] = {0x6D, 0x4F, 0x57}; + if (srslte_viterbi_init(&q->decoder, SRSLTE_VITERBI_37, poly, SRSLTE_NPDSCH_MAX_TBS_CRC, true)) { + goto clean; + } + if (srslte_crc_init(&q->crc, SRSLTE_LTE_CRC24A, SRSLTE_NPDSCH_CRC_LEN)) { + goto clean; + } + q->encoder.K = 7; + q->encoder.R = 3; + q->encoder.tail_biting = true; + memcpy(q->encoder.poly, poly, 3 * sizeof(int)); + + q->d = srslte_vec_malloc(sizeof(cf_t) * q->max_re); + if (!q->d) { + goto clean; + } + for (uint32_t i = 0; i < SRSLTE_MAX_PORTS; i++) { + q->ce[i] = srslte_vec_malloc(sizeof(cf_t) * q->max_re); + if (!q->ce[i]) { + goto clean; + } + for (int k = 0; k < q->max_re / 2; k++) { + q->ce[i][k] = 1; + } + q->x[i] = srslte_vec_malloc(sizeof(cf_t) * q->max_re); + if (!q->x[i]) { + goto clean; + } + q->symbols[i] = srslte_vec_malloc(sizeof(cf_t) * q->max_re); + if (!q->symbols[i]) { + goto clean; + } + q->sib_symbols[i] = srslte_vec_malloc(sizeof(cf_t) * q->max_re); + if (!q->sib_symbols[i]) { + goto clean; + } + } + q->llr = srslte_vec_malloc(sizeof(float) * q->max_re * 2); + if (!q->llr) { + goto clean; + } + bzero(q->llr, sizeof(float) * q->max_re * 2); + + q->temp = srslte_vec_malloc(sizeof(uint8_t) * SRSLTE_NPDSCH_MAX_TBS_CRC); + if (!q->temp) { + goto clean; + } + q->rm_b = srslte_vec_malloc(sizeof(float) * q->max_re * 2); + if (!q->rm_b) { + goto clean; + } + ret = SRSLTE_SUCCESS; + } +clean: + if (ret == SRSLTE_ERROR) { + srslte_npdsch_free(q); + } + return ret; +} + +void srslte_npdsch_free(srslte_npdsch_t* q) +{ + if (q->d) { + free(q->d); + } + if (q->temp) { + free(q->temp); + } + if (q->rm_b) { + free(q->rm_b); + } + if (q->llr) { + free(q->llr); + } + for (uint32_t i = 0; i < SRSLTE_MAX_PORTS; i++) { + if (q->ce[i]) { + free(q->ce[i]); + } + if (q->x[i]) { + free(q->x[i]); + } + if (q->symbols[i]) { + free(q->symbols[i]); + } + if (q->sib_symbols[i]) { + free(q->sib_symbols[i]); + } + } + for (uint32_t i = 0; i < SRSLTE_NPDSCH_NUM_SEQ; i++) { + srslte_sequence_free(&q->seq[i]); + } + + srslte_modem_table_free(&q->mod); + srslte_viterbi_free(&q->decoder); + bzero(q, sizeof(srslte_npdsch_t)); +} + +int srslte_npdsch_set_cell(srslte_npdsch_t* q, srslte_nbiot_cell_t cell) +{ + int ret = SRSLTE_ERROR_INVALID_INPUTS; + + if (q != NULL && srslte_nbiot_cell_isvalid(&cell)) { + q->cell = cell; + + INFO("NPDSCH: Cell config n_id_ncell=%d, %d ports, %d PRBs base cell, max_symbols: %d\n", + q->cell.n_id_ncell, + q->cell.nof_ports, + q->cell.base.nof_prb, + q->max_re); + + ret = SRSLTE_SUCCESS; + } + return ret; +} + +/* Precalculate the NPDSCH scramble sequences for a given RNTI. This function takes a while + * to execute, so shall be called once the final C-RNTI has been allocated for the session. + * It computes sequences for all subframes for both even and odd SFN's, a total of 20 + */ +int srslte_npdsch_set_rnti(srslte_npdsch_t* q, uint16_t rnti) +{ + for (int k = 0; k < 2; k++) { + for (int i = 0; i < SRSLTE_NOF_SF_X_FRAME; i++) { + if (srslte_sequence_npdsch(&q->seq[k * SRSLTE_NOF_SF_X_FRAME + i], + rnti, + 0, + k, + 2 * i, + q->cell.n_id_ncell, + q->max_re * srslte_mod_bits_x_symbol(SRSLTE_MOD_QPSK))) { + return SRSLTE_ERROR; + } + } + } + + q->rnti_is_set = true; + q->rnti = rnti; + return SRSLTE_SUCCESS; +} + +int srslte_npdsch_decode(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_rx_t* softbuffer, + cf_t* sf_symbols, + cf_t* ce[SRSLTE_MAX_PORTS], + float noise_estimate, + uint32_t sfn, + uint8_t* data) +{ + if (q != NULL && sf_symbols != NULL && data != NULL && cfg != NULL) { + if (q->rnti_is_set) { + return srslte_npdsch_decode_rnti(q, cfg, softbuffer, sf_symbols, ce, noise_estimate, q->rnti, sfn, data, 0); + } else { + fprintf(stderr, "Must call srslte_npdsch_set_rnti() before calling srslte_npdsch_decode()\n"); + return SRSLTE_ERROR; + } + } else { + return SRSLTE_ERROR_INVALID_INPUTS; + } +} + +/** Decodes the NPDSCH from the received symbols + */ +int srslte_npdsch_decode_rnti(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_rx_t* softbuffer, + cf_t* sf_symbols, + cf_t* ce[SRSLTE_MAX_PORTS], + float noise_estimate, + uint16_t rnti, + uint32_t sfn, + uint8_t* data, + uint32_t rep_counter) +{ + // Set pointers for layermapping & precoding + uint32_t n; + cf_t* x[SRSLTE_MAX_LAYERS]; + + if (q != NULL && sf_symbols != NULL && data != NULL && cfg != NULL) { + INFO("%d.x: Decoding NPDSCH: RNTI: 0x%x, Mod %s, TBS: %d, NofSymbols: %d * %d, NofBitsE: %d * %d\n", + sfn, + rnti, + srslte_mod_string(cfg->grant.mcs[0].mod), + cfg->grant.mcs[0].tbs, + cfg->grant.nof_sf, + cfg->nbits.nof_re, + cfg->grant.nof_sf, + cfg->nbits.nof_bits); + + // number of layers equals number of ports + for (int i = 0; i < q->cell.nof_ports; i++) { + x[i] = q->x[i]; + } + memset(&x[q->cell.nof_ports], 0, sizeof(cf_t*) * (SRSLTE_MAX_LAYERS - q->cell.nof_ports)); + + // extract RE of all subframes of this grant + uint32_t total_syms = 0; + for (int i = 0; i < cfg->grant.nof_sf; i++) { + // extract symbols + n = srslte_npdsch_get(q, &sf_symbols[i * CURRENT_SFLEN_RE], &q->symbols[0][i * cfg->nbits.nof_re], &cfg->grant); + if (n != cfg->nbits.nof_re) { + fprintf(stderr, "Error expecting %d symbols but got %d\n", cfg->nbits.nof_re, n); + return SRSLTE_ERROR; + } + + // extract channel estimates + for (int k = 0; k < q->cell.nof_ports; k++) { + n = srslte_npdsch_get(q, &ce[k][i * CURRENT_SFLEN_RE], &q->ce[k][i * cfg->nbits.nof_re], &cfg->grant); + if (n != cfg->nbits.nof_re) { + fprintf(stderr, "Error expecting %d symbols but got %d\n", cfg->nbits.nof_re, n); + return SRSLTE_ERROR; + } + } + total_syms += cfg->nbits.nof_re; + } + assert(total_syms == cfg->grant.nof_sf * cfg->nbits.nof_re); + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_rx_mapping_output.bin: NPDSCH after extracting symbols\n", 0); + srslte_vec_save_file( + "npdsch_rx_mapping_output.bin", q->symbols[0], cfg->grant.nof_sf * cfg->nbits.nof_re * sizeof(cf_t)); + } +#endif + + /* TODO: only diversity is supported */ + if (q->cell.nof_ports == 1) { + // no need for layer demapping + srslte_predecoding_single( + q->symbols[0], q->ce[0], q->d, NULL, cfg->grant.nof_sf * cfg->nbits.nof_re, 1.0, noise_estimate); + } else { + srslte_predecoding_diversity( + q->symbols[0], q->ce, x, q->cell.nof_ports, cfg->grant.nof_sf * cfg->nbits.nof_re, 1.0); + srslte_layerdemap_diversity( + x, q->d, q->cell.nof_ports, cfg->grant.nof_sf * cfg->nbits.nof_re / q->cell.nof_ports); + } + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_rx_predecode_output.bin: NPDSCH after predecoding symbols\n", 0); + srslte_vec_save_file( + "npdsch_rx_predecode_output.bin", q->d, cfg->grant.nof_sf * cfg->nbits.nof_re * sizeof(cf_t)); + } +#endif + + // demodulate symbols + srslte_demod_soft_demodulate(SRSLTE_MOD_QPSK, q->d, q->llr, cfg->grant.nof_sf * cfg->nbits.nof_re); + +#if 0 + uint8_t demodbuf[320]; + hard_qpsk_demod(q->d,demodbuf,cfg->nbits.nof_re); + + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_rx_demod_output.bin: NPDSCH after (hard) de-modulation\n",0); + srslte_vec_save_file("npdsch_rx_demod_output.bin", demodbuf, cfg->nbits.nof_bits); + } +#endif + + // descramble + if (q->cell.is_r14 && rnti == SRSLTE_SIRNTI) { + srslte_sequence_t seq; + if (srslte_sequence_npdsch_bcch_r14( + &seq, cfg->grant.start_sfn, q->cell.n_id_ncell, cfg->grant.nof_sf * cfg->nbits.nof_bits)) { + return SRSLTE_ERROR; + } + srslte_scrambling_f_offset(&seq, q->llr, 0, cfg->grant.nof_sf * cfg->nbits.nof_bits); + srslte_sequence_free(&seq); + } else { + if (rnti != q->rnti) { + srslte_sequence_t seq; + if (srslte_sequence_npdsch(&seq, + rnti, + 0, + cfg->grant.start_sfn, + 2 * cfg->grant.start_sfidx, + q->cell.n_id_ncell, + cfg->grant.nof_sf * cfg->nbits.nof_bits)) { + return SRSLTE_ERROR; + } + srslte_scrambling_f_offset(&seq, q->llr, 0, cfg->grant.nof_sf * cfg->nbits.nof_bits); + srslte_sequence_free(&seq); + } else { + // odd SFN's take the second half of the seq array + int seq_pos = ((cfg->grant.start_sfn % 2) * SRSLTE_NOF_SF_X_FRAME) + cfg->grant.start_sfidx; + srslte_scrambling_f_offset(&q->seq[seq_pos], q->llr, 0, cfg->grant.nof_sf * cfg->nbits.nof_bits); + } + } + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_rx_descramble_output.bin: NPDSCH after de-scrambling\n", 0); + srslte_vec_save_file("npdsch_rx_descramble_output.bin", q->llr, cfg->nbits.nof_bits); + } +#endif + + // decode only this transmission + return srslte_npdsch_rm_and_decode(q, cfg, q->llr, data); + } else { + fprintf(stderr, "srslte_npdsch_decode_rnti() called with invalid parameters.\n"); + return SRSLTE_ERROR_INVALID_INPUTS; + } +} + +int srslte_npdsch_rm_and_decode(srslte_npdsch_t* q, srslte_npdsch_cfg_t* cfg, float* softbits, uint8_t* data) +{ + // unrate-matching + uint32_t coded_len = 3 * (cfg->grant.mcs[0].tbs + SRSLTE_NPDSCH_CRC_LEN); + bzero(q->rm_f, sizeof(float) * SRSLTE_NPDSCH_MAX_TBS_ENC); + srslte_rm_conv_rx(softbits, cfg->grant.nof_sf * cfg->nbits.nof_bits, q->rm_f, coded_len); + + // TODO: normalization needed? + + // viterbi decoder + srslte_viterbi_decode_f(&q->decoder, q->rm_f, q->temp, cfg->grant.mcs[0].tbs + SRSLTE_NPDSCH_CRC_LEN); + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_rx_viterbidecode_output.bin: NPDSCH after viterbi decoding\n", 0); + srslte_vec_save_file("npdsch_rx_viterbidecode_output.bin", q->temp, cfg->grant.mcs[0].tbs + SRSLTE_NPDSCH_CRC_LEN); + } +#endif + + // verify CRC sum + uint8_t* x = &q->temp[cfg->grant.mcs[0].tbs]; + uint32_t tx_sum = srslte_bit_pack(&x, SRSLTE_NPDSCH_CRC_LEN); + uint32_t rx_sum = srslte_crc_checksum(&q->crc, q->temp, cfg->grant.mcs[0].tbs); + + if (rx_sum == tx_sum && rx_sum != 0) { + srslte_bit_pack_vector(q->temp, data, cfg->grant.mcs[0].tbs); + return SRSLTE_SUCCESS; + } else { + return SRSLTE_ERROR; + } +} + +/* This functions encodes an NPDSCH and maps it on to the provided subframe. + * It only ever writes a single subframe but it can be called multiple times + * to write more subframes of a previously encoded NPDSCH. For this purpose + * it uses the is_encoded flag in the NPDSCH config object. + * The same applies to its sister functions below. + */ +int srslte_npdsch_encode(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_tx_t* softbuffer, + uint8_t* data, + cf_t* sf_symbols[SRSLTE_MAX_PORTS]) +{ + if (q != NULL && data != NULL && cfg != NULL) { + if (q->rnti_is_set) { + return srslte_npdsch_encode_rnti(q, cfg, softbuffer, data, q->rnti, sf_symbols); + } else { + fprintf(stderr, "Must call srslte_npdsch_set_rnti() to set the encoder/decoder RNTI\n"); + return SRSLTE_ERROR; + } + } else { + return SRSLTE_ERROR_INVALID_INPUTS; + } +} + +/** Converts the PDSCH data bits to symbols mapped to the slot ready for transmission + */ +int srslte_npdsch_encode_rnti(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_tx_t* softbuffer, + uint8_t* data, + uint16_t rnti, + cf_t* sf_symbols[SRSLTE_MAX_PORTS]) +{ + if (rnti != q->rnti) { + srslte_sequence_t seq; + // FIXME: skip sequence init if cfg->is_encoded==true + if (srslte_sequence_npdsch(&seq, + rnti, + 0, + cfg->grant.start_sfidx, + 2 * cfg->grant.start_sfidx, + q->cell.n_id_ncell, + cfg->nbits.nof_bits * cfg->grant.nof_sf)) { + return SRSLTE_ERROR; + } + int r = srslte_npdsch_encode_seq(q, cfg, softbuffer, data, &seq, sf_symbols); + srslte_sequence_free(&seq); + return r; + } else { + int seq_pos = ((cfg->grant.start_sfn % 2) * SRSLTE_NOF_SF_X_FRAME) + cfg->grant.start_sfidx; + return srslte_npdsch_encode_seq(q, cfg, softbuffer, data, &q->seq[seq_pos], sf_symbols); + } +} + +int srslte_npdsch_encode_seq(srslte_npdsch_t* q, + srslte_npdsch_cfg_t* cfg, + srslte_softbuffer_tx_t* softbuffer, + uint8_t* data, + srslte_sequence_t* seq, + cf_t* sf_symbols[SRSLTE_MAX_PORTS]) +{ + /* Set pointers for layermapping & precoding */ + cf_t* x[SRSLTE_MAX_LAYERS]; + int ret = SRSLTE_ERROR_INVALID_INPUTS; + + if (q != NULL && data != NULL && cfg != NULL) { + for (int i = 0; i < q->cell.nof_ports; i++) { + if (sf_symbols[i] == NULL) { + return SRSLTE_ERROR_INVALID_INPUTS; + } + + // Set up pointer for Tx symbols + q->tx_syms[i] = (cfg->has_bcch) ? q->sib_symbols[i] : q->symbols[i]; + } + + if (cfg->grant.mcs[0].tbs == 0) { + return SRSLTE_ERROR_INVALID_INPUTS; + } + + if (cfg->nbits.nof_re > q->max_re) { + fprintf(stderr, + "Error too many RE per subframe (%d). NPDSCH configured for %d RE (%d PRB)\n", + cfg->nbits.nof_re, + q->max_re, + q->cell.base.nof_prb); + return SRSLTE_ERROR_INVALID_INPUTS; + } + + // make sure to run run full NPDSCH procedure only once + if (!cfg->is_encoded) { + INFO("Encoding NPDSCH: Mod %s, NofBits: %d, NofSymbols: %d * %d, NofBitsE: %d * %d\n", + srslte_mod_string(cfg->grant.mcs[0].mod), + cfg->grant.mcs[0].tbs, + cfg->grant.nof_sf, + cfg->nbits.nof_re, + cfg->grant.nof_sf, + cfg->nbits.nof_bits); + + /* number of layers equals number of ports */ + for (int i = 0; i < q->cell.nof_ports; i++) { + x[i] = q->x[i]; + } + memset(&x[q->cell.nof_ports], 0, sizeof(cf_t*) * (SRSLTE_MAX_LAYERS - q->cell.nof_ports)); + + int len = cfg->grant.mcs[0].tbs; + + // unpack input + srslte_bit_unpack_vector(data, q->data, len); + + // attach CRC + srslte_crc_attach(&q->crc, q->data, len); + len += SRSLTE_NPDSCH_CRC_LEN; + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_tx_convcoder_input.bin: NPDSCH before convolution coding\n", 0); + srslte_vec_save_file("npdsch_tx_convcoder_input.bin", q->data, len); + } +#endif + + // encode + srslte_convcoder_encode(&q->encoder, q->data, q->data_enc, len); + len *= 3; + + // rate-match to allocated bits and scramble output + srslte_rm_conv_tx(q->data_enc, len, q->rm_b, cfg->nbits.nof_bits * cfg->grant.nof_sf); + len = cfg->nbits.nof_bits * cfg->grant.nof_sf; + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_tx_scramble_input.bin: NPDSCH before scrambling\n", 0); + srslte_vec_save_file("npdsch_tx_scramble_input.bin", q->rm_b, len); + } +#endif + + // scramble + srslte_scrambling_b_offset(seq, q->rm_b, 0, len); + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_tx_mod_input.bin: NPDSCH before modulation\n", 0); + srslte_vec_save_file("npdsch_tx_mod_input.bin", q->rm_b, len); + } +#endif + + // modulate bits + srslte_mod_modulate(&q->mod, (uint8_t*)q->rm_b, q->d, len); + len = cfg->nbits.nof_re * cfg->grant.nof_sf; + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_tx_precode_input.bin: NPDSCH before precoding symbols\n", 0); + srslte_vec_save_file("npdsch_tx_precode_input.bin", q->d, len * sizeof(cf_t)); + } +#endif + + // TODO: only diversity supported + if (q->cell.base.nof_ports > 1) { + srslte_layermap_diversity(q->d, x, q->cell.base.nof_ports, len); + srslte_precoding_diversity(x, q->tx_syms, q->cell.base.nof_ports, len / q->cell.base.nof_ports, 1.0); + } else { + memcpy(q->tx_syms[0], q->d, len * sizeof(cf_t)); + } + +#if DUMP_SIGNALS + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_tx_mapping_input.bin: NPDSCH before mapping to resource elements\n", 0); + srslte_vec_save_file("npdsch_tx_mapping_input.bin", q->tx_syms[0], len * sizeof(cf_t)); + } +#endif + + cfg->is_encoded = true; + } + + // mapping to resource elements + if (cfg->is_encoded) { + INFO("Mapping %d NPDSCH REs, sf_idx=%d/%d rep=%d/%d total=%d/%d\n", + cfg->nbits.nof_re, + cfg->sf_idx + 1, + cfg->grant.nof_sf, + cfg->rep_idx + 1, + cfg->grant.nof_rep, + cfg->num_sf + 1, + cfg->grant.nof_sf * cfg->grant.nof_rep); + for (int i = 0; i < q->cell.nof_ports; i++) { + srslte_npdsch_put(q, &q->tx_syms[i][cfg->sf_idx * cfg->nbits.nof_re], sf_symbols[i], &cfg->grant); + } + cfg->num_sf++; + + // Decide whether we retransmit the same SF or the next + if (cfg->has_bcch) { + // NPDSCH with BCCH is always transmitted in sequence + cfg->sf_idx++; + if (cfg->sf_idx == cfg->grant.nof_sf) { + cfg->rep_idx++; + } + } else { + // NPDSCH without BCCH transmits up to 3 repetitions after another + cfg->rep_idx++; + int m = SRSLTE_MIN(cfg->grant.nof_rep, 4); + if (cfg->rep_idx % m == 0) { + cfg->sf_idx++; + // start with first SF again after all have been tx'ed m-times + if (cfg->sf_idx == cfg->grant.nof_sf) { + cfg->sf_idx = 0; + } else { + cfg->rep_idx -= m; + } + } + } + } + ret = SRSLTE_SUCCESS; + } + return ret; +} + +/* Configures the structure srslte_npdsch_cfg_t from a DL grant. + * If grant is NULL, the grant is assumed to be already stored in cfg->grant + */ +int srslte_npdsch_cfg(srslte_npdsch_cfg_t* cfg, + srslte_nbiot_cell_t cell, + srslte_ra_nbiot_dl_grant_t* grant, + uint32_t sf_idx) +{ + if (cfg) { + if (grant) { + memcpy(&cfg->grant, grant, sizeof(srslte_ra_nbiot_dl_grant_t)); + } + + // Compute number of RE + srslte_ra_nbiot_dl_grant_to_nbits(&cfg->grant, cell, sf_idx, &cfg->nbits); + cfg->sf_idx = 0; + cfg->rep_idx = 0; + cfg->num_sf = 0; + cfg->is_encoded = false; + cfg->has_bcch = cfg->grant.has_sib1; // The UE needs to set this to true for other SIBs too + + return SRSLTE_SUCCESS; + } else { + return SRSLTE_ERROR_INVALID_INPUTS; + } +} + +/** Reads the HFN from the SIB1-NB, according to TS 36.331 v13.2.0 Section 6.7.1 + * + * The function assumes that the 2 LSB read from the MIB-NB are already set inside + * the argument structure. It extracts the 8 MSB from the SIB1 and updates the HFN + * accordingly. + * + * \param msg The packed SIB1-NB + * \param sib The SIB structure containing the MIB-part of the HFN + */ +void srslte_npdsch_sib1_unpack(uint8_t* const msg, srslte_sys_info_block_type_1_nb_t* sib) +{ + uint8_t unpacked[SRSLTE_NPDSCH_MAX_TBS]; + srslte_bit_unpack_vector(msg, unpacked, SRSLTE_NPDSCH_MAX_TBS); + + uint8_t* tmp = unpacked; + if (sib) { + tmp += 12; + sib->hyper_sfn = ((srslte_bit_pack(&tmp, 8) & 0xFF) << 2 | (sib->hyper_sfn & 0x3)) & 0x3FF; + } +} diff --git a/lib/src/phy/phch/test/CMakeLists.txt b/lib/src/phy/phch/test/CMakeLists.txt index 4553fe5d4..9e95b6334 100644 --- a/lib/src/phy/phch/test/CMakeLists.txt +++ b/lib/src/phy/phch/test/CMakeLists.txt @@ -312,6 +312,68 @@ add_test(pmch_test_qpsk pmch_test -m 6 -n 50) add_test(pmch_test_qam16 pmch_test -m 15 -n 100) add_test(pmch_test_qam64 pmch_test -m 25 -n 100) + +######################################################################## +# NPDSCH TEST +######################################################################## + +add_executable(npdsch_test npdsch_test.c) +target_link_libraries(npdsch_test srslte_phy) + +add_test(npdsch_test_tbs208 npdsch_test -m 12) +add_test(npdsch_test_tbs104 npdsch_test -m 7) +add_test(npdsch_test_tbs40 npdsch_test -m 3) +add_test(npdsch_test_tbs16 npdsch_test -m 0) + +# Resource element extraction for different operation modes and cell configuration +# Standalone mode with one Tx port gives maximum number of NPDSCH symbols +add_test(npdsch_test_cellid0_standalone_1port npdsch_test -l 0 -M 3 -x 160) +add_test(npdsch_test_cellid1_standalone_1port npdsch_test -l 1 -M 3 -x 160) +add_test(npdsch_test_cellid2_standalone_1port npdsch_test -l 2 -M 3 -x 160) +add_test(npdsch_test_cellid3_standalone_1port npdsch_test -l 3 -M 3 -x 160) +add_test(npdsch_test_cellid4_standalone_1port npdsch_test -l 4 -M 3 -x 160) +add_test(npdsch_test_cellid5_standalone_1port npdsch_test -l 5 -M 3 -x 160) + +# Standalone mode with two Tx ports +add_test(npdsch_test_cellid0_standalone_2port npdsch_test -l 0 -M 3 -P 2 -x 152) +add_test(npdsch_test_cellid1_standalone_2port npdsch_test -l 1 -M 3 -P 2 -x 152) +add_test(npdsch_test_cellid2_standalone_2port npdsch_test -l 2 -M 3 -P 2 -x 152) +add_test(npdsch_test_cellid3_standalone_2port npdsch_test -l 3 -M 3 -P 2 -x 152) +add_test(npdsch_test_cellid4_standalone_2port npdsch_test -l 4 -M 3 -P 2 -x 152) +add_test(npdsch_test_cellid5_standalone_2port npdsch_test -l 5 -M 3 -P 2 -x 152) + +# Inband same PCI with 1 LTE antenna port and two NB-IoT ports +add_test(npdsch_test_cellid0_inband_1port_2port npdsch_test -l 0 -M 0 -p 1 -P 2 -x 144) +add_test(npdsch_test_cellid1_inband_1port_2port npdsch_test -l 1 -M 0 -p 1 -P 2 -x 144) +add_test(npdsch_test_cellid2_inband_1port_2port npdsch_test -l 2 -M 0 -p 1 -P 2 -x 144) +add_test(npdsch_test_cellid3_inband_1port_2port npdsch_test -l 3 -M 0 -p 1 -P 2 -x 144) +add_test(npdsch_test_cellid4_inband_1port_2port npdsch_test -l 4 -M 0 -p 1 -P 2 -x 144) +add_test(npdsch_test_cellid5_inband_1port_2port npdsch_test -l 5 -M 0 -p 1 -P 2 -x 144) + +# Inband same PCI with 2 antenna ports each +add_test(npdsch_test_cellid0_inband_2port_2port npdsch_test -l 0 -M 0 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid1_inband_2port_2port npdsch_test -l 1 -M 0 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid2_inband_2port_2port npdsch_test -l 2 -M 0 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid3_inband_2port_2port npdsch_test -l 3 -M 0 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid4_inband_2port_2port npdsch_test -l 4 -M 0 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid5_inband_2port_2port npdsch_test -l 5 -M 0 -p 2 -P 2 -x 136) + +# Inband different PCI with 2 antenna ports each +add_test(npdsch_test_cellid0_inband_diffpci_2port_2port npdsch_test -l 0 -M 1 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid1_inband_diffpci_2port_2port npdsch_test -l 1 -M 1 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid2_inband_diffpci_2port_2port npdsch_test -l 2 -M 1 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid3_inband_diffpci_2port_2port npdsch_test -l 3 -M 1 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid4_inband_diffpci_2port_2port npdsch_test -l 4 -M 1 -p 2 -P 2 -x 136) +add_test(npdsch_test_cellid5_inband_diffpci_2port_2port npdsch_test -l 5 -M 1 -p 2 -P 2 -x 136) + +######################################################################## +# NB-IoT DCI TEST +######################################################################## + +add_executable(dci_nbiot_test dci_nbiot_test.c) +target_link_libraries(dci_nbiot_test srslte_phy) +add_test(dci_nbiot_test dci_nbiot_test) + ######################################################################## # FILE TEST ######################################################################## @@ -334,6 +396,9 @@ target_link_libraries(pdsch_pdcch_file_test srslte_phy) add_executable(npbch_file_test npbch_file_test.c) target_link_libraries(npbch_file_test srslte_phy) +add_executable(npdsch_npdcch_file_test npdsch_npdcch_file_test.c) +target_link_libraries(npdsch_npdcch_file_test srslte_phy) + add_test(pbch_file_test pbch_file_test -i ${CMAKE_CURRENT_SOURCE_DIR}/signal.1.92M.dat) add_executable(pmch_file_test pmch_file_test.c) @@ -359,6 +424,9 @@ target_link_libraries(npdcch_file_test srslte_phy) add_test(npdcch_formatN0_file_test npdcch_file_test -c 0 -t 8624 -r 258 -L 1 -l 0 -v -o FormatN0 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_nbiot_dci_formatN0_L_1_nid0_tti_8624_rnti_0x102.bin) add_test(npdcch_formatN1_file_test npdcch_file_test -c 0 -t 5461 -r 137 -L 2 -l 0 -v -o FormatN1 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_nbiot_dci_formatN1_nid0_tti_5461_rnti_0x89.bin) +add_test(npdsch_npdcch_dci_formatN0_test npdsch_npdcch_file_test -c 0 -s 4 -w 862 -r 0x102 -v -o FormatN0 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_nbiot_dci_formatN0_L_1_nid0_tti_8624_rnti_0x102.bin) +add_test(npdsch_npdcch_dci_formatN1_test npdsch_npdcch_file_test -c 0 -s 1 -w 546 -r 0x89 -v -o FormatN1 -i ${CMAKE_CURRENT_SOURCE_DIR}/signal_nbiot_dci_formatN1_nid0_tti_5461_rnti_0x89.bin) + ######################################################################## # PUSCH TEST ######################################################################## diff --git a/lib/src/phy/phch/test/dci_nbiot_test.c b/lib/src/phy/phch/test/dci_nbiot_test.c new file mode 100644 index 000000000..c8b0d2519 --- /dev/null +++ b/lib/src/phy/phch/test/dci_nbiot_test.c @@ -0,0 +1,119 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 +#include +#include +#include + +#include "srslte/phy/ue/ue_dl_nbiot.h" +#include "srslte/phy/utils/bit.h" + +void usage(char* prog) +{ + printf("Usage: %s [cpndv]\n", prog); + printf("\t-v [set srslte_verbose to debug, default none]\n"); +} + +void parse_args(int argc, char** argv) +{ + int opt; + while ((opt = getopt(argc, argv, "cpndv")) != -1) { + switch (opt) { + case 'v': + srslte_verbose++; + break; + default: + usage(argv[0]); + exit(-1); + } + } +} + +// This timing test is supposed to test that a NPDSCH that +// is supposed to start on a SIB1 subframe is in fact postponed +// to the next valid DL subframe. +int dl_timing_test() +{ + srslte_nbiot_cell_t cell = {.base = {.nof_prb = 1, .nof_ports = 1, .cp = SRSLTE_CP_NORM, .id = 0}, + .nbiot_prb = 0, + .n_id_ncell = 0, + .nof_ports = 1, + .mode = SRSLTE_NBIOT_MODE_STANDALONE}; + + cf_t rx_buff[SRSLTE_SF_LEN_PRB_NBIOT]; + cf_t* buff_ptrs[SRSLTE_MAX_PORTS] = {rx_buff, NULL, NULL, NULL}; + srslte_nbiot_ue_dl_t ue_dl; + if (srslte_nbiot_ue_dl_init(&ue_dl, buff_ptrs, SRSLTE_NBIOT_MAX_PRB, SRSLTE_NBIOT_NUM_RX_ANTENNAS)) { + fprintf(stderr, "Error initializing UE DL\n"); + return SRSLTE_ERROR; + } + + if (srslte_nbiot_ue_dl_set_cell(&ue_dl, cell)) { + fprintf(stderr, "Setting cell in UE DL\n"); + return SRSLTE_ERROR; + } + + srslte_mib_nb_t mib; + mib.sched_info_sib1 = 2; + srslte_nbiot_ue_dl_set_mib(&ue_dl, mib); + + // a dummy grant + uint8_t dci_bits_packed[] = {0x81, 0x00, 0x00}; + srslte_dci_msg_t dci_tmp; + dci_tmp.format = SRSLTE_DCI_FORMATN1; + dci_tmp.nof_bits = 23; + srslte_bit_unpack_vector(dci_bits_packed, dci_tmp.payload, dci_tmp.nof_bits); + + // turn DCI into grant + srslte_ra_nbiot_dl_dci_t dl_dci; + srslte_ra_nbiot_dl_grant_t dl_grant; + int sfn = 185; + int sf_idx = 9; + srslte_nbiot_dci_msg_to_dl_grant(&dci_tmp, 0x1234, &dl_dci, &dl_grant, sfn, sf_idx, 3, SRSLTE_NBIOT_MODE_STANDALONE); + + // make sure NPDSCH start is not on a SIB1 subframe + srslte_nbiot_ue_dl_check_grant(&ue_dl, &dl_grant); + + // print final grant + srslte_ra_nbiot_dl_grant_fprint(stdout, &dl_grant); + + srslte_nbiot_ue_dl_free(&ue_dl); + + // only success if starting subframe is not a SIB1 subframe + if (dl_grant.start_sfidx != 4) { + return SRSLTE_SUCCESS; + } + + return SRSLTE_ERROR; +} + +int main(int argc, char** argv) +{ + parse_args(argc, argv); + + if (dl_timing_test() != 0) { + printf("Error running DL timing test.\n"); + return SRSLTE_ERROR; + } + + return SRSLTE_SUCCESS; +} diff --git a/lib/src/phy/phch/test/npdsch_npdcch_file_test.c b/lib/src/phy/phch/test/npdsch_npdcch_file_test.c new file mode 100644 index 000000000..74c3eb7c1 --- /dev/null +++ b/lib/src/phy/phch/test/npdsch_npdcch_file_test.c @@ -0,0 +1,331 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 +#include +#include +#include + +#include "srslte/phy/channel/ch_awgn.h" +#include "srslte/phy/io/filesource.h" +#include "srslte/phy/phch/npdsch.h" +#include "srslte/phy/ue/ue_dl_nbiot.h" +#include "srslte/phy/utils/debug.h" + +#define DUMP_SIGNAL 0 + +char* input_file_name = NULL; + +srslte_nbiot_cell_t cell = {.base = {.nof_prb = 1, .nof_ports = 1, .cp = SRSLTE_CP_NORM, .id = 0}, + .nbiot_prb = 0, + .n_id_ncell = 0, + .nof_ports = 1, + .mode = SRSLTE_NBIOT_MODE_STANDALONE}; + +int flen; + +uint16_t rnti = SRSLTE_SIRNTI; + +int sfn = 0; +int nof_frames = 0; +int max_frames = 1; +uint32_t sf_idx = 0; +uint32_t sched_info_tag = 0; // Value of schedulingInfoSIB1-NB-r13 +srslte_mib_nb_t mib; +bool decode_sib1 = false; +float snr = -1.0; + +srslte_dci_format_t dci_format = SRSLTE_DCI_FORMAT1; +srslte_filesource_t fsrc; +srslte_nbiot_ue_dl_t ue_dl; +cf_t* buff_ptrs[SRSLTE_MAX_PORTS] = {NULL, NULL, NULL, NULL}; + +void usage(char* prog) +{ + printf("Usage: %s [rovcnwmpstx] -i input_file\n", prog); + printf("\t-o DCI format [Default %s]\n", srslte_dci_format_string(dci_format)); + printf("\t-c n_id_ncell [Default %d]\n", cell.n_id_ncell); + printf("\t-s Start subframe_idx [Default %d]\n", sf_idx); + printf("\t-w Start SFN [Default %d]\n", nof_frames); + printf("\t-t Value of schedulingInfoSIB1-NB-r13 [Default %d]\n", sched_info_tag); + printf("\t-k Decode SIB1 [Default %s]\n", decode_sib1 ? "Yes" : "No"); + printf("\t-r rnti [Default 0x%x]\n", rnti); + printf("\t-p cell.nof_ports [Default %d]\n", cell.base.nof_ports); + printf("\t-n cell.nof_prb [Default %d]\n", cell.base.nof_prb); + printf("\t-m max_frames [Default %d]\n", max_frames); + printf("\t-x SNR-10 (apply noise to input file) [Default %f]\n", snr); + printf("\t-v [set srslte_verbose to debug, default none]\n"); +} + +void parse_args(int argc, char** argv) +{ + int opt; + while ((opt = getopt(argc, argv, "irovcnmwpkstx")) != -1) { + switch (opt) { + case 'i': + input_file_name = argv[optind]; + break; + case 'c': + cell.n_id_ncell = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 's': + sf_idx = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 't': + sched_info_tag = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'r': + rnti = strtoul(argv[optind], NULL, 0); + break; + case 'w': + sfn = strtoul(argv[optind], NULL, 0); + break; + case 'm': + max_frames = strtoul(argv[optind], NULL, 0); + break; + case 'n': + cell.base.nof_prb = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'k': + decode_sib1 = true; + break; + case 'p': + cell.base.nof_ports = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'o': + dci_format = srslte_dci_format_from_string(argv[optind]); + if (dci_format == SRSLTE_DCI_NOF_FORMATS) { + fprintf(stderr, "Error unsupported format %s\n", argv[optind]); + exit(-1); + } + break; + case 'x': + snr = atof(argv[optind]); + break; + case 'v': + srslte_verbose++; + break; + default: + usage(argv[0]); + exit(-1); + } + } + if (!input_file_name) { + usage(argv[0]); + exit(-1); + } +} + +int base_init() +{ + if (srslte_filesource_init(&fsrc, input_file_name, SRSLTE_COMPLEX_FLOAT_BIN)) { + fprintf(stderr, "Error opening file %s\n", input_file_name); + exit(-1); + } + + flen = 2 * (SRSLTE_SLOT_LEN(srslte_symbol_sz(cell.base.nof_prb))); + buff_ptrs[0] = malloc(flen * sizeof(cf_t)); + if (!buff_ptrs[0]) { + perror("malloc"); + exit(-1); + } + + if (srslte_nbiot_ue_dl_init(&ue_dl, buff_ptrs, SRSLTE_NBIOT_MAX_PRB, SRSLTE_NBIOT_NUM_RX_ANTENNAS)) { + fprintf(stderr, "Error initializing UE DL\n"); + return -1; + } + + if (srslte_nbiot_ue_dl_set_cell(&ue_dl, cell)) { + fprintf(stderr, "Configuring cell in UE DL\n"); + return -1; + } + + mib.sched_info_sib1 = sched_info_tag; + srslte_nbiot_ue_dl_set_mib(&ue_dl, mib); + srslte_nbiot_ue_dl_set_rnti(&ue_dl, rnti); + // srslte_mib_nb_printf(stdout, 0, &mib); + + DEBUG("Memory init OK\n"); + return 0; +} + +void base_free() +{ + srslte_filesource_free(&fsrc); + srslte_nbiot_ue_dl_free(&ue_dl); + + for (int i = 0; i < SRSLTE_MAX_PORTS; i++) { + if (buff_ptrs[i] != NULL) { + free(buff_ptrs[i]); + } + } +} + +int main(int argc, char** argv) +{ + int dci_formatN0_detected = 0; + int dci_formatN1_detected = 0; + srslte_dci_format_t last_dci_format = SRSLTE_DCI_FORMAT0; + + if (argc < 3) { + usage(argv[0]); + exit(-1); + } + parse_args(argc, argv); + + if (base_init()) { + fprintf(stderr, "Error initializing memory\n"); + exit(-1); + } + + // adjust SNR input to allow for negative values + if (snr != -1.0) { + snr -= 10.0; + printf("Target SNR: %.2fdB\n", snr); + } + + uint8_t* data = malloc(100000); + + int nread = 0; + + if (decode_sib1) { + srslte_nbiot_ue_dl_decode_sib1(&ue_dl, sfn); + } + + do { + nread = srslte_filesource_read(&fsrc, buff_ptrs[0], flen); + if (nread > 0) { + DEBUG("%d.%d: Reading %d samples.\n", sfn, sf_idx, nread); + + // add some noise to the signal + if (snr != -1.0) { + float nstd = powf(10.0f, -snr / 20.0f); + srslte_ch_awgn_c(buff_ptrs[0], buff_ptrs[0], nstd, nread); + } + + // try to decode + if (srslte_ra_nbiot_is_valid_dl_sf(sfn * 10 + sf_idx)) { + int n; + if (srslte_nbiot_ue_dl_has_grant(&ue_dl)) { + // attempt to decode NPDSCH + n = srslte_nbiot_ue_dl_decode_npdsch(&ue_dl, buff_ptrs[0], data, sfn, sf_idx, rnti); + if (n == SRSLTE_SUCCESS) { + INFO("NPDSCH decoded ok.\n"); + + if (decode_sib1) { + srslte_nbiot_ue_dl_decode_sib1(&ue_dl, sfn); + } + } else if (n == SRSLTE_ERROR && decode_sib1) { + // reactivate SIB1 reception + srslte_nbiot_ue_dl_decode_sib1(&ue_dl, sfn); + } + } else { + // decode NPDCCH + srslte_dci_msg_t dci_msg; + n = srslte_nbiot_ue_dl_decode_npdcch(&ue_dl, buff_ptrs[0], sfn, sf_idx, rnti, &dci_msg); + if (n == SRSLTE_NBIOT_UE_DL_FOUND_DCI) { + INFO("Found %s DCI for RNTI=0x%x\n", srslte_dci_format_string(dci_msg.format), rnti); + last_dci_format = dci_msg.format; + + if (dci_msg.format == SRSLTE_DCI_FORMATN0) { + // try to convert to UL grant + srslte_ra_nbiot_ul_dci_t dci_unpacked; + srslte_ra_nbiot_ul_grant_t grant; + // Creates the UL NPUSCH resource allocation grant from a DCI format N0 message + if (srslte_nbiot_dci_msg_to_ul_grant( + &dci_msg, &dci_unpacked, &grant, sfn * 10 + sf_idx, SRSLTE_NPUSCH_SC_SPACING_15000)) { + fprintf(stderr, "Error unpacking DCI\n"); + return SRSLTE_ERROR; + } + dci_formatN0_detected++; + + // we don't use UL grants any further + } else if (dci_msg.format == SRSLTE_DCI_FORMATN1) { + // try to convert DCI to DL grant + srslte_ra_nbiot_dl_dci_t dci_unpacked; + srslte_ra_nbiot_dl_grant_t grant; + if (srslte_nbiot_dci_msg_to_dl_grant( + &dci_msg, rnti, &dci_unpacked, &grant, sfn, sf_idx, 64 /* fixme: remove */, cell.mode)) { + fprintf(stderr, "Error unpacking DCI\n"); + return SRSLTE_ERROR; + } + + dci_formatN1_detected++; + + // activate grant + srslte_nbiot_ue_dl_set_grant(&ue_dl, &grant); + } else { + fprintf(stderr, "Unsupported DCI format.\n"); + return SRSLTE_ERROR; + } + } + } + } + + sf_idx++; + if (sf_idx == 10) { + sfn = (sfn + 1) % 1024; + nof_frames++; + sf_idx = 0; + } + } + } while (nread > 0 && nof_frames <= max_frames); + + uint32_t pkts_ok = ue_dl.pkts_ok; + printf("pkt_total=%d\n", ue_dl.pkts_total); + printf("pkt_ok=%d\n", pkts_ok); + printf("pkt_errors=%d\n", ue_dl.pkt_errors); + printf("bler=%.2f\n", ue_dl.pkts_total ? (float)100 * ue_dl.pkt_errors / ue_dl.pkts_total : 0); + printf("rate=%.2f\n", ((ue_dl.bits_total / ((nof_frames * 10 + sf_idx) / 1000.0)) / 1000.0)); + printf("dci_detected=%d\n", ue_dl.nof_detected); + printf("dci_formatN0_detected=%d\n", dci_formatN0_detected); + printf("dci_formatN1_detected=%d\n", dci_formatN1_detected); + printf("num_frames=%d\n", nof_frames); + +#if DUMP_SIGNAL + printf("Saving signal...\n"); + srslte_nb_ue_dl_save_signal(&ue_dl, &ue_dl.softbuffer, sf_idx); +#endif + + base_free(); + if (data != NULL) { + free(data); + } + + // exit value depends on actual test + + // check if we are testing for correct DCI deconding + if (dci_format == SRSLTE_DCI_FORMATN0 || dci_format == SRSLTE_DCI_FORMATN1 || dci_format == SRSLTE_DCI_FORMATN2) { + // this is a DCI test, check if last DCI matches requested type + if (last_dci_format == dci_format) { + return SRSLTE_SUCCESS; + } else { + return SRSLTE_ERROR; + } + } + + // success if at least one NPDSCH could be decoded + if (pkts_ok > 0) + return SRSLTE_SUCCESS; + + // return error by default + return SRSLTE_ERROR; +} diff --git a/lib/src/phy/phch/test/npdsch_test.c b/lib/src/phy/phch/test/npdsch_test.c new file mode 100644 index 000000000..b83e4a7ca --- /dev/null +++ b/lib/src/phy/phch/test/npdsch_test.c @@ -0,0 +1,489 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 +#include +#include +#include +#include +#include + +#include "srslte/phy/ch_estimation/chest_dl_nbiot.h" +#include "srslte/phy/ch_estimation/refsignal_dl.h" +#include "srslte/phy/ch_estimation/refsignal_dl_nbiot.h" +#include "srslte/phy/dft/ofdm.h" +#include "srslte/phy/io/filesource.h" +#include "srslte/phy/phch/npdsch.h" +#include "srslte/phy/utils/debug.h" +#include "srslte/phy/utils/vector.h" + +#define SRSLTE_NBIOT_SFLEN SRSLTE_SF_LEN(srslte_symbol_sz(cell.base.nof_prb)) +#define SFN 0 +#define SF_IDX 1 + +#define SRSLTE_NBIOT_NOF_RE_X_PRB (SRSLTE_CP_NORM_SF_NSYMB * SRSLTE_NRE) + +// Enable to measure execution time +//#define DO_OFDM + +uint32_t nof_ports_lte = 0; +uint32_t nof_ports_nbiot = 1; +srslte_nbiot_mode_t mode = SRSLTE_NBIOT_MODE_INBAND_SAME_PCI; +uint32_t expected_nof_re = 0; + +uint32_t n_id_ncell = 0; +uint32_t cfi = 2; +uint32_t subframe = 1; +uint32_t rv_idx = 0; +uint16_t rnti = 1234; +uint16_t i_tbs_val = 0; +char* input_file = NULL; + +void usage(char* prog) +{ + printf("Usage: %s [fmMlsrRFpnv] \n", prog); + printf("\t-f read signal from file [Default generate it with pdsch_encode()]\n"); + printf("\t-m i_tbs value [Default %d]\n", i_tbs_val); + printf("\t-M NB-IoT deployment mode (0=InbandSamePCI,1=InbandDifferentPCI,2=GuardBand,3=Standalone) [Default %d]\n", + mode); + printf("\t-l n_id_ncell [Default %d]\n", n_id_ncell); + printf("\t-s subframe [Default %d]\n", subframe); + printf("\t-r rv_idx [Default %d]\n", rv_idx); + printf("\t-R rnti [Default %d]\n", rnti); + printf("\t-p Base cell cell nof_ports [Default %d]\n", nof_ports_lte); + printf("\t-P NB-IoT cell nof_ports [Default %d]\n", nof_ports_nbiot); + printf("\t-x Expected number of resource elements [Default %d]\n", expected_nof_re); + printf("\t-v [set srslte_verbose to debug, default none]\n"); +} + +void parse_args(int argc, char** argv) +{ + int opt; + while ((opt = getopt(argc, argv, "fmMlsrRpPvx")) != -1) { + switch (opt) { + case 'f': + input_file = argv[optind]; + break; + case 's': + subframe = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'r': + rv_idx = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'R': + rnti = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'p': + nof_ports_lte = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'P': + nof_ports_nbiot = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'M': + mode = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'm': + i_tbs_val = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'l': + n_id_ncell = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'x': + expected_nof_re = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'v': + srslte_verbose++; + break; + default: + usage(argv[0]); + exit(-1); + } + } +} + +/* fills a list with RE indices containing either NRS or CRS given a particular cell configration + * @return number of RE containing reference signals + */ +int get_ref_res(srslte_nbiot_cell_t cell, int32_t* re_with_refs) +{ + bzero(re_with_refs, SRSLTE_NBIOT_MAX_NUM_RE_WITH_REFS * sizeof(uint32_t)); + uint32_t num_ref = 0; + + // add all RE that contain NRS + for (int p = 0; p < cell.nof_ports; p++) { + DEBUG("Adding NRS for port=%d n_id_ncell=%d\n", p, cell.n_id_ncell); + uint32_t nof_syms = srslte_refsignal_dl_nbiot_nof_symbols(p); + for (int l = 0; l < nof_syms; l++) { + uint32_t nsymbol = srslte_refsignal_nrs_nsymbol(l); + DEBUG(" - adding NRS for symbol=%d\n", nsymbol); + + // two references per symbol + for (int m = 0; m < SRSLTE_NBIOT_NUM_NRS_X_SYM_X_PORT; m++) { + uint32_t fidx = srslte_refsignal_dl_nbiot_fidx(cell, l, p, m); + uint32_t re_idx = SRSLTE_RE_IDX(cell.base.nof_prb, nsymbol, fidx); + DEBUG(" - adding NRS at re_idx=%d with fidx=%d\n", re_idx, fidx); + re_with_refs[num_ref] = re_idx; + num_ref++; + } + } + } + + // add all RE that contain CRS + for (int p = 0; p < cell.base.nof_ports; p++) { + DEBUG("Adding CRS for port=%d cell_id=%d\n", p, cell.base.id); + uint32_t nof_syms = srslte_refsignal_cs_nof_symbols(NULL, NULL, p); + for (int l = 0; l < nof_syms; l++) { + uint32_t nsymbol = srslte_refsignal_cs_nsymbol(l, cell.base.cp, p); + DEBUG(" - adding CRS for symbol=%d\n", nsymbol); + + // two references per symbol + for (int m = 0; m < 2; m++) { + uint32_t fidx = ((srslte_refsignal_cs_v(p, l) + (cell.base.id % 6)) % 6) + m * 6; + uint32_t re_idx = SRSLTE_RE_IDX(cell.base.nof_prb, nsymbol, fidx); + DEBUG(" - adding CRS at re_idx=%d with fidx=%d\n", re_idx, fidx); + re_with_refs[num_ref] = re_idx; + num_ref++; + } + } + } + + // print all RE's that contain references + printf("RE that contain reference signals:\n"); + srslte_vec_fprint_i(stdout, re_with_refs, num_ref); + + return num_ref; +} + +int extract_re(srslte_nbiot_cell_t cell, uint32_t l_start, uint32_t expected_nof_re) +{ + srslte_npdsch_t npdsch; + bzero(&npdsch, sizeof(srslte_npdsch_t)); + npdsch.cell = cell; + + if (!srslte_nbiot_cell_isvalid(&npdsch.cell)) { + printf("cell is not properly configured\n"); + return SRSLTE_ERROR; + } + + // fill dummy values in RE + cf_t sf_syms[SRSLTE_NBIOT_NOF_RE_X_PRB]; + for (int i = 0; i < SRSLTE_NBIOT_NOF_RE_X_PRB; i++) { + sf_syms[i] = i; + } + + srslte_ra_nbiot_dl_grant_t dl_grant; + bzero(&dl_grant, sizeof(srslte_ra_nbiot_dl_grant_t)); + dl_grant.l_start = l_start; + + cf_t npdsch_syms[SRSLTE_NPDSCH_MAX_RE]; + int nof_ext_syms = srslte_npdsch_cp(&npdsch, sf_syms, npdsch_syms, &dl_grant, false); + +#if 0 + if (SRSLTE_VERBOSE_ISDEBUG()) { + for (int i = 0; i < nof_ext_syms; i++) { + printf("re%d:", i); + srslte_vec_fprint_c(stdout, &npdsch_syms[i], 1); + } + } +#endif + + // check number of extracted REs + if (nof_ext_syms != expected_nof_re) { + printf("RE extraction failed (expected %d, but got %d)!\n", expected_nof_re, nof_ext_syms); + return SRSLTE_ERROR; + } + + // check that there are not REs containing NRS or CRS + int32_t re_with_refs[SRSLTE_NBIOT_MAX_NUM_RE_WITH_REFS]; + int num_re_with_refs = get_ref_res(cell, re_with_refs); + + // for each extracted data symbol, check if it collides with a reference signal + for (int i = 0; i < num_re_with_refs; i++) { + for (int l = 0; l < nof_ext_syms; l++) { + if (npdsch_syms[l] == re_with_refs[i]) { + printf("Extracted data RE is also a reference signal %d: ", l); + srslte_vec_fprint_c(stdout, &npdsch_syms[l], 1); + return SRSLTE_ERROR; + } + } + } + + return SRSLTE_SUCCESS; +} + +int re_extract_test(int argc, char** argv) +{ + parse_args(argc, argv); + + if (expected_nof_re == 0) { + printf("Skipping RE extraction test because number of expected REs isn't given!\n"); + return SRSLTE_SUCCESS; + } + + srslte_nbiot_cell_t cell = {}; + + // Standalone mode with l_start=0 gives the maximum number of REs + cell.mode = mode; + cell.base.nof_prb = 1; + cell.base.id = (mode == SRSLTE_NBIOT_MODE_INBAND_SAME_PCI) ? n_id_ncell : rand() % SRSLTE_NUM_PCI; + cell.n_id_ncell = n_id_ncell; + cell.nof_ports = nof_ports_nbiot; + cell.base.nof_ports = nof_ports_lte; + uint32_t l_start = 0; + + if (extract_re(cell, l_start, expected_nof_re) != SRSLTE_SUCCESS) { + return SRSLTE_ERROR; + } + + return SRSLTE_SUCCESS; +} + +int coding_test(int argc, char** argv) +{ + int ret = SRSLTE_ERROR; + struct timeval t[3]; + srslte_ra_nbiot_dl_grant_t grant; + srslte_npdsch_cfg_t npdsch_cfg; + srslte_npdsch_t npdsch; + uint8_t* data = NULL; + uint8_t* rx_data = NULL; + cf_t* ce[SRSLTE_MAX_PORTS] = {NULL}; + cf_t* sf_symbols = NULL; + cf_t* slot_symbols[SRSLTE_MAX_PORTS] = {NULL}; + srslte_ofdm_t ofdm_tx, ofdm_rx; + + parse_args(argc, argv); + + // setup cell config for this test + srslte_nbiot_cell_t cell = {}; + cell.base.nof_prb = 1; + cell.base.cp = SRSLTE_CP_NORM; + cell.base.nof_ports = 1; + cell.nof_ports = 1; + cell.mode = SRSLTE_NBIOT_MODE_STANDALONE; + cell.n_id_ncell = n_id_ncell; + + if (!srslte_nbiot_cell_isvalid(&cell)) { + printf("Cell is not properly configured\n"); + return ret; + } + + bzero(&npdsch, sizeof(srslte_npdsch_t)); + bzero(&npdsch_cfg, sizeof(srslte_npdsch_cfg_t)); + bzero(ce, sizeof(cf_t*) * SRSLTE_MAX_PORTS); + bzero(slot_symbols, sizeof(cf_t*) * SRSLTE_MAX_PORTS); + + srslte_ra_nbiot_dl_dci_t dci; + bzero(&dci, sizeof(srslte_ra_nbiot_dl_dci_t)); + dci.mcs_idx = i_tbs_val; + dci.rv_idx = rv_idx; + if (srslte_ra_nbiot_dl_dci_to_grant(&dci, &grant, SFN, SF_IDX, DUMMY_R_MAX, false, cell.mode)) { + fprintf(stderr, "Error computing resource allocation\n"); + return ret; + } + + if (!srslte_nbiot_cell_isvalid(&cell)) { + fprintf(stderr, "Cell configuration invalid.\n"); + exit(-1); + } + + /* Configure NPDSCH */ + if (srslte_npdsch_cfg(&npdsch_cfg, cell, &grant, subframe)) { + fprintf(stderr, "Error configuring NPDSCH\n"); + exit(-1); + } + + /* init memory */ + for (int i = 0; i < SRSLTE_MAX_PORTS; i++) { + ce[i] = srslte_vec_malloc(sizeof(cf_t) * SRSLTE_SF_LEN_RE(cell.base.nof_prb, cell.base.cp)); + if (!ce[i]) { + perror("srslte_vec_malloc"); + goto quit; + } + for (int j = 0; j < SRSLTE_SF_LEN_RE(cell.base.nof_prb, cell.base.cp); j++) { + ce[i][j] = 1; + } + slot_symbols[i] = srslte_vec_malloc(sizeof(cf_t) * SRSLTE_SF_LEN_RE(cell.base.nof_prb, cell.base.cp)); + if (!slot_symbols[i]) { + perror("srslte_vec_malloc"); + goto quit; + } + memset(slot_symbols[i], 0, sizeof(cf_t) * SRSLTE_SF_LEN_RE(cell.base.nof_prb, cell.base.cp)); + } + + sf_symbols = srslte_vec_malloc(sizeof(cf_t) * SRSLTE_NBIOT_SFLEN); + memset(sf_symbols, 0, sizeof(cf_t) * SRSLTE_NBIOT_SFLEN); + + data = srslte_vec_malloc(sizeof(uint8_t) * grant.mcs[0].tbs / 8); + if (!data) { + perror("srslte_vec_malloc"); + goto quit; + } + + rx_data = srslte_vec_malloc(sizeof(uint8_t) * grant.mcs[0].tbs / 8); + if (!rx_data) { + perror("srslte_vec_malloc"); + goto quit; + } + + srslte_ofdm_tx_init(&ofdm_tx, cell.base.cp, slot_symbols[0], sf_symbols, cell.base.nof_prb); + srslte_ofdm_rx_init(&ofdm_rx, cell.base.cp, sf_symbols, slot_symbols[0], cell.base.nof_prb); + + if (srslte_npdsch_init(&npdsch)) { + fprintf(stderr, "Error creating NPDSCH object\n"); + goto quit; + } + + if (srslte_npdsch_set_cell(&npdsch, cell)) { + fprintf(stderr, "Error configuring NPDSCH object\n"); + goto quit; + } + + srslte_npdsch_set_rnti(&npdsch, rnti); + + if (input_file) { + srslte_filesource_t fsrc; + if (srslte_filesource_init(&fsrc, input_file, SRSLTE_COMPLEX_FLOAT_BIN)) { + fprintf(stderr, "Error opening file %s\n", input_file); + exit(-1); + } +#ifdef DO_OFDM + srslte_filesource_read(&fsrc, sf_symbols, SRSLTE_SF_LEN_PRB(cell.base.nof_prb)); +#else + srslte_filesource_read(&fsrc, slot_symbols[0], SRSLTE_SF_LEN_RE(cell.base.nof_prb, cell.base.cp)); +#endif + + srslte_chest_dl_nbiot_t chest; + if (srslte_chest_dl_nbiot_init(&chest, SRSLTE_NBIOT_MAX_PRB)) { + printf("Error initializing equalizer\n"); + exit(-1); + } + if (srslte_chest_dl_nbiot_set_cell(&chest, cell) != SRSLTE_SUCCESS) { + fprintf(stderr, "Error setting channel estimator's cell configuration\n"); + return -1; + } + + srslte_chest_dl_nbiot_estimate(&chest, slot_symbols[0], ce, subframe); + srslte_chest_dl_nbiot_free(&chest); + + srslte_filesource_free(&fsrc); + } + + // generate random data + srand(time(NULL)); + for (int i = 0; i < grant.mcs[0].tbs / 8; i++) { + data[i] = rand() % 256; + } + + if (!input_file) { + if (srslte_npdsch_encode(&npdsch, &npdsch_cfg, NULL, data, slot_symbols)) { + fprintf(stderr, "Error encoding NPDSCH\n"); + goto quit; + } + } + +#ifdef DO_OFDM + // Transform to OFDM symbols + srslte_ofdm_tx_sf(&ofdm_tx); +#endif + + if (SRSLTE_VERBOSE_ISDEBUG()) { + DEBUG("SAVED FILE npdsch_tx_sf.bin: transmitted subframe symbols\n"); + srslte_vec_save_file("npdsch_tx_sf.bin", sf_symbols, SRSLTE_NBIOT_SFLEN * sizeof(cf_t)); + } + + int M = 1; + int r = 0; + gettimeofday(&t[1], NULL); + + for (int i = 0; i < M; i++) { +#ifdef DO_OFDM + srslte_ofdm_rx_sf(&ofdm_rx); +#endif + r = srslte_npdsch_decode(&npdsch, &npdsch_cfg, NULL, slot_symbols[0], ce, 0, 0, rx_data); + } + gettimeofday(&t[2], NULL); + get_time_interval(t); + + // compare transmitted and received data + printf("Tx payload: "); + srslte_vec_fprint_b(stdout, data, grant.mcs[0].tbs / 8); + printf("Rx payload: "); + srslte_vec_fprint_b(stdout, rx_data, grant.mcs[0].tbs / 8); + + if (memcmp(data, rx_data, sizeof(uint8_t) * grant.mcs[0].tbs / 8) == 0) { + printf("DECODED in %.2f (PHY bitrate=%.2f Mbps. Processing bitrate=%.2f Mbps)\n", + (float)t[0].tv_usec / M, + (float)grant.mcs[0].tbs / 1000, + (float)grant.mcs[0].tbs * M / t[0].tv_usec); + } else { + printf("Error!\n"); + } + if (r) { + ret = SRSLTE_ERROR; + goto quit; + } + + ret = SRSLTE_SUCCESS; +quit: + srslte_npdsch_free(&npdsch); + + for (int i = 0; i < SRSLTE_MAX_PORTS; i++) { + if (ce[i]) { + free(ce[i]); + } + if (slot_symbols[i]) { + free(slot_symbols[i]); + } + } + if (data) { + free(data); + } + if (rx_data) { + free(rx_data); + } + + if (sf_symbols) { + free(sf_symbols); + } + + srslte_ofdm_tx_free(&ofdm_tx); + srslte_ofdm_rx_free(&ofdm_rx); + return ret; +} + +int main(int argc, char** argv) +{ + int ret = SRSLTE_ERROR; + + if (re_extract_test(argc, argv) != SRSLTE_SUCCESS) { + printf("Resource element extraction test failed!\n"); + return ret; + } + + if (coding_test(argc, argv) != SRSLTE_SUCCESS) { + printf("NPDSCH En- and Decoding test failed!\n"); + return ret; + } + + ret = SRSLTE_SUCCESS; + return ret; +} diff --git a/lib/src/phy/ue/test/CMakeLists.txt b/lib/src/phy/ue/test/CMakeLists.txt index 9bb025e20..651b28602 100644 --- a/lib/src/phy/ue/test/CMakeLists.txt +++ b/lib/src/phy/ue/test/CMakeLists.txt @@ -30,6 +30,10 @@ add_executable(pucch_resource_test pucch_resource_test.c) target_link_libraries(pucch_resource_test srslte_phy) add_test(pucch_resource_test pucch_resource_test) +add_executable(ue_dl_nbiot_test ue_dl_nbiot_test.c) +target_link_libraries(ue_dl_nbiot_test srslte_phy pthread) +add_test(ue_dl_nbiot_test ue_dl_nbiot_test) + if(RF_FOUND) add_executable(ue_mib_sync_test_nbiot_usrp ue_mib_sync_test_nbiot_usrp.c) target_link_libraries(ue_mib_sync_test_nbiot_usrp srslte_phy srslte_rf pthread) diff --git a/lib/src/phy/ue/test/ue_dl_nbiot_test.c b/lib/src/phy/ue/test/ue_dl_nbiot_test.c new file mode 100644 index 000000000..4e99f8e61 --- /dev/null +++ b/lib/src/phy/ue/test/ue_dl_nbiot_test.c @@ -0,0 +1,160 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 +#include +#include +#include + +#include "srslte/phy/ue/ue_dl_nbiot.h" + +srslte_nbiot_cell_t cell = { + .base = {.nof_prb = 1, .nof_ports = 1, .cp = SRSLTE_CP_NORM, .id = 0}, + .nbiot_prb = 0, + .nof_ports = 1, + .n_id_ncell = 0, +}; + +void usage(char* prog) +{ + printf("Usage: %s [v]\n", prog); + printf("\t-l n_id_ncell to sync [Default %d]\n", cell.n_id_ncell); + printf("\t-v srslte_verbose\n"); +} + +void parse_args(int argc, char** argv) +{ + int opt; + while ((opt = getopt(argc, argv, "lv")) != -1) { + switch (opt) { + case 'l': + cell.n_id_ncell = (uint32_t)strtol(argv[optind], NULL, 10); + break; + case 'v': + srslte_verbose++; + break; + default: + usage(argv[0]); + exit(-1); + } + } +} + +int dl_grant_with_sib1_test() +{ + int ret = SRSLTE_ERROR; + cf_t rx_buff[SRSLTE_SF_LEN_PRB_NBIOT]; + cf_t* buff_ptrs[SRSLTE_MAX_PORTS] = {rx_buff, NULL, NULL, NULL}; + + srslte_nbiot_ue_dl_t ue_dl; + srslte_nbiot_ue_dl_init(&ue_dl, buff_ptrs, SRSLTE_NBIOT_MAX_PRB, SRSLTE_NBIOT_NUM_RX_ANTENNAS); + if (srslte_nbiot_ue_dl_set_cell(&ue_dl, cell)) { + fprintf(stderr, "Setting cell in UE DL\n"); + return ret; + } + + // pass MIB to compute SIB1 parameters + srslte_mib_nb_t mib; + mib.sched_info_sib1 = 3; + // srslte_mib_nb_printf(stdout, cell, &mib); + srslte_nbiot_ue_dl_set_mib(&ue_dl, mib); + + // setup grant who's start collides with a SIB1 transmission + srslte_ra_nbiot_dl_grant_t grant; + srslte_nbiot_ue_dl_get_sib1_grant(&ue_dl, 0, &grant); + + // call helper to fix grant + srslte_nbiot_ue_dl_check_grant(&ue_dl, &grant); + + // grant must not start on sf_idx 4 + if (grant.start_sfidx != 4) { + ret = SRSLTE_SUCCESS; + } + + srslte_nbiot_ue_dl_free(&ue_dl); + + return ret; +} + +int dl_grant_with_sib2_test() +{ + int ret = SRSLTE_ERROR; + cf_t rx_buff[SRSLTE_SF_LEN_PRB_NBIOT]; + cf_t* buff_ptrs[SRSLTE_MAX_PORTS] = {rx_buff, NULL, NULL, NULL}; + + srslte_nbiot_ue_dl_t ue_dl; + srslte_nbiot_ue_dl_init(&ue_dl, buff_ptrs, SRSLTE_NBIOT_MAX_PRB, SRSLTE_NBIOT_NUM_RX_ANTENNAS); + if (srslte_nbiot_ue_dl_set_cell(&ue_dl, cell)) { + fprintf(stderr, "Setting cell in UE DL\n"); + return ret; + } + + // pass MIB to compute SIB1 parameters + srslte_mib_nb_t mib; + mib.sched_info_sib1 = 3; + srslte_nbiot_ue_dl_set_mib(&ue_dl, mib); + + // configure SIB2-NB parameters + srslte_nbiot_si_params_t sib2_params; + sib2_params.n = 1; + sib2_params.si_periodicity = 128; + sib2_params.si_radio_frame_offset = 0; + sib2_params.si_repetition_pattern = 2; // Every 2nd radio frame + sib2_params.si_tb = 208; + sib2_params.si_window_length = 160; + srslte_nbiot_ue_dl_set_si_params(&ue_dl, SRSLTE_NBIOT_SI_TYPE_SIB2, sib2_params); + + int dummy_tti = 0; + srslte_nbiot_ue_dl_decode_sib(&ue_dl, dummy_tti, dummy_tti, SRSLTE_NBIOT_SI_TYPE_SIB2, sib2_params); + + // setup grant who's start collides with the above configured SI transmission + srslte_ra_nbiot_dl_grant_t grant; + grant.start_sfn = 898; + grant.start_sfidx = 1; + + // UE DL object should automatically fix the grant + srslte_nbiot_ue_dl_check_grant(&ue_dl, &grant); + + if (grant.start_sfn == 899 && grant.start_sfidx == 3) { + ret = SRSLTE_SUCCESS; + } + + srslte_nbiot_ue_dl_free(&ue_dl); + + return ret; +} + +int main(int argc, char** argv) +{ + parse_args(argc, argv); + + if (dl_grant_with_sib1_test()) { + printf("DL grant with SIB1 test failed\n"); + return SRSLTE_ERROR; + } + + if (dl_grant_with_sib2_test()) { + printf("DL grant with SIB2 test failed\n"); + return SRSLTE_ERROR; + } + + return SRSLTE_SUCCESS; +} diff --git a/lib/src/phy/ue/ue_dl_nbiot.c b/lib/src/phy/ue/ue_dl_nbiot.c new file mode 100644 index 000000000..12c7cece6 --- /dev/null +++ b/lib/src/phy/ue/ue_dl_nbiot.c @@ -0,0 +1,1054 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE 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. + * + * srsLTE 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 "srslte/phy/ue/ue_dl_nbiot.h" + +#include +#include +#include +#include + +#define DUMP_SIGNALS 0 +#define CURRENT_SFLEN_RE SRSLTE_SF_LEN_RE(SRSLTE_NBIOT_MAX_PRB, SRSLTE_CP_NORM) + +static srslte_dci_format_t nb_ue_formats[] = {SRSLTE_DCI_FORMATN0, SRSLTE_DCI_FORMATN1}; +const uint32_t nof_nb_ue_formats = 2; + +static srslte_dci_format_t nb_common_formats[] = {SRSLTE_DCI_FORMATN1, SRSLTE_DCI_FORMATN2}; +const uint32_t nb_nof_common_formats = 2; + +int srslte_nbiot_ue_dl_init(srslte_nbiot_ue_dl_t* q, + cf_t* in_buffer[SRSLTE_MAX_PORTS], + uint32_t max_prb, + uint32_t nof_rx_antennas) +{ + int ret = SRSLTE_ERROR_INVALID_INPUTS; + + if (q != NULL) { + ret = SRSLTE_ERROR; + + bzero(q, sizeof(srslte_nbiot_ue_dl_t)); + + q->pkt_errors = 0; + q->pkts_total = 0; + q->pkts_ok = 0; + q->bits_total = 0; + q->sample_offset = 0; + q->mib_set = false; + + // for transmissions using only single subframe + q->sf_symbols = srslte_vec_cf_malloc(CURRENT_SFLEN_RE); + if (!q->sf_symbols) { + perror("malloc"); + goto clean_exit; + } + srslte_vec_cf_zero(q->sf_symbols, CURRENT_SFLEN_RE); + + for (uint32_t i = 0; i < SRSLTE_MAX_PORTS; i++) { + q->ce[i] = srslte_vec_cf_malloc(CURRENT_SFLEN_RE); + if (!q->ce[i]) { + perror("malloc"); + goto clean_exit; + } + for (int k = 0; k < CURRENT_SFLEN_RE; k++) { + q->ce[i][k] = 1; + } + } + + // allocate memory for symbols and estimates for tx spanning multiple subframes + // TODO: only buffer softbits rather than raw samples + q->sf_buffer = srslte_vec_cf_malloc(CURRENT_SFLEN_RE * SRSLTE_NPDSCH_MAX_NOF_SF); + if (!q->sf_buffer) { + perror("malloc"); + goto clean_exit; + } + srslte_vec_cf_zero(q->sf_buffer, CURRENT_SFLEN_RE * SRSLTE_NPDSCH_MAX_NOF_SF); + + for (uint32_t i = 0; i < SRSLTE_MAX_PORTS; i++) { + q->ce_buffer[i] = srslte_vec_cf_malloc(CURRENT_SFLEN_RE * SRSLTE_NPDSCH_MAX_NOF_SF); + if (!q->ce_buffer[i]) { + perror("malloc"); + goto clean_exit; + } + for (int k = 0; k < (CURRENT_SFLEN_RE * SRSLTE_NPDSCH_MAX_NOF_SF); k++) { + q->ce_buffer[i][k] = 1; + } + } + + // allocate memory for soft-bits + q->llr = srslte_vec_f_malloc(CURRENT_SFLEN_RE * SRSLTE_NPDSCH_MAX_NOF_SF * 2); + if (!q->llr) { + goto clean_exit; + } + srslte_vec_f_zero(q->llr, CURRENT_SFLEN_RE * SRSLTE_NPDSCH_MAX_NOF_SF * 2); + + // initialize memory + if (srslte_ofdm_rx_init(&q->fft, SRSLTE_CP_NORM, in_buffer[0], q->sf_symbols, max_prb)) { + fprintf(stderr, "Error initiating FFT\n"); + goto clean_exit; + } + srslte_ofdm_set_freq_shift(&q->fft, SRSLTE_NBIOT_FREQ_SHIFT_FACTOR); + // srslte_ofdm_set_normalize(&q->fft, true); + + if (srslte_chest_dl_nbiot_init(&q->chest, max_prb)) { + fprintf(stderr, "Error initiating channel estimator\n"); + goto clean_exit; + } + + if (srslte_npdcch_init(&q->npdcch)) { + fprintf(stderr, "Error creating PDCCH object\n"); + goto clean_exit; + } + + if (srslte_npdsch_init(&q->npdsch)) { + fprintf(stderr, "Error creating PDSCH object\n"); + goto clean_exit; + } + if (srslte_softbuffer_rx_init(&q->softbuffer, max_prb)) { + fprintf(stderr, "Error initiating soft buffer\n"); + goto clean_exit; + } + if (srslte_cfo_init(&q->sfo_correct, max_prb * SRSLTE_NRE)) { + fprintf(stderr, "Error initiating SFO correct\n"); + goto clean_exit; + } + srslte_cfo_set_tol(&q->sfo_correct, 1e-5 / q->fft.symbol_sz); + + ret = SRSLTE_SUCCESS; + } else { + fprintf(stderr, "Invalid UE DL object\n"); + } + +clean_exit: + if (ret == SRSLTE_ERROR) { + srslte_nbiot_ue_dl_free(q); + } + return ret; +} + +void srslte_nbiot_ue_dl_free(srslte_nbiot_ue_dl_t* q) +{ + if (q) { + srslte_ofdm_rx_free(&q->fft); + srslte_chest_dl_nbiot_free(&q->chest); + srslte_npdcch_free(&q->npdcch); + srslte_npdsch_free(&q->npdsch); + srslte_cfo_free(&q->sfo_correct); + srslte_softbuffer_rx_free(&q->softbuffer); + if (q->sf_symbols) { + free(q->sf_symbols); + } + for (uint32_t i = 0; i < SRSLTE_MAX_PORTS; i++) { + if (q->ce[i]) { + free(q->ce[i]); + } + } + if (q->sf_buffer) { + free(q->sf_buffer); + } + for (uint32_t i = 0; i < SRSLTE_MAX_PORTS; i++) { + if (q->ce_buffer[i]) { + free(q->ce_buffer[i]); + } + } + if (q->llr) { + free(q->llr); + } + bzero(q, sizeof(srslte_nbiot_ue_dl_t)); + } +} + +int srslte_nbiot_ue_dl_set_cell(srslte_nbiot_ue_dl_t* q, srslte_nbiot_cell_t cell) +{ + int ret = SRSLTE_ERROR_INVALID_INPUTS; + + if (q != NULL && srslte_nbiot_cell_isvalid(&cell)) { + + q->pkt_errors = 0; + q->pkts_total = 0; + q->sample_offset = 0; + + if (q->cell.n_id_ncell != cell.n_id_ncell || q->cell.base.nof_prb == 0) { + + q->cell = cell; + + if (srslte_chest_dl_nbiot_set_cell(&q->chest, q->cell)) { + fprintf(stderr, "Error setting channel estimator's cell configuration\n"); + return -1; + } + + if (srslte_npdcch_set_cell(&q->npdcch, q->cell)) { + fprintf(stderr, "Error resizing NPDCCH object\n"); + return SRSLTE_ERROR; + } + + if (srslte_npdsch_set_cell(&q->npdsch, q->cell)) { + fprintf(stderr, "Error creating NPDSCH object\n"); + return SRSLTE_ERROR; + } + q->current_rnti = 0; + } + ret = SRSLTE_SUCCESS; + } else { + fprintf(stderr, + "Invalid cell properties: n_id_ncell=%d, Ports=%d, base cell's PRBs=%d\n", + cell.n_id_ncell, + cell.nof_ports, + cell.base.nof_prb); + } + return ret; +} + +// Section 5.2.1.2a in 3GPP 36.331 +void srslte_nbiot_ue_dl_set_mib(srslte_nbiot_ue_dl_t* q, srslte_mib_nb_t mib) +{ + // compute frames that contain SIB1-NB transmissions (there are 4 SIB1 cycles before SFN counter wraps) + uint32_t num_rep = srslte_ra_n_rep_sib1_nb(&mib); + uint32_t sib1_start = srslte_ra_nbiot_get_starting_sib1_frame(q->cell.n_id_ncell, &mib); + uint32_t idx = 0; + + for (int k = 0; k < 4; k++) { + // in each period, the SIB is transmitted nrep-times over 16 consecutive frames in every second frame + for (int i = 0; i < num_rep; i++) { + q->sib1_sfn[idx] = k * SIB1_NB_TTI + sib1_start + (i * SIB1_NB_TTI / num_rep); + idx++; + } + } + assert(idx <= 4 * SIB1_NB_MAX_REP && idx == 4 * num_rep); + + if (SRSLTE_VERBOSE_ISINFO()) { + INFO("%d DL SIB1-NB SFNs: ", 4 * num_rep); + srslte_vec_fprint_i(stdout, (int*)q->sib1_sfn, num_rep * 4); + } + + // compute _all_ SIB1 subframes and store them in lookup table + for (int i = 0; i < SIB1_NB_MAX_REP * 4; i++) { + // all even SFN within the next 16 SFN starting at q->sib1_sfn[i] are valid + for (int k = 0; k < 16; k += 2) { + uint32_t sib1_tti = (q->sib1_sfn[i] + k) * 10 + 4; + q->si_tti[sib1_tti] = true; + } + } + + // store MIB + q->mib = mib; + q->mib_set = true; +} + +void srslte_nbiot_ue_dl_set_si_params(srslte_nbiot_ue_dl_t* q, + srslte_nbiot_si_type_t type, + srslte_nbiot_si_params_t params) +{ + if (type < SRSLTE_NBIOT_SI_TYPE_NITEMS) { + + if (params.n != 0) { + // calculate TTIs for this SI + int x = (params.n - 1) * params.si_window_length; + + for (int sfn = 0; sfn < 1024; sfn++) { + int tmp_hfn = 0; + if (((tmp_hfn * 1024 + sfn) % params.si_periodicity) == (floor(x / 10.0) + params.si_radio_frame_offset)) { + // this flags the start of the SI + + // SI is always transmitted in subframe 1 + int si_start_tti = 10 * sfn + 1; + INFO("Start SI tx in TTI=%d\n", si_start_tti); + + do { + // TBS 56 and 120 are sent over 2 sub-frames, all other over 8 + int num_sf = (params.si_tb > 120) ? 8 : 2; + int si_tti = si_start_tti; + while (num_sf > 0) { + // only add if it's a valid DL SF and is not used for SIB1 + if (srslte_ra_nbiot_is_valid_dl_sf(si_tti) && + !srslte_nbiot_ue_dl_is_sib1_sf(q, si_tti / 10, si_tti % 10)) { + q->si_tti[si_tti] = true; + num_sf--; + } + si_tti = (si_tti + 1) % 10240; + } + // advance start TTI to beginning of next repetition + si_start_tti += params.si_repetition_pattern * 10; + } while (si_start_tti <= sfn * 10 + params.si_window_length); // SI is repeated within it's window + } + } + } + + // store parameter + q->si_params[type] = params; + } +} + +bool srslte_nbiot_ue_dl_is_sib1_sf(srslte_nbiot_ue_dl_t* q, uint32_t sfn, uint32_t sf_idx) +{ + bool ret = false; + if (sf_idx != 4) + return ret; + + if (q->mib_set) { + // TODO: replace linear search + for (int i = 0; i < SIB1_NB_MAX_REP * 4; i++) { + // every second frame within the next 16 SFN starting at q->sib1_sfn[i] are valid + uint32_t valid_si_sfn = sfn + srslte_ra_nbiot_sib1_start(q->cell.n_id_ncell, &q->mib); + if ((sfn >= q->sib1_sfn[i]) && (sfn < q->sib1_sfn[i] + 16) && (valid_si_sfn % 2 == 0)) { + ret = true; + break; + } + } + } else { + fprintf(stderr, "Can't compute SIB1 location because MIB-NB not set.\n"); + } + return ret; +} + +bool srslte_nbiot_ue_dl_is_si_tti(srslte_nbiot_ue_dl_t* q, uint32_t sfn, uint32_t sf_idx) +{ + return q->si_tti[sfn * 10 + sf_idx]; +} + +uint32_t srslte_nbiot_ue_dl_get_next_sib1_start(srslte_nbiot_ue_dl_t* q, uint32_t current_sfn) +{ + if (q->mib_set) { + uint32_t offset = srslte_ra_nbiot_get_starting_sib1_frame(q->cell.n_id_ncell, &q->mib); + for (int k = 0; k < 4; k++) { + uint32_t period = k * SIB1_NB_TTI + offset; + if (period > current_sfn) + return period; + } + return q->sib1_sfn[0]; + } else { + fprintf(stderr, "Can't compute SIB1 start because MIB-NB not set.\n"); + } + return SRSLTE_SUCCESS; +} + +void srslte_nbiot_ue_dl_get_sib1_grant(srslte_nbiot_ue_dl_t* q, uint32_t sfn, srslte_ra_nbiot_dl_grant_t* grant) +{ + // compute parameter for SIB1 transmissions + srslte_ra_nbiot_dl_dci_t ra_dl_sib1; + bzero(&ra_dl_sib1, sizeof(srslte_ra_nbiot_dl_dci_t)); + ra_dl_sib1.alloc.has_sib1 = true; + ra_dl_sib1.alloc.sched_info_sib1 = q->mib.sched_info_sib1; + srslte_ra_nbiot_dl_dci_to_grant(&ra_dl_sib1, + grant, + srslte_nbiot_ue_dl_get_next_sib1_start(q, sfn), + 4, + DUMMY_R_MAX, + true, + q->cell.mode); + grant->has_sib1 = true; +} + +// Calculate next SIB window as in 5.2.3a in TS 36.331 +// FIXME: get rid of this brute-force calculation +void srslte_nbiot_ue_dl_get_next_si_sfn(uint32_t current_hfn, + uint32_t current_sfn, + srslte_nbiot_si_params_t params, + uint32_t* si_hfn, + uint32_t* si_sfn) +{ + int x = (params.n - 1) * params.si_window_length; + int tmp_sfn = current_sfn + 1; + int tmp_hfn = current_hfn; + while (((tmp_hfn * 1024 + tmp_sfn) % params.si_periodicity) != (floor(x / 10.0) + params.si_radio_frame_offset)) { + tmp_sfn = (tmp_sfn + 1) % 1024; + if (si_sfn == 0) { + tmp_hfn = (tmp_hfn + 1) % 1024; + } + } + if (si_hfn && si_sfn) { + *si_hfn = tmp_hfn % 1024; + *si_sfn = tmp_sfn % 1024; + } +} + +void srslte_nbiot_ue_dl_get_sib_grant(srslte_nbiot_ue_dl_t* q, + uint32_t hfn, + uint32_t sfn, + srslte_nbiot_si_params_t params, + srslte_ra_nbiot_dl_grant_t* grant) +{ + srslte_ra_nbiot_dl_dci_t si_dci; + bzero(&si_dci, sizeof(srslte_ra_nbiot_dl_dci_t)); + + si_dci.alloc.has_sib1 = false; + si_dci.alloc.is_ra = false; + si_dci.alloc.i_delay = 0; + si_dci.alloc.i_sf = (params.si_tb > 120) ? 6 : 1; // TBS 56 and 120 are sent over 2 sub-frames, all other over 8 + si_dci.alloc.i_rep = 0; + si_dci.alloc.harq_ack = 1; + si_dci.alloc.i_n_start = 0; + + srslte_ra_nbiot_dl_dci_to_grant(&si_dci, grant, sfn, sfn % 2, DUMMY_R_MAX, true, q->cell.mode); + grant->mcs[0].tbs = params.si_tb; // The TBS is given by SI parameters set in SIB1 + srslte_nbiot_ue_dl_get_next_si_sfn(hfn, sfn, params, &grant->start_hfn, &grant->start_sfn); + grant->start_sfidx = 1; +} + +void srslte_nbiot_ue_dl_decode_sib1(srslte_nbiot_ue_dl_t* q, uint32_t current_sfn) +{ + // activate SIB1 grant and configure NPDSCH + INFO( + "%d.x: Activated SIB1 decoding in sfn=%d\n", current_sfn, srslte_nbiot_ue_dl_get_next_sib1_start(q, current_sfn)); + srslte_ra_nbiot_dl_grant_t grant; + srslte_nbiot_ue_dl_get_sib1_grant(q, current_sfn, &grant); + srslte_nbiot_ue_dl_set_grant(q, &grant); +} + +void srslte_nbiot_ue_dl_decode_sib(srslte_nbiot_ue_dl_t* q, + uint32_t hfn, + uint32_t sfn, + srslte_nbiot_si_type_t type, + srslte_nbiot_si_params_t params) +{ + if (q->has_dl_grant) { + INFO("Already processing grant, skipping this.\n"); + return; + } + + if (type == SRSLTE_NBIOT_SI_TYPE_SIB2) { + assert(params.n == 1); + // calculate SIB2 params + srslte_ra_nbiot_dl_grant_t grant; + srslte_nbiot_ue_dl_get_sib_grant(q, hfn, sfn, params, &grant); + srslte_nbiot_ue_dl_set_grant(q, &grant); + INFO("%d.x: Activated SIB2 reception in hfn=%d, sfn=%d\n", + sfn, + q->npdsch_cfg.grant.start_hfn, + q->npdsch_cfg.grant.start_sfn); + } else { + INFO("Not handling this SI type.\n"); + } +} + +void srslte_nbiot_ue_dl_set_grant(srslte_nbiot_ue_dl_t* q, srslte_ra_nbiot_dl_grant_t* grant) +{ + // configure NPDSCH object + srslte_npdsch_cfg(&q->npdsch_cfg, q->cell, grant, grant->start_sfidx); + q->has_dl_grant = true; +} + +void srslte_nbiot_ue_dl_flush_grant(srslte_nbiot_ue_dl_t* q) +{ + bzero(&q->npdsch_cfg, sizeof(srslte_npdsch_cfg_t)); + q->has_dl_grant = false; +} + +bool srslte_nbiot_ue_dl_has_grant(srslte_nbiot_ue_dl_t* q) +{ + return q->has_dl_grant; +} + +/* Helper function to verify a DL grant does not start + * on a subframe that is not a valid DL subframe, i.e., contains + * synchronization signal or is an SI subframe. + * + * \param q pointer to the UE DL object + * \param grant the DL grant to check + */ +void srslte_nbiot_ue_dl_check_grant(srslte_nbiot_ue_dl_t* q, srslte_ra_nbiot_dl_grant_t* grant) +{ + // make sure NPDSCH start is not on a SI subframe + while (srslte_nbiot_ue_dl_is_si_tti(q, grant->start_sfn, grant->start_sfidx) || + !srslte_ra_nbiot_is_valid_dl_sf(grant->start_sfn * 10 + grant->start_sfidx)) { + grant->start_sfidx++; + if (grant->start_sfidx == 10) { + grant->start_sfidx = 0; + grant->start_sfn = (grant->start_sfn + 1) % 1024; + } + } +} + +/* Precalculate the NPDSCH scramble sequences for a given RNTI. This function takes a while + * to execute, so shall be called once the final C-RNTI has been allocated for the session. + */ +void srslte_nbiot_ue_dl_set_rnti(srslte_nbiot_ue_dl_t* q, uint16_t rnti) +{ + srslte_npdsch_set_rnti(&q->npdsch, rnti); + q->current_rnti = rnti; +} + +void srslte_nbiot_ue_dl_reset(srslte_nbiot_ue_dl_t* q) +{ + srslte_softbuffer_rx_reset(&q->softbuffer); + bzero(&q->npdsch_cfg, sizeof(srslte_npdsch_cfg_t)); +} + +void srslte_nbiot_ue_dl_set_sample_offset(srslte_nbiot_ue_dl_t* q, float sample_offset) +{ + q->sample_offset = sample_offset; +} + +int srslte_nbiot_ue_dl_decode_fft_estimate(srslte_nbiot_ue_dl_t* q, uint32_t sf_idx, bool is_dl_sf) +{ + int ret = SRSLTE_ERROR_INVALID_INPUTS; + + if (q != NULL && sf_idx < SRSLTE_NOF_SF_X_FRAME) { + + ret = SRSLTE_ERROR; + + // Run FFT for all subframe data + srslte_ofdm_rx_sf(&q->fft); + + // Correct SFO multiplying by complex exponential in the time domain + if (q->sample_offset) { + for (int i = 0; i < 2 * SRSLTE_CP_NSYMB(q->cell.base.cp); i++) { + srslte_cfo_correct(&q->sfo_correct, + &q->sf_symbols[i * q->cell.base.nof_prb * SRSLTE_NRE], + &q->sf_symbols[i * q->cell.base.nof_prb * SRSLTE_NRE], + q->sample_offset / q->fft.symbol_sz); + } + } + + bool sf_has_nrs = false; + + if (q->cell.nof_ports == 0) { + sf_has_nrs = srslte_ra_nbiot_dl_has_ref_signal(sf_idx); + } else if (q->cell.mode == SRSLTE_NBIOT_MODE_STANDALONE || q->cell.mode == SRSLTE_NBIOT_MODE_GUARDBAND) { + // FIXME: differentiate between before and after SIB reception + sf_has_nrs = srslte_ra_nbiot_dl_has_ref_signal_standalone(sf_idx); + } else { + sf_has_nrs = srslte_ra_nbiot_dl_has_ref_signal_inband(sf_idx); + } + + if (sf_has_nrs || is_dl_sf) { + assert(sf_idx != 5); + ret = srslte_nbiot_ue_dl_decode_estimate(q, sf_idx); + } else { + ret = SRSLTE_SUCCESS; + } + } + + return ret; +} + +int srslte_nbiot_ue_dl_decode_estimate(srslte_nbiot_ue_dl_t* q, uint32_t sf_idx) +{ + if (q && sf_idx < SRSLTE_NOF_SF_X_FRAME) { + // Get channel estimates for each port + srslte_chest_dl_nbiot_estimate(&q->chest, q->sf_symbols, q->ce, sf_idx); + return SRSLTE_SUCCESS; + } else { + return SRSLTE_ERROR_INVALID_INPUTS; + } +} + +int srslte_nbiot_ue_dl_decode_rnti_packet(srslte_nbiot_ue_dl_t* q, + srslte_ra_nbiot_dl_grant_t* grant, + uint8_t* data, + uint32_t sfn, + uint32_t sf_idx, + uint16_t rnti, + cf_t* symbols, + cf_t* ce[SRSLTE_MAX_PORTS], + uint32_t rep_counter) +{ + // Uncomment next line to do ZF by default + // float noise_estimate = 0; + float noise_estimate = srslte_chest_dl_nbiot_get_noise_estimate(&q->chest); + return srslte_npdsch_decode_rnti( + &q->npdsch, &q->npdsch_cfg, &q->softbuffer, symbols, ce, noise_estimate, rnti, sfn, data, rep_counter); +} + +void srslte_nbiot_ue_dl_tb_decoded(srslte_nbiot_ue_dl_t* q, uint8_t* data) +{ + // print decoded message + if (SRSLTE_VERBOSE_ISINFO()) { + INFO("Decoded Message: "); + srslte_vec_fprint_byte(stdout, data, q->npdsch_cfg.grant.mcs[0].tbs / 8); + } + // do book-keeping + q->pkts_ok++; + q->bits_total += q->npdsch_cfg.grant.mcs[0].tbs; + + // de-activate grant + q->has_dl_grant = false; +} + +uint32_t srslte_nbiot_ue_dl_get_ncce(srslte_nbiot_ue_dl_t* q) +{ + return q->last_n_cce; +} + +#define MAX_CANDIDATES_UE 3 // From 36.213 Table 16.6-1 NPDCCH Format0 and NPDCCH Format1 +#define MAX_CANDIDATES_COM \ + 1 // From 36.213 Table 16.6-2 and Table 16.6-3, only AL2 is defined here which uses NPDCCH Format1 +#define MAX_CANDIDATES (MAX_CANDIDATES_UE + MAX_CANDIDATES_COM) + +typedef struct { + srslte_dci_format_t format; + srslte_dci_location_t loc[MAX_CANDIDATES]; + uint32_t nof_locations; +} dci_blind_search_t; + +static int +dci_blind_search(srslte_nbiot_ue_dl_t* q, dci_blind_search_t* search_space, uint16_t rnti, srslte_dci_msg_t* dci_msg) +{ + int ret = SRSLTE_ERROR; + uint16_t crc_rem = 0; + if (rnti) { + ret = 0; + int i = 0; + while (!ret && i < search_space->nof_locations) { + DEBUG("Searching format %s in %d,%d\n", + srslte_dci_format_string(search_space->format), + search_space->loc[i].ncce, + search_space->loc[i].L); + if (srslte_npdcch_decode_msg(&q->npdcch, dci_msg, &search_space->loc[i], search_space->format, &crc_rem)) { + fprintf(stderr, "Error decoding DCI msg\n"); + return SRSLTE_ERROR; + } + if (crc_rem == rnti) { + if (dci_msg->format == search_space->format) { + ret = 1; + memcpy(&q->last_location, &search_space->loc[i], sizeof(srslte_dci_location_t)); + } + } + i++; + } + } else { + fprintf(stderr, "RNTI not specified\n"); + } + return ret; +} + +int srslte_nbiot_ue_dl_find_dl_dci(srslte_nbiot_ue_dl_t* q, uint32_t sf_idx, uint16_t rnti, srslte_dci_msg_t* dci_msg) +{ + if (rnti == SRSLTE_SIRNTI || rnti == SRSLTE_PRNTI || SRSLTE_RNTI_ISRAR(rnti)) { + return srslte_nbiot_ue_dl_find_dl_dci_type_siprarnti(q, rnti, dci_msg); + } else { + return srslte_nbiot_ue_dl_find_dl_dci_type_crnti(q, sf_idx, rnti, dci_msg); + } +} + +// Blind search for SI/P/RA-RNTI +int srslte_nbiot_ue_dl_find_dl_dci_type_siprarnti(srslte_nbiot_ue_dl_t* q, uint16_t rnti, srslte_dci_msg_t* dci_msg) +{ + int ret = SRSLTE_SUCCESS; + // Configure and run DCI blind search + dci_blind_search_t search_space; + + search_space.nof_locations = srslte_npdcch_common_locations(search_space.loc, MAX_CANDIDATES_COM); + DEBUG( + "Searching SI/P/RA-RNTI in %d common locations, %d formats\n", search_space.nof_locations, nb_nof_common_formats); + // Search for RNTI only if there is room for the common search space + if (search_space.nof_locations > 0) { + for (int f = 0; f < nb_nof_common_formats; f++) { + search_space.format = nb_common_formats[f]; + if ((ret = dci_blind_search(q, &search_space, rnti, dci_msg))) { + return ret; + } + } + } + return SRSLTE_SUCCESS; +} + +// Blind search for C-RNTI +int srslte_nbiot_ue_dl_find_dl_dci_type_crnti(srslte_nbiot_ue_dl_t* q, + uint32_t sf_idx, + uint16_t rnti, + srslte_dci_msg_t* dci_msg) +{ + int ret = SRSLTE_SUCCESS; + + // Search UE-specific search space + dci_blind_search_t search_space; + search_space.nof_locations = srslte_npdcch_ue_locations(search_space.loc, MAX_CANDIDATES_UE); + DEBUG("x.%d: Searching DL C-RNTI=0x%x in %d locations, %d formats\n", + sf_idx, + rnti, + search_space.nof_locations, + nof_nb_ue_formats); + for (int f = 0; f < nof_nb_ue_formats; f++) { + search_space.format = nb_ue_formats[f]; + if ((ret = dci_blind_search(q, &search_space, rnti, dci_msg))) { + return ret; + } + } + return ret; +} + +/** Attempts to decode NPDSCH with pre-configured grant + * - It does not decode SIBs if no explicit grant is present + */ +int srslte_nbiot_ue_dl_decode_npdsch(srslte_nbiot_ue_dl_t* q, + cf_t* input, + uint8_t* data, + uint32_t sfn, + uint32_t sf_idx, + uint16_t rnti) +{ + int ret = SRSLTE_ERROR; + + // skip subframe without grant and if it's not a valid downlink subframe + if (q->has_dl_grant == false || srslte_ra_nbiot_is_valid_dl_sf(sfn * 10 + sf_idx) == false) { + DEBUG("%d.%d: Skipping NPDSCH processing.\n", sfn, sf_idx); + return SRSLTE_NBIOT_UE_DL_SKIP_SF; + } + + // run FFT and estimate channel + DEBUG("%d.%d: Estimating channel.\n", sfn, sf_idx); + if ((srslte_nbiot_ue_dl_decode_fft_estimate(q, sf_idx, true)) < 0) { + return ret; + } + + // skip SIB1 sub-frames + if (srslte_nbiot_ue_dl_is_sib1_sf(q, sfn, sf_idx) && q->has_dl_grant) { + if (q->npdsch_cfg.grant.has_sib1 == false) { + // skip SIB1 decoding if grant is being processed + DEBUG("%d.%d: Skipping SIB1 due to ongoing DL reception.\n", sfn, sf_idx); + return SRSLTE_NBIOT_EXPECT_MORE_SF; + } + } + + // Wait with reception until subframe specified in grant (TODO: add hfn check) + if ((q->npdsch_cfg.grant.start_sfn == sfn && q->npdsch_cfg.grant.start_sfidx == sf_idx) || q->npdsch_cfg.num_sf > 0) { + // count first NPDSCH subframe as received frame + if (q->npdsch_cfg.num_sf == 0) { + q->pkts_total++; + } + + // handle actual reception + if (q->npdsch_cfg.has_bcch) { + ret = srslte_nbiot_ue_dl_decode_npdsch_bcch(q, data, sfn * 10 + sf_idx); + } else { + ret = srslte_nbiot_ue_dl_decode_npdsch_no_bcch(q, data, sfn * 10 + sf_idx, rnti); + } + } else { + DEBUG("%d.%d: WARNING: DL grant still active. Possibly needs to be deactivated.\n", sfn, sf_idx); + ret = SRSLTE_NBIOT_UE_DL_SKIP_SF; + } + + return ret; +} + +/** Handles subframe reception of a NPDSCH which doesn't carry the BCCH + * - In this NPDSCH config, up to four repetitons are transmitted one after another + */ +int srslte_nbiot_ue_dl_decode_npdsch_no_bcch(srslte_nbiot_ue_dl_t* q, uint8_t* data, uint32_t tti, uint16_t rnti) +{ + int ret = SRSLTE_ERROR; + + INFO("%d.%d: NPDSCH processing sf_idx=%d/%d rep=%d/%d tot=%d/%d\n", + tti / 10, + tti % 10, + q->npdsch_cfg.sf_idx + 1, + q->npdsch_cfg.grant.nof_sf, + q->npdsch_cfg.rep_idx + 1, + q->npdsch_cfg.grant.nof_rep, + q->npdsch_cfg.num_sf + 1, + q->npdsch_cfg.grant.nof_sf * q->npdsch_cfg.grant.nof_rep); + + if (q->npdsch_cfg.num_sf % q->npdsch_cfg.grant.nof_rep == 0) { + // copy data and ce symbols for first repetition of each subframe + memcpy(&q->sf_buffer[q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], q->sf_symbols, CURRENT_SFLEN_RE * sizeof(cf_t)); + for (int i = 0; i < q->cell.nof_ports; i++) { + memcpy(&q->ce_buffer[i][q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], q->ce[i], CURRENT_SFLEN_RE * sizeof(cf_t)); + } + } else { + // accumulate subframe samples and channel estimates + srslte_vec_sum_ccc(&q->sf_buffer[q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + q->sf_symbols, + &q->sf_buffer[q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + CURRENT_SFLEN_RE); + for (int i = 0; i < q->cell.nof_ports; i++) { + srslte_vec_sum_ccc(&q->ce_buffer[i][q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + q->ce[i], + &q->ce_buffer[i][q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + CURRENT_SFLEN_RE); + } + } + q->npdsch_cfg.num_sf++; + // srslte_nbiot_ue_dl_save_signal(q, input, sfn, sf_idx); + + q->npdsch_cfg.rep_idx++; + int m = SRSLTE_MIN(q->npdsch_cfg.grant.nof_rep, 4); + if (q->npdsch_cfg.rep_idx % m == 0) { + // average accumulated samples + srslte_vec_sc_prod_ccc(&q->sf_buffer[q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + 1.0 / m, + &q->sf_buffer[q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + CURRENT_SFLEN_RE); + for (int i = 0; i < q->cell.nof_ports; i++) { + srslte_vec_sc_prod_ccc(&q->ce_buffer[i][q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + 1.0 / m, + &q->ce_buffer[i][q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], + CURRENT_SFLEN_RE); + } + + q->npdsch_cfg.sf_idx++; + if (q->npdsch_cfg.sf_idx == q->npdsch_cfg.grant.nof_sf) { + q->npdsch_cfg.sf_idx = 0; + } else { + q->npdsch_cfg.rep_idx -= m; + } + } + + if (q->npdsch_cfg.num_sf == q->npdsch_cfg.grant.nof_sf * q->npdsch_cfg.grant.nof_rep) { + // try to decode NPDSCH + INFO("%d.%d: Trying to decode NPDSCH with %d subframe(s).\n", tti / 10, tti % 10, q->npdsch_cfg.grant.nof_sf); + if (srslte_nbiot_ue_dl_decode_rnti_packet(q, + &q->npdsch_cfg.grant, + data, + tti / 10, + tti % 10, + rnti, + q->sf_buffer, + q->ce_buffer, + q->npdsch_cfg.rep_idx) != SRSLTE_SUCCESS) { + // decoding failed + INFO("%d.%d: Error decoding NPDSCH with %d repetitions.\n", tti / 10, tti % 10, q->npdsch_cfg.rep_idx); + q->pkt_errors++; + q->has_dl_grant = false; + ret = SRSLTE_ERROR; + } else { + srslte_nbiot_ue_dl_tb_decoded(q, data); + ret = SRSLTE_SUCCESS; + } + } else { + DEBUG("%d.%d: Waiting for %d more subframes.\n", + tti / 10, + tti % 10, + q->npdsch_cfg.grant.nof_sf * q->npdsch_cfg.grant.nof_rep - q->npdsch_cfg.num_sf); + ret = SRSLTE_NBIOT_EXPECT_MORE_SF; + } + return ret; +} + +/** Handles subframe reception of a NPDSCH carrying the BCCH + * - According to TS36.211 Section 10.2.3.4 for NPDSCH carrying BCCH, all SF are + * transmitted in sequence before repetions are transmitted + */ +int srslte_nbiot_ue_dl_decode_npdsch_bcch(srslte_nbiot_ue_dl_t* q, uint8_t* data, uint32_t tti) +{ + int ret = SRSLTE_NBIOT_EXPECT_MORE_SF; + + // TODO: this only captures SIB1, not other SIBs + // only subframe 4 of every second frame is an SIB1 + // make sure we also look on odd SFNs (if n_rep=16 and n_id_ncell%2=1) + uint32_t valid_si_sfn = tti / 10 + srslte_ra_nbiot_sib1_start(q->cell.n_id_ncell, &q->mib); + if (valid_si_sfn % 2 == 0 && tti % 10 == 4) { + INFO("%d.%d: NPDSCH processing sf_idx=%d/%d rep=%d/%d tot=%d/%d\n", + tti / 10, + tti % 10, + q->npdsch_cfg.sf_idx + 1, + q->npdsch_cfg.grant.nof_sf, + q->npdsch_cfg.rep_idx + 1, + q->npdsch_cfg.grant.nof_rep, + q->npdsch_cfg.num_sf + 1, + q->npdsch_cfg.grant.nof_sf * q->npdsch_cfg.grant.nof_rep); + + // copy data and ce symbols + memcpy(&q->sf_buffer[q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], q->sf_symbols, CURRENT_SFLEN_RE * sizeof(cf_t)); + for (int i = 0; i < q->cell.nof_ports; i++) { + memcpy(&q->ce_buffer[i][q->npdsch_cfg.sf_idx * CURRENT_SFLEN_RE], q->ce[i], CURRENT_SFLEN_RE * sizeof(cf_t)); + } + q->npdsch_cfg.num_sf++; + q->npdsch_cfg.sf_idx++; + + // check if we already have received the entire transmission + if (q->npdsch_cfg.num_sf % q->npdsch_cfg.grant.nof_sf == 0) { + // try to decode NPDSCH + INFO("%d.%d: Trying to decode NPDSCH with %d subframe(s).\n", tti / 10, tti % 10, q->npdsch_cfg.grant.nof_sf); + if (srslte_nbiot_ue_dl_decode_rnti_packet(q, + &q->npdsch_cfg.grant, + data, + tti / 10, + tti % 10, + SRSLTE_SIRNTI, + q->sf_buffer, + q->ce_buffer, + q->npdsch_cfg.rep_idx) != SRSLTE_SUCCESS) { + // decoding failed, check for possible repetitions + if (q->npdsch_cfg.rep_idx == 0) { + // store soft-bits of first repetition + memcpy(q->llr, q->npdsch.llr, q->npdsch_cfg.grant.nof_sf * q->npdsch_cfg.nbits.nof_bits); + } else { + INFO("Soft-combining NPDSCH repetition %d\n", q->npdsch_cfg.rep_idx); + srslte_vec_sum_fff(q->llr, q->npdsch.llr, q->llr, q->npdsch_cfg.grant.nof_sf * q->npdsch_cfg.nbits.nof_bits); + + // try to decode combined soft-bits + INFO("%d.%d: Trying to decode NPDSCH with %d subframe(s) after %d repetitions.\n", + tti / 10, + tti % 10, + q->npdsch_cfg.grant.nof_sf, + q->npdsch_cfg.rep_idx); + + if (srslte_npdsch_rm_and_decode(&q->npdsch, &q->npdsch_cfg, q->llr, data) == SRSLTE_SUCCESS) { + // frame decoded ok after one or more repetitions + srslte_nbiot_ue_dl_tb_decoded(q, data); + q->has_dl_grant = false; + return SRSLTE_SUCCESS; + } else { + ret = SRSLTE_NBIOT_EXPECT_MORE_SF; + } + } + + // NPDSCH carrying the BCCH starts with subframe zero again + q->npdsch_cfg.sf_idx = 0; + q->npdsch_cfg.rep_idx++; + + if (q->npdsch_cfg.rep_idx < q->npdsch_cfg.grant.nof_rep) { + DEBUG("%d.%d: Couldn't decode NPDSCH, waiting for next repetition\n", tti / 10, tti % 10); + ret = SRSLTE_NBIOT_EXPECT_MORE_SF; + } else { + + INFO("%d.%d: Error decoding NPDSCH with %d repetitions.\n", tti / 10, tti % 10, q->npdsch_cfg.rep_idx); + ret = SRSLTE_ERROR; + q->pkt_errors++; // count as error after all repetitons failed + q->has_dl_grant = false; + } + } else { + // frame decoded ok after first transmission + srslte_nbiot_ue_dl_tb_decoded(q, data); + return SRSLTE_SUCCESS; + } + } else { + DEBUG("%d.%d: Waiting for more subframes.\n", tti / 10, tti % 10); + ret = SRSLTE_NBIOT_EXPECT_MORE_SF; + } + } + return ret; +} + +/** Applies the following operations to a subframe of synchronized samples: + * - OFDM demodulation + * - Channel estimation (only for subframes containing NRS) + * - NPDCCH decoding: Find DCI for RNTI given by rnti paramter + */ +int srslte_nbiot_ue_dl_decode_npdcch(srslte_nbiot_ue_dl_t* q, + cf_t* input, + uint32_t sfn, + uint32_t sf_idx, + uint16_t rnti, + srslte_dci_msg_t* dci_msg) +{ + int ret = SRSLTE_ERROR; + + // skip subframe with grant and if it's not a valid downlink subframe + if (q->has_dl_grant || srslte_ra_nbiot_is_valid_dl_sf(sfn * 10 + sf_idx) == false) { + DEBUG("%d.%d: Skipping NPDCCH processing.\n", sfn, sf_idx); + return ret; + } + + // run FFT and estimate channel + DEBUG("%d.%d: Estimating channel.\n", sfn, sf_idx); + if ((srslte_nbiot_ue_dl_decode_fft_estimate(q, sf_idx, true)) < 0) { + return ret; + } + + // handle SI sub-frames + if (srslte_nbiot_ue_dl_is_si_tti(q, sfn, sf_idx) && q->has_dl_grant) { + if (q->npdsch_cfg.grant.has_sib1 == false) { + // skip SI decoding if grant is being processed + DEBUG("%d.%d: Skipping SI SF due to ongoing DL reception.\n", sfn, sf_idx); + return SRSLTE_NBIOT_EXPECT_MORE_SF; + } + } + + // If no grant is present, try to decode NPDCCH + float noise_est = srslte_chest_dl_nbiot_get_noise_estimate(&q->chest); + if (srslte_npdcch_extract_llr(&q->npdcch, q->sf_symbols, q->ce, noise_est, sf_idx)) { + fprintf(stderr, "Error extracting LLRs\n"); + return SRSLTE_ERROR; + } + + DEBUG("%d.%d: Looking for DCI for RNTI=0x%x.\n", sfn, sf_idx, rnti); + if (srslte_nbiot_ue_dl_find_dl_dci(q, sf_idx, rnti, dci_msg) == 1) { + // a DCI was found + INFO("%d.%d: Found DCI for RNTI=0x%x.\n", sfn, sf_idx, rnti); + q->nof_detected++; + ret = SRSLTE_NBIOT_UE_DL_FOUND_DCI; + } +#if DUMP_SIGNALS + srslte_nbiot_ue_dl_save_signal(q, input, sfn, sf_idx); +#endif + + return ret; +} + +// This funtion searches for UL DCI's, i.e., Format N0, for a given RNTI +// assuming that the equalized subframe symbols are already stored in the DL object, +// i.e. srslte_nbiot_ue_dl_decode_npdcch() needs to be called before +int srslte_nbiot_ue_dl_find_ul_dci(srslte_nbiot_ue_dl_t* q, uint32_t tti, uint32_t rnti, srslte_dci_msg_t* dci_msg) +{ + DEBUG("%d.%d: Looking for UL DCI for RNTI=0x%x.\n", tti / 10, tti % 10, rnti); + + // Search UE-specific search space + dci_blind_search_t search_space; + search_space.nof_locations = srslte_npdcch_ue_locations(search_space.loc, MAX_CANDIDATES_UE); + DEBUG("x.%d: Searching UL C-RNTI=0x%x in %d locations, %d formats\n", + tti % 10, + rnti, + search_space.nof_locations, + nof_nb_ue_formats); + for (int f = 0; f < nof_nb_ue_formats; f++) { + search_space.format = nb_ue_formats[f]; + if ((dci_blind_search(q, &search_space, rnti, dci_msg)) == 1) { + q->nof_detected++; + return SRSLTE_NBIOT_UE_DL_FOUND_DCI; + } + } + return SRSLTE_ERROR; +} + +void srslte_nbiot_ue_dl_save_signal(srslte_nbiot_ue_dl_t* q, cf_t* input, uint32_t sfn, uint32_t sf_idx) +{ + uint32_t num_symbols = SRSLTE_SF_LEN(SRSLTE_NBIOT_FFT_SIZE); +#define MAX_FNAME_LEN 50 + char fname[MAX_FNAME_LEN]; + + // RAW samples + snprintf(fname, MAX_FNAME_LEN, "nb_ue_dl_sfn%d_sf%d_raw_samples.bin", sfn, sf_idx); + DEBUG("SAVED FILE %s: rx'ed samples\n", fname); + srslte_vec_save_file(fname, input, num_symbols * sizeof(cf_t)); + + // NPDCCH + num_symbols = q->npdcch.num_decoded_symbols; + snprintf(fname, MAX_FNAME_LEN, "nb_ue_dl_sfn%d_sf%d_npdcch_symbols.bin", sfn, sf_idx); + DEBUG("SAVED FILE %s: rx'ed downlink symbols\n", fname); + srslte_vec_save_file(fname, q->npdcch.symbols[0], num_symbols * sizeof(cf_t)); + + snprintf(fname, MAX_FNAME_LEN, "nb_ue_dl_sfn%d_sf%d_npdcch_symbols_eq.bin", sfn, sf_idx); + DEBUG("SAVED FILE %s: eq rx'ed downlink symbols\n", fname); + srslte_vec_save_file(fname, q->npdcch.d, num_symbols * sizeof(cf_t)); + + // NPDSCH + num_symbols = q->npdsch_cfg.nbits.nof_re * q->npdsch_cfg.grant.nof_sf; + snprintf(fname, MAX_FNAME_LEN, "nb_ue_dl_sfn%d_sf%d_npdsch_symbols.bin", sfn, sf_idx); + DEBUG("SAVED FILE %s: rx'ed downlink symbols\n", fname); + srslte_vec_save_file(fname, q->npdsch.symbols[0], num_symbols * sizeof(cf_t)); + + snprintf(fname, MAX_FNAME_LEN, "nb_ue_dl_sfn%d_sf%d_npdsch_symbols_eq.bin", sfn, sf_idx); + DEBUG("SAVED FILE %s: eq rx'ed downlink symbols\n", fname); + srslte_vec_save_file(fname, q->npdsch.d, num_symbols * sizeof(cf_t)); + + // CE + snprintf(fname, MAX_FNAME_LEN, "nb_ue_dl_sfn%d_sf%d_ce0.bin", sfn, sf_idx); + DEBUG("SAVED FILE %s: downlink channel estimates port 0\n", fname); + srslte_vec_save_file(fname, q->ce_buffer[0], num_symbols * sizeof(cf_t)); + + if (q->cell.nof_ports > 1) { + snprintf(fname, MAX_FNAME_LEN, "nb_ue_dl_sfn%d_sf%d_ce1.bin", sfn, sf_idx); + DEBUG("SAVED FILE %s: downlink channel estimates port 1\n", fname); + srslte_vec_save_file(fname, q->ce_buffer[1], num_symbols * sizeof(cf_t)); + } +}