From 61bdeeed8b43cad3e2cd6ddf2cdcf5de8b86f5d5 Mon Sep 17 00:00:00 2001 From: pranesh-r-ps Date: Wed, 28 Feb 2024 13:30:46 +0000 Subject: [PATCH] Add channel estimation and equalization integration test --- CMakeLists.txt | 5 +- .../ChannelEqualization/main.cpp | 0 .../ChannelEstimation/main.cpp | 28 +++ .../Integration/main.cpp | 93 ++++++++ .../channel_generation.hpp} | 141 ++---------- .../channel_verification.hpp | 205 ++++++++++++++++++ 6 files changed, 343 insertions(+), 129 deletions(-) rename test/{ => ChannelEstimationAndEqualization}/ChannelEqualization/main.cpp (100%) create mode 100644 test/ChannelEstimationAndEqualization/ChannelEstimation/main.cpp create mode 100644 test/ChannelEstimationAndEqualization/Integration/main.cpp rename test/{ChannelEstimation/main.cpp => ChannelEstimationAndEqualization/channel_generation.hpp} (65%) create mode 100644 test/ChannelEstimationAndEqualization/channel_verification.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 67e1eae..f305de0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -364,8 +364,9 @@ endfunction() add_armral_test(arm_fir_filter_cs16_decimate_2 test/FIR/arm_fir_filter_cs16_decimate_2/main.cpp) add_armral_test(modulation test/Modulation/main.cpp) add_armral_test(demodulation test/Demodulation/main.cpp) - add_armral_test(channel_estimation test/ChannelEstimation/main.cpp) - add_armral_test(channel_equalization test/ChannelEqualization/main.cpp) + add_armral_test(channel_estimation test/ChannelEstimationAndEqualization/ChannelEstimation/main.cpp) + add_armral_test(channel_equalization test/ChannelEstimationAndEqualization/ChannelEqualization/main.cpp) + add_armral_test(integration_test test/ChannelEstimationAndEqualization/Integration/main.cpp) add_armral_test(mu_law_compression test/MuLaw/Compression/main.cpp) add_armral_test(mu_law_decompression test/MuLaw/Decompression/main.cpp) add_armral_test(vec_dot_16_32_bit test/VectorDotProd/vecDot16_32bit/main.cpp) diff --git a/test/ChannelEqualization/main.cpp b/test/ChannelEstimationAndEqualization/ChannelEqualization/main.cpp similarity index 100% rename from test/ChannelEqualization/main.cpp rename to test/ChannelEstimationAndEqualization/ChannelEqualization/main.cpp diff --git a/test/ChannelEstimationAndEqualization/ChannelEstimation/main.cpp b/test/ChannelEstimationAndEqualization/ChannelEstimation/main.cpp new file mode 100644 index 0000000..90312ed --- /dev/null +++ b/test/ChannelEstimationAndEqualization/ChannelEstimation/main.cpp @@ -0,0 +1,28 @@ +/* + Arm RAN Acceleration Library + Copyright 2024 Arm Limited and/or its affiliates +*/ +#include "../channel_verification.hpp" +#include "armral.h" +#include + +int main() { + bool passed = true; + + passed &= armral::verify_channel::run_all_tests( + "ComplexChannelEstimation", false, armral_cmplx_channel_estimation_f32); + + passed &= armral::verify_channel::run_all_tests( + "ComplexChannelEstimationNoAlloc", false, + [](armral_config_ce_params ce_params, armral_cmplx_f32_t *p_src_x, + auto... args) { + int buff_size = ce_params.num_symbols * ce_params.num_sc * + ce_params.num_tx * ce_params.num_rx * + sizeof(armral_cmplx_f32_t) * 3; + std::vector buffer(buff_size + 3); + return armral_cmplx_channel_estimation_f32_noalloc( + ce_params, p_src_x, args..., buffer.data()); + }); + + exit(passed ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/test/ChannelEstimationAndEqualization/Integration/main.cpp b/test/ChannelEstimationAndEqualization/Integration/main.cpp new file mode 100644 index 0000000..60fc860 --- /dev/null +++ b/test/ChannelEstimationAndEqualization/Integration/main.cpp @@ -0,0 +1,93 @@ +/* + Arm RAN Acceleration Library + Copyright 2024 Arm Limited and/or its affiliates +*/ +#include "../channel_verification.hpp" +#include "armral.h" +#include + +/** + * Key points for integrating channel estimation and equalization modules + */ +/** + * Indexing in channel estimation module: + * + * The channel estimation module takes the transmit and received signals only + * for the pilot resource elements and generates the channel estimate for all + * the (pilot and data) subcarriers and symbols. Therefore, the indexing of the + * channel estimate needs to be as `H[pilot_freq_idxs[sc_id], + * pilot_time_idxs[sym_id], tx_id, rx_id]` whereas, for the pilot transmitted + * (or received) signals, it is ‘X[sc_id, sym_id, tx_id]’. Hence, the indexing + * needs to be taken care of. + * + * Rearranging blocks in the right dimension: + * + * The transmitted pilot signals in the channel estimation module are stored in + * a single continuous array of size `num_subcarriers * num_pilot_symbols * + * num_tx` in row-major form. However, while performing equalization in blocks, + * `num_chnls > 1`, it is to be noted that the size of the block is `num_tx * + * num_chnls`. The parameter `num_chnls` is the number of consecutive + * subcarriers in the pilot symbol that are grouped together for estimation or + * equalization. Therefore, the block needs to be rearranged accordingly before + * performing channel equalization. Refer to line 50,51 in the + * `/src/UpperPHY/ChannelEstimation/arm_channel_estimation.cpp` file. + * + * Note: While enabling the macro `ARMRAL_CHANNEL_ESTIMATION_PER_RB`, all the + * `num_chnls` consecutive subcarriers in the same pilot symbol have the same + * channel estimate value replicated in all the subcarriers. + * + * Alternatively, the `num_chnls` can always be set to one so that the transmit + * symbol in each resource element can be individually decoded, irrespective of + * the value of `num_chnls` in the channel estimation module. + * + * EVM Results: + * + * The final EVM values after equalization depend on other factors outside the + * channel estimation module, like channel quality and channel dimensions. If + * the channel has less interference between the adjacent antennae, i.e., the + * channel is close to a diagonal matrix, the final EVM will be less. On the + * other hand, if the interference between the antennae is stronger, the + * equalizer output will be more distorted, and hence, the EVM will be more. + * + * The channel dimensions also matter for the EVM results. The channel equation + * is given by: + * + *
+ *  y = Hx + n
+ * 
+ * + * where `H` is the channel matrix, `y` is the received signal, `x` is the + * transmitted signal, and `n` is the noise. The goal of the equalizer is to + * estimate the transmitted signal `x` from the received signal `y` and the + * channel estimate `H`. If the number of receive antennae is more than the + * number of transmit antennae, the above expression is over-determined. I.e., + * it has more equations than unknowns and can be solved with good + * approximation. Whereas, if the number of transmit antennae is more than the + * number of receive antennae, the above expression is under-determined. I.e., + * it has fewer equations than unknowns. The EVM in this case will be higher + * than the former case. + * + * The EVM also depends on the signal-to-noise ratio (SNR). With higher SNR, the + * EVM will be lower, and vice versa. + */ + +int main() { + bool passed = true; + + passed &= armral::verify_channel::run_all_tests( + "ComplexChannelEstimation", true, armral_cmplx_channel_estimation_f32); + + passed &= armral::verify_channel::run_all_tests( + "ComplexChannelEstimationNoAlloc", true, + [](armral_config_ce_params ce_params, armral_cmplx_f32_t *p_src_x, + auto... args) { + int buff_size = ce_params.num_symbols * ce_params.num_sc * + ce_params.num_tx * ce_params.num_rx * + sizeof(armral_cmplx_f32_t) * 3; + std::vector buffer(buff_size + 3); + return armral_cmplx_channel_estimation_f32_noalloc( + ce_params, p_src_x, args..., buffer.data()); + }); + + exit(passed ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/test/ChannelEstimation/main.cpp b/test/ChannelEstimationAndEqualization/channel_generation.hpp similarity index 65% rename from test/ChannelEstimation/main.cpp rename to test/ChannelEstimationAndEqualization/channel_generation.hpp index c64c7d5..2807e94 100644 --- a/test/ChannelEstimation/main.cpp +++ b/test/ChannelEstimationAndEqualization/channel_generation.hpp @@ -4,15 +4,19 @@ */ #include "armral.h" #include "cf32_utils.hpp" -#include -#include #include -#define ARMRAL_MAX_CHANNEL_ARRAY_SIZE 175000 -#define ARMRAL_MAX_PILOT_ARRAY_SIZE 1600 #define ARMRAL_NUM_SC_PER_RB 12 -namespace { +/** + * This header file contains the required functions to generate a channel model. + * + * The functions to generate the channel are placed in this header file, + * separate from other functions, making it easier to locate and modify only the + * required functions when using a different channel model. + */ + +namespace armral::channel_generate { /* * Function to fill the channel estimation parameters like pilot indices from * a set of primary parameters like number of pilot symbols. @@ -100,7 +104,7 @@ armral_config_ce_params fill_ce_params(uint16_t num_td_pilot, * * This is a simple model with randomly generated channel values and the * variation across the adjacent resource elements(RE) are modelled linearly - * to similar channel behaviour within the bandwidth/slot. + * to have similar channel behaviour within the bandwidth/slot. */ void generate_reference_channel(armral_config_ce_params ce_params, armral_cmplx_f32_t *p_dst_ref_h) { @@ -111,6 +115,7 @@ void generate_reference_channel(armral_config_ce_params ce_params, // Generate random channel matrix for reference RE std::vector p_mat_h_ref(h_mat_size); p_mat_h_ref = crandom.vector(h_mat_size, {-1, -1}, {1, 1}); + uint16_t group_size = ARMRAL_NUM_SC_PER_RB + ((uint16_t)((ce_params.num_tx - 1) / ARMRAL_NUM_SC_PER_RB)) * @@ -170,8 +175,8 @@ void generate_pilot_data(armral_config_ce_params ce_params, for (uint16_t j = 0; j < ce_params.num_pilot_td; j++) { crand_signal = crandom.one({-1, -1}, {1, 1}); // Assign random QPSK signals for pilots - crand_signal.re = (crand_signal.re < 0) ? (-1.0) : (1.0); - crand_signal.im = (crand_signal.im < 0) ? (-1.0) : (1.0); + crand_signal.re = (crand_signal.re < 0) ? (-0.7071) : (0.7071); + crand_signal.im = (crand_signal.im < 0) ? (-0.7071) : (0.7071); if (ce_params.num_tx == 1) { // For single transmit antenna, the pilots are present in alternate // subcarriers, while the rest of the subcarriers carry no signal @@ -221,122 +226,4 @@ void generate_pilot_data(armral_config_ce_params ce_params, } } -/* - * Function to verify if the channel estimates are close enough to the - * reference channel. The accuracy is measure in terms of mean squared error. - * - * The Error Vector Magnitude (EVM) limit is taken to be 8% in reference to - * section 6.4.2.1 of 3GPP TS 38.101-2 document. - */ -uint16_t check_result(armral_config_ce_params ce_params, - armral_cmplx_f32_t *p_dst_h, - armral_cmplx_f32_t *p_dst_ref_h) { - const float32_t evm_limit = 0.08; - armral_cmplx_f32_t diff; - uint32_t h_size = ce_params.num_symbols * ce_params.num_sc * - ce_params.num_tx * ce_params.num_rx; - float32_t mean_sq_error = 0; - for (uint32_t h_idx = 0; h_idx < h_size; h_idx++) { - diff.re = p_dst_h[h_idx].re - p_dst_ref_h[h_idx].re; - diff.im = p_dst_h[h_idx].im - p_dst_ref_h[h_idx].im; - mean_sq_error += diff.re * diff.re + diff.im * diff.im; - } - mean_sq_error = mean_sq_error / h_size; - printf("Mean squared error = %f, evm_limit = %f\t", mean_sq_error, evm_limit); - if (mean_sq_error > evm_limit) { - printf("Test case failed\n"); - } else { - printf("Test case Passed\n"); - } - return (uint16_t)(mean_sq_error > evm_limit); -} - -template -bool run_all_tests( - char const *function_name, - ChannelEstimationFunction channel_estimation_function_under_test) { - printf("\nTesting framework for %s\n", function_name); - - bool passed = true; - const uint16_t par_list_num_td_pilots[] = {1, 2, 4}; - const uint16_t par_list_num_fd_pilots[] = {12, 24}; - const uint16_t par_list_num_antennae[] = {1, 2, 3, 4, 8, 16}; - uint16_t num_test_failed = 0; - armral_status ret_status; - armral_config_ce_params ce_params; - armral_cmplx_f32_t p_src_x[ARMRAL_MAX_PILOT_ARRAY_SIZE]; - armral_cmplx_f32_t p_src_y[ARMRAL_MAX_PILOT_ARRAY_SIZE]; - armral_cmplx_f32_t p_dst_h[ARMRAL_MAX_CHANNEL_ARRAY_SIZE]; - armral_cmplx_f32_t p_dst_ref_h[ARMRAL_MAX_CHANNEL_ARRAY_SIZE]; - float32_t noise_pwr = 0; - - // Run tests for different number of pilots in time and frequency domain - for (uint16_t num_td_pilot : par_list_num_td_pilots) { - for (uint16_t num_fd_pilot : par_list_num_fd_pilots) { - for (uint16_t num_tx_antenna : par_list_num_antennae) { - for (uint16_t num_rx_antenna : par_list_num_antennae) { - // Set parameters - ce_params = fill_ce_params(num_td_pilot, num_fd_pilot, num_tx_antenna, - num_rx_antenna); - printf("Num TD pilots = %d, Num SC = %d, Num TX = %d, Num RX = %d, " - "Num Chnls = %d\n", - ce_params.num_pilot_td, ce_params.num_sc, ce_params.num_tx, - ce_params.num_rx, ce_params.num_chnls); - - // Create reference matrices - generate_reference_channel(ce_params, p_dst_ref_h); - - // Generate pilot signals and observations - generate_pilot_data(ce_params, p_src_x, p_src_y, p_dst_ref_h); - - // Execute the function - ret_status = channel_estimation_function_under_test( - ce_params, p_src_x, p_src_y, p_dst_h, &noise_pwr); - if (ret_status == ARMRAL_SUCCESS) { - // Compare the channel estimate with the reference channel - num_test_failed += check_result(ce_params, p_dst_h, p_dst_ref_h); - } else if (ret_status == ARMRAL_ARGUMENT_ERROR) { - if (ce_params.num_pilot_fd < ce_params.num_tx) { - // This configuration is not supported - printf("Invalid argument\n"); - } else { - printf("Argument Error\n"); - num_test_failed++; - } - } else { - // Unknown return values are considered as failed case - printf("Unknown result:\tTest case failed\n"); - num_test_failed++; - } - } - } - } - } - // Test passed if all cases passed - printf("\nNumber of failed cases = %d\n", num_test_failed); - printf("\nChannel Estimation test completed\n"); - passed = (num_test_failed == 0); - return passed; -} -} // Anonymous namespace - -int main() { - bool passed = true; - - passed &= run_all_tests("ComplexChannelEstimation", - armral_cmplx_channel_estimation_f32); - - passed &= run_all_tests("ComplexChannelEstimationNoAlloc", - [](armral_config_ce_params ce_params, - armral_cmplx_f32_t *p_src_x, auto... args) { - int buff_size = - ce_params.num_symbols * ce_params.num_sc * - ce_params.num_tx * ce_params.num_rx * - sizeof(armral_cmplx_f32_t) * 3; - std::vector buffer(buff_size + 3); - return armral_cmplx_channel_estimation_f32_noalloc( - ce_params, p_src_x, args..., buffer.data()); - }); - - exit(passed ? EXIT_SUCCESS : EXIT_FAILURE); -} +} // namespace armral::channel_generate diff --git a/test/ChannelEstimationAndEqualization/channel_verification.hpp b/test/ChannelEstimationAndEqualization/channel_verification.hpp new file mode 100644 index 0000000..f955b2e --- /dev/null +++ b/test/ChannelEstimationAndEqualization/channel_verification.hpp @@ -0,0 +1,205 @@ +/* + Arm RAN Acceleration Library + Copyright 2024 Arm Limited and/or its affiliates +*/ +#include "armral.h" +#include "channel_generation.hpp" + +/** + * This header file contains the required functions to verify the results. + */ + +/* + * Define the maximum tolerable Error Vector Magnitude (EVM) for the test case + * to pass. + * + * The maximum acceptable EVM is 8%, according to section 6.4.2.1 of the 3GPP TS + * 38.101-2 document. The defined EVM limit can vary depending on the use case + * but should be less than 8%. + */ +#define EVM_LIMIT 0.04 + +namespace armral::verify_channel { +/* + * Function to calculate the error vector magnitude (EVM) between the reference + * data and the estimated data. + */ +float32_t get_evm(uint32_t size, armral_cmplx_f32_t *p_ref, + armral_cmplx_f32_t *p_est) { + float32_t evm = 0; + armral_cmplx_f32_t temp; + for (uint32_t idx = 0; idx < size; idx++) { + temp.re = p_ref[idx].re - p_est[idx].re; + temp.im = p_ref[idx].im - p_est[idx].im; + evm += temp.re * temp.re + temp.im * temp.im; + } + return (evm / size); +} + +/* + * Function to verify if the channel estimates are close enough to the + * reference channel. The accuracy is measure in terms of mean squared error. + * + * The Error Vector Magnitude (EVM) limit is defined to be a value less than 8% + * according to section 6.4.2.1 of the 3GPP TS 38.101-2 document. + */ +uint16_t check_result(armral_config_ce_params ce_params, + armral_cmplx_f32_t *p_dst_h, + armral_cmplx_f32_t *p_dst_ref_h) { + uint32_t h_size = ce_params.num_symbols * ce_params.num_sc * + ce_params.num_tx * ce_params.num_rx; + float32_t mean_sq_error = get_evm(h_size, p_dst_ref_h, p_dst_h); + printf("Mean squared error = %f, EVM limit = %f\t", mean_sq_error, EVM_LIMIT); + if (mean_sq_error > EVM_LIMIT) { + printf("Test case FAIL\n"); + } else { + printf("Test case PASS\n"); + } + return (uint16_t)(mean_sq_error > EVM_LIMIT); +} + +/* + * Function to perform equalization on the pilot data and display the equalizer + * output. This is a sample function to demonstrate the integration of the + * channel equalization module with the channel estimation module. + */ +void equalize_pilot_data(armral_config_ce_params ce_params, + armral_cmplx_f32_t *p_src_h, + armral_cmplx_f32_t *p_src_y, float32_t *noise_est, + armral_cmplx_f32_t *p_dst_x_tild) { + uint32_t x_mat_size = ce_params.num_tx; + uint32_t y_mat_size = ce_params.num_rx; + uint32_t h_mat_size = ce_params.num_tx * ce_params.num_rx; + + for (uint32_t i = 0; i < ce_params.num_sc; i++) { + for (uint32_t j = 0; j < ce_params.num_pilot_td; j++) { + uint32_t x_idx = (i * ce_params.num_pilot_td + j) * x_mat_size; + uint32_t y_idx = (i * ce_params.num_pilot_td + j) * y_mat_size; + uint32_t h_idx = + (i * ce_params.num_symbols + ce_params.pilot_time_indices[j]) * + h_mat_size; + armral_cmplx_channel_equalization_f32( + ce_params.num_tx, ce_params.num_rx, 1, (p_src_h + h_idx), + (p_src_y + y_idx), noise_est, (p_dst_x_tild + x_idx)); + } + } +} + +/* + * Function to run the channel estimation function for all the possible values + * of input parameters. The function returns a boolean value of `true` only + * if all the valid test cases pass. The function also performs equalization + * with the estimated channel if the value of the argument `equalize_data` is + * `true`. + */ +template +bool run_all_tests( + char const *function_name, bool equalize_data, + ChannelEstimationFunction channel_estimation_function_under_test) { + if (equalize_data) { + printf("\nTesting integration framework for %s\n", function_name); + } else { + printf("\nTesting framework for %s\n", function_name); + } + + bool passed = true; + const uint16_t par_list_num_td_pilots[] = {1, 2, 4}; + const uint16_t par_list_num_fd_pilots[] = {12, 24}; + const uint16_t par_list_num_antennae[] = {1, 2, 3, 4, 8, 16}; + uint16_t num_test_failed = 0; + float32_t noise_pwr = 0; + + // Run tests for different number of pilots in time and frequency domain + for (uint16_t num_td_pilot : par_list_num_td_pilots) { + for (uint16_t num_fd_pilot : par_list_num_fd_pilots) { + for (uint16_t num_tx_antenna : par_list_num_antennae) { + for (uint16_t num_rx_antenna : par_list_num_antennae) { + // Set parameters + armral_config_ce_params ce_params = + armral::channel_generate::fill_ce_params( + num_td_pilot, num_fd_pilot, num_tx_antenna, num_rx_antenna); + printf("\nNum TD pilots = %d, Num SC = %d, Num TX = %d, Num RX = %d, " + "Num Chnls = %d\n", + ce_params.num_pilot_td, ce_params.num_sc, ce_params.num_tx, + ce_params.num_rx, ce_params.num_chnls); + + // Declare variables for channel and pilot signals + uint32_t h_size = ce_params.num_sc * ce_params.num_symbols * + ce_params.num_tx * ce_params.num_rx; + uint32_t x_size = + ce_params.num_sc * ce_params.num_pilot_td * ce_params.num_tx; + uint32_t y_size = + ce_params.num_sc * ce_params.num_pilot_td * ce_params.num_rx; + std::vector p_dst_ref_h(h_size); + std::vector p_dst_h(h_size); + std::vector p_src_x(x_size); + std::vector p_src_y(y_size); + + // Create reference matrices + armral::channel_generate::generate_reference_channel( + ce_params, p_dst_ref_h.data()); + + // Generate pilot signals and observations + armral::channel_generate::generate_pilot_data( + ce_params, p_src_x.data(), p_src_y.data(), p_dst_ref_h.data()); + + // Execute the function + armral_status ret_status = channel_estimation_function_under_test( + ce_params, p_src_x.data(), p_src_y.data(), p_dst_h.data(), + &noise_pwr); + if (ret_status == ARMRAL_SUCCESS) { + // Compare the channel estimate with the reference channel + printf("Channel Estimation:\t"); + num_test_failed += + check_result(ce_params, p_dst_h.data(), p_dst_ref_h.data()); + if (equalize_data) { + printf("Integration Check:\t"); + // Equalize the received signal with respect to both estimated and + // reference channels to compare the EVMs + std::vector p_dst_x_tild(x_size); + equalize_pilot_data(ce_params, p_dst_h.data(), p_src_y.data(), + &noise_pwr, p_dst_x_tild.data()); + float32_t evm_est_ch = + get_evm(x_size, p_src_x.data(), p_dst_x_tild.data()); + equalize_pilot_data(ce_params, p_dst_ref_h.data(), p_src_y.data(), + &noise_pwr, p_dst_x_tild.data()); + float32_t evm_ref_ch = + get_evm(x_size, p_src_x.data(), p_dst_x_tild.data()); + // Compare the EVMs with estimated and reference channel + // The EVM limit is the same as that for channel estimation + float32_t evm_diff = fabs(evm_est_ch - evm_ref_ch); + printf("EVM difference = %f, EVM limit = %f\t\t", evm_diff, + EVM_LIMIT); + if (evm_diff > EVM_LIMIT) { + printf("Test case FAIL\n"); + } else { + printf("Test case PASS\n"); + } + num_test_failed += (uint16_t)(evm_diff > EVM_LIMIT); + } + } else if (ret_status == ARMRAL_ARGUMENT_ERROR) { + if (ce_params.num_pilot_fd < ce_params.num_tx) { + // This configuration is not supported + printf("Invalid argument\n"); + } else { + printf("Argument Error\n"); + num_test_failed++; + } + } else { + // Unknown return values are considered as failed case + printf("Unknown result:\tTest case failed\n"); + num_test_failed++; + } + } + } + } + } + + // Test passed if all cases passed + printf("\nNumber of failed cases = %d\n", num_test_failed); + printf("\nChannel Estimation test completed\n"); + passed = (num_test_failed == 0); + return passed; +} + +} // namespace armral::verify_channel -- GitLab