From 431739a76758e6315261bed534a168cbe394a6dd Mon Sep 17 00:00:00 2001 From: Marc Majoral Date: Wed, 18 Dec 2019 16:23:17 +0100 Subject: [PATCH] added Galileo E1 FPGA acuisition unit test --- src/tests/test_main.cc | 1 + ...e1_pcps_ambiguous_acquisition_test_fpga.cc | 493 ++++++++++++++++++ 2 files changed, 494 insertions(+) create mode 100644 src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc diff --git a/src/tests/test_main.cc b/src/tests/test_main.cc index ce048c510..64acede83 100644 --- a/src/tests/test_main.cc +++ b/src/tests/test_main.cc @@ -117,6 +117,7 @@ DECLARE_string(log_dir); #if FPGA_BLOCKS_TEST #include "unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_acquisition_test_fpga.cc" +#include "unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc" #include "unit-tests/signal-processing-blocks/tracking/gps_l1_ca_dll_pll_tracking_test_fpga.cc" #endif diff --git a/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc b/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc new file mode 100644 index 000000000..ab1fb6947 --- /dev/null +++ b/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc @@ -0,0 +1,493 @@ +/*! + * \file gps_l1_ca_pcps_acquisition_test_fpga.cc + * \brief This class implements an acquisition test for + * GpsL1CaPcpsAcquisitionFpga class based on some input parameters. + * \author Marc Majoral, 2017. mmajoral(at)cttc.cat + * + * ------------------------------------------------------------------------- + * + * Copyright (C) 2010-2019 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * GNSS-SDR is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNSS-SDR 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNSS-SDR. If not, see . + * + * ------------------------------------------------------------------------- + */ + +#include "concurrent_queue.h" +#include "fpga_switch.h" +#include "gnss_block_factory.h" +#include "gnss_block_interface.h" +#include "gnss_sdr_valve.h" +#include "gnss_synchro.h" +#include "galileo_e1_pcps_ambiguous_acquisition_fpga.h" +//#include "gps_l1_ca_pcps_acquisition_fpga.h" +#include "in_memory_configuration.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for abs, pow, floor +#include // for O_WRONLY +#include +#include +#include // for pthread stuff +#ifdef GR_GREATER_38 +#include +#else +#include +#endif + +struct DMA_handler_args_galileo_e1_pcps_ambiguous_acq_test +{ + std::string file; + int32_t nsamples_tx; + int32_t skip_used_samples; + unsigned int freq_band; // 0 for GPS L1/ Galileo E1, 1 for GPS L5/Galileo E5 +}; + +struct acquisition_handler_args_galileo_e1_pcps_ambiguous_acq_test +{ + std::shared_ptr acquisition; +}; + +class GalileoE1PcpsAmbiguousAcquisitionTestFpga : public ::testing::Test +{ +public: + bool acquire_signal(); + std::string implementation = "GPS_L1_CA_DLL_PLL_Tracking_Fpga"; + std::vector gnss_synchro_vec; + + static const int32_t TEST_ACQ_SKIP_SAMPLES = 1024; + static const int BASEBAND_SAMPLING_FREQ = 4000000; + +protected: + + GalileoE1PcpsAmbiguousAcquisitionTestFpga(); + ~GalileoE1PcpsAmbiguousAcquisitionTestFpga() = default; + + void init(); + + gr::top_block_sptr top_block; + std::shared_ptr factory; + std::shared_ptr config; + Gnss_Synchro gnss_synchro; + size_t item_size; + unsigned int doppler_max; + unsigned int doppler_step; + unsigned int nsamples_to_transfer; + +}; + + + +GalileoE1PcpsAmbiguousAcquisitionTestFpga::GalileoE1PcpsAmbiguousAcquisitionTestFpga() +{ + factory = std::make_shared(); + config = std::make_shared(); + item_size = sizeof(gr_complex); + gnss_synchro = Gnss_Synchro(); + doppler_max = 5000; + doppler_step = 100; +} + +void* handler_DMA_galileo_e1_pcps_ambiguous_acq_test(void* arguments) +{ + //const float MAX_SAMPLE_VALUE = 0.097781330347061; + const float MAX_SAMPLE_VALUE = 0.096257761120796; + const int DMA_BITS_PER_SAMPLE = 8; + const float DMA_SCALING_FACTOR = (pow(2, DMA_BITS_PER_SAMPLE - 1) - 1) / MAX_SAMPLE_VALUE; + const int MAX_INPUT_SAMPLES_TOTAL = 16384; + + auto* args = (struct DMA_handler_args_galileo_e1_pcps_ambiguous_acq_test*)arguments; + + std::string Filename = args->file; // input filename + int32_t skip_used_samples = args->skip_used_samples; + int32_t nsamples_tx = args->nsamples_tx; + + std::vector input_samples(MAX_INPUT_SAMPLES_TOTAL * 2); + std::vector input_samples_dma(MAX_INPUT_SAMPLES_TOTAL * 2 * 2); + bool file_completed = false; + int32_t nsamples_remaining; + int32_t nsamples_block_size; + unsigned int dma_index; + + int tx_fd; // DMA descriptor + std::ifstream infile; + + infile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + try + { + infile.open(Filename, std::ios::binary); + } + catch (const std::ifstream::failure &e) + { + std::cerr << "Exception opening file " << Filename << std::endl; + return nullptr; + } + + //************************************************************************** + // Open DMA device + //************************************************************************** + tx_fd = open("/dev/loop_tx", O_WRONLY); + if (tx_fd < 0) + { + std::cout << "Cannot open loop device" << std::endl; + return nullptr; + } + + //************************************************************************** + // Open input file + //************************************************************************** + + uint32_t skip_samples = 0; //static_cast(FLAGS_skip_samples); + + if (skip_samples + skip_used_samples > 0) + { + try + { + infile.ignore((skip_samples + skip_used_samples) * 2); + } + catch (const std::ifstream::failure &e) + { + std::cerr << "Exception reading file " << Filename << std::endl; + } + } + + nsamples_remaining = nsamples_tx; + nsamples_block_size = 0; + + while (file_completed == false) + { + dma_index = 0; + + if (nsamples_remaining > MAX_INPUT_SAMPLES_TOTAL) + { + nsamples_block_size = MAX_INPUT_SAMPLES_TOTAL; + } + else + { + nsamples_block_size = nsamples_remaining; + } + + try + { + // 2 bytes per complex sample + infile.read(reinterpret_cast(input_samples.data()), nsamples_block_size * 2 * sizeof(float)); + } + catch (const std::ifstream::failure &e) + { + std::cerr << "Exception reading file " << Filename << std::endl; + } + + for (int index0 = 0; index0 < (nsamples_block_size * 2); index0 += 2) + { + + if (args->freq_band == 0) + { + // channel 1 (queue 1) -> E5/L5 + input_samples_dma[dma_index] = 0; + input_samples_dma[dma_index + 1] = 0; + // channel 0 (queue 0) -> E1/L1 + input_samples_dma[dma_index + 2] = static_cast(input_samples[index0]*DMA_SCALING_FACTOR); + input_samples_dma[dma_index + 3] = static_cast(input_samples[index0 + 1]*DMA_SCALING_FACTOR); + } + else + { + // channel 1 (queue 1) -> E5/L5 + input_samples_dma[dma_index] = static_cast(input_samples[index0]*DMA_SCALING_FACTOR); + input_samples_dma[dma_index + 1] = static_cast(input_samples[index0 + 1]*DMA_SCALING_FACTOR); + // channel 0 (queue 0) -> E1/L1 + input_samples_dma[dma_index + 2] = 0; + input_samples_dma[dma_index + 3] = 0; + } + + dma_index += 4; + + } + + if (write(tx_fd, input_samples_dma.data(), nsamples_block_size * 2 * 2) != nsamples_block_size * 2 * 2) + { + std::cerr << "Error: DMA could not send all the required samples " << std::endl; + } + + // Throttle the DMA + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + + nsamples_remaining -= nsamples_block_size; + + if (nsamples_remaining == 0) + { + file_completed = true; + } + + } + + try + { + infile.close(); + } + catch (const std::ifstream::failure &e) + { + std::cerr << "Exception closing files " << Filename << std::endl; + } + + try + { + close(tx_fd); + } + catch (const std::ifstream::failure &e) + { + std::cerr << "Exception closing loop device " << std::endl; + } + + return nullptr; +} + + + +void* handler_acquisition_galileo_e1_pcps_ambiguous_acq_test(void* arguments) +{ + // the acquisition is a blocking function so we have to + // create a thread + auto* args = (struct acquisition_handler_args_galileo_e1_pcps_ambiguous_acq_test*)arguments; + args->acquisition->reset(); + return nullptr; +} + + +// When using the FPGA the acquisition class calls the states +// of the channel finite state machine directly. This is done +// in order to reduce the latency of the receiver when going +// from acquisition to tracking. In order to execute the +// acquisition in the unit tests we need to create a derived +// class of the channel finite state machine. Some of the states +// of the channel state machine are modified here, in order to +// simplify the instantiation of the acquisition class in the +// unit test. +class ChannelFsm_galileo_e1_pcps_ambiguous_acq_test: public ChannelFsm +{ +public: + + bool Event_valid_acquisition() override + { + acquisition_successful = true; + return true; + } + + + bool Event_failed_acquisition_repeat() override + { + acquisition_successful = false; + return true; + } + + + bool Event_failed_acquisition_no_repeat() override + { + acquisition_successful = false; + return true; + } + + bool Event_check_test_result() + { + return acquisition_successful; + } + + void Event_clear_test_result() + { + acquisition_successful = false; + } + +private: + + bool acquisition_successful; + +}; + + + +bool GalileoE1PcpsAmbiguousAcquisitionTestFpga::acquire_signal() +{ + + pthread_t thread_DMA, thread_acquisition; + + // 1. Setup GNU Radio flowgraph (file_source -> Acquisition_10m) + int SV_ID = 1; // initial sv id + + // fsm + std::shared_ptr channel_fsm_; + channel_fsm_ = std::make_shared(); + bool acquisition_successful; + + // Satellite signal definition + Gnss_Synchro tmp_gnss_synchro; + tmp_gnss_synchro.Channel_ID = 0; + + std::shared_ptr acquisition; + +// std::string System_and_Signal; + std::string signal; + struct DMA_handler_args_galileo_e1_pcps_ambiguous_acq_test args; + struct acquisition_handler_args_galileo_e1_pcps_ambiguous_acq_test args_acq; + + std::string file = "data/Galileo_E1_ID_1_Fs_4Msps_8ms.dat"; + args.file = file; // DMA file configuration + + // instantiate the FPGA switch and set the + // switch position to DMA. + std::shared_ptr switch_fpga; + switch_fpga = std::make_shared("/dev/uio1"); + switch_fpga->set_switch_position(0); // set switch position to DMA + + // create the correspondign acquisition block according to the desired tracking signal + tmp_gnss_synchro.System = 'E'; + signal = "1B"; + const char* str = signal.c_str(); // get a C style null terminated string + std::memcpy(static_cast(tmp_gnss_synchro.Signal), str, 2); // copy string into synchro char array: 2 char + null + tmp_gnss_synchro.PRN = SV_ID; +// System_and_Signal = "GPS L1 CA"; + const std::string& role = "Acquisition"; + acquisition = std::make_shared(config.get(), "Acquisition", 0, 0); + + args.freq_band = 1; // frequency band on which the DMA has to transfer the samples + + acquisition->set_gnss_synchro(&tmp_gnss_synchro); + acquisition->set_channel_fsm(channel_fsm_); + acquisition->set_channel(1); + acquisition->set_doppler_max(doppler_max); + acquisition->set_doppler_step(doppler_step); + acquisition->set_doppler_center(0); + acquisition->set_threshold(0.001); + + nsamples_to_transfer = static_cast(std::round(static_cast(BASEBAND_SAMPLING_FREQ) / (GALILEO_E1_CODE_CHIP_RATE_CPS / GALILEO_E1_B_CODE_LENGTH_CHIPS))); + + channel_fsm_->Event_clear_test_result(); + + acquisition->stop_acquisition(); // reset the whole system including the sample counters + acquisition->init(); + acquisition->set_local_code(); + + args.skip_used_samples = 0; + + // Configure the DMA to send the required samples to perform an acquisition + args.nsamples_tx = nsamples_to_transfer; + + // run the acquisition. The acquisition must run in a separate thread because it is a blocking function + args_acq.acquisition = acquisition; + + if (pthread_create(&thread_acquisition, nullptr, handler_acquisition_galileo_e1_pcps_ambiguous_acq_test, reinterpret_cast(&args_acq)) < 0) + { + std::cout << "ERROR cannot create acquisition Process" << std::endl; + } + + // wait to give time for the acquisition thread to set up the acquisition HW accelerator in the FPGA + usleep(1000000); + + // create DMA child process + if (pthread_create(&thread_DMA, nullptr, handler_DMA_galileo_e1_pcps_ambiguous_acq_test, reinterpret_cast(&args)) < 0) + { + std::cout << "ERROR cannot create DMA Process" << std::endl; + } + + // wait until the acquisition is finished + pthread_join(thread_acquisition, nullptr); + + // wait for the child DMA process to finish + pthread_join(thread_DMA, nullptr); + + acquisition_successful = channel_fsm_->Event_check_test_result(); + + if (acquisition_successful) + { + gnss_synchro_vec.push_back(tmp_gnss_synchro); + } + + if (!gnss_synchro_vec.empty()) + { + return true; + } + else + { + return false; + } +} + + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::init() +{ + gnss_synchro.Channel_ID = 0; + gnss_synchro.System = 'E'; + std::string signal = "1B"; + signal.copy(gnss_synchro.Signal, 2, 0); + gnss_synchro.PRN = 1; + config->set_property("GNSS-SDR.internal_fs_sps", "4000000"); + config->set_property("Acquisition.implementation", "Galileo_E1_PCPS_Ambiguous_Acquisition_Fpga"); + config->set_property("Acquisition.threshold", "0.00001"); + config->set_property("Acquisition.doppler_max", std::to_string(doppler_max)); + config->set_property("Acquisition.doppler_step", std::to_string(doppler_step)); + config->set_property("Acquisition.repeat_satellite", "false"); + + // the test file is sampled @ 4MSPs only ,so we have to use the FPGA queue corresponding + // to the L5/E5a frequency band in order to avoid the L1/E1 factor :4 downsampling filter + config->set_property("Acquisition.downsampling_factor", "1"); + config->set_property("Acquisition.select_queue_Fpga", "1"); + config->set_property("Acquisition.total_block_exp", "14"); +} + +TEST_F(GalileoE1PcpsAmbiguousAcquisitionTestFpga, ValidationOfResults) +{ + struct DMA_handler_args_galileo_e1_pcps_ambiguous_acq_test args; + + std::chrono::time_point start, end; + std::chrono::duration elapsed_seconds(0); + + double expected_delay_samples = 2920; // 18250; + double expected_doppler_hz = -632; + + init(); + + start = std::chrono::system_clock::now(); + + ASSERT_EQ(acquire_signal(), true); + + end = std::chrono::system_clock::now(); + elapsed_seconds = end - start; + + uint32_t n = 0; // there is only one channel + std::cout << "Acquired " << nsamples_to_transfer << " samples in " << elapsed_seconds.count() * 1e6 << " microseconds" << std::endl; + + double delay_error_samples = std::abs(expected_delay_samples - gnss_synchro_vec.at(n).Acq_delay_samples); + auto delay_error_chips = static_cast(delay_error_samples * 1023 / 4000); + double doppler_error_hz = std::abs(expected_doppler_hz - gnss_synchro_vec.at(n).Acq_doppler_hz); + + // the acquisition grid is not available when using the FPGA + + EXPECT_LE(doppler_error_hz, 666) << "Doppler error exceeds the expected value: 666 Hz = 2/(3*integration period)"; + EXPECT_LT(delay_error_chips, 0.5) << "Delay error exceeds the expected value: 0.5 chips"; + +} +