/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "OfdmGenerator.h" #include "PcDebug.h" #include #include #include #include #include #include #include static const size_t MAX_CLIP_STATS = 10; using FFTW_TYPE = fftwf_complex; OfdmGeneratorCF32::OfdmGeneratorCF32(size_t nbSymbols, size_t nbCarriers, size_t spacing, bool& enableCfr, float& cfrClip, float& cfrErrorClip, bool inverse) : ModCodec(), RemoteControllable("ofdm"), myFftPlan(nullptr), myFftIn(nullptr), myFftOut(nullptr), myNbSymbols(nbSymbols), myNbCarriers(nbCarriers), mySpacing(spacing), myCfr(enableCfr), myCfrClip(cfrClip), myCfrErrorClip(cfrErrorClip), myCfrFft(nullptr), // Initialise the PAPRStats to a few seconds worth of samples myPaprBeforeCFR(nbSymbols * 50), myPaprAfterCFR(nbSymbols * 50) { PDEBUG("OfdmGenerator::OfdmGenerator(%zu, %zu, %zu, %s) @ %p\n", nbSymbols, nbCarriers, spacing, inverse ? "true" : "false", this); if (nbCarriers > spacing) { throw std::runtime_error("OfdmGenerator nbCarriers > spacing!"); } /* register the parameters that can be remote controlled */ RC_ADD_PARAMETER(cfr, "Enable crest factor reduction"); RC_ADD_PARAMETER(clip, "CFR: Clip to amplitude"); RC_ADD_PARAMETER(errorclip, "CFR: Limit error"); RC_ADD_PARAMETER(clip_stats, "CFR: statistics (clip ratio, errorclip ratio)"); RC_ADD_PARAMETER(papr, "PAPR measurements (before CFR, after CFR)"); if (inverse) { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = 0; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = (nbCarriers + 1) / 2; myNegSize = nbCarriers / 2; } else { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = nbCarriers / 2; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = 0; myNegSize = nbCarriers / 2; } myZeroDst = myPosDst + myPosSize; myZeroSize = myNegDst - myZeroDst; PDEBUG(" myPosDst: %u\n", myPosDst); PDEBUG(" myPosSrc: %u\n", myPosSrc); PDEBUG(" myPosSize: %u\n", myPosSize); PDEBUG(" myNegDst: %u\n", myNegDst); PDEBUG(" myNegSrc: %u\n", myNegSrc); PDEBUG(" myNegSize: %u\n", myNegSize); PDEBUG(" myZeroDst: %u\n", myZeroDst); PDEBUG(" myZeroSize: %u\n", myZeroSize); const int N = mySpacing; // The size of the FFT myFftIn = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); myFftOut = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); fftwf_set_timelimit(2); myFftPlan = fftwf_plan_dft_1d(N, myFftIn, myFftOut, FFTW_BACKWARD, FFTW_MEASURE); myCfrPostClip = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); myCfrPostFft = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); myCfrFft = fftwf_plan_dft_1d(N, myCfrPostClip, myCfrPostFft, FFTW_FORWARD, FFTW_MEASURE); if (sizeof(complexf) != sizeof(FFTW_TYPE)) { printf("sizeof(complexf) %zu\n", sizeof(complexf)); printf("sizeof(FFT_TYPE) %zu\n", sizeof(FFTW_TYPE)); throw std::runtime_error( "OfdmGenerator::process complexf size is not FFT_TYPE size!"); } } OfdmGeneratorCF32::~OfdmGeneratorCF32() { PDEBUG("OfdmGenerator::~OfdmGenerator() @ %p\n", this); if (myFftIn) { fftwf_free(myFftIn); } if (myFftOut) { fftwf_free(myFftOut); } if (myFftPlan) { fftwf_destroy_plan(myFftPlan); } if (myCfrPostClip) { fftwf_free(myCfrPostClip); } if (myCfrPostFft) { fftwf_free(myCfrPostFft); } if (myCfrFft) { fftwf_destroy_plan(myCfrFft); } } int OfdmGeneratorCF32::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("OfdmGenerator::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); dataOut->setLength(myNbSymbols * mySpacing * sizeof(complexf)); FFTW_TYPE *in = reinterpret_cast(dataIn->getData()); FFTW_TYPE *out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexf); size_t sizeOut = dataOut->getLength() / sizeof(complexf); if (sizeIn != myNbSymbols * myNbCarriers) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * myNbCarriers); throw std::runtime_error( "OfdmGenerator::process input size not valid!"); } if (sizeOut != myNbSymbols * mySpacing) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * mySpacing); throw std::runtime_error( "OfdmGenerator::process output size not valid!"); } // It is not guaranteed that fftw keeps the FFT input vector intact. // That's why we copy it to the reference. std::vector reference; // IFFT output before CFR applied, for MER calc std::vector before_cfr; size_t num_clip = 0; size_t num_error_clip = 0; // For performance reasons, do not calculate MER for every symbol. myMERCalcIndex = (myMERCalcIndex + 1) % myNbSymbols; // The PAPRStats' clear() is not threadsafe, do not access it // from the RC functions. if (myPaprClearRequest.exchange(false)) { myPaprBeforeCFR.clear(); myPaprAfterCFR.clear(); } for (size_t i = 0; i < myNbSymbols; i++) { myFftIn[0][0] = 0; myFftIn[0][1] = 0; /* For TM I this is: * ZeroDst=769 ZeroSize=511 * PosSrc=0 PosDst=1 PosSize=768 * NegSrc=768 NegDst=1280 NegSize=768 */ memset(&myFftIn[myZeroDst], 0, myZeroSize * sizeof(FFTW_TYPE)); memcpy(&myFftIn[myPosDst], &in[myPosSrc], myPosSize * sizeof(FFTW_TYPE)); memcpy(&myFftIn[myNegDst], &in[myNegSrc], myNegSize * sizeof(FFTW_TYPE)); if (myCfr) { reference.resize(mySpacing); memcpy(reinterpret_cast(reference.data()), myFftIn, mySpacing * sizeof(FFTW_TYPE)); } fftwf_execute(myFftPlan); // IFFT from myFftIn to myFftOut if (myCfr) { complexf *symbol = reinterpret_cast(myFftOut); myPaprBeforeCFR.process_block(symbol, mySpacing); if (myMERCalcIndex == i) { before_cfr.resize(mySpacing); memcpy(reinterpret_cast(before_cfr.data()), myFftOut, mySpacing * sizeof(FFTW_TYPE)); } /* cfr_one_iteration runs the myFftPlan again at the end, and * therefore writes the output data to myFftOut. */ const auto stat = cfr_one_iteration(symbol, reference.data()); // i == 0 always zero power, so the MER ends up being NaN if (i > 0) { myPaprAfterCFR.process_block(symbol, mySpacing); } if (i > 0 and myMERCalcIndex == i) { /* MER definition, ETSI ETR 290, Annex C * * \sum I^2 + Q^2 * MER[dB] = 10 log_10( ---------------- ) * \sum dI^2 + dQ^2 * Where I and Q are the ideal coordinates, and dI and dQ are * the errors in the received datapoints. * * In our case, we consider the constellation points given to the * OfdmGenerator as "ideal", and we compare the CFR output to it. */ double sum_iq = 0; double sum_delta = 0; for (size_t j = 0; j < mySpacing; j++) { sum_iq += (double)std::norm(before_cfr[j]); sum_delta += (double)std::norm(symbol[j] - before_cfr[j]); } // Clamp to 90dB, otherwise the MER average is going to be inf const double mer = sum_delta > 0 ? 10.0 * std::log10(sum_iq / sum_delta) : 90; myMERs.push_back(mer); } num_clip += stat.clip_count; num_error_clip += stat.errclip_count; } memcpy(out, myFftOut, mySpacing * sizeof(FFTW_TYPE)); in += myNbCarriers; out += mySpacing; } if (myCfr) { std::lock_guard lock(myCfrRcMutex); const double num_samps = myNbSymbols * mySpacing; const double clip_ratio = (double)num_clip / num_samps; myClipRatios.push_back(clip_ratio); while (myClipRatios.size() > MAX_CLIP_STATS) { myClipRatios.pop_front(); } const double errclip_ratio = (double)num_error_clip / num_samps; myErrorClipRatios.push_back(errclip_ratio); while (myErrorClipRatios.size() > MAX_CLIP_STATS) { myErrorClipRatios.pop_front(); } while (myMERs.size() > MAX_CLIP_STATS) { myMERs.pop_front(); } } return sizeOut; } OfdmGeneratorCF32::cfr_iter_stat_t OfdmGeneratorCF32::cfr_one_iteration( complexf *symbol, const complexf *reference) { // use std::norm instead of std::abs to avoid calculating the // square roots const float clip_squared = myCfrClip * myCfrClip; OfdmGeneratorCF32::cfr_iter_stat_t ret; // Clip for (size_t i = 0; i < mySpacing; i++) { const float mag_squared = std::norm(symbol[i]); if (mag_squared > clip_squared) { // normalise absolute value to myCfrClip: // x_clipped = x * clip / |x| // = x * sqrt(clip_squared) / sqrt(mag_squared) // = x * sqrt(clip_squared / mag_squared) symbol[i] *= std::sqrt(clip_squared / mag_squared); ret.clip_count++; } } // Take FFT of our clipped signal memcpy(myCfrPostClip, symbol, mySpacing * sizeof(FFTW_TYPE)); fftwf_execute(myCfrFft); // FFT from myCfrPostClip to myCfrPostFft // Calculate the error in frequency domain by subtracting our reference // and clip it to myCfrErrorClip. By adding this clipped error signal // to our FFT output, we compensate the introduced error to some // extent. const float err_clip_squared = myCfrErrorClip * myCfrErrorClip; std::vector error_norm(mySpacing); for (size_t i = 0; i < mySpacing; i++) { // FFTW computes an unnormalised transform, i.e. a FFT-IFFT pair // or vice-versa gives back the original vector scaled by a factor // FFT-size. Because we're comparing our constellation point // (calculated with IFFT-clip-FFT) against reference (input to // the IFFT), we need to divide by our FFT size. const complexf constellation_point = reinterpret_cast(myCfrPostFft)[i] / (float)mySpacing; complexf error = reference[i] - constellation_point; const float mag_squared = std::norm(error); error_norm[i] = mag_squared; if (mag_squared > err_clip_squared) { error *= std::sqrt(err_clip_squared / mag_squared); ret.errclip_count++; } // Update the input to the FFT directly to avoid another copy for the // subsequence IFFT complexf *fft_in = reinterpret_cast(myFftIn); fft_in[i] = constellation_point + error; } // Run our error-compensated symbol through the IFFT again fftwf_execute(myFftPlan); // IFFT from myFftIn to myFftOut return ret; } void OfdmGeneratorCF32::set_parameter(const std::string& parameter, const std::string& value) { using namespace std; stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "cfr") { ss >> myCfr; myPaprClearRequest.store(true); } else if (parameter == "clip") { ss >> myCfrClip; myPaprClearRequest.store(true); } else if (parameter == "errorclip") { ss >> myCfrErrorClip; myPaprClearRequest.store(true); } else if (parameter == "clip_stats" or parameter == "papr") { throw ParameterError("Parameter '" + parameter + "' is read-only"); } else { stringstream ss_err; ss_err << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } const std::string OfdmGeneratorCF32::get_parameter(const std::string& parameter) const { using namespace std; stringstream ss; if (parameter == "cfr") { ss << myCfr; } else if (parameter == "clip") { ss << std::fixed << myCfrClip; } else if (parameter == "errorclip") { ss << std::fixed << myCfrErrorClip; } else if (parameter == "clip_stats") { std::lock_guard lock(myCfrRcMutex); if (myClipRatios.empty() or myErrorClipRatios.empty() or myMERs.empty()) { ss << "No stats available"; } else { const double avg_clip_ratio = std::accumulate(myClipRatios.begin(), myClipRatios.end(), 0.0) / myClipRatios.size(); const double avg_errclip_ratio = std::accumulate(myErrorClipRatios.begin(), myErrorClipRatios.end(), 0.0) / myErrorClipRatios.size(); const double avg_mer = std::accumulate(myMERs.begin(), myMERs.end(), 0.0) / myMERs.size(); ss << "Statistics : " << std::fixed << avg_clip_ratio * 100 << "%"" samples clipped, " << avg_errclip_ratio * 100 << "%"" errors clipped. " << "MER after CFR: " << avg_mer << " dB"; } } else if (parameter == "papr") { const double papr_before = myPaprBeforeCFR.calculate_papr(); const double papr_after = myPaprAfterCFR.calculate_papr(); ss << "PAPR [dB]: " << std::fixed << (papr_before == 0 ? string("N/A") : to_string(papr_before)) << ", " << (papr_after == 0 ? string("N/A") : to_string(papr_after)); } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t OfdmGeneratorCF32::get_all_values() const { json::map_t map; // TODO needs rework of the values return map; } OfdmGeneratorFixed::OfdmGeneratorFixed(size_t nbSymbols, size_t nbCarriers, size_t spacing, bool& enableCfr, float& cfrClip, float& cfrErrorClip, bool inverse) : ModCodec(), myNbSymbols(nbSymbols), myNbCarriers(nbCarriers), mySpacing(spacing) { PDEBUG("OfdmGenerator::OfdmGenerator(%zu, %zu, %zu, %s) @ %p\n", nbSymbols, nbCarriers, spacing, inverse ? "true" : "false", this); etiLog.level(info) << "Using KISS FFT by Mark Borgerding for fixed-point transform"; if (nbCarriers > spacing) { throw std::runtime_error("OfdmGenerator nbCarriers > spacing!"); } if (inverse) { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = 0; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = (nbCarriers + 1) / 2; myNegSize = nbCarriers / 2; } else { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = nbCarriers / 2; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = 0; myNegSize = nbCarriers / 2; } myZeroDst = myPosDst + myPosSize; myZeroSize = myNegDst - myZeroDst; PDEBUG(" myPosDst: %u\n", myPosDst); PDEBUG(" myPosSrc: %u\n", myPosSrc); PDEBUG(" myPosSize: %u\n", myPosSize); PDEBUG(" myNegDst: %u\n", myNegDst); PDEBUG(" myNegSrc: %u\n", myNegSrc); PDEBUG(" myNegSize: %u\n", myNegSize); PDEBUG(" myZeroDst: %u\n", myZeroDst); PDEBUG(" myZeroSize: %u\n", myZeroSize); const int N = mySpacing; // The size of the FFT const size_t nbytes = N * sizeof(kiss_fft_cpx); myFftIn = (kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); myFftOut = (kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); memset(myFftIn, 0, nbytes); myKissCfg = kiss_fft_alloc(N, inverse, nullptr, nullptr); } OfdmGeneratorFixed::~OfdmGeneratorFixed() { if (myKissCfg) KISS_FFT_FREE(myKissCfg); if (myFftIn) KISS_FFT_FREE(myFftIn); if (myFftOut) KISS_FFT_FREE(myFftOut); } int OfdmGeneratorFixed::process(Buffer* const dataIn, Buffer* dataOut) { dataOut->setLength(myNbSymbols * mySpacing * sizeof(kiss_fft_cpx)); kiss_fft_cpx *in = reinterpret_cast(dataIn->getData()); kiss_fft_cpx *out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(kiss_fft_cpx); size_t sizeOut = dataOut->getLength() / sizeof(kiss_fft_cpx); if (sizeIn != myNbSymbols * myNbCarriers) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * myNbCarriers); throw std::runtime_error( "OfdmGenerator::process input size not valid!"); } if (sizeOut != myNbSymbols * mySpacing) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * mySpacing); throw std::runtime_error( "OfdmGenerator::process output size not valid!"); } for (size_t i = 0; i < myNbSymbols; i++) { myFftIn[0].r = 0; myFftIn[0].i = 0; /* For TM I this is: * ZeroDst=769 ZeroSize=511 * PosSrc=0 PosDst=1 PosSize=768 * NegSrc=768 NegDst=1280 NegSize=768 */ memset(&myFftIn[myZeroDst], 0, myZeroSize * sizeof(kiss_fft_cpx)); memcpy(&myFftIn[myPosDst], &in[myPosSrc], myPosSize * sizeof(kiss_fft_cpx)); memcpy(&myFftIn[myNegDst], &in[myNegSrc], myNegSize * sizeof(kiss_fft_cpx)); kiss_fft(myKissCfg, myFftIn, myFftOut); memcpy(out, myFftOut, mySpacing * sizeof(kiss_fft_cpx)); in += myNbCarriers; out += mySpacing; } return sizeOut; } #ifdef HAVE_DEXTER #include #define CHIP_PATH "/dev/gpiochip0" #define LINE_FWD_INV 0 #define LINE_CONFIG_TDATA_VALID 31 // GPIO mapping on write: // bit 31 config_tdata_tvalid // bit 30 resets the latches in the xfft_wrapper // bits 15..0 are 1:1 xfft `config_tdata` // `GPIO[0] = FWD_INV` according to Vivado static struct gpiod_line_request * request_output_lines(const char *chip_path, const unsigned int *offsets, enum gpiod_line_value *values, unsigned int num_lines, const char *consumer) { struct gpiod_request_config *rconfig = NULL; struct gpiod_line_request *request = NULL; struct gpiod_line_settings *settings; struct gpiod_line_config *lconfig; struct gpiod_chip *chip; unsigned int i; int ret; chip = gpiod_chip_open(chip_path); if (!chip) return NULL; settings = gpiod_line_settings_new(); if (!settings) goto close_chip; gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT); lconfig = gpiod_line_config_new(); if (!lconfig) goto free_settings; for (i = 0; i < num_lines; i++) { ret = gpiod_line_config_add_line_settings(lconfig, &offsets[i], 1, settings); if (ret) goto free_line_config; } gpiod_line_config_set_output_values(lconfig, values, num_lines); if (consumer) { rconfig = gpiod_request_config_new(); if (!rconfig) goto free_line_config; gpiod_request_config_set_consumer(rconfig, consumer); } request = gpiod_chip_request_lines(chip, rconfig, lconfig); gpiod_request_config_free(rconfig); free_line_config: gpiod_line_config_free(lconfig); free_settings: gpiod_line_settings_free(settings); close_chip: gpiod_chip_close(chip); return request; } // The GPIO is connected to the config AXI bus of the xfft block. // 15..0 is the config data; 31 is tvalid void set_fft_accelerator_config(bool inverse) { constexpr size_t NUM_LINES = 2; unsigned int line_offsets[NUM_LINES]; enum gpiod_line_value values[NUM_LINES]; line_offsets[0] = LINE_CONFIG_TDATA_VALID; values[0] = GPIOD_LINE_VALUE_INACTIVE; line_offsets[1] = LINE_FWD_INV; values[1] = inverse ? GPIOD_LINE_VALUE_INACTIVE : GPIOD_LINE_VALUE_ACTIVE; struct gpiod_line_request *request; request = request_output_lines(CHIP_PATH, line_offsets, values, NUM_LINES, "fft-config"); if (!request) { fprintf(stderr, "failed to request line: %s\n", strerror(errno)); throw std::runtime_error("Request GPIO lines error"); } usleep(100000); values[0] = GPIOD_LINE_VALUE_ACTIVE; gpiod_line_request_set_values(request, values); usleep(100000); values[0] = GPIOD_LINE_VALUE_INACTIVE; gpiod_line_request_set_values(request, values); gpiod_line_request_release(request); } OfdmGeneratorDEXTER::OfdmGeneratorDEXTER(size_t nbSymbols, size_t nbCarriers, size_t spacing, bool& enableCfr, float& cfrClip, float& cfrErrorClip, bool inverse) : ModCodec(), myNbSymbols(nbSymbols), myNbCarriers(nbCarriers), mySpacing(spacing) { PDEBUG("OfdmGeneratorDEXTER::OfdmGeneratorDEXTER(%zu, %zu, %zu, %s) @ %p\n", nbSymbols, nbCarriers, spacing, inverse ? "true" : "false", this); etiLog.level(info) << "Using DEXTER FFT Accelerator for fixed-point transform"; set_fft_accelerator_config(inverse); if (nbCarriers > spacing) { throw std::runtime_error("OfdmGenerator nbCarriers > spacing!"); } if (inverse) { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = 0; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = (nbCarriers + 1) / 2; myNegSize = nbCarriers / 2; } else { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = nbCarriers / 2; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = 0; myNegSize = nbCarriers / 2; } myZeroDst = myPosDst + myPosSize; myZeroSize = myNegDst - myZeroDst; PDEBUG(" myPosDst: %u\n", myPosDst); PDEBUG(" myPosSrc: %u\n", myPosSrc); PDEBUG(" myPosSize: %u\n", myPosSize); PDEBUG(" myNegDst: %u\n", myNegDst); PDEBUG(" myNegSrc: %u\n", myNegSrc); PDEBUG(" myNegSize: %u\n", myNegSize); PDEBUG(" myZeroDst: %u\n", myZeroDst); PDEBUG(" myZeroSize: %u\n", myZeroSize); const size_t nbytes_in = mySpacing * sizeof(complexfix); const size_t nbytes_out = mySpacing * sizeof(complexfix_wide); #define IIO_ENSURE(expr, err) { \ if (!(expr)) { \ etiLog.log(error, "%s (%s:%d)\n", err, __FILE__, __LINE__); \ throw std::runtime_error("Failed to set FFT for OfdmGeneratorDEXTER"); \ } \ } IIO_ENSURE((m_ctx = iio_create_default_context()), "No context"); IIO_ENSURE(m_dev_in = iio_context_find_device(m_ctx, "fft-accelerator-in"), "no dev"); IIO_ENSURE(m_dev_out = iio_context_find_device(m_ctx, "fft-accelerator-out"), "no dev"); IIO_ENSURE(m_channel_in = iio_device_find_channel(m_dev_in, "voltage0", true), "no channel"); IIO_ENSURE(m_channel_out = iio_device_find_channel(m_dev_out, "voltage0", false), "no channel"); iio_channel_enable(m_channel_in); iio_channel_enable(m_channel_out); m_buf_in = iio_device_create_buffer(m_dev_in, nbytes_in, false); if (!m_buf_in) { throw std::runtime_error("OfdmGeneratorDEXTER could not create in buffer"); } m_buf_out = iio_device_create_buffer(m_dev_out, nbytes_out, false); if (!m_buf_out) { throw std::runtime_error("OfdmGeneratorDEXTER could not create out buffer"); } } OfdmGeneratorDEXTER::~OfdmGeneratorDEXTER() { if (m_buf_in) { iio_buffer_destroy(m_buf_in); m_buf_in = nullptr; } if (m_buf_out) { iio_buffer_destroy(m_buf_out); m_buf_out = nullptr; } if (m_channel_in) { iio_channel_disable(m_channel_in); m_channel_in = nullptr; } if (m_channel_out) { iio_channel_disable(m_channel_out); m_channel_out = nullptr; } if (m_ctx) { iio_context_destroy(m_ctx); m_ctx = nullptr; } } int OfdmGeneratorDEXTER::process(Buffer* const dataIn, Buffer* dataOut) { dataOut->setLength(myNbSymbols * mySpacing * sizeof(complexfix_wide)); complexfix *in = reinterpret_cast(dataIn->getData()); complexfix_wide *out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexfix); size_t sizeOut = dataOut->getLength() / sizeof(complexfix_wide); if (sizeIn != myNbSymbols * myNbCarriers) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * myNbCarriers); throw std::runtime_error( "OfdmGenerator::process input size not valid!"); } if (sizeOut != myNbSymbols * mySpacing) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * mySpacing); throw std::runtime_error("OfdmGenerator::process output size not valid!"); } ptrdiff_t iio_buf_size = (uint8_t*)iio_buffer_end(m_buf_in) - (uint8_t*)iio_buffer_start(m_buf_in); if (iio_buf_size != (ssize_t)(mySpacing * sizeof(complexfix))) { throw std::runtime_error("OfdmGenerator::process incorrect iio buffer size!"); } for (size_t i = 0; i < myNbSymbols; i++) { complexfix *fft_in = reinterpret_cast(iio_buffer_start(m_buf_in)); /* For TM I this is: * ZeroDst=769 ZeroSize=511 * PosSrc=0 PosDst=1 PosSize=768 * NegSrc=768 NegDst=1280 NegSize=768 */ fft_in[0] = static_cast(0); for (size_t i = 0; i < myZeroSize; i++) { fft_in[myZeroDst + i] = static_cast(0); } memcpy(&fft_in[myPosDst], &in[myPosSrc], myPosSize * sizeof(complexfix)); memcpy(&fft_in[myNegDst], &in[myNegSrc], myNegSize * sizeof(complexfix)); ssize_t nbytes_tx = iio_buffer_push(m_buf_in); if (nbytes_tx < 0) { throw std::runtime_error("OfdmGenerator::process error pushing IIO buffer!"); } in += myNbCarriers; // Keep one buffer in flight while we're doing shuffling data around here, // this improves performance. // I believe that, by default, IIO allocates four buffers in total. if (i > 0) { ssize_t nbytes_rx = iio_buffer_refill(m_buf_out); if (nbytes_rx < 0) { throw std::runtime_error("OfdmGenerator::process error refilling IIO buffer!"); } ptrdiff_t p_inc = iio_buffer_step(m_buf_out); if (p_inc != 1) { throw std::runtime_error("OfdmGenerator::process Wrong p_inc"); } // The FFT Accelerator takes 16-bit I + 16-bit Q, and outputs 32-bit I and 32-bit Q. // The formatconvert will take care of this const uint8_t *fft_out = (const uint8_t*)iio_buffer_first(m_buf_out, m_channel_out); const uint8_t *fft_out_end = (const uint8_t*)iio_buffer_end(m_buf_out); constexpr size_t sizeof_out_iq = sizeof(complexfix_wide); if ((fft_out_end - fft_out) != (ssize_t)(mySpacing * sizeof_out_iq)) { fprintf(stderr, "FFT_OUT: %p %p %zu %zu\n", fft_out, fft_out_end, (fft_out_end - fft_out), mySpacing * sizeof_out_iq); throw std::runtime_error("OfdmGenerator::process fft_out length invalid!"); } memcpy(out, fft_out, mySpacing * sizeof_out_iq); out += mySpacing; } } ssize_t nbytes_rx = iio_buffer_refill(m_buf_out); if (nbytes_rx < 0) { throw std::runtime_error("OfdmGenerator::process error refilling IIO buffer!"); } ptrdiff_t p_inc = iio_buffer_step(m_buf_out); if (p_inc != 1) { throw std::runtime_error("OfdmGenerator::process Wrong p_inc"); } // The FFT Accelerator takes 16-bit I + 16-bit Q, and outputs 32-bit I and 32-bit Q. // The formatconvert will take care of this const uint8_t *fft_out = (const uint8_t*)iio_buffer_first(m_buf_out, m_channel_out); const uint8_t *fft_out_end = (const uint8_t*)iio_buffer_end(m_buf_out); constexpr size_t sizeof_out_iq = sizeof(complexfix_wide); if ((fft_out_end - fft_out) != (ssize_t)(mySpacing * sizeof_out_iq)) { fprintf(stderr, "FFT_OUT: %p %p %zu %zu\n", fft_out, fft_out_end, (fft_out_end - fft_out), mySpacing * sizeof_out_iq); throw std::runtime_error("OfdmGenerator::process fft_out length invalid!"); } memcpy(out, fft_out, mySpacing * sizeof_out_iq); return sizeOut; } #endif // HAVE_DEXTER