diff --git a/lib/include/srslte/phy/channel/fading.h b/lib/include/srslte/phy/channel/fading.h new file mode 100644 index 000000000..e0c9fcc15 --- /dev/null +++ b/lib/include/srslte/phy/channel/fading.h @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2019 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_FADING_H +#define SRSLTE_FADING_H + +#include "srslte/phy/dft/dft.h" +#include + +#define SRSLTE_CHANNEL_FADING_MAXTAPS 9 + +typedef enum { + srslte_channel_fading_model_none = 0, + srslte_channel_fading_model_epa, + srslte_channel_fading_model_eva, + srslte_channel_fading_model_etu, +} srslte_channel_fading_model_t; + +typedef struct { + // Configuration parameters + float srate; // Sampling rate: 1.92e6, 3.84e6, ..., 23.04e6, 30.72e6 + srslte_channel_fading_model_t model; // None, EPA, EVA, ETU + float doppler; // Maximum doppler: 5, 70, 300 + + // Internal tap parametrization + uint32_t N; // FFT size + uint32_t path_delay; // Path delay + double coeff_w[SRSLTE_CHANNEL_FADING_MAXTAPS]; // Angular Speed, random + double coeff_a[SRSLTE_CHANNEL_FADING_MAXTAPS]; // Modulation Coefficient + double coeff_p[SRSLTE_CHANNEL_FADING_MAXTAPS]; // Initial phase, random + + // Utils + srslte_dft_plan_t fft; // DFT to frequency domain + srslte_dft_plan_t ifft; // DFT to time domain + cf_t* temp; // Temporal buffer, length fft_size + cf_t* h_freq; // Channel frequency response, length fft_size + cf_t* y_freq; // Intermediate frequency domain buffer + + // State variables + cf_t* state; // Length fft_size/2 +} srslte_channel_fading_t; + +SRSLTE_API int srslte_channel_fading_init(srslte_channel_fading_t* q, double srate, const char* model); + +SRSLTE_API void srslte_channel_fading_free(srslte_channel_fading_t* q); + +SRSLTE_API double srslte_channel_fading_execute( + srslte_channel_fading_t* q, const cf_t* in, cf_t* out, uint32_t nof_samples, double init_time); + +#endif // SRSLTE_FADING_H diff --git a/lib/src/phy/channel/CMakeLists.txt b/lib/src/phy/channel/CMakeLists.txt index 0ea8799c5..22b9c8599 100644 --- a/lib/src/phy/channel/CMakeLists.txt +++ b/lib/src/phy/channel/CMakeLists.txt @@ -20,3 +20,5 @@ file(GLOB SOURCES "*.c") add_library(srslte_channel OBJECT ${SOURCES}) + +add_subdirectory(test) \ No newline at end of file diff --git a/lib/src/phy/channel/fading.c b/lib/src/phy/channel/fading.c new file mode 100644 index 000000000..5bc56466a --- /dev/null +++ b/lib/src/phy/channel/fading.c @@ -0,0 +1,270 @@ +/* + * Copyright 2013-2019 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/channel/fading.h" +#include "srslte/phy/utils/vector.h" + +#include +#include +#include +#include +#include + +#define COEFF_A_MIN 100 +#define COEFF_A_MAX 2000 + +/* + * Tables provided in 36.104 R10 section B.2 Multi-path fading propagation conditions + */ +const static uint32_t nof_taps[4] = {1, 7, 9, 9}; + +const static float excess_tap_delay_ns[4][SRSLTE_CHANNEL_FADING_MAXTAPS] = { + /* None */ {0, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN}, + /* EPA */ {0, 30, 70, 90, 110, 190, 410, NAN, NAN}, + /* EVA */ {0, 30, 150, 310, 370, 710, 1090, 1730, 2510}, + /* ETU */ {0, 50, 120, 200, 230, 500, 1600, 2300, 5000}}; + +const static float relative_power_db[4][SRSLTE_CHANNEL_FADING_MAXTAPS] = { + /* None */ {+0.0f, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN}, + /* EPA */ {+0.0f, -1.0f, -2.0f, -3.0f, -8.0f, -17.2f, -20.8f, NAN, NAN}, + /* EVA */ {+0.0f, -1.5f, -1.4f, -3.6f, -0.6f, -9.1f, -7.0f, -12.0f, -16.9f}, + /* ETU */ {-1.0f, -1.0f, -1.0f, +0.0f, +0.0f, +0.0f, -3.0f, -5.0f, -7.0f}, +}; + +static inline int parse_model(srslte_channel_fading_t* q, const char* str) +{ + int ret = SRSLTE_SUCCESS; + uint32_t offset = 3; + + if (strncmp("none", str, 4) == 0) { + q->model = srslte_channel_fading_model_none; + offset = 4; + } else if (strncmp("epa", str, 3) == 0) { + q->model = srslte_channel_fading_model_epa; + } else if (strncmp("eva", str, 3) == 0) { + q->model = srslte_channel_fading_model_eva; + } else if (strncmp("etu", str, 3) == 0) { + q->model = srslte_channel_fading_model_etu; + } else { + ret = SRSLTE_ERROR; + } + + if (ret == SRSLTE_SUCCESS) { + if (strlen(str) > offset) { + q->doppler = (float)strtod(&str[offset], NULL); + if (isnan(q->doppler) || isinf(q->doppler)) { + ret = SRSLTE_ERROR; + } + } else { + ret = SRSLTE_ERROR; + } + } + + return ret; +} + +static inline float get_doppler_dispersion(double t, double a, double w, double p) +{ + return (float)(a * sin(w * t + p)); +} + +static inline void +generate_tap(float delay_ns, float power_db, float srate, float phase, cf_t* buf, uint32_t N, uint32_t path_delay) +{ + float amplitude = powf(10.0f, power_db / 20.0f); + float O = (delay_ns * 1e-9f * srate + path_delay) / (float)N; + cf_t a0 = amplitude * cexpf(-_Complex_I * phase) / N; + + for (int n = 0; n < N; n++) { + buf[n] = a0; + } + + srslte_vec_apply_cfo(buf, -O, buf, N); +} + +static void generate_taps(srslte_channel_fading_t* q, double time) +{ + // Initialise freq response + bzero(q->h_freq, sizeof(cf_t) * q->N); + + // Generate taps + for (int i = 0; i < nof_taps[q->model]; i++) { + // Compute phase for thee doppler dispersion + float phase = get_doppler_dispersion(time, q->coeff_a[i], q->coeff_w[i], q->coeff_p[i]); + + // Generate tab + generate_tap(excess_tap_delay_ns[q->model][i], relative_power_db[q->model][i], q->srate, phase, q->temp, q->N, + q->path_delay); + + // Add to frequency response + srslte_vec_sum_ccc(q->h_freq, q->temp, q->h_freq, q->N); + } + + // at this stage, q->h_freq should contain the frequency response +} + +static void filter_segment(srslte_channel_fading_t* q, const cf_t* input, cf_t* output, uint32_t nsamples) +{ + // Fill Input vector + memcpy(q->temp, input, sizeof(cf_t) * nsamples); + bzero(&q->temp[nsamples], sizeof(cf_t) * (q->N - nsamples)); + + // Do FFT + srslte_dft_run_c_zerocopy(&q->fft, q->temp, q->y_freq); + + // Apply channel + srslte_vec_prod_ccc(q->y_freq, q->h_freq, q->y_freq, q->N); + + // Do iFFT + srslte_dft_run_c_zerocopy(&q->ifft, q->y_freq, q->temp); + + // Add state + srslte_vec_sum_ccc(q->temp, q->state, q->temp, q->N); + + // Copy output + memcpy(output, q->temp, sizeof(cf_t) * nsamples); + + // Copy state + memcpy(q->state, &q->temp[nsamples], sizeof(cf_t) * (q->N - nsamples)); + bzero(&q->state[q->N - nsamples], sizeof(cf_t) * nsamples); +} + +int srslte_channel_fading_init(srslte_channel_fading_t* q, double srate, const char* model) +{ + int ret = SRSLTE_ERROR; + + if (q) { + // Parse model + if (parse_model(q, model) != SRSLTE_SUCCESS) { + fprintf(stderr, "Error: invalid channel model '%s'\n", model); + goto clean_exit; + } + + // Fill srate + q->srate = (float)srate; + + // Populate internal parameters + q->N = SRSLTE_MAX((uint32_t)1 << (uint32_t)( + round(log2(excess_tap_delay_ns[q->model][nof_taps[q->model] - 1] * 1e-9 * srate)) + 3), + 64); + q->path_delay = q->N / 4; + for (int i = 0; i < nof_taps[q->model]; i++) { + q->coeff_a[i] = (((double)rand() / (double)RAND_MAX) * (COEFF_A_MAX - COEFF_A_MIN)) + COEFF_A_MIN; + q->coeff_w[i] = 2.0 * M_PI * q->doppler / q->coeff_a[i]; + q->coeff_p[i] = ((double)rand() / (double)RAND_MAX) * M_PI / 2.0; + } + + // Plan FFT + if (srslte_dft_plan_c(&q->fft, q->N, SRSLTE_DFT_FORWARD) != SRSLTE_SUCCESS) { + fprintf(stderr, "Error: planning fft\n"); + goto clean_exit; + } + + // Plan iFFT + if (srslte_dft_plan_c(&q->ifft, q->N, SRSLTE_DFT_BACKWARD) != SRSLTE_SUCCESS) { + fprintf(stderr, "Error: planning ifft\n"); + goto clean_exit; + } + + // Allocate memory + q->temp = srslte_vec_malloc(sizeof(cf_t) * q->N); + if (!q->temp) { + fprintf(stderr, "Error: allocating h_freq\n"); + goto clean_exit; + } + + q->h_freq = srslte_vec_malloc(sizeof(cf_t) * q->N); + if (!q->h_freq) { + fprintf(stderr, "Error: allocating h_freq\n"); + goto clean_exit; + } + + q->y_freq = srslte_vec_malloc(sizeof(cf_t) * q->N); + if (!q->y_freq) { + fprintf(stderr, "Error: allocating y_freq\n"); + goto clean_exit; + } + + q->state = srslte_vec_malloc(sizeof(cf_t) * q->N); + if (!q->state) { + fprintf(stderr, "Error: allocating y_freq\n"); + goto clean_exit; + } + bzero(q->state, sizeof(cf_t) * q->N); + } + + ret = SRSLTE_SUCCESS; + +clean_exit: + return ret; +} + +void srslte_channel_fading_free(srslte_channel_fading_t* q) +{ + if (q) { + srslte_dft_plan_free(&q->fft); + srslte_dft_plan_free(&q->ifft); + + if (q->temp) { + free(q->temp); + } + + if (q->h_freq) { + free(q->h_freq); + } + + if (q->y_freq) { + free(q->y_freq); + } + + if (q->state) { + free(q->state); + } + } +} + +double srslte_channel_fading_execute( + srslte_channel_fading_t* q, const cf_t* in, cf_t* out, uint32_t nsamples, double init_time) +{ + uint32_t counter = 0; + + if (q) { + // Generate taps + generate_taps(q, init_time); + + while (counter < nsamples) { + // Do not process more than N / 4 samples + uint32_t n = SRSLTE_MIN(q->N / 4, nsamples - counter); + + // Execute + filter_segment(q, &in[counter], &out[counter], n); + + // Increment time + init_time += n / q->srate; + + // Increment counter + counter += n; + } + } + + // Return time + return init_time; +} diff --git a/lib/src/phy/channel/test/CMakeLists.txt b/lib/src/phy/channel/test/CMakeLists.txt new file mode 100644 index 000000000..9965d081a --- /dev/null +++ b/lib/src/phy/channel/test/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Copyright 2013-2019 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/. +# + +add_executable(fading_channel_test fading_channel_test.c) + +if(SRSGUI_FOUND) + target_link_libraries(fading_channel_test ${SRSGUI_LIBRARIES}) +endif(SRSGUI_FOUND) + +target_link_libraries(fading_channel_test srslte_phy srslte_common srslte_phy ${SEC_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +add_test(fading_channel_test_epa5 fading_channel_test -m epa5 -s 26.04e6 -t 1000) +add_test(fading_channel_test_eva70 fading_channel_test -m eva70 -s 23.04e6 -t 1000) +add_test(fading_channel_test_etu300 fading_channel_test -m etu70 -s 23.04e6 -t 1000) \ No newline at end of file diff --git a/lib/src/phy/channel/test/fading_channel_test.c b/lib/src/phy/channel/test/fading_channel_test.c new file mode 100644 index 000000000..cf0ee9e19 --- /dev/null +++ b/lib/src/phy/channel/test/fading_channel_test.c @@ -0,0 +1,247 @@ +/* + * Copyright 2013-2019 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/channel/fading.h" +#include "srslte/phy/utils/vector.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_GUI +#include "srsgui/srsgui.h" +static bool enable_gui = false; +#endif /* ENABLE_GUI */ + +static srslte_channel_fading_t channel_fading; + +static char default_model[] = "epa5"; +static uint32_t duration_ms = 1000; +static char* model = default_model; +static uint32_t srate = (uint32_t)30.72e6; + +#define INPUT_TYPE 0 /* 0: Dirac Delta; Otherwise: Random*/ + +static void usage(char* prog) +{ + printf("Usage: %s [mts]\n", prog); + printf("\t-m Channel model: epa5, eva70, etu300 [Default %s]\n", model); + printf("\t-t Simulation time in ms: [Default %d]\n", duration_ms); + printf("\t-s Sampling rate in Hz: [Default %d]\n", srate); +#ifdef ENABLE_GUI + printf("\t-g Enable GUI: [Default %s]\n", enable_gui ? "enabled" : "disabled"); +#endif /* ENABLE_GUI */ +} + +static void parse_args(int argc, char** argv) +{ + int opt; + while ((opt = getopt(argc, argv, "mtsg")) != -1) { + switch (opt) { + case 'm': + model = argv[optind]; + break; + case 't': + duration_ms = (uint32_t)atof(argv[optind]); + break; + case 's': + srate = (uint32_t)atof(argv[optind]); + break; +#ifdef ENABLE_GUI + case 'g': + enable_gui ^= true; + break; +#endif /* ENABLE_GUI */ + default: + usage(argv[0]); + exit(-1); + } + } +} + +int main(int argc, char** argv) +{ + int ret = SRSLTE_ERROR; + cf_t* input_buffer = NULL; + cf_t* output_buffer = NULL; + struct timeval t[3] = {}; + uint64_t time_usec = 0; + + parse_args(argc, argv); + + srslte_dft_plan_t ifft; + srslte_dft_plan_c(&ifft, srate / 1000, SRSLTE_DFT_BACKWARD); + +#ifdef ENABLE_GUI + plot_real_t plot_fft = NULL; + plot_real_t plot_h = NULL; + plot_real_t plot_imp = NULL; + + srslte_dft_plan_t fft = {}; + cf_t* fft_buffer = NULL; + float* fft_mag = NULL; + float* imp = NULL; + + if (enable_gui) { + sdrgui_init(); + sdrgui_init_title("SRS Fading channel"); + + plot_real_init(&plot_h); + plot_real_setTitle(&plot_h, "Ideal channel"); + plot_real_setYAxisScale(&plot_h, -60, 20); + plot_real_addToWindowGrid(&plot_h, (char*)"fading", 0, 0); + + plot_real_init(&plot_fft); + plot_real_setTitle(&plot_fft, "FFT measured"); + plot_real_setYAxisScale(&plot_fft, -60, 20); + plot_real_addToWindowGrid(&plot_fft, (char*)"fading", 0, 1); + + plot_real_init(&plot_imp); + plot_real_setTitle(&plot_imp, "Impulse"); + plot_real_setYAxisScale(&plot_imp, 0, 5); + plot_real_addToWindowGrid(&plot_imp, (char*)"fading", 0, 2); + + srslte_dft_plan_c(&fft, srate / 1000, SRSLTE_DFT_FORWARD); + + fft_buffer = srslte_vec_malloc(sizeof(cf_t) * srate / 1000); + if (!fft_buffer) { + fprintf(stderr, "Error: malloc\n"); + goto clean_exit; + } + + fft_mag = srslte_vec_malloc(sizeof(float) * srate / 1000); + if (!fft_mag) { + fprintf(stderr, "Error: malloc\n"); + goto clean_exit; + } + + imp = srslte_vec_malloc(sizeof(float) * srate / 1000); + if (!imp) { + fprintf(stderr, "Error: malloc\n"); + goto clean_exit; + } + } +#endif /* ENABLE_GUI */ + + // Initialise channel + if (srslte_channel_fading_init(&channel_fading, srate, model)) { + fprintf(stderr, "Error: initialising fading channel. model=%s, srate=%d\n", model, srate); + goto clean_exit; + } + + // Allocate buffers + input_buffer = srslte_vec_malloc(sizeof(cf_t) * srate / 1000); + if (!input_buffer) { + fprintf(stderr, "Error: allocating input buffer\n"); + goto clean_exit; + } + +#if INPUT_TYPE == 0 + bzero(input_buffer, sizeof(cf_t) * srate / 1000); + input_buffer[0] = 1; +#else + for (int i = 0; i < srate / 1000; i++) { + input_buffer[i] = cexpf(_Complex_I * ((double)rand() / (double)RAND_MAX) * M_PI / 2.0) / ((double)srate / 1000.0); + } + srslte_dft_run_c(&ifft, input_buffer, input_buffer); +#endif + + output_buffer = srslte_vec_malloc(sizeof(cf_t) * srate / 1000); + if (!output_buffer) { + fprintf(stderr, "Error: allocating output buffer\n"); + goto clean_exit; + } + + printf("-- Starting Fading channel simulator. srate=%.2fMHz; model=%s; duration=%dms\n", (double)srate / 1e6, model, + duration_ms); + + for (int i = 0; i < duration_ms; i++) { + gettimeofday(&t[1], NULL); + srslte_channel_fading_execute(&channel_fading, input_buffer, output_buffer, srate / 1000, (double)i / 1000.0); + gettimeofday(&t[2], NULL); + get_time_interval(t); + time_usec += (uint64_t)(t->tv_sec * 1e6 + t->tv_usec); + +#ifdef ENABLE_GUI + if (enable_gui) { + srslte_dft_run_c_zerocopy(&fft, output_buffer, fft_buffer); + srslte_vec_prod_conj_ccc(fft_buffer, fft_buffer, fft_buffer, srate / 1000); + for (int i = 0; i < srate / 1000; i++) { + fft_mag[i] = 10.0f * log10f(__real__ fft_buffer[i]); + } + plot_real_setNewData(&plot_fft, fft_mag, srate / 1000); + + for (int i = 0; i < channel_fading.N; i++) { + fft_mag[i] = 20.0f * log10f(cabsf(channel_fading.h_freq[i])); + } + plot_real_setNewData(&plot_h, fft_mag, channel_fading.N); + + for (int i = 0; i < srate / 1000; i++) { + imp[i] = cabsf(output_buffer[i]); + } + plot_real_setNewData(&plot_imp, imp, channel_fading.N); + + usleep(1000); + } +#endif /* ENABLE_GUI */ + } + + ret = SRSLTE_SUCCESS; + +clean_exit: + if (ret) { + printf("Error\n"); + } else { + printf("Ok ... %.1f MSps\n", duration_ms * (srate / 1000.0) / (double)time_usec); + } + + srslte_dft_plan_free(&ifft); + +#ifdef ENABLE_GUI + if (enable_gui) { + + if (fft_mag) { + free(fft_mag); + } + if (imp) { + free(imp); + } + if (fft_buffer) { + free(fft_buffer); + } + srslte_dft_plan_free(&fft); + sdrgui_exit(); + } +#endif /* ENABLE_GUI */ + if (input_buffer) { + free(input_buffer); + } + if (output_buffer) { + free(output_buffer); + } + srslte_channel_fading_free(&channel_fading); + srslte_dft_exit(); + exit(ret); +}