diff --git a/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc b/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc index 15eafc4a2..043f4c292 100644 --- a/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc +++ b/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_ambiguous_acquisition_test_fpga.cc @@ -1,8 +1,9 @@ /*! * \file galileo_e1_pcps_acquisition_test_fpga.cc - * \brief This class implements an acquisition test Galileo FPGA acquisition + * \brief This class implements an acquisition test for the + * Galileo FPGA acquisition * \authors * @@ -11,31 +12,38 @@ * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. * This file is part of GNSS-SDR. * - * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors) * SPDX-License-Identifier: GPL-3.0-or-later * * ----------------------------------------------------------------------------- */ -#include "Galileo_E1.h" -#include "acquisition_dump_reader.h" -#include "concurrent_queue.h" -#include "fpga_switch.h" -#include "gnss_block_interface.h" -#include "gnss_sdr_filesystem.h" -#include "gnss_signal.h" -#include "gnss_synchro.h" -#include "in_memory_configuration.h" -#include "pcps_acquisition_adapter_fpga.h" -#include "test_flags.h" -#include -#include -#include -#include -#include // for abs, pow, floor -#include // for O_WRONLY -#include -#include +#include "Galileo_E1.h" // Galileo E1 signal parameters +#include "acquisition_interface.h" // AcquisitionInterface +#include "channel_fsm.h" // ChannelFsm +#include "fpga_dma-proxy.h" // Fpga_DMA +#include "fpga_switch.h" // Fpga_Switch +#include "gnss_synchro.h" // Gnss_Synchro +#include "in_memory_configuration.h" // InMemoryConfiguration +#include "pcps_acquisition_adapter_fpga.h" // PcpsAcquisitionAdapterFpga +#include "uio_fpga.h" // find_uio_dev_file_name +#include // GoogleTest +#include // clocks, durations, milliseconds +#include // std::abs +#include // std::size_t +#include // fixed-width integer types +#include // std::memcpy +#include // open, O_RDWR, O_SYNC +#include // std::ifstream +#include // std::cout, std::endl +#include // std::shared_ptr, std::make_shared +#include // std::string, std::to_string +#include // std::string_view +#include // mmap, munmap +#include // std::thread, std::this_thread +#include // close, usleep +#include // std::vector + #if USE_GLOG_AND_GFLAGS #include @@ -49,211 +57,80 @@ #include #endif - -struct DMA_handler_args_galileo_e1_pcps_ambiguous_acq_test -{ - std::string file; - int32_t nsamples_tx; - int32_t skip_used_samples; - float scaling_factor; -}; - -struct acquisition_handler_args_galileo_e1_pcps_ambiguous_acq_test -{ - std::shared_ptr acquisition; -}; +class ChannelFsm_galileo_e1_pcps_ambiguous_acq_test; class GalileoE1PcpsAmbiguousAcquisitionTestFpga : public ::testing::Test { -public: - bool acquire_signal(); - std::string implementation = "GPS_L1_CA_DLL_PLL_Tracking_FPGA"; - std::vector gnss_synchro_vec; - - const int32_t TEST_ACQ_SKIP_SAMPLES = 1024; - const int BASEBAND_SAMPLING_FREQ = 4000000; - const float MAX_SAMPLE_VALUE = 0.096257761120796; - const int DMA_BITS_PER_SAMPLE = 8; - const float DMA_SIGNAL_SCALING_FACTOR = (pow(2, DMA_BITS_PER_SAMPLE - 1) - 1) / MAX_SAMPLE_VALUE; - protected: + // acquisition configuration + inline static constexpr int BASEBAND_SAMPLING_RATE_SPS = 12500000; + inline static constexpr int DOPPLER_MAX_HZ = 5000; + inline static constexpr int DOPPLER_STEP_HZ = 125; + inline static constexpr float ACQUISITION_THRESHOLD = 2.25f; + inline static constexpr int TOTAL_BLK_EXP = 12; + inline static constexpr std::string_view IMPLEMENTATION = "Galileo_E1_PCPS_Ambiguous_Acquisition_FPGA"; + + // acquisition expected results + inline static constexpr double EXPECTED_DELAY_SAMPLES = 42964; + inline static constexpr double EXPECTED_DOPPLER_HZ = 1625; + + // Signal parameters + inline static constexpr int SV_ID = 1; + inline static constexpr int COHERENT_INTEGRATION_TIME_ms = 4; + inline static constexpr char SYSTEM = 'E'; + inline static constexpr std::string_view SIGNAL = "1B"; + inline static constexpr unsigned int NSAMPLES = + static_cast( + (static_cast(BASEBAND_SAMPLING_RATE_SPS) * + static_cast(COHERENT_INTEGRATION_TIME_ms)) / + 1000U); + + // File to DMA control + inline static constexpr int COMPLEX_SAMPLE_SIZE_BYTES = sizeof(int8_t) * 2; // interleaved byte + inline static constexpr int SAMPLE_BLOCK_SIZE_BYTES = 16384; + inline static constexpr std::string_view SIGNAL_FILE_PATH = "./signal_samples/Galileo_E1_ID_1_Fs_12.5Msps_10ms.dat"; + + // FPGA switch + inline static constexpr int POST_PROCESSING_MODE = 0; // Select post-processing mode (read a signal from a recorded file) + + // FPGA Dynamic bit selection + inline static constexpr int DYN_BIT_SEL_DEV_NUM = 0; // device 0 is connected to the L1/E1 frequency-band path. + inline static constexpr size_t FPGA_PAGE_SIZE_BYTES = 0x1000; + inline static constexpr uint32_t DYN_BIT_SEL_SHIFT_OUT_BITS = 0; // No bit shift; select the least significant bits. + inline static constexpr std::string_view DYN_BIT_SEL_DEVICE_NAME = "dynamic_bits_selector"; // device name + inline static constexpr int SOBITS_REG_ADDR = 0; // Shift out bits register address + GalileoE1PcpsAmbiguousAcquisitionTestFpga(); ~GalileoE1PcpsAmbiguousAcquisitionTestFpga() = default; void init(); + void create_and_open_DMA(std::shared_ptr &dma_fpga); + void create_switch(std::shared_ptr &fpga_switch); + void open_and_map_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel); + void create_acquisition(std::shared_ptr &acquisition); + + void configure_switch(std::shared_ptr &fpga_switch); + void configure_dynamic_bit_selector(volatile unsigned *&d_map_base_dyn_bit_sel); + void configure_acquisition(std::shared_ptr &acquisition, Gnss_Synchro &tmp_gnss_synchro, std::shared_ptr &channel_fsm_); + + void run_DMA_process(std::shared_ptr &dma_fpga); + void run_acquisition_process(std::shared_ptr acquisition); + + void close_DMA(std::shared_ptr &dma_fpga); + void close_switch(std::shared_ptr &fpga_switch); + void unmap_and_close_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel); + void release_acquisition(std::shared_ptr &acquisition); + std::shared_ptr config; - - unsigned int doppler_max; - unsigned int doppler_step; - unsigned int nsamples_to_transfer; }; - -GalileoE1PcpsAmbiguousAcquisitionTestFpga::GalileoE1PcpsAmbiguousAcquisitionTestFpga() -{ - config = std::make_shared(); - - doppler_max = 5000; - doppler_step = 100; - nsamples_to_transfer = 0; -} - - -void* handler_DMA_galileo_e1_pcps_ambiguous_acq_test(void* arguments) -{ - 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 << '\n'; - return nullptr; - } - - // ************************************************************************* - // Open DMA device - // ************************************************************************* - tx_fd = open("/dev/loop_tx", O_WRONLY); - if (tx_fd < 0) - { - std::cout << "Cannot open loop device\n"; - 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 << '\n'; - } - } - - 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 << '\n'; - } - - for (int index0 = 0; index0 < (nsamples_block_size * 2); index0 += 2) - { - // channel 1 (queue 1) -> E5/L5 - input_samples_dma[dma_index] = static_cast(input_samples[index0] * args->scaling_factor); - input_samples_dma[dma_index + 1] = static_cast(input_samples[index0 + 1] * args->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 \n"; - } - - // 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 << '\n'; - } - - try - { - close(tx_fd); - } - catch (const std::ifstream::failure& e) - { - std::cerr << "Exception closing loop device \n"; - } - - 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 of the channel finite state machine. class ChannelFsm_galileo_e1_pcps_ambiguous_acq_test : public ChannelFsm { public: @@ -289,152 +166,439 @@ private: bool acquisition_successful{}; }; - -bool GalileoE1PcpsAmbiguousAcquisitionTestFpga::acquire_signal() +GalileoE1PcpsAmbiguousAcquisitionTestFpga::GalileoE1PcpsAmbiguousAcquisitionTestFpga() { - pthread_t thread_DMA, thread_acquisition; + config = std::make_shared(); +} - // 1. Setup GNU Radio flowgraph (file_source -> Acquisition_10m) - int SV_ID = 1; // initial sv id +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::run_acquisition_process(std::shared_ptr acquisition) +{ + if (!acquisition) + { + FAIL() << "Null acquisition"; + } + acquisition->reset(); +} - // fsm - std::shared_ptr channel_fsm_; - channel_fsm_ = std::make_shared(); - bool acquisition_successful; +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::init() +{ + config->set_property("GNSS-SDR.internal_fs_sps", std::to_string(BASEBAND_SAMPLING_RATE_SPS)); + config->set_property("Acquisition.threshold", std::to_string(ACQUISITION_THRESHOLD)); + config->set_property("Acquisition.doppler_max", std::to_string(DOPPLER_MAX_HZ)); + config->set_property("Acquisition.doppler_step", std::to_string(DOPPLER_STEP_HZ)); + config->set_property("Acquisition.total_block_exp", std::to_string(TOTAL_BLK_EXP)); +} - // Satellite signal definition - Gnss_Synchro tmp_gnss_synchro; - tmp_gnss_synchro.Channel_ID = 0; +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::create_and_open_DMA(std::shared_ptr &dma_fpga) +{ + dma_fpga = std::make_shared(); + if (dma_fpga->DMA_open()) + { + FAIL() << "Cannot open loop device\n"; + } +} - std::shared_ptr acquisition; +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::run_DMA_process(std::shared_ptr &dma_fpga) +{ + std::ifstream infile; + infile.exceptions(std::ifstream::failbit | std::ifstream::badbit); - 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; + // open the file + try + { + infile.open(std::string{SIGNAL_FILE_PATH}, std::ios::binary); + } + catch (const std::ifstream::failure &e) + { + FAIL() << "Exception opening file " << SIGNAL_FILE_PATH; + } - // set the scaling factor - args.scaling_factor = DMA_SIGNAL_SCALING_FACTOR; + // rx signal vector + std::vector input_samples(SAMPLE_BLOCK_SIZE_BYTES); // complex samples - std::string file = "data/Galileo_E1_ID_1_Fs_4Msps_8ms.dat"; - args.file = std::move(file); // DMA file configuration + // pointer to DMA buffer + int8_t *dma_buffer; - // instantiate the FPGA switch and set the - // switch position to DMA. - std::shared_ptr switch_fpga; - switch_fpga = std::make_shared(); - switch_fpga->set_switch_position(0); // set switch position to DMA + // Open DMA device + if (dma_fpga->DMA_open()) + { + FAIL() << "Cannot open loop device\n"; + } + dma_buffer = dma_fpga->get_buffer_address(); - // 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; - acquisition = std::make_shared(config.get(), "Acquisition", "Galileo_E1_PCPS_Ambiguous_Acquisition_FPGA", 0, 0, GAL_1B); + uint64_t nbytes_remaining = NSAMPLES * COMPLEX_SAMPLE_SIZE_BYTES; + uint32_t read_buffer_size = SAMPLE_BLOCK_SIZE_BYTES; + // run the DMA + bool run_DMA = true; + while (run_DMA) + { + // if (nbytes_remaining < read_buffer_size) + if (nbytes_remaining < SAMPLE_BLOCK_SIZE_BYTES) + { + read_buffer_size = nbytes_remaining; + } + nbytes_remaining = nbytes_remaining - read_buffer_size; + + // read file + try + { + infile.read(reinterpret_cast(input_samples.data()), read_buffer_size); + } + catch (const std::ifstream::failure &e) + { + FAIL() << "Exception reading file " << SIGNAL_FILE_PATH; + } + + const std::streamsize bytes_read = infile.gcount(); + + if (bytes_read != static_cast(read_buffer_size)) + { + FAIL() << "Error reading " << SIGNAL_FILE_PATH + << ": requested " << read_buffer_size + << " bytes, but read only " << bytes_read << " bytes"; + } + + uint32_t dma_index = 0; + + for (int index0 = 0; index0 < (bytes_read); index0 = index0 + 2) + { + // no signal on the L5/E5a frequency band + dma_buffer[dma_index] = 0; + dma_buffer[dma_index + 1] = 0; + // L1/E1 frequency band + dma_buffer[dma_index + 2] = input_samples[index0]; + dma_buffer[dma_index + 1 + 2] = input_samples[index0 + 1]; + dma_index += 4; + } + + if (bytes_read > 0) + { + if (dma_fpga->DMA_write(bytes_read * 2)) + { + FAIL() << "Error: DMA could not send all the required samples"; + } + // Throttle the DMA + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if (nbytes_remaining == 0) + { + // the input file is completely processed. Stop the receiver. + run_DMA = false; + } + } + + if (dma_fpga->DMA_close()) + { + FAIL() << "Error closing loop device "; + } + + try + { + infile.close(); + } + catch (const std::ifstream::failure &e) + { + FAIL() << "Exception closing file " << SIGNAL_FILE_PATH; + } +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::close_DMA(std::shared_ptr &dma_fpga) +{ + dma_fpga.reset(); +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::create_switch(std::shared_ptr &fpga_switch) +{ + fpga_switch = std::make_shared(); +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::configure_switch(std::shared_ptr &fpga_switch) +{ + fpga_switch->set_switch_position(POST_PROCESSING_MODE); // set switch position to post-processing mode +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::close_switch(std::shared_ptr &fpga_switch) +{ + fpga_switch.reset(); +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::open_and_map_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel) +{ + // find the uio device file corresponding to the dynamic bit selector 0 module. + std::string device_name; + int dev_descr_freq_band_1; + if (find_uio_dev_file_name(device_name, std::string{DYN_BIT_SEL_DEVICE_NAME}, DYN_BIT_SEL_DEV_NUM) < 0) + { + FAIL() << "Cannot find the FPGA uio device file corresponding to device name " << DYN_BIT_SEL_DEVICE_NAME; + } + + // dynamic bits selection corresponding to frequency band 1 + if ((dyn_bit_sel_dev_descr = open(device_name.c_str(), O_RDWR | O_SYNC)) == -1) + { + FAIL() << "Cannot open deviceio" << device_name; + } + if (dyn_bit_sel_dev_descr == -1) + { + FAIL() << "Cannot open device " << device_name; + } + + d_map_base_dyn_bit_sel = reinterpret_cast(mmap(nullptr, FPGA_PAGE_SIZE_BYTES, + PROT_READ | PROT_WRITE, MAP_SHARED, dyn_bit_sel_dev_descr, 0)); + + if (d_map_base_dyn_bit_sel == reinterpret_cast(-1)) + { + FAIL() << "Cannot map the FPGA dynamic bit selection module"; + } +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::configure_dynamic_bit_selector(volatile unsigned *&d_map_base_dyn_bit_sel) +{ + // set the dynamic bit selection to shift the received signal by DYN_BIT_SEL_SHIFT_OUT_BITS bits + d_map_base_dyn_bit_sel[SOBITS_REG_ADDR] = DYN_BIT_SEL_SHIFT_OUT_BITS; +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::unmap_and_close_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel) +{ + // close device + auto *aux = const_cast(d_map_base_dyn_bit_sel); + if (munmap(static_cast(aux), FPGA_PAGE_SIZE_BYTES) == -1) + { + FAIL() << "Failed to unmap memory uio\n"; + } + close(dyn_bit_sel_dev_descr); +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::create_acquisition(std::shared_ptr &acquisition) +{ + acquisition = std::make_shared(config.get(), "Acquisition", std::string{IMPLEMENTATION}, 0, 0, GAL_1B); +} + +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::configure_acquisition(std::shared_ptr &acquisition, Gnss_Synchro &tmp_gnss_synchro, std::shared_ptr &channel_fsm_) +{ acquisition->set_gnss_synchro(&tmp_gnss_synchro); acquisition->set_channel_fsm(channel_fsm_); acquisition->set_channel(1); acquisition->set_doppler_center(0); + channel_fsm_->Event_clear_test_result(); + acquisition->set_local_code(); +} +void GalileoE1PcpsAmbiguousAcquisitionTestFpga::release_acquisition(std::shared_ptr &acquisition) +{ + acquisition.reset(); +} - nsamples_to_transfer = static_cast(std::round(static_cast(BASEBAND_SAMPLING_FREQ) / (GALILEO_E1_CODE_CHIP_RATE_CPS / GALILEO_E1_B_CODE_LENGTH_CHIPS))); +TEST_F(GalileoE1PcpsAmbiguousAcquisitionTestFpga, Instantiate) +{ + init(); + // instantiate the DMA + std::shared_ptr dma_fpga; + create_and_open_DMA(dma_fpga); + + // instantiate the switch + std::shared_ptr switch_fpga; + create_switch(switch_fpga); + + // instantiate the dynamic bit selection + int dyn_bit_sel_dev_descr; + volatile unsigned *d_map_base_dyn_bit_sel; + open_and_map_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // instantiate the acquisition IP + std::shared_ptr acquisition; + create_acquisition(acquisition); + + // close the DMA + close_DMA(dma_fpga); + + // close the switch + close_switch(switch_fpga); + + // close the dynamic bit selection + unmap_and_close_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // close the acquisition IP + release_acquisition(acquisition); +} + +TEST_F(GalileoE1PcpsAmbiguousAcquisitionTestFpga, ConnectAndRun) +{ + init(); + + // instantiate the DMA + std::shared_ptr dma_fpga; + create_and_open_DMA(dma_fpga); + + // instantiate the switch + std::shared_ptr switch_fpga; + create_switch(switch_fpga); + + // instantiate the dynamic bit selection + int dyn_bit_sel_dev_descr; + volatile unsigned *d_map_base_dyn_bit_sel; + open_and_map_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // instantiate the acquisition IP + std::shared_ptr acquisition; + create_acquisition(acquisition); + + // configure the switch + configure_switch(switch_fpga); + + // configure the dynamic bit selection + configure_dynamic_bit_selector(d_map_base_dyn_bit_sel); + + // FSM + std::shared_ptr channel_fsm_; + channel_fsm_ = std::make_shared(); channel_fsm_->Event_clear_test_result(); - acquisition->stop_acquisition(); // reset the whole system including the sample counters - acquisition->set_local_code(); + // Gnss_Synchro + Gnss_Synchro tmp_gnss_synchro; + tmp_gnss_synchro.Channel_ID = 0; + tmp_gnss_synchro.System = SYSTEM; + std::memcpy(static_cast(tmp_gnss_synchro.Signal), std::string{SIGNAL}.c_str(), 2); // copy string into synchro char array: 2 char + null + tmp_gnss_synchro.PRN = SV_ID; - args.skip_used_samples = 0; + configure_acquisition(acquisition, tmp_gnss_synchro, channel_fsm_); - // Configure the DMA to send the required samples to perform an acquisition - args.nsamples_tx = nsamples_to_transfer; + std::chrono::time_point start, end; + std::chrono::duration elapsed_seconds(0); + start = std::chrono::system_clock::now(); - // run the acquisition. The acquisition must run in a separate thread because it is a blocking function - args_acq.acquisition = std::move(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\n"; - } + std::thread thread_acquisition = std::thread([&] { run_acquisition_process(acquisition); }); // 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::thread thread_file_to_dma = std::thread([&] { run_DMA_process(dma_fpga); }); + + // wait for the acquisition process to finish + if (thread_acquisition.joinable()) { - std::cout << "ERROR cannot create DMA Process\n"; + thread_acquisition.join(); } - // 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) + // wait for the DMA process to finish + if (thread_file_to_dma.joinable()) { - gnss_synchro_vec.push_back(tmp_gnss_synchro); + thread_file_to_dma.join(); } - if (!gnss_synchro_vec.empty()) - { - return true; - } - else - { - return false; - } -} - - -void GalileoE1PcpsAmbiguousAcquisitionTestFpga::init() -{ - 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.001"); - 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\n"; + // close the DMA + close_DMA(dma_fpga); - 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); + // close the switch + close_switch(switch_fpga); - // the acquisition grid is not available when using the FPGA + // close the dynamic bit selection + unmap_and_close_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); - 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"; + // close the acquisition IP + release_acquisition(acquisition); + + std::cout << "Processed " << NSAMPLES << " samples in " << elapsed_seconds.count() * 1e6 << " microseconds\n"; +} + +TEST_F(GalileoE1PcpsAmbiguousAcquisitionTestFpga, ValidationOfResults) +{ + init(); + + // instantiate the DMA + std::shared_ptr dma_fpga; + create_and_open_DMA(dma_fpga); + + // instantiate the switch + std::shared_ptr switch_fpga; + create_switch(switch_fpga); + + // instantiate the dynamic bit selection + int dyn_bit_sel_dev_descr; + volatile unsigned *d_map_base_dyn_bit_sel; + open_and_map_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // instantiate the acquisition IP + std::shared_ptr acquisition; + create_acquisition(acquisition); + + // configure the switch + configure_switch(switch_fpga); + + // configure the dynamic bit selection + configure_dynamic_bit_selector(d_map_base_dyn_bit_sel); + + // FSM + std::shared_ptr channel_fsm_; + channel_fsm_ = std::make_shared(); + channel_fsm_->Event_clear_test_result(); + + // Gnss_Synchro + Gnss_Synchro tmp_gnss_synchro; + tmp_gnss_synchro.Channel_ID = 0; + tmp_gnss_synchro.System = SYSTEM; + std::memcpy(static_cast(tmp_gnss_synchro.Signal), std::string{SIGNAL}.c_str(), 2); // copy string into synchro char array: 2 char + null + tmp_gnss_synchro.PRN = SV_ID; + + configure_acquisition(acquisition, tmp_gnss_synchro, channel_fsm_); + + std::chrono::time_point start, end; + std::chrono::duration elapsed_seconds(0); + start = std::chrono::system_clock::now(); + + std::thread thread_acquisition = std::thread([&] { run_acquisition_process(acquisition); }); + + // wait to give time for the acquisition thread to set up the acquisition HW accelerator in the FPGA + usleep(1000000); + + // create DMA child process + std::thread thread_file_to_dma = std::thread([&] { run_DMA_process(dma_fpga); }); + + // wait for the acquisition process to finish + if (thread_acquisition.joinable()) + { + thread_acquisition.join(); + } + + // wait for the DMA process to finish + if (thread_file_to_dma.joinable()) + { + thread_file_to_dma.join(); + } + + end = std::chrono::system_clock::now(); + elapsed_seconds = end - start; + + bool acquisition_successful = channel_fsm_->Event_check_test_result(); + + // close the DMA + close_DMA(dma_fpga); + + // close the switch + close_switch(switch_fpga); + + // close the dynamic bit selection + unmap_and_close_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // close the acquisition IP + release_acquisition(acquisition); + + std::cout << "Processed " << NSAMPLES << " samples in " << elapsed_seconds.count() * 1e6 << " microseconds\n"; + + ASSERT_EQ(true, acquisition_successful) << "Acquisition failure. Expected result: true=ACQ SUCCESS."; + + std::cout << "Delay: " << tmp_gnss_synchro.Acq_delay_samples << '\n'; + std::cout << "Doppler: " << tmp_gnss_synchro.Acq_doppler_hz << '\n'; + + double delay_error_samples = std::abs(EXPECTED_DELAY_SAMPLES - tmp_gnss_synchro.Acq_delay_samples); + auto delay_error_chips = static_cast(delay_error_samples * (GALILEO_E1_CODE_CHIP_RATE_CPS / BASEBAND_SAMPLING_RATE_SPS)); + double doppler_error_hz = std::abs(EXPECTED_DOPPLER_HZ - tmp_gnss_synchro.Acq_doppler_hz); + + EXPECT_LE(doppler_error_hz, 166) << "Doppler error exceeds the expected value: 166 Hz = 2/(3*integration period)"; + EXPECT_LT(delay_error_chips, 0.175) << "Delay error exceeds the expected value: 0.175 chips"; } diff --git a/tests/unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_acquisition_test_fpga.cc b/tests/unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_acquisition_test_fpga.cc index 562097f20..3792ab971 100644 --- a/tests/unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_acquisition_test_fpga.cc +++ b/tests/unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_acquisition_test_fpga.cc @@ -3,7 +3,7 @@ * \brief This class implements an acquisition test for * PcpsAcquisitionAdapterFpga class based on some input parameters. * \authors
    - *
  • Marc Majoral, 2019. mmajoral(at)cttc.cat + *
  • Marc Majoral, 2019-2026. mmajoral(at)cttc.cat *
  • Luis Esteve, 2012. luis(at)epsilon-formacion.com *
* @@ -18,23 +18,32 @@ * ----------------------------------------------------------------------------- */ -#include "GPS_L1_CA.h" -#include "acquisition_dump_reader.h" -#include "concurrent_queue.h" -#include "fpga_switch.h" -#include "gnss_block_interface.h" -#include "gnss_sdr_filesystem.h" -#include "gnss_synchro.h" -#include "in_memory_configuration.h" -#include "pcps_acquisition_adapter_fpga.h" -#include "test_flags.h" -#include -#include -#include -#include // for abs, pow, floor -#include // for O_WRONLY -#include -#include +#include "GPS_L1_CA.h" // GPS L1 C/A signal parameters +#include "acquisition_interface.h" // AcquisitionInterface +#include "channel_fsm.h" // ChannelFsm +#include "fpga_dma-proxy.h" // Fpga_DMA +#include "fpga_switch.h" // Fpga_Switch +#include "gnss_synchro.h" // Gnss_Synchro +#include "in_memory_configuration.h" // InMemoryConfiguration +#include "pcps_acquisition_adapter_fpga.h" // PcpsAcquisitionAdapterFpga +#include "uio_fpga.h" // find_uio_dev_file_name +#include // GoogleTest +#include // clocks, durations, milliseconds +#include // std::abs +#include // std::size_t +#include // fixed-width integer types +#include // std::memcpy +#include // open, O_RDWR, O_SYNC +#include // std::ifstream +#include // std::cout, std::endl +#include // std::shared_ptr, std::make_shared +#include // std::string, std::to_string +#include // std::string_view +#include // mmap, munmap +#include // std::thread, std::this_thread +#include // close, usleep +#include // std::vector + #if USE_GLOG_AND_GFLAGS #include @@ -48,212 +57,81 @@ #include #endif - -struct DMA_handler_args_gps_l1_acq_test -{ - std::string file; - int32_t nsamples_tx; - int32_t skip_used_samples; - float scaling_factor; -}; - -struct acquisition_handler_args_gps_l1_acq_test -{ - std::shared_ptr acquisition; -}; +class ChannelFsm_gps_l1_ca_pcps_ambiguous_acq_test; class GpsL1CaPcpsAcquisitionTestFpga : public ::testing::Test { -public: - bool acquire_signal(); - std::string implementation = "GPS_L1_CA_DLL_PLL_Tracking_FPGA"; - std::vector gnss_synchro_vec; - - const int32_t TEST_ACQ_SKIP_SAMPLES = 1024; - const int BASEBAND_SAMPLING_FREQ = 4000000; - const float MAX_SAMPLE_VALUE = 0.096257761120796; - const int DMA_BITS_PER_SAMPLE = 8; - const float DMA_SIGNAL_SCALING_FACTOR = (pow(2, DMA_BITS_PER_SAMPLE - 1) - 1) / MAX_SAMPLE_VALUE; - protected: + // acquisition configuration + inline static constexpr int BASEBAND_SAMPLING_RATE_SPS = 12500000; + inline static constexpr int DOPPLER_MAX_HZ = 5000; + inline static constexpr int DOPPLER_STEP_HZ = 250; + inline static constexpr float ACQUISITION_THRESHOLD = 2.25f; + inline static constexpr int TOTAL_BLK_EXP = 10; + inline static constexpr std::string_view IMPLEMENTATION = "GPS_L1_CA_PCPS_Acquisition_FPGA"; + + // acquisition expected results + inline static constexpr double EXPECTED_DELAY_SAMPLES = 7576; + inline static constexpr double EXPECTED_DOPPLER_HZ = -3000; + + // Signal parameters + inline static constexpr int SV_ID = 1; + inline static constexpr int COHERENT_INTEGRATION_TIME_ms = 1; + inline static constexpr char SYSTEM = 'G'; + inline static constexpr std::string_view SIGNAL = "1C"; + inline static constexpr unsigned int NSAMPLES = + static_cast( + (static_cast(BASEBAND_SAMPLING_RATE_SPS) * + static_cast(COHERENT_INTEGRATION_TIME_ms)) / + 1000U); + + // File to DMA control + inline static constexpr int COMPLEX_SAMPLE_SIZE_BYTES = sizeof(int8_t) * 2; // interleaved byte + inline static constexpr int SAMPLE_BLOCK_SIZE_BYTES = 16384; + inline static constexpr std::string_view SIGNAL_FILE_PATH = "./signal_samples/GPS_L1CA_ID_1_Fs_12.5Msps_10ms.dat"; + + // FPGA switch + inline static constexpr int POST_PROCESSING_MODE = 0; // Select post-processing mode (read a signal from a recorded file) + + // FPGA Dynamic bit selection + inline static constexpr int DYN_BIT_SEL_DEV_NUM = 0; // device 0 is connected to the L1/E1 frequency-band path. + inline static constexpr size_t FPGA_PAGE_SIZE_BYTES = 0x1000; + inline static constexpr uint32_t DYN_BIT_SEL_SHIFT_OUT_BITS = 0; // No bit shift; select the least significant bits. + inline static constexpr std::string_view DYN_BIT_SEL_DEVICE_NAME = "dynamic_bits_selector"; // device name + inline static constexpr int SOBITS_REG_ADDR = 0; // Shift out bits register address + GpsL1CaPcpsAcquisitionTestFpga(); ~GpsL1CaPcpsAcquisitionTestFpga() = default; void init(); + void create_and_open_DMA(std::shared_ptr &dma_fpga); + void create_switch(std::shared_ptr &fpga_switch); + void open_and_map_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel); + void create_acquisition(std::shared_ptr &acquisition); + + void configure_switch(std::shared_ptr &fpga_switch); + void configure_dynamic_bit_selector(volatile unsigned *&d_map_base_dyn_bit_sel); + void configure_acquisition(std::shared_ptr &acquisition, Gnss_Synchro &tmp_gnss_synchro, std::shared_ptr &channel_fsm_); + + void run_DMA_process(std::shared_ptr &dma_fpga); + void run_acquisition_process(std::shared_ptr acquisition); + + void close_DMA(std::shared_ptr &dma_fpga); + void close_switch(std::shared_ptr &fpga_switch); + void unmap_and_close_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel); + void release_acquisition(std::shared_ptr &acquisition); + std::shared_ptr config; - - unsigned int doppler_max; - unsigned int doppler_step; - unsigned int nsamples_to_transfer; }; - -GpsL1CaPcpsAcquisitionTestFpga::GpsL1CaPcpsAcquisitionTestFpga() -{ - config = std::make_shared(); - - doppler_max = 5000; - doppler_step = 100; - nsamples_to_transfer = 0; -} - - -void* handler_DMA_gps_l1_acq_test(void* arguments) -{ - const int MAX_INPUT_SAMPLES_TOTAL = 16384; - - auto* args = (struct DMA_handler_args_gps_l1_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 << '\n'; - return nullptr; - } - - // ************************************************************************* - // Open DMA device - // ************************************************************************* - tx_fd = open("/dev/loop_tx", O_WRONLY); - if (tx_fd < 0) - { - std::cout << "Cannot open loop device\n"; - 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 << '\n'; - } - } - - 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 << '\n'; - } - - for (int index0 = 0; index0 < (nsamples_block_size * 2); index0 += 2) - { - // channel 1 (queue 1) -> E5/L5 - input_samples_dma[dma_index] = static_cast(input_samples[index0] * args->scaling_factor); - input_samples_dma[dma_index + 1] = static_cast(input_samples[index0 + 1] * args->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 \n"; - } - - // 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 << '\n'; - } - - try - { - close(tx_fd); - } - catch (const std::ifstream::failure& e) - { - std::cerr << "Exception closing loop device \n"; - } - - return nullptr; -} - - -void* handler_acquisition_gps_l1_acq_test(void* arguments) -{ - // the acquisition is a blocking function so we have to - // create a thread - auto* args = (struct acquisition_handler_args_gps_l1_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_gps_l1_acq_test : public ChannelFsm +// class of the channel finite state machine. +class ChannelFsm_gps_l1_ca_pcps_ambiguous_acq_test : public ChannelFsm { public: bool Event_valid_acquisition() override @@ -288,151 +166,438 @@ private: bool acquisition_successful{}; }; - -bool GpsL1CaPcpsAcquisitionTestFpga::acquire_signal() +GpsL1CaPcpsAcquisitionTestFpga::GpsL1CaPcpsAcquisitionTestFpga() { - pthread_t thread_DMA, thread_acquisition; + config = std::make_shared(); +} - // 1. Setup GNU Radio flowgraph (file_source -> Acquisition_10m) - int SV_ID = 1; // initial sv id +void GpsL1CaPcpsAcquisitionTestFpga::run_acquisition_process(std::shared_ptr acquisition) +{ + if (!acquisition) + { + FAIL() << "Null acquisition"; + } + acquisition->reset(); +} - // fsm - std::shared_ptr channel_fsm_; - channel_fsm_ = std::make_shared(); - bool acquisition_successful; +void GpsL1CaPcpsAcquisitionTestFpga::init() +{ + config->set_property("GNSS-SDR.internal_fs_sps", std::to_string(BASEBAND_SAMPLING_RATE_SPS)); + config->set_property("Acquisition.threshold", std::to_string(ACQUISITION_THRESHOLD)); + config->set_property("Acquisition.doppler_max", std::to_string(DOPPLER_MAX_HZ)); + config->set_property("Acquisition.doppler_step", std::to_string(DOPPLER_STEP_HZ)); + config->set_property("Acquisition.total_block_exp", std::to_string(TOTAL_BLK_EXP)); +} - // Satellite signal definition - Gnss_Synchro tmp_gnss_synchro; - tmp_gnss_synchro.Channel_ID = 0; +void GpsL1CaPcpsAcquisitionTestFpga::create_and_open_DMA(std::shared_ptr &dma_fpga) +{ + dma_fpga = std::make_shared(); + if (dma_fpga->DMA_open()) + { + FAIL() << "Cannot open loop device\n"; + } +} - std::shared_ptr acquisition; +void GpsL1CaPcpsAcquisitionTestFpga::run_DMA_process(std::shared_ptr &dma_fpga) +{ + std::ifstream infile; + infile.exceptions(std::ifstream::failbit | std::ifstream::badbit); - std::string signal; - struct DMA_handler_args_gps_l1_acq_test args; - struct acquisition_handler_args_gps_l1_acq_test args_acq; + // open the file + try + { + infile.open(std::string{SIGNAL_FILE_PATH}, std::ios::binary); + } + catch (const std::ifstream::failure &e) + { + FAIL() << "Exception opening file " << SIGNAL_FILE_PATH; + } - // set the scaling factor - args.scaling_factor = DMA_SIGNAL_SCALING_FACTOR; + // rx signal vector + std::vector input_samples(SAMPLE_BLOCK_SIZE_BYTES); // complex samples - std::string file = "data/GPS_L1_CA_ID_1_Fs_4Msps_2ms.dat"; - args.file = std::move(file); // DMA file configuration + // pointer to DMA buffer + int8_t *dma_buffer; - // instantiate the FPGA switch and set the - // switch position to DMA. - std::shared_ptr switch_fpga; - switch_fpga = std::make_shared(); - switch_fpga->set_switch_position(0); // set switch position to DMA + // Open DMA device + if (dma_fpga->DMA_open()) + { + FAIL() << "Cannot open loop device\n"; + } + dma_buffer = dma_fpga->get_buffer_address(); - // create the correspondign acquisition block according to the desired tracking signal - tmp_gnss_synchro.System = 'G'; - signal = "1C"; - const char* str = signal.c_str(); // get a C style null terminated string - std::memcpy(static_cast(tmp_gnss_synchro.Signal), str, 3); // copy string into synchro char array: 2 char + null - tmp_gnss_synchro.PRN = SV_ID; - acquisition = std::make_shared(config.get(), "Acquisition", "GPS_L1_CA_PCPS_Acquisition_FPGA", 0, 0, GPS_1C); + uint64_t nbytes_remaining = NSAMPLES * COMPLEX_SAMPLE_SIZE_BYTES; + uint32_t read_buffer_size = SAMPLE_BLOCK_SIZE_BYTES; + // run the DMA + bool run_DMA = true; + while (run_DMA) + { + // if (nbytes_remaining < read_buffer_size) + if (nbytes_remaining < SAMPLE_BLOCK_SIZE_BYTES) + { + read_buffer_size = nbytes_remaining; + } + nbytes_remaining = nbytes_remaining - read_buffer_size; + + // read file + try + { + infile.read(reinterpret_cast(input_samples.data()), read_buffer_size); + } + catch (const std::ifstream::failure &e) + { + FAIL() << "Exception reading file " << SIGNAL_FILE_PATH; + } + + const std::streamsize bytes_read = infile.gcount(); + + if (bytes_read != static_cast(read_buffer_size)) + { + FAIL() << "Error reading " << SIGNAL_FILE_PATH + << ": requested " << read_buffer_size + << " bytes, but read only " << bytes_read << " bytes"; + } + + uint32_t dma_index = 0; + + for (int index0 = 0; index0 < (bytes_read); index0 = index0 + 2) + { + // no signal on the L5/E5a frequency band + dma_buffer[dma_index] = 0; + dma_buffer[dma_index + 1] = 0; + // L1/E1 frequency band + dma_buffer[dma_index + 2] = input_samples[index0]; + dma_buffer[dma_index + 1 + 2] = input_samples[index0 + 1]; + dma_index += 4; + } + + if (bytes_read > 0) + { + if (dma_fpga->DMA_write(bytes_read * 2)) + { + FAIL() << "Error: DMA could not send all the required samples"; + } + // Throttle the DMA + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if (nbytes_remaining == 0) + { + // the input file is completely processed. Stop the receiver. + run_DMA = false; + } + } + + if (dma_fpga->DMA_close()) + { + FAIL() << "Error closing loop device "; + } + + try + { + infile.close(); + } + catch (const std::ifstream::failure &e) + { + FAIL() << "Exception closing file " << SIGNAL_FILE_PATH; + } +} + +void GpsL1CaPcpsAcquisitionTestFpga::close_DMA(std::shared_ptr &dma_fpga) +{ + dma_fpga.reset(); +} + +void GpsL1CaPcpsAcquisitionTestFpga::create_switch(std::shared_ptr &fpga_switch) +{ + fpga_switch = std::make_shared(); +} + +void GpsL1CaPcpsAcquisitionTestFpga::configure_switch(std::shared_ptr &fpga_switch) +{ + fpga_switch->set_switch_position(POST_PROCESSING_MODE); // set switch position to post-processing mode +} + +void GpsL1CaPcpsAcquisitionTestFpga::close_switch(std::shared_ptr &fpga_switch) +{ + fpga_switch.reset(); +} + +void GpsL1CaPcpsAcquisitionTestFpga::open_and_map_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel) +{ + // find the uio device file corresponding to the dynamic bit selector 0 module. + std::string device_name; + int dev_descr_freq_band_1; + if (find_uio_dev_file_name(device_name, std::string{DYN_BIT_SEL_DEVICE_NAME}, DYN_BIT_SEL_DEV_NUM) < 0) + { + FAIL() << "Cannot find the FPGA uio device file corresponding to device name " << DYN_BIT_SEL_DEVICE_NAME; + } + + // dynamic bits selection corresponding to frequency band 1 + if ((dyn_bit_sel_dev_descr = open(device_name.c_str(), O_RDWR | O_SYNC)) == -1) + { + FAIL() << "Cannot open deviceio" << device_name; + } + if (dyn_bit_sel_dev_descr == -1) + { + FAIL() << "Cannot open device " << device_name; + } + + d_map_base_dyn_bit_sel = reinterpret_cast(mmap(nullptr, FPGA_PAGE_SIZE_BYTES, + PROT_READ | PROT_WRITE, MAP_SHARED, dyn_bit_sel_dev_descr, 0)); + + if (d_map_base_dyn_bit_sel == reinterpret_cast(-1)) + { + FAIL() << "Cannot map the FPGA dynamic bit selection module"; + } +} + +void GpsL1CaPcpsAcquisitionTestFpga::configure_dynamic_bit_selector(volatile unsigned *&d_map_base_dyn_bit_sel) +{ + // set the dynamic bit selection to shift the received signal by DYN_BIT_SEL_SHIFT_OUT_BITS bits + d_map_base_dyn_bit_sel[SOBITS_REG_ADDR] = DYN_BIT_SEL_SHIFT_OUT_BITS; +} + +void GpsL1CaPcpsAcquisitionTestFpga::unmap_and_close_dynamic_bit_selector(int &dyn_bit_sel_dev_descr, volatile unsigned *&d_map_base_dyn_bit_sel) +{ + // close device + auto *aux = const_cast(d_map_base_dyn_bit_sel); + if (munmap(static_cast(aux), FPGA_PAGE_SIZE_BYTES) == -1) + { + FAIL() << "Failed to unmap memory uio\n"; + } + close(dyn_bit_sel_dev_descr); +} + +void GpsL1CaPcpsAcquisitionTestFpga::create_acquisition(std::shared_ptr &acquisition) +{ + acquisition = std::make_shared(config.get(), "Acquisition", std::string{IMPLEMENTATION}, 0, 0, GPS_1C); +} + +void GpsL1CaPcpsAcquisitionTestFpga::configure_acquisition(std::shared_ptr &acquisition, Gnss_Synchro &tmp_gnss_synchro, std::shared_ptr &channel_fsm_) +{ acquisition->set_gnss_synchro(&tmp_gnss_synchro); acquisition->set_channel_fsm(channel_fsm_); acquisition->set_channel(1); acquisition->set_doppler_center(0); + channel_fsm_->Event_clear_test_result(); + acquisition->set_local_code(); +} +void GpsL1CaPcpsAcquisitionTestFpga::release_acquisition(std::shared_ptr &acquisition) +{ + acquisition.reset(); +} - nsamples_to_transfer = static_cast(std::round(static_cast(BASEBAND_SAMPLING_FREQ) / (GPS_L1_CA_CODE_RATE_CPS / GPS_L1_CA_CODE_LENGTH_CHIPS))); +TEST_F(GpsL1CaPcpsAcquisitionTestFpga, Instantiate) +{ + init(); + // instantiate the DMA + std::shared_ptr dma_fpga; + create_and_open_DMA(dma_fpga); + + // instantiate the switch + std::shared_ptr switch_fpga; + create_switch(switch_fpga); + + // instantiate the dynamic bit selection + int dyn_bit_sel_dev_descr; + volatile unsigned *d_map_base_dyn_bit_sel; + open_and_map_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // instantiate the acquisition IP + std::shared_ptr acquisition; + create_acquisition(acquisition); + + // close the DMA + close_DMA(dma_fpga); + + // close the switch + close_switch(switch_fpga); + + // close the dynamic bit selection + unmap_and_close_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // close the acquisition IP + release_acquisition(acquisition); +} + +TEST_F(GpsL1CaPcpsAcquisitionTestFpga, ConnectAndRun) +{ + init(); + + // instantiate the DMA + std::shared_ptr dma_fpga; + create_and_open_DMA(dma_fpga); + + // instantiate the switch + std::shared_ptr switch_fpga; + create_switch(switch_fpga); + + // instantiate the dynamic bit selection + int dyn_bit_sel_dev_descr; + volatile unsigned *d_map_base_dyn_bit_sel; + open_and_map_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // instantiate the acquisition IP + std::shared_ptr acquisition; + create_acquisition(acquisition); + + // configure the switch + configure_switch(switch_fpga); + + // configure the dynamic bit selection + configure_dynamic_bit_selector(d_map_base_dyn_bit_sel); + + // FSM + std::shared_ptr channel_fsm_; + channel_fsm_ = std::make_shared(); channel_fsm_->Event_clear_test_result(); - acquisition->stop_acquisition(); // reset the whole system including the sample counters - acquisition->set_local_code(); + // Gnss_Synchro + Gnss_Synchro tmp_gnss_synchro; + tmp_gnss_synchro.Channel_ID = 0; + tmp_gnss_synchro.System = SYSTEM; + std::memcpy(static_cast(tmp_gnss_synchro.Signal), std::string{SIGNAL}.c_str(), 2); // copy string into synchro char array: 2 char + null + tmp_gnss_synchro.PRN = SV_ID; - args.skip_used_samples = 0; + configure_acquisition(acquisition, tmp_gnss_synchro, channel_fsm_); - // Configure the DMA to send the required samples to perform an acquisition - args.nsamples_tx = nsamples_to_transfer; + std::chrono::time_point start, end; + std::chrono::duration elapsed_seconds(0); + start = std::chrono::system_clock::now(); - // run the acquisition. The acquisition must run in a separate thread because it is a blocking function - args_acq.acquisition = std::move(acquisition); - - if (pthread_create(&thread_acquisition, nullptr, handler_acquisition_gps_l1_acq_test, reinterpret_cast(&args_acq)) < 0) - { - std::cout << "ERROR cannot create acquisition Process\n"; - } + std::thread thread_acquisition = std::thread([&] { run_acquisition_process(acquisition); }); // 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_gps_l1_acq_test, reinterpret_cast(&args)) < 0) + std::thread thread_file_to_dma = std::thread([&] { run_DMA_process(dma_fpga); }); + + // wait for the acquisition process to finish + if (thread_acquisition.joinable()) { - std::cout << "ERROR cannot create DMA Process\n"; + thread_acquisition.join(); } - // 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) + // wait for the DMA process to finish + if (thread_file_to_dma.joinable()) { - gnss_synchro_vec.push_back(tmp_gnss_synchro); + thread_file_to_dma.join(); } - if (!gnss_synchro_vec.empty()) - { - return true; - } - else - { - return false; - } -} - - -void GpsL1CaPcpsAcquisitionTestFpga::init() -{ - config->set_property("GNSS-SDR.internal_fs_sps", "4000000"); - config->set_property("Acquisition.implementation", "GPS_L1_CA_PCPS_Acquisition_FPGA"); - config->set_property("Acquisition.threshold", "0.001"); - 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", "12"); -} - - -TEST_F(GpsL1CaPcpsAcquisitionTestFpga, ValidationOfResults) -{ - struct DMA_handler_args_gps_l1_acq_test args; - - std::chrono::time_point start, end; - std::chrono::duration elapsed_seconds(0); - - double expected_delay_samples = 524; - double expected_doppler_hz = 1680; - - 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\n"; + // close the DMA + close_DMA(dma_fpga); - 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); + // close the switch + close_switch(switch_fpga); - // the acquisition grid is not available when using the FPGA + // close the dynamic bit selection + unmap_and_close_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // close the acquisition IP + release_acquisition(acquisition); + + std::cout << "Processed " << NSAMPLES << " samples in " << elapsed_seconds.count() * 1e6 << " microseconds\n"; +} + +TEST_F(GpsL1CaPcpsAcquisitionTestFpga, ValidationOfResults) +{ + init(); + + // instantiate the DMA + std::shared_ptr dma_fpga; + create_and_open_DMA(dma_fpga); + + // instantiate the switch + std::shared_ptr switch_fpga; + create_switch(switch_fpga); + + // instantiate the dynamic bit selection + int dyn_bit_sel_dev_descr; + volatile unsigned *d_map_base_dyn_bit_sel; + open_and_map_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // instantiate the acquisition IP + std::shared_ptr acquisition; + create_acquisition(acquisition); + + // configure the switch + configure_switch(switch_fpga); + + // configure the dynamic bit selection + configure_dynamic_bit_selector(d_map_base_dyn_bit_sel); + + // FSM + std::shared_ptr channel_fsm_; + channel_fsm_ = std::make_shared(); + channel_fsm_->Event_clear_test_result(); + + // Gnss_Synchro + Gnss_Synchro tmp_gnss_synchro; + tmp_gnss_synchro.Channel_ID = 0; + tmp_gnss_synchro.System = SYSTEM; + std::memcpy(static_cast(tmp_gnss_synchro.Signal), std::string{SIGNAL}.c_str(), 2); // copy string into synchro char array: 2 char + null + tmp_gnss_synchro.PRN = SV_ID; + + configure_acquisition(acquisition, tmp_gnss_synchro, channel_fsm_); + + std::chrono::time_point start, end; + std::chrono::duration elapsed_seconds(0); + start = std::chrono::system_clock::now(); + + std::thread thread_acquisition = std::thread([&] { run_acquisition_process(acquisition); }); + + // wait to give time for the acquisition thread to set up the acquisition HW accelerator in the FPGA + usleep(1000000); + + // create DMA child process + std::thread thread_file_to_dma = std::thread([&] { run_DMA_process(dma_fpga); }); + + // wait for the acquisition process to finish + if (thread_acquisition.joinable()) + { + thread_acquisition.join(); + } + + // wait for the DMA process to finish + if (thread_file_to_dma.joinable()) + { + thread_file_to_dma.join(); + } + + end = std::chrono::system_clock::now(); + elapsed_seconds = end - start; + + bool acquisition_successful = channel_fsm_->Event_check_test_result(); + + // close the DMA + close_DMA(dma_fpga); + + // close the switch + close_switch(switch_fpga); + + // close the dynamic bit selection + unmap_and_close_dynamic_bit_selector(dyn_bit_sel_dev_descr, d_map_base_dyn_bit_sel); + + // close the acquisition IP + release_acquisition(acquisition); + + std::cout << "Processed " << NSAMPLES << " samples in " << elapsed_seconds.count() * 1e6 << " microseconds\n"; + + ASSERT_EQ(true, acquisition_successful) << "Acquisition failure. Expected result: true=ACQ SUCCESS."; + + std::cout << "Delay: " << tmp_gnss_synchro.Acq_delay_samples << '\n'; + std::cout << "Doppler: " << tmp_gnss_synchro.Acq_doppler_hz << '\n'; + + double delay_error_samples = std::abs(EXPECTED_DELAY_SAMPLES - tmp_gnss_synchro.Acq_delay_samples); + auto delay_error_chips = static_cast(delay_error_samples * (GPS_L1_CA_CODE_RATE_CPS / BASEBAND_SAMPLING_RATE_SPS)); + double doppler_error_hz = std::abs(EXPECTED_DOPPLER_HZ - tmp_gnss_synchro.Acq_doppler_hz); 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";