diff options
author | Maximilian Stiefel <stiefel.maximilian@online.de> | 2020-01-07 17:03:59 -0800 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2020-01-08 10:19:44 -0800 |
commit | 445aa4792dcdf4a8bf14600e6d931451e36f0eaa (patch) | |
tree | 8a011d807a5f30fdf8cac945b5ba294a0937d5af | |
parent | 90c49ef6d4d215f1db01f591d682947fea727655 (diff) |
digital: ofdm: Allow multiple CP lengths for cyclic prefixer
This allows to specify multiple CP lengths that can be used one
after another, for example for LTE modulators.
- Improve sanity checking
- Maintains old API
-rw-r--r-- | gr-digital/examples/ofdm/tx_ofdm.grc | 155 | ||||
-rw-r--r-- | gr-digital/grc/digital_ofdm_cyclic_prefixer.block.yml | 20 | ||||
-rw-r--r-- | gr-digital/include/gnuradio/digital/ofdm_cyclic_prefixer.h | 36 | ||||
-rw-r--r-- | gr-digital/lib/ofdm_cyclic_prefixer_impl.cc | 166 | ||||
-rw-r--r-- | gr-digital/lib/ofdm_cyclic_prefixer_impl.h | 25 | ||||
-rw-r--r-- | gr-digital/python/digital/qa_ofdm_cyclic_prefixer.py | 95 |
6 files changed, 399 insertions, 98 deletions
diff --git a/gr-digital/examples/ofdm/tx_ofdm.grc b/gr-digital/examples/ofdm/tx_ofdm.grc index 4d68086dcc..309dedbb4f 100644 --- a/gr-digital/examples/ofdm/tx_ofdm.grc +++ b/gr-digital/examples/ofdm/tx_ofdm.grc @@ -1,6 +1,7 @@ options: parameters: author: '' + catch_exceptions: 'True' category: Custom cmake_opt: '' comment: '' @@ -24,6 +25,9 @@ options: title: OFDM Tx window_size: 1280, 1024 states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [16, 12.0] rotation: 0 state: enabled @@ -35,6 +39,9 @@ blocks: comment: '' value: '64' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [208, 12.0] rotation: 0 state: enabled @@ -44,6 +51,9 @@ blocks: comment: '' value: digital.header_format_ofdm(occupied_carriers, 1, length_tag_key,) states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [912, 68.0] rotation: 0 state: enabled @@ -53,6 +63,9 @@ blocks: comment: '' value: digital.constellation_bpsk() states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [592, 12.0] rotation: 0 state: enabled @@ -62,6 +75,9 @@ blocks: comment: '' value: '"packet_len"' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [368, 12.0] rotation: 0 state: enabled @@ -72,6 +88,9 @@ blocks: value: (list(range(-26, -21)) + list(range(-20, -7)) + list(range(-6, 0)) + list(range(1, 7)) + list(range(8, 21)) + list(range(22, 27)),) states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [512, 68.0] rotation: 0 state: enabled @@ -81,6 +100,9 @@ blocks: comment: '' value: '96' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [496, 12.0] rotation: 0 state: enabled @@ -90,6 +112,9 @@ blocks: comment: '' value: digital.constellation_qpsk() states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [752, 12.0] rotation: 0 state: enabled @@ -99,6 +124,9 @@ blocks: comment: '' value: ((-21, -7, 7, 21,),) states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [672, 68.0] rotation: 0 state: enabled @@ -108,6 +136,9 @@ blocks: comment: '' value: ((1, 1, 1, -1,),) states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [800, 68.0] rotation: 0 state: enabled @@ -117,6 +148,9 @@ blocks: comment: '' value: '0' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [912, 12.0] rotation: 0 state: enabled @@ -126,6 +160,9 @@ blocks: comment: '' value: '50000' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [272, 12.0] rotation: 0 state: enabled @@ -140,6 +177,9 @@ blocks: 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [208, 68.0] rotation: 0 state: enabled @@ -151,6 +191,9 @@ blocks: 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0, 0] ' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [336, 68.0] rotation: 0 state: enabled @@ -168,6 +211,9 @@ blocks: repeat: 'True' type: byte states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [16, 164.0] rotation: 0 state: enabled @@ -183,6 +229,9 @@ blocks: type: complex vlen: '1' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [200, 692.0] rotation: 0 state: enabled @@ -200,6 +249,9 @@ blocks: maxoutbuf: '0' minoutbuf: '0' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [640, 244.0] rotation: 0 state: enabled @@ -217,6 +269,9 @@ blocks: maxoutbuf: '0' minoutbuf: '0' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [880, 180.0] rotation: 0 state: enabled @@ -233,6 +288,9 @@ blocks: type: byte vlen: '1' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [176, 180.0] rotation: 0 state: enabled @@ -249,6 +307,9 @@ blocks: type: byte vlen: '1' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [712, 828.0] rotation: 0 state: enabled @@ -265,6 +326,9 @@ blocks: type: complex vlen: '1' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [360, 684.0] rotation: 0 state: enabled @@ -282,6 +346,9 @@ blocks: type: complex vlen: '1' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [512, 360.0] rotation: 0 state: enabled @@ -298,6 +365,9 @@ blocks: type: complex vlen: '1' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [552, 692.0] rotation: 0 state: enabled @@ -316,6 +386,9 @@ blocks: seed: '0' taps: 1.0 + 1.0j states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [200, 812.0] rotation: 0 state: enabled @@ -333,6 +406,9 @@ blocks: out_type: complex symbol_table: header_mod.points() states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [216, 320.0] rotation: 0 state: enabled @@ -350,6 +426,9 @@ blocks: out_type: complex symbol_table: payload_mod.points() states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [216, 392.0] rotation: 0 state: enabled @@ -365,6 +444,9 @@ blocks: minoutbuf: '0' packed: 'True' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [384, 172.0] rotation: 0 state: enabled @@ -384,6 +466,9 @@ blocks: pilot_symbols: pilot_symbols sync_words: (sync_word1, sync_word2) states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [200, 476.0] rotation: 0 state: enabled @@ -393,13 +478,16 @@ blocks: affinity: '' alias: '' comment: '' - cp_len: fft_len//4 + cp_len: fft_len/4 input_size: fft_len maxoutbuf: '0' minoutbuf: '0' rolloff: rolloff tagname: length_tag_key states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [624, 508.0] rotation: 0 state: enabled @@ -424,6 +512,9 @@ blocks: sync_word1: sync_word1 sync_word2: sync_word2 states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [440, 764.0] rotation: 0 state: enabled @@ -438,6 +529,9 @@ blocks: maxoutbuf: '0' minoutbuf: '0' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [640, 180.0] rotation: 0 state: enabled @@ -456,6 +550,9 @@ blocks: type: complex window: () states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [416, 492.0] rotation: 0 state: enabled @@ -466,6 +563,9 @@ blocks: comment: '' stream_id: Header Bits states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [1088, 188.0] rotation: 0 state: enabled @@ -543,6 +643,9 @@ blocks: ymax: '10' ymin: '-140' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [960, 756.0] rotation: 0 state: enabled @@ -563,16 +666,16 @@ blocks: alpha9: '1.0' autoscale: 'True' axislabels: 'True' - color1: '"blue"' - color10: '"blue"' - color2: '"red"' - color3: '"green"' - color4: '"black"' - color5: '"cyan"' - color6: '"magenta"' - color7: '"yellow"' - color8: '"dark red"' - color9: '"dark green"' + color1: blue + color10: dark blue + color2: red + color3: green + color4: black + color5: cyan + color6: magenta + color7: yellow + color8: dark red + color9: dark green comment: '' ctrlpanel: 'False' entags: 'True' @@ -637,6 +740,9 @@ blocks: ymin: '-1' yunit: '""' states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [960, 668.0] rotation: 0 state: enabled @@ -647,6 +753,9 @@ blocks: comment: '' stream_id: Time Domain states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [856, 524] rotation: 0 state: enabled @@ -657,6 +766,9 @@ blocks: comment: '' stream_id: Payload Bits states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [832, 252.0] rotation: 0 state: enabled @@ -667,6 +779,9 @@ blocks: comment: '' stream_id: Pre-OFDM states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [744, 372.0] rotation: 0 state: enabled @@ -677,6 +792,9 @@ blocks: comment: '' stream_id: Tx Signal states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [960, 620.0] rotation: 0 state: enabled @@ -687,6 +805,9 @@ blocks: comment: '' stream_id: Header Bits states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [8, 316.0] rotation: 0 state: enabled @@ -697,6 +818,9 @@ blocks: comment: '' stream_id: Payload Bits states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [8, 388.0] rotation: 0 state: enabled @@ -707,6 +831,9 @@ blocks: comment: '' stream_id: Pre-OFDM states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [8, 524.0] rotation: 0 state: enabled @@ -717,6 +844,9 @@ blocks: comment: '' stream_id: Time Domain states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [8, 692.0] rotation: 0 state: enabled @@ -727,6 +857,9 @@ blocks: comment: '' stream_id: Tx Signal states: + bus_sink: false + bus_source: false + bus_structure: null coordinate: [8, 844.0] rotation: 0 state: enabled diff --git a/gr-digital/grc/digital_ofdm_cyclic_prefixer.block.yml b/gr-digital/grc/digital_ofdm_cyclic_prefixer.block.yml index 2bae7310eb..fd34e8a3c5 100644 --- a/gr-digital/grc/digital_ofdm_cyclic_prefixer.block.yml +++ b/gr-digital/grc/digital_ofdm_cyclic_prefixer.block.yml @@ -8,8 +8,8 @@ parameters: dtype: int default: fft_len - id: cp_len - label: CP Length - dtype: int + label: CP Length(s) + dtype: raw default: fft_len/4 - id: rolloff label: Rolloff @@ -32,8 +32,16 @@ outputs: templates: imports: from gnuradio import digital - make: digital.ofdm_cyclic_prefixer(${input_size}, ${input_size} + ${cp_len}, ${rolloff}, - ${tagname}) + make: |- + digital.ofdm_cyclic_prefixer( + ${input_size}, + % if isinstance(context.get('cp_len')(), (tuple, list)): + ${cp_len}, + % else: + ${input_size} + ${cp_len}, + % endif + ${rolloff}, + ${tagname}) cpp_templates: includes: ['#include <gnuradio/digital/ofdm_cyclic_prefixer.h>'] @@ -41,7 +49,11 @@ cpp_templates: make: |- this->${id} = digital::ofdm_cyclic_prefixer::make( ${input_size}, + % if isinstance(context.get('cp_len')(), (tuple, list)): + ${cp_len}, + % else: ${input_size} + ${cp_len}, + % endif ${rolloff}, ${tagname}); link: ['gnuradio-digital'] diff --git a/gr-digital/include/gnuradio/digital/ofdm_cyclic_prefixer.h b/gr-digital/include/gnuradio/digital/ofdm_cyclic_prefixer.h index f0f0d16097..be4c1e1e77 100644 --- a/gr-digital/include/gnuradio/digital/ofdm_cyclic_prefixer.h +++ b/gr-digital/include/gnuradio/digital/ofdm_cyclic_prefixer.h @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2013 Free Software Foundation, Inc. + * Copyright 2013, 2018 Free Software Foundation, Inc. * * This file is part of GNU Radio * @@ -30,7 +30,7 @@ namespace gr { namespace digital { /*! - * \brief Adds a cyclic prefix and performs pulse shaping on OFDM symbols. + * \brief Adds a cyclic prefix and performs optional pulse shaping on OFDM symbols. * \ingroup ofdm_blk * * \details @@ -45,6 +45,19 @@ namespace digital { * the pulse shaping. * * The pulse shape is a raised cosine in the time domain. + * + * Different CP lengths as for instance needed in LTE are supported. This + * is why one of the inputs is std::vector<int>. After every CP given has + * been prepended to symbols, each with the length of the IFFT operation, + * the mechanism will wrap around and start over. To give an example, the + * input tuple for LTE with an FFT length of 2048 would be (160,) + + * (144,)*6, which is equal to (160, 144, 144, 144, 144, 144, 144). A + * uniform CP would be indicated by (uniform_cp_length, ). + * + * This block does some sanity checking: 1. It is not allowed to have a + * vector of CP lengths, which are only 0. 2. Not a single CP in the + * vector must be longer than the rolloff. 3. Not a single CP is allowed to + * be < 0. */ class DIGITAL_API ofdm_cyclic_prefixer : virtual public tagged_stream_block { @@ -52,15 +65,26 @@ public: typedef boost::shared_ptr<ofdm_cyclic_prefixer> sptr; /*! - * \param input_size FFT length (i.e. length of the OFDM symbols) - * \param output_size FFT length + cyclic prefix length (in samples) - * \param rolloff_len Length of the rolloff flank in samples - * \param len_tag_key For framed processing the key of the length tag + * \param input_size IFFT length (i.e. length of the OFDM symbols). + * \param output_size FFT length + cyclic prefix length (in samples). + * \param rolloff_len Length of the rolloff flank in samples. + * \param len_tag_key For framed processing the key of the length tag. */ static sptr make(size_t input_size, size_t output_size, int rolloff_len = 0, const std::string& len_tag_key = ""); + + /*! + * \param fft_len IFFT length (i.e. length of the OFDM symbols). + * \param cp_lengths CP lengths. Wraps around after reaching the end. + * \param rolloff_len Length of the rolloff flank in samples. + * \param len_tag_key For framed processing the key of the length tag. + */ + static sptr make(int fft_len, + const std::vector<int>& cp_lengths, + int rolloff_len = 0, + const std::string& len_tag_key = ""); }; } // namespace digital diff --git a/gr-digital/lib/ofdm_cyclic_prefixer_impl.cc b/gr-digital/lib/ofdm_cyclic_prefixer_impl.cc index f79238e461..36bae1098e 100644 --- a/gr-digital/lib/ofdm_cyclic_prefixer_impl.cc +++ b/gr-digital/lib/ofdm_cyclic_prefixer_impl.cc @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2013,2018 Free Software Foundation, Inc. + * Copyright 2013, 2018 Free Software Foundation, Inc. * * This file is part of GNU Radio * @@ -26,73 +26,117 @@ #include "ofdm_cyclic_prefixer_impl.h" #include <gnuradio/io_signature.h> -#include <gnuradio/math.h> namespace gr { namespace digital { +// Do not break backwards compatibility and overload the make function. ofdm_cyclic_prefixer::sptr ofdm_cyclic_prefixer::make(size_t input_size, size_t output_size, int rolloff_len, const std::string& len_tag_key) { + int fft_len = input_size; + std::vector<int> cp_lengths( + 1, static_cast<int>(output_size - input_size)); // Cast to silence compiler :( return gnuradio::get_initial_sptr( - new ofdm_cyclic_prefixer_impl(input_size, output_size, rolloff_len, len_tag_key)); + new ofdm_cyclic_prefixer_impl(fft_len, cp_lengths, rolloff_len, len_tag_key)); } +ofdm_cyclic_prefixer::sptr ofdm_cyclic_prefixer::make(int fft_len, + const std::vector<int>& cp_lengths, + int rolloff_len, + const std::string& len_tag_key) +{ + return gnuradio::get_initial_sptr( + new ofdm_cyclic_prefixer_impl(fft_len, cp_lengths, rolloff_len, len_tag_key)); +} -ofdm_cyclic_prefixer_impl::ofdm_cyclic_prefixer_impl(size_t input_size, - size_t output_size, +ofdm_cyclic_prefixer_impl::ofdm_cyclic_prefixer_impl(int fft_len, + const std::vector<int>& cp_lengths, int rolloff_len, const std::string& len_tag_key) - : tagged_stream_block("ofdm_cyclic_prefixer", - io_signature::make(1, 1, input_size * sizeof(gr_complex)), - io_signature::make(1, 1, sizeof(gr_complex)), - len_tag_key), - d_fft_len(input_size), - d_output_size(output_size), - d_cp_size(output_size - input_size), + : gr::tagged_stream_block("ofdm_cyclic_prefixer", + gr::io_signature::make(1, 1, fft_len * sizeof(gr_complex)), + gr::io_signature::make(1, 1, sizeof(gr_complex)), + len_tag_key), + d_fft_len(fft_len), + d_state(0), + d_cp_max(0), + d_cp_min(std::numeric_limits<int>::max()), d_rolloff_len(rolloff_len), + d_cp_lengths(cp_lengths), d_up_flank((rolloff_len ? rolloff_len - 1 : 0), 0), d_down_flank((rolloff_len ? rolloff_len - 1 : 0), 0), - d_delay_line(0, 0) + d_delay_line(0, 0), + d_len_tag_key(len_tag_key) { - set_relative_rate((uint64_t)d_output_size, 1); - - // Flank of length 1 would just be rectangular + // Sanity + if (d_cp_lengths.empty()) { + throw std::invalid_argument(this->alias() + + std::string(": CP lengths vector can not be empty.")); + } + for (size_t i = 0; i < d_cp_lengths.size(); i++) { + if (d_cp_lengths[i] != 0) { + break; + } + if (i == d_cp_lengths.size() - 1) { + throw std::invalid_argument( + this->alias() + + std::string(": Please provide at least one CP which is != 0.")); + } + } + for (const int cp_length : d_cp_lengths) { + d_cp_max = std::max(d_cp_max, cp_length); + d_cp_min = std::min(d_cp_min, cp_length); + } + if (d_cp_min < 0) { + throw std::invalid_argument(this->alias() + + std::string(": The minimum CP allowed is 0.")); + } + // Give the buffer allocator and scheduler a hint about the ratio between input and + // output. + set_relative_rate(d_cp_max + d_fft_len); + // Flank of length 1 would just be rectangular. if (d_rolloff_len == 1) { d_rolloff_len = 0; + GR_LOG_WARN(d_logger, + "Set rolloff to 0, because 1 would result in a boxcar function."); } if (d_rolloff_len) { d_delay_line.resize(d_rolloff_len - 1, 0); - if (rolloff_len > d_cp_size) { + // More sanity + if (d_rolloff_len > d_cp_min) { throw std::invalid_argument( - "cyclic prefixer: rolloff len must smaller than the cyclic prefix."); + this->alias() + std::string(": Rolloff length must be smaller than any " + "of the cyclic prefix lengths.")); } - // The actual flanks are one sample shorter than d_rolloff_len, because the - // first sample of the up- and down flank is always zero and one, respectively + /* The actual flanks are one sample shorter than d_rolloff_len, because the + first sample of the up- and down flank is always zero and one, respectively.*/ for (int i = 1; i < d_rolloff_len; i++) { - d_up_flank[i - 1] = 0.5 * (1 + cos(GR_M_PI * i / rolloff_len - GR_M_PI)); + d_up_flank[i - 1] = 0.5 * (1 + cos(M_PI * i / rolloff_len - M_PI)); d_down_flank[i - 1] = - 0.5 * (1 + cos(GR_M_PI * (rolloff_len - i) / rolloff_len - GR_M_PI)); + 0.5 * (1 + cos(M_PI * (rolloff_len - i) / rolloff_len - M_PI)); } } - - if (len_tag_key.empty()) { - set_output_multiple(d_output_size); + if (d_len_tag_key.empty()) { + // noutput_items is set to be a multiple of the largest possible output size. + // It is always OK to return less (in case of the shorter CP). + set_output_multiple(d_fft_len + d_cp_max); } else { + // Avoid automatic tag propagation and propagate them manually. set_tag_propagation_policy(TPP_DONT); } } ofdm_cyclic_prefixer_impl::~ofdm_cyclic_prefixer_impl() {} - int ofdm_cyclic_prefixer_impl::calculate_output_stream_length( const gr_vector_int& ninput_items) { - int nout = ninput_items[0] * d_output_size + d_delay_line.size(); - return nout; + int noutput_items = ninput_items[0] * (d_cp_max + d_fft_len) + + (d_len_tag_key.empty() ? 0 : d_delay_line.size()); + return noutput_items; } @@ -112,49 +156,59 @@ int ofdm_cyclic_prefixer_impl::work(int noutput_items, gr_complex* in = (gr_complex*)input_items[0]; gr_complex* out = (gr_complex*)output_items[0]; int symbols_to_read = 0; - - // 1) Figure out if we're in freewheeling or packet mode - if (!d_length_tag_key_str.empty()) { + // 1) Figure out if we're in freewheeling or packet mode. + if (!d_len_tag_key.empty()) { symbols_to_read = ninput_items[0]; - noutput_items = symbols_to_read * d_output_size + d_delay_line.size(); } else { - symbols_to_read = std::min(noutput_items / (int)d_output_size, ninput_items[0]); - noutput_items = symbols_to_read * d_output_size; + symbols_to_read = + std::min(noutput_items / (int)(d_fft_len + d_cp_max), ninput_items[0]); } - - // 2) Do the cyclic prefixing and, optionally, the pulse shaping + noutput_items = 0; + // 2) Do the cyclic prefixing and, optionally, the pulse shaping. for (int sym_idx = 0; sym_idx < symbols_to_read; sym_idx++) { - memcpy((void*)(out + d_cp_size), (void*)in, d_fft_len * sizeof(gr_complex)); - memcpy((void*)out, - (void*)(in + d_fft_len - d_cp_size), - d_cp_size * sizeof(gr_complex)); + memcpy(static_cast<void*>(out + d_cp_lengths[d_state]), + static_cast<void*>(in), + d_fft_len * sizeof(gr_complex)); + memcpy(static_cast<void*>(out), + static_cast<void*>(in + d_fft_len - d_cp_lengths[d_state]), + d_cp_lengths[d_state] * sizeof(gr_complex)); if (d_rolloff_len) { for (int i = 0; i < d_rolloff_len - 1; i++) { out[i] = out[i] * d_up_flank[i] + d_delay_line[i]; + /* This is basically a cyclic suffix, but completely shifted into the next + symbol. The data rate does not change. */ d_delay_line[i] = in[i] * d_down_flank[i]; } } in += d_fft_len; - out += d_output_size; - } - - // 3) If we're in packet mode: - // - flush the delay line, if applicable - // - Propagate tags - if (!d_length_tag_key_str.empty()) { - if (d_rolloff_len) { - for (unsigned i = 0; i < d_delay_line.size(); i++) { - *out++ = d_delay_line[i]; - } - d_delay_line.assign(d_delay_line.size(), 0); - } + out += d_fft_len + d_cp_lengths[d_state]; + // Raise the number of noutput_items depending on how long the current output was. + noutput_items += d_fft_len + d_cp_lengths[d_state]; + // Propagate tags. + unsigned last_state = d_state > 0 ? d_state - 1 : d_cp_lengths.size() - 1; std::vector<tag_t> tags; - get_tags_in_range(tags, 0, nitems_read(0), nitems_read(0) + symbols_to_read); + get_tags_in_range( + tags, 0, nitems_read(0) + sym_idx, nitems_read(0) + sym_idx + 1); for (unsigned i = 0; i < tags.size(); i++) { - tags[i].offset = - ((tags[i].offset - nitems_read(0)) * d_output_size) + nitems_written(0); + tags[i].offset = ((tags[i].offset - nitems_read(0)) * + (d_fft_len + d_cp_lengths[last_state])) + + nitems_written(0); add_item_tag(0, tags[i].offset, tags[i].key, tags[i].value); } + // Finally switch to next state. + ++d_state %= d_cp_lengths.size(); + } + /* 3) If we're in packet mode: + - flush the delay line, if applicable */ + if (!d_len_tag_key.empty()) { + if (d_rolloff_len) { + std::memcpy(static_cast<void*>(out), + static_cast<void*>(d_delay_line.data()), + sizeof(gr_complex) * d_delay_line.size()); + d_delay_line.assign(d_delay_line.size(), 0); + // Make last symbol a bit longer. + noutput_items += d_delay_line.size(); + } } else { consume_each(symbols_to_read); } diff --git a/gr-digital/lib/ofdm_cyclic_prefixer_impl.h b/gr-digital/lib/ofdm_cyclic_prefixer_impl.h index 50b05f95d4..4e4cab5ddd 100644 --- a/gr-digital/lib/ofdm_cyclic_prefixer_impl.h +++ b/gr-digital/lib/ofdm_cyclic_prefixer_impl.h @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2013 Free Software Foundation, Inc. + * Copyright 2013, 2018 Free Software Foundation, Inc. * * This file is part of GNU Radio * @@ -24,6 +24,7 @@ #define INCLUDED_DIGITAL_OFDM_CYCLIC_PREFIXER_IMPL_H #include <gnuradio/digital/ofdm_cyclic_prefixer.h> +#include <vector> namespace gr { namespace digital { @@ -31,26 +32,34 @@ namespace digital { class ofdm_cyclic_prefixer_impl : public ofdm_cyclic_prefixer { private: - size_t d_fft_len; - //! FFT length + CP length in samples - size_t d_output_size; - //! Length of the cyclic prefix in samples - int d_cp_size; + //! FFT length + int d_fft_len; + //! State, that determines the current output length used. + unsigned d_state; + //! Variable being initialized with the largest CP. + int d_cp_max; + //! Variable being initialized with the smallest CP. + int d_cp_min; //! Length of pulse rolloff in samples int d_rolloff_len; + //! Vector, that holds different CP lengths + std::vector<int> d_cp_lengths; //! Buffers the up-flank (at the beginning of the cyclic prefix) std::vector<float> d_up_flank; //! Buffers the down-flank (which trails the symbol) std::vector<float> d_down_flank; + //! Vector, that holds tail of the predecessor symbol. std::vector<gr_complex> d_delay_line; // We do this explicitly to avoid outputting // zeroes (i.e. no history!) + //! Holds the length tag key. + const std::string d_len_tag_key; protected: int calculate_output_stream_length(const gr_vector_int& ninput_items); public: - ofdm_cyclic_prefixer_impl(size_t input_size, - size_t output_size, + ofdm_cyclic_prefixer_impl(int fft_len, + const std::vector<int>& cp_lengths, int rolloff_len, const std::string& len_tag_key); ~ofdm_cyclic_prefixer_impl(); diff --git a/gr-digital/python/digital/qa_ofdm_cyclic_prefixer.py b/gr-digital/python/digital/qa_ofdm_cyclic_prefixer.py index fc486fa6a2..665236cd03 100644 --- a/gr-digital/python/digital/qa_ofdm_cyclic_prefixer.py +++ b/gr-digital/python/digital/qa_ofdm_cyclic_prefixer.py @@ -1,26 +1,27 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +# +# Copyright 2007-2018 Free Software Foundation, Inc. # -# Copyright 2007,2010,2011,2013,2014 Free Software Foundation, Inc. -# # This file is part of GNU Radio -# +# # GNU Radio 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, or (at your option) # any later version. -# +# # GNU Radio 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 GNU Radio; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -# - -from __future__ import division +# +""" +Unit tests for OFDM cyclic prefixer. +""" from gnuradio import gr, gr_unittest, digital, blocks import pmt @@ -51,7 +52,7 @@ class test_ofdm_cyclic_prefixer (gr_unittest.TestCase): fft_len = 8 cp_len = 2 rolloff = 2 - expected_result = (7.0 / 2, 8, 1, 2, 3, 4, 5, 6, 7, 8, # 1.0/2 + expected_result = (7.0 / 2, 8, 1, 2, 3, 4, 5, 6, 7, 8, # 1.0/2 7.0 / 2+1.0 / 2, 8, 1, 2, 3, 4, 5, 6, 7, 8) src = blocks.vector_source_c(list(range(1, fft_len+1)) * 2, False, fft_len) cp = digital.ofdm_cyclic_prefixer(fft_len, fft_len + cp_len, rolloff) @@ -65,7 +66,7 @@ class test_ofdm_cyclic_prefixer (gr_unittest.TestCase): fft_len = 8 cp_len = 2 tag_name = "ts_last" - expected_result = (7.0 / 2, 8, 1, 2, 3, 4, 5, 6, 7, 8, # 1.0/2 + expected_result = (7.0 / 2, 8, 1, 2, 3, 4, 5, 6, 7, 8, # 1.0/2 7.0 / 2+1.0 / 2, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1.0 / 2) tag2 = gr.tag_t() tag2.offset = 1 @@ -84,7 +85,75 @@ class test_ofdm_cyclic_prefixer (gr_unittest.TestCase): ] self.assertEqual(tags, expected_tags) + def test_wo_tags_no_rolloff_multiple_cps(self): + "Two CP lengths, no rolloff and no tags." + fft_len = 8 + cp_lengths = (3, 2, 2) + expected_result = (5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, # 1 + 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, # 2 + 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, # 3 + 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, # 4 + 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, # 5 + ) + src = blocks.vector_source_c(list(range(fft_len))*5, False, fft_len) + cp = digital.ofdm_cyclic_prefixer(fft_len, cp_lengths) + sink = blocks.vector_sink_c() + self.tb.connect(src, cp, sink) + self.tb.run() + self.assertEqual(sink.data(), expected_result) -if __name__ == '__main__': - gr_unittest.run(test_ofdm_cyclic_prefixer, "test_ofdm_cyclic_prefixer.xml") + def test_wo_tags_2s_rolloff_multiple_cps(self): + "Two CP lengths, 2-sample rolloff and no tags." + fft_len = 8 + cp_lengths = (3, 2, 2) + rolloff = 2 + expected_result = (6.0/2,7,8,1,2,3,4,5,6,7,8, #1 + 7.0/2 + 1.0/2,8,1,2,3,4,5,6,7,8, #2 + 7.0/2 + 1.0/2,8,1,2,3,4,5,6,7,8, #3 + 6.0/2 + 1.0/2,7,8,1,2,3,4,5,6,7,8,#4 + 7.0/2 + 1.0/2,8,1,2,3,4,5,6,7,8 #5 + ) + src = blocks.vector_source_c(list(range(1, fft_len+1))*5, False, fft_len) + cp = digital.ofdm_cyclic_prefixer(fft_len, cp_lengths, rolloff) + sink = blocks.vector_sink_c() + self.tb.connect(src, cp, sink) + self.tb.run() + self.assertEqual(sink.data(), expected_result) + + def test_with_tags_2s_rolloff_multiples_cps(self): + "Two CP lengths, 2-sample rolloff and tags." + fft_len = 8 + cp_lengths = (3, 2, 2) + rolloff = 2 + tag_name = "ts_last" + expected_result = ( + 6.0/2, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, #1 + 7.0/2+1.0/2, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1.0/2 #Last tail + ) + # First test tag + tag0 = gr.tag_t() + tag0.offset = 0 + tag0.key = pmt.string_to_symbol("first_tag") + tag0.value = pmt.from_long(24) + # Second test tag + tag1 = gr.tag_t() + tag1.offset = 1 + tag1.key = pmt.string_to_symbol("second_tag") + tag1.value = pmt.from_long(42) + src = blocks.vector_source_c(list(range(1, fft_len+1)) * 2, False, fft_len, (tag0, tag1)) + cp = digital.ofdm_cyclic_prefixer(fft_len, cp_lengths, rolloff, tag_name) + sink = blocks.tsb_vector_sink_c(tsb_key=tag_name) + self.tb.connect(src, blocks.stream_to_tagged_stream(gr.sizeof_gr_complex, fft_len, 2, tag_name), cp, sink) + self.tb.run() + self.assertEqual(sink.data()[0], expected_result) + tags = [gr.tag_to_python(x) for x in sink.tags()] + tags = sorted([(x.offset, x.key, x.value) for x in tags]) + expected_tags = [ + (0, "first_tag", 24), + (fft_len + cp_lengths[0], "second_tag", 42) + ] + self.assertEqual(tags, expected_tags) + +if __name__ == '__main__': + gr_unittest.run(test_ofdm_cyclic_prefixer) |