From 9a617cf3f92fda5db6607fcd9bdf6277085d0bd7 Mon Sep 17 00:00:00 2001 From: luca Date: Thu, 7 Jun 2018 11:39:46 +0200 Subject: [PATCH 01/22] VBLAST: Add encoder block and QA test. --- gr-digital/grc/digital_vblast_encoder_cc.xml | 21 +++++ .../include/gnuradio/digital/CMakeLists.txt | 3 +- .../gnuradio/digital/vblast_encoder_cc.h | 60 ++++++++++++++ gr-digital/lib/CMakeLists.txt | 1 + gr-digital/lib/vblast_encoder_cc_impl.cc | 79 +++++++++++++++++++ gr-digital/lib/vblast_encoder_cc_impl.h | 54 +++++++++++++ .../python/digital/qa_vblast_encoder_cc.py | 78 ++++++++++++++++++ gr-digital/swig/digital_swig0.i | 3 + 8 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 gr-digital/grc/digital_vblast_encoder_cc.xml create mode 100644 gr-digital/include/gnuradio/digital/vblast_encoder_cc.h create mode 100644 gr-digital/lib/vblast_encoder_cc_impl.cc create mode 100644 gr-digital/lib/vblast_encoder_cc_impl.h create mode 100755 gr-digital/python/digital/qa_vblast_encoder_cc.py diff --git a/gr-digital/grc/digital_vblast_encoder_cc.xml b/gr-digital/grc/digital_vblast_encoder_cc.xml new file mode 100644 index 00000000000..431ec66abb5 --- /dev/null +++ b/gr-digital/grc/digital_vblast_encoder_cc.xml @@ -0,0 +1,21 @@ + + VBLAST encoder + digital_vblast_encoder_cc + [MIMO] + from gnuradio import digital + digital.vblast_encoder_cc($num_outputs) + + Number of Outputs + num_outputs + raw + + + in + complex + + + out + complex + num_outputs + + diff --git a/gr-digital/include/gnuradio/digital/CMakeLists.txt b/gr-digital/include/gnuradio/digital/CMakeLists.txt index 8a912c7138b..7599b4ca57a 100644 --- a/gr-digital/include/gnuradio/digital/CMakeLists.txt +++ b/gr-digital/include/gnuradio/digital/CMakeLists.txt @@ -121,6 +121,7 @@ install(FILES header_payload_demux.h alamouti_encoder_cc.h alamouti_decoder_cc.h - diversity_combiner_cc.h DESTINATION ${GR_INCLUDE_DIR}/gnuradio/digital + diversity_combiner_cc.h + vblast_encoder_cc.h DESTINATION ${GR_INCLUDE_DIR}/gnuradio/digital COMPONENT "digital_devel" ) diff --git a/gr-digital/include/gnuradio/digital/vblast_encoder_cc.h b/gr-digital/include/gnuradio/digital/vblast_encoder_cc.h new file mode 100644 index 00000000000..8b9cdd5b5a2 --- /dev/null +++ b/gr-digital/include/gnuradio/digital/vblast_encoder_cc.h @@ -0,0 +1,60 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + + +#ifndef INCLUDED_DIGITAL_VBLAST_ENCODER_CC_H +#define INCLUDED_DIGITAL_VBLAST_ENCODER_CC_H + +#include +#include + +namespace gr { + namespace digital { + + /*! + * \brief VBLAST encoder. + * \ingroup digital + * Encodes a serial input data stream into a vertical transmission vector + * which is transmitted at once over multiple antennas. + * + */ + class DIGITAL_API vblast_encoder_cc : virtual public gr::sync_decimator + { + public: + typedef boost::shared_ptr sptr; + + /*! + * \brief Return a shared_ptr to a new instance of digital::vblast_encoder_cc. + * + * To avoid accidental use of raw pointers, digital::vblast_encoder_cc's + * constructor is in a private implementation + * class. digital::vblast_encoder_cc::make is the public interface for + * creating new instances. + */ + static sptr make(uint16_t num_outputs); + }; + + } // namespace digital +} // namespace gr + +#endif /* INCLUDED_DIGITAL_VBLAST_ENCODER_CC_H */ + diff --git a/gr-digital/lib/CMakeLists.txt b/gr-digital/lib/CMakeLists.txt index a50f679e84f..e6716f9a12e 100644 --- a/gr-digital/lib/CMakeLists.txt +++ b/gr-digital/lib/CMakeLists.txt @@ -135,6 +135,7 @@ list(APPEND digital_sources alamouti_encoder_cc_impl.cc alamouti_decoder_cc_impl.cc diversity_combiner_cc_impl.cc + vblast_encoder_cc_impl.cc ) #Add Windows DLL resource file if using MSVC diff --git a/gr-digital/lib/vblast_encoder_cc_impl.cc b/gr-digital/lib/vblast_encoder_cc_impl.cc new file mode 100644 index 00000000000..470868983b4 --- /dev/null +++ b/gr-digital/lib/vblast_encoder_cc_impl.cc @@ -0,0 +1,79 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "vblast_encoder_cc_impl.h" + +namespace gr { + namespace digital { + + vblast_encoder_cc::sptr + vblast_encoder_cc::make(uint16_t num_outputs) + { + return gnuradio::get_initial_sptr + (new vblast_encoder_cc_impl(num_outputs)); + } + + /* + * The private constructor + */ + vblast_encoder_cc_impl::vblast_encoder_cc_impl(uint16_t num_outputs) + : gr::sync_decimator("vblast_encoder_cc", + gr::io_signature::make(1, 1, sizeof(gr_complex)), + gr::io_signature::make(num_outputs, num_outputs, sizeof(gr_complex)), num_outputs), + d_num_outputs(num_outputs) + {} + + /* + * Our virtual destructor. + */ + vblast_encoder_cc_impl::~vblast_encoder_cc_impl() + { + } + + int + vblast_encoder_cc_impl::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + const gr_complex *in = (const gr_complex *) input_items[0]; + + // Iterate over output ports. + for (int i = 0; i < d_num_outputs; ++i) { + gr_complex *out = (gr_complex *) output_items[i]; + // Write output data to current output port. + for (int j = 0; j < noutput_items; ++j) { + out[j] = in[j*d_num_outputs + i]; + } + } + + // Tell runtime system how many output items we produced. + return noutput_items; + } + + } /* namespace digital */ +} /* namespace gr */ + diff --git a/gr-digital/lib/vblast_encoder_cc_impl.h b/gr-digital/lib/vblast_encoder_cc_impl.h new file mode 100644 index 00000000000..98100819752 --- /dev/null +++ b/gr-digital/lib/vblast_encoder_cc_impl.h @@ -0,0 +1,54 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + +#ifndef INCLUDED_DIGITAL_VBLAST_ENCODER_CC_IMPL_H +#define INCLUDED_DIGITAL_VBLAST_ENCODER_CC_IMPL_H + +#include + +namespace gr { + namespace digital { +/*! \brief VBLAST encoder. + * Encodes a serial input data stream into a vertical transmission vector + * which is transmitted at once over multiple antennas. + */ + class vblast_encoder_cc_impl : public vblast_encoder_cc + { + private: + uint16_t d_num_outputs; /*!< Number of output ports on which the data is divided. + * This equals the number of your transmit antennas.*/ + + public: + vblast_encoder_cc_impl(uint16_t num_outputs); + ~vblast_encoder_cc_impl(); + + // Where all the action really happens + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + }; + + } // namespace digital +} // namespace gr + +#endif /* INCLUDED_DIGITAL_VBLAST_ENCODER_CC_IMPL_H */ + diff --git a/gr-digital/python/digital/qa_vblast_encoder_cc.py b/gr-digital/python/digital/qa_vblast_encoder_cc.py new file mode 100755 index 00000000000..036a0683d2c --- /dev/null +++ b/gr-digital/python/digital/qa_vblast_encoder_cc.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2018 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 gnuradio import gr, gr_unittest +from gnuradio import blocks +import digital_swig as digital +import numpy as np + +class qa_vblast_encoder_cc (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + # Function which calculates the expected result. + def encode_vblast(self, input, M): + output = np.empty(shape=[M, len(input)/M], dtype=complex) + # Calculate output data for all ports. + for m in range(0, M): + output[m] = input[m::M] + return output + + def test_001_t (self): + # Define test params. + data_length = 20 + repetitions = 5 + + # 5 tests validating the correct output of the encoder with random input data. + for i in range(repetitions): + # Dice number of transmit antennas. + num_outputs = np.random.randint(1, 17) + # Generate random input data. + data = np.random.randn(data_length*num_outputs) + 1j * np.random.randn(data_length*num_outputs) + + # Build up the test flowgraph. + src = blocks.vector_source_c(data=data) + vblast = digital.vblast_encoder_cc(num_outputs) + + sink = [] + self.tb.connect(src, vblast) + for m in range(0, num_outputs): + sink.append(blocks.vector_sink_c()) + self.tb.connect((vblast, m), sink[m]) + # Run flowgraph. + self.tb.run() + + # Calculate expected result. + expected_result = self.encode_vblast(data, num_outputs) + + # Check if the expected result equals the actual result. + for m in range(0, num_outputs): + self.assertComplexTuplesAlmostEqual(expected_result[m], sink[m].data(), 4) + + +if __name__ == '__main__': + gr_unittest.run(qa_vblast_encoder_cc, "qa_vblast_encoder_cc.xml") diff --git a/gr-digital/swig/digital_swig0.i b/gr-digital/swig/digital_swig0.i index 4d382758738..2edc1d81080 100644 --- a/gr-digital/swig/digital_swig0.i +++ b/gr-digital/swig/digital_swig0.i @@ -74,6 +74,7 @@ #include "gnuradio/digital/alamouti_encoder_cc.h" #include "gnuradio/digital/alamouti_decoder_cc.h" #include "gnuradio/digital/diversity_combiner_cc.h" +#include "gnuradio/digital/vblast_encoder_cc.h" %} %include "gnuradio/digital/binary_slicer_fb.h" @@ -113,6 +114,7 @@ %include "gnuradio/digital/alamouti_encoder_cc.h" %include "gnuradio/digital/alamouti_decoder_cc.h" %include "gnuradio/digital/diversity_combiner_cc.h" +%include "gnuradio/digital/vblast_encoder_cc.h" GR_SWIG_BLOCK_MAGIC2(digital, binary_slicer_fb); GR_SWIG_BLOCK_MAGIC2(digital, cma_equalizer_cc); @@ -146,6 +148,7 @@ GR_SWIG_BLOCK_MAGIC2(digital, ofdm_sync_sc_cfb); GR_SWIG_BLOCK_MAGIC2(digital, alamouti_encoder_cc); GR_SWIG_BLOCK_MAGIC2(digital, alamouti_decoder_cc); GR_SWIG_BLOCK_MAGIC2(digital, diversity_combiner_cc); +GR_SWIG_BLOCK_MAGIC2(digital, vblast_encoder_cc); GR_SWIG_BLOCK_MAGIC_FACTORY(digital, cpmmod_bc, gmskmod_bc); From 94b42684254ad8e0471c39af5d2ced5bdde785bb Mon Sep 17 00:00:00 2001 From: luca Date: Mon, 11 Jun 2018 14:45:52 +0200 Subject: [PATCH 02/22] VBLAST: Init decoder block. --- .../include/gnuradio/digital/CMakeLists.txt | 3 +- .../gnuradio/digital/vblast_decoder_cc.h | 58 +++++++++ gr-digital/lib/CMakeLists.txt | 1 + gr-digital/lib/vblast_decoder_cc_impl.cc | 111 ++++++++++++++++++ gr-digital/lib/vblast_decoder_cc_impl.h | 54 +++++++++ .../python/digital/qa_vblast_decoder_cc.py | 43 +++++++ gr-digital/swig/digital_swig0.i | 3 + 7 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 gr-digital/include/gnuradio/digital/vblast_decoder_cc.h create mode 100644 gr-digital/lib/vblast_decoder_cc_impl.cc create mode 100644 gr-digital/lib/vblast_decoder_cc_impl.h create mode 100755 gr-digital/python/digital/qa_vblast_decoder_cc.py diff --git a/gr-digital/include/gnuradio/digital/CMakeLists.txt b/gr-digital/include/gnuradio/digital/CMakeLists.txt index 7599b4ca57a..d9161bdc42a 100644 --- a/gr-digital/include/gnuradio/digital/CMakeLists.txt +++ b/gr-digital/include/gnuradio/digital/CMakeLists.txt @@ -122,6 +122,7 @@ install(FILES alamouti_encoder_cc.h alamouti_decoder_cc.h diversity_combiner_cc.h - vblast_encoder_cc.h DESTINATION ${GR_INCLUDE_DIR}/gnuradio/digital + vblast_encoder_cc.h + vblast_decoder_cc.h DESTINATION ${GR_INCLUDE_DIR}/gnuradio/digital COMPONENT "digital_devel" ) diff --git a/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h b/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h new file mode 100644 index 00000000000..0a067bc2c8e --- /dev/null +++ b/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h @@ -0,0 +1,58 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + + +#ifndef INCLUDED_DIGITAL_VBLAST_DECODER_CC_H +#define INCLUDED_DIGITAL_VBLAST_DECODER_CC_H + +#include +#include + +namespace gr { + namespace digital { + + /*! + * \brief <+description of block+> + * \ingroup digital + * + */ + class DIGITAL_API vblast_decoder_cc : virtual public gr::sync_interpolator + { + public: + typedef boost::shared_ptr sptr; + + /*! + * \brief Return a shared_ptr to a new instance of digital::vblast_decoder_cc. + * + * To avoid accidental use of raw pointers, digital::vblast_decoder_cc's + * constructor is in a private implementation + * class. digital::vblast_decoder_cc::make is the public interface for + * creating new instances. + */ + static sptr make(uint16_t num_inputs); + }; + + } // namespace digital +} // namespace gr + +#endif /* INCLUDED_DIGITAL_VBLAST_DECODER_CC_H */ + diff --git a/gr-digital/lib/CMakeLists.txt b/gr-digital/lib/CMakeLists.txt index e6716f9a12e..97c9276abd8 100644 --- a/gr-digital/lib/CMakeLists.txt +++ b/gr-digital/lib/CMakeLists.txt @@ -136,6 +136,7 @@ list(APPEND digital_sources alamouti_decoder_cc_impl.cc diversity_combiner_cc_impl.cc vblast_encoder_cc_impl.cc + vblast_decoder_cc_impl.cc ) #Add Windows DLL resource file if using MSVC diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc new file mode 100644 index 00000000000..ceca5aa83f1 --- /dev/null +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -0,0 +1,111 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "vblast_decoder_cc_impl.h" + +namespace gr { + namespace digital { + + const std::string vblast_decoder_cc_impl::s = "csi"; + const pmt::pmt_t vblast_decoder_cc_impl::d_key = pmt::string_to_symbol(s); + + vblast_decoder_cc::sptr + vblast_decoder_cc::make(uint16_t num_inputs) + { + return gnuradio::get_initial_sptr + (new vblast_decoder_cc_impl(num_inputs)); + } + + /* + * The private constructor + */ + vblast_decoder_cc_impl::vblast_decoder_cc_impl(uint16_t num_inputs) + : gr::sync_interpolator("vblast_decoder_cc", + gr::io_signature::make(num_inputs, num_inputs, sizeof(gr_complex)), + gr::io_signature::make(1, 1, sizeof(gr_complex)), num_inputs), + d_num_inputs(num_inputs) + {} + + /* + * Our virtual destructor. + */ + vblast_decoder_cc_impl::~vblast_decoder_cc_impl() + { + } + + int + vblast_decoder_cc_impl::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + const gr_complex *in = (const gr_complex *) input_items[0]; + gr_complex *out = (gr_complex *) output_items[0]; + uint16_t nprocessed = 0; // Number of read and written items. + + // Collect all tags of the input buffer with key "csi" in the vector 'tags'. + get_tags_in_window(tags, 0, 0, noutput_items, d_key); + + uint16_t symbol_length; // Number of items in the current symbol. + + if(tags.size() == 0){ // Input buffer includes no tags at all. + // Handle all samples in buffer as they belong to the current symbol. + symbol_length = noutput_items; + //TODO Process symbol. + nprocessed += symbol_length; + } else { // Input buffer includes tags. + if (tags[0].offset - nitems_read(0) > 0){ + /* There are items in the input buffer, before the first tag arrives, + * which belong to the previous symbol. */ + symbol_length = tags[0].offset - nitems_read(0); + //TODO Process_symbol. + nprocessed += symbol_length; + } + // Iterate over tags in buffer. + for (unsigned int i = 0; i < tags.size(); ++i) { + // Calculate the number of items before the next tag. + if (i < tags.size() - 1) { + symbol_length = tags[i + 1].offset - tags[i].offset; + } else { + symbol_length = noutput_items - tags[i].offset + nitems_read(0); + } + // Get CSI from tag. + d_csi = pmt::c32vector_elements(tags[i].value); + // Calculate the weighting vector for the next symbol with the received CSI. + //TODO Calculate the weighting vector. + // Process the symbol with the calculated weighting vector. + //TODO Process_symbol. + nprocessed += symbol_length; + } + } + + // Tell runtime system how many output items we produced. + return noutput_items; + } + + } /* namespace digital */ +} /* namespace gr */ + diff --git a/gr-digital/lib/vblast_decoder_cc_impl.h b/gr-digital/lib/vblast_decoder_cc_impl.h new file mode 100644 index 00000000000..a81de103b07 --- /dev/null +++ b/gr-digital/lib/vblast_decoder_cc_impl.h @@ -0,0 +1,54 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + +#ifndef INCLUDED_DIGITAL_VBLAST_DECODER_CC_IMPL_H +#define INCLUDED_DIGITAL_VBLAST_DECODER_CC_IMPL_H + +#include + +namespace gr { + namespace digital { + + class vblast_decoder_cc_impl : public vblast_decoder_cc + { + private: + uint16_t d_num_inputs; /*!< Number of inputs ports. This equals the interpolation rate.*/ + std::vector tags; /*!< Vector that stores the tags in input buffer. */ + static const std::string s; /*!< String that matches the key of the CSI tags. */ + static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ + std::vector d_csi; /*!< Current channel matrix. */ + + public: + vblast_decoder_cc_impl(uint16_t num_inputs); + ~vblast_decoder_cc_impl(); + + // Where all the action really happens + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + }; + + } // namespace digital +} // namespace gr + +#endif /* INCLUDED_DIGITAL_VBLAST_DECODER_CC_IMPL_H */ + diff --git a/gr-digital/python/digital/qa_vblast_decoder_cc.py b/gr-digital/python/digital/qa_vblast_decoder_cc.py new file mode 100755 index 00000000000..750bb8c2a01 --- /dev/null +++ b/gr-digital/python/digital/qa_vblast_decoder_cc.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2018 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 gnuradio import gr, gr_unittest +from gnuradio import blocks +import digital_swig as digital + +class qa_vblast_decoder_cc (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001_t (self): + # set up fg + self.tb.run () + # check data + + +if __name__ == '__main__': + gr_unittest.run(qa_vblast_decoder_cc, "qa_vblast_decoder_cc.xml") diff --git a/gr-digital/swig/digital_swig0.i b/gr-digital/swig/digital_swig0.i index 2edc1d81080..7242db2e02d 100644 --- a/gr-digital/swig/digital_swig0.i +++ b/gr-digital/swig/digital_swig0.i @@ -75,6 +75,7 @@ #include "gnuradio/digital/alamouti_decoder_cc.h" #include "gnuradio/digital/diversity_combiner_cc.h" #include "gnuradio/digital/vblast_encoder_cc.h" +#include "gnuradio/digital/vblast_decoder_cc.h" %} %include "gnuradio/digital/binary_slicer_fb.h" @@ -115,6 +116,7 @@ %include "gnuradio/digital/alamouti_decoder_cc.h" %include "gnuradio/digital/diversity_combiner_cc.h" %include "gnuradio/digital/vblast_encoder_cc.h" +%include "gnuradio/digital/vblast_decoder_cc.h" GR_SWIG_BLOCK_MAGIC2(digital, binary_slicer_fb); GR_SWIG_BLOCK_MAGIC2(digital, cma_equalizer_cc); @@ -149,6 +151,7 @@ GR_SWIG_BLOCK_MAGIC2(digital, alamouti_encoder_cc); GR_SWIG_BLOCK_MAGIC2(digital, alamouti_decoder_cc); GR_SWIG_BLOCK_MAGIC2(digital, diversity_combiner_cc); GR_SWIG_BLOCK_MAGIC2(digital, vblast_encoder_cc); +GR_SWIG_BLOCK_MAGIC2(digital, vblast_decoder_cc); GR_SWIG_BLOCK_MAGIC_FACTORY(digital, cpmmod_bc, gmskmod_bc); From 3c566a908f3a86a23ad937fa11152185c24d8d85 Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 13 Jun 2018 12:56:45 +0200 Subject: [PATCH 03/22] VBLAST: Add precalculated ZF equalizer for 2x2 MIMO scheme. --- gr-digital/lib/vblast_decoder_cc_impl.cc | 66 +++++++++++++++--- gr-digital/lib/vblast_decoder_cc_impl.h | 9 ++- .../python/digital/qa_vblast_decoder_cc.py | 69 +++++++++++++++++-- 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index ceca5aa83f1..ae34e5bd622 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -24,9 +24,12 @@ #include "config.h" #endif +#include #include #include "vblast_decoder_cc_impl.h" +using namespace boost; + namespace gr { namespace digital { @@ -48,7 +51,11 @@ namespace gr { gr::io_signature::make(num_inputs, num_inputs, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex)), num_inputs), d_num_inputs(num_inputs) - {} + { + // Init CSI array and mimo_equalizer. + d_csi = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); + d_mimo_equalizer = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); + } /* * Our virtual destructor. @@ -57,12 +64,49 @@ namespace gr { { } + void + vblast_decoder_cc_impl::update_mimo_equalizer() { + switch (d_num_inputs){ + case 1: { + // SISO case. + + break; + } + case 2: { + gr_complex c = d_csi[0][0]*d_csi[1][1] - d_csi[0][1]*d_csi[1][0]; + d_mimo_equalizer[0][0] = d_csi[1][1]/c; + d_mimo_equalizer[0][1] = -d_csi[0][1]/c; + d_mimo_equalizer[1][0] = -d_csi[1][0]/c; + d_mimo_equalizer[1][1] = d_csi[0][0]/c; + break; + } + default: { + + } + } + } + + void + vblast_decoder_cc_impl::equalize_symbol(gr_vector_const_void_star input, + gr_complex* out, + uint32_t offset, + uint32_t length) { + std::fill(out, &out[length*d_num_inputs], 0.0); + for (int n = 0; n < d_num_inputs; ++n) { + gr_complex *in = &((gr_complex *) input[n])[offset/d_num_inputs]; + for (unsigned int i = 0; i < length; ++i) { + for (int j = 0; j < d_num_inputs; ++j) { + out[i*d_num_inputs + j] += d_mimo_equalizer[j][n] * in[i]; + } + } + } + } + int vblast_decoder_cc_impl::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { - const gr_complex *in = (const gr_complex *) input_items[0]; gr_complex *out = (gr_complex *) output_items[0]; uint16_t nprocessed = 0; // Number of read and written items. @@ -74,30 +118,32 @@ namespace gr { if(tags.size() == 0){ // Input buffer includes no tags at all. // Handle all samples in buffer as they belong to the current symbol. symbol_length = noutput_items; - //TODO Process symbol. + equalize_symbol(input_items, out, nprocessed, symbol_length); nprocessed += symbol_length; } else { // Input buffer includes tags. if (tags[0].offset - nitems_read(0) > 0){ /* There are items in the input buffer, before the first tag arrives, * which belong to the previous symbol. */ - symbol_length = tags[0].offset - nitems_read(0); - //TODO Process_symbol. + symbol_length = (tags[0].offset - nitems_read(0))*d_num_inputs; + equalize_symbol(input_items, out, nprocessed, symbol_length); nprocessed += symbol_length; } // Iterate over tags in buffer. for (unsigned int i = 0; i < tags.size(); ++i) { // Calculate the number of items before the next tag. if (i < tags.size() - 1) { - symbol_length = tags[i + 1].offset - tags[i].offset; + symbol_length = (tags[i + 1].offset - tags[i].offset)*d_num_inputs; } else { - symbol_length = noutput_items - tags[i].offset + nitems_read(0); + symbol_length = noutput_items - (tags[i].offset - nitems_read(0))*d_num_inputs; } // Get CSI from tag. - d_csi = pmt::c32vector_elements(tags[i].value); + for (unsigned j = 0; j < pmt::length(tags[i].value); ++j) { + d_csi[j] = pmt::c32vector_elements(pmt::vector_ref(tags[i].value, j)); + } // Calculate the weighting vector for the next symbol with the received CSI. - //TODO Calculate the weighting vector. + update_mimo_equalizer(); // Process the symbol with the calculated weighting vector. - //TODO Process_symbol. + equalize_symbol(input_items, &out[nprocessed], nprocessed, symbol_length); nprocessed += symbol_length; } } diff --git a/gr-digital/lib/vblast_decoder_cc_impl.h b/gr-digital/lib/vblast_decoder_cc_impl.h index a81de103b07..36fe7f8a415 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.h +++ b/gr-digital/lib/vblast_decoder_cc_impl.h @@ -35,7 +35,14 @@ namespace gr { std::vector tags; /*!< Vector that stores the tags in input buffer. */ static const std::string s; /*!< String that matches the key of the CSI tags. */ static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ - std::vector d_csi; /*!< Current channel matrix. */ + pmt::pmt_t d_pmt_csi; + std::vector > d_csi; /*!< Current channel matrix. */ + + std::vector > d_mimo_equalizer; + + void update_mimo_equalizer(); + + void equalize_symbol(gr_vector_const_void_star input, gr_complex* out, uint32_t offset, uint32_t length); public: vblast_decoder_cc_impl(uint16_t num_inputs); diff --git a/gr-digital/python/digital/qa_vblast_decoder_cc.py b/gr-digital/python/digital/qa_vblast_decoder_cc.py index 750bb8c2a01..35615d70f90 100755 --- a/gr-digital/python/digital/qa_vblast_decoder_cc.py +++ b/gr-digital/python/digital/qa_vblast_decoder_cc.py @@ -24,6 +24,8 @@ from gnuradio import gr, gr_unittest from gnuradio import blocks import digital_swig as digital +import numpy as np +import pmt class qa_vblast_decoder_cc (gr_unittest.TestCase): @@ -33,10 +35,69 @@ def setUp (self): def tearDown (self): self.tb = None - def test_001_t (self): - # set up fg - self.tb.run () - # check data + def dice_csi_tags(self, data, num_inputs, num_tags, tag_pos): + tags = [] + expected_result = np.empty([np.size(data, 0)*np.size(data,1)], dtype=complex) + for i in range(0, num_tags): + # Randomly generate CSI for one symbol. + csi = (np.random.randn(num_inputs, num_inputs) + 1j * np.random.randn(num_inputs, num_inputs)) + # Assign the CSI vector to a PMT vector. + csi_pmt = pmt.make_vector(num_inputs, pmt.make_c32vector(num_inputs, 1.0)) + for k, rx in enumerate(csi): + line_vector_pmt = pmt.make_c32vector(num_inputs, csi[k][0]) + for l, tx in enumerate(csi[k]): + pmt.c32vector_set(v=line_vector_pmt, k=l, x=csi[k][l]) + pmt.vector_set(csi_pmt, k, line_vector_pmt) + + # Append stream tags with CSI to data stream. + tags.append(gr.tag_utils.python_to_tag((tag_pos[i], + pmt.string_to_symbol("csi"), + csi_pmt, + pmt.from_long(0)))) + + # Calculate expected result. + expected_result[tag_pos[i]*num_inputs::] = np.reshape(np.transpose(np.dot(np.linalg.inv(csi), data[::, tag_pos[i]::])), (np.size(data, 0)*(np.size(data,1)-tag_pos[i]))) + return tags, expected_result + +# 5 tests validating the correct output of the decoder with random input data. + def test_001_t(self): + # Define test params. + data_length = 20 + repetitions = 5 + num_tags = 4 + num_inputs = 2 + + for i in range(repetitions): + # Generate random input data. + data = np.random.randn(num_inputs, data_length) + 1j * np.random.randn(num_inputs, data_length) + # Generate random tag positions. + tag_pos = np.random.randint(low=0, high=data_length, size=num_tags) + tag_pos[0] = 0 + tag_pos = np.sort(tag_pos) + # Calculate expected result. + tags, expected_result = self.dice_csi_tags(data, + num_inputs, + num_tags, + tag_pos) + + # Build up the test flowgraph. + src = [] + src.append(blocks.vector_source_c(data=data[0], + repeat=False, + tags=tags)) + for n in range(1, num_inputs): + src.append(blocks.vector_source_c(data=data[n], + repeat=False)) + vblast_decoder = digital.vblast_decoder_cc(num_inputs) + sink = blocks.vector_sink_c() + self.tb.connect(src[0], vblast_decoder, sink) + for n in range(1, num_inputs): + self.tb.connect(src[n], (vblast_decoder, n)) + # Run flowgraph. + self.tb.run() + + # Check if the expected result equals the actual result. + self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 4) if __name__ == '__main__': From a6db04838bdac345d841321c3f0dedb5c3def8fa Mon Sep 17 00:00:00 2001 From: luca Date: Mon, 18 Jun 2018 15:16:35 +0200 Subject: [PATCH 04/22] VBLAST: Add precalculated MMSE equalizer for 2x2 MIMO scheme. --- .../gnuradio/digital/vblast_decoder_cc.h | 2 +- gr-digital/lib/vblast_decoder_cc_impl.cc | 67 ++++++++++++++----- gr-digital/lib/vblast_decoder_cc_impl.h | 6 +- .../python/digital/qa_vblast_decoder_cc.py | 7 +- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h b/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h index 0a067bc2c8e..862b8165224 100644 --- a/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h +++ b/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h @@ -48,7 +48,7 @@ namespace gr { * class. digital::vblast_decoder_cc::make is the public interface for * creating new instances. */ - static sptr make(uint16_t num_inputs); + static sptr make(uint16_t num_inputs, std::string equalizer_type); }; } // namespace digital diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index ae34e5bd622..14feb9bb3ca 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -24,6 +24,7 @@ #include "config.h" #endif +#include #include #include #include "vblast_decoder_cc_impl.h" @@ -37,24 +38,26 @@ namespace gr { const pmt::pmt_t vblast_decoder_cc_impl::d_key = pmt::string_to_symbol(s); vblast_decoder_cc::sptr - vblast_decoder_cc::make(uint16_t num_inputs) + vblast_decoder_cc::make(uint16_t num_inputs, std::string equalizer_type) { return gnuradio::get_initial_sptr - (new vblast_decoder_cc_impl(num_inputs)); + (new vblast_decoder_cc_impl(num_inputs, equalizer_type)); } /* * The private constructor */ - vblast_decoder_cc_impl::vblast_decoder_cc_impl(uint16_t num_inputs) + vblast_decoder_cc_impl::vblast_decoder_cc_impl(uint16_t num_inputs, std::string equalizer_type) : gr::sync_interpolator("vblast_decoder_cc", gr::io_signature::make(num_inputs, num_inputs, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex)), num_inputs), - d_num_inputs(num_inputs) + d_num_inputs(num_inputs), + d_equalizer_type(equalizer_type) { // Init CSI array and mimo_equalizer. d_csi = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); d_mimo_equalizer = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); + d_snr = std::vector(num_inputs, 1.0); } /* @@ -66,23 +69,53 @@ namespace gr { void vblast_decoder_cc_impl::update_mimo_equalizer() { - switch (d_num_inputs){ - case 1: { - // SISO case. + if (d_equalizer_type.compare("ZF") == 0) { + // Zero forcing equalizer. + switch (d_num_inputs) { + case 1: { + // SISO case. - break; - } - case 2: { - gr_complex c = d_csi[0][0]*d_csi[1][1] - d_csi[0][1]*d_csi[1][0]; - d_mimo_equalizer[0][0] = d_csi[1][1]/c; - d_mimo_equalizer[0][1] = -d_csi[0][1]/c; - d_mimo_equalizer[1][0] = -d_csi[1][0]/c; - d_mimo_equalizer[1][1] = d_csi[0][0]/c; - break; + break; + } + case 2: { + gr_complex c = d_csi[0][0] * d_csi[1][1] - d_csi[0][1] * d_csi[1][0]; + d_mimo_equalizer[0][0] = d_csi[1][1] / c; + d_mimo_equalizer[0][1] = -d_csi[0][1] / c; + d_mimo_equalizer[1][0] = -d_csi[1][0] / c; + d_mimo_equalizer[1][1] = d_csi[0][0] / c; + break; + } + default: { + + } } - default: { + } else if (d_equalizer_type.compare("MMSE") == 0){ + // Minimum mean squared error equalizer. + switch (d_num_inputs) { + case 1: { + // SISO case. + break; + } + case 2: { + gr_complex a = std::norm(d_csi[0][0]) + std::norm(d_csi[1][0]) + (1/d_snr[0]); + gr_complex b = std::conj(d_csi[0][0])*d_csi[0][1] + std::conj(d_csi[1][0])*d_csi[1][1]; + gr_complex c = std::conj(d_csi[0][1])*d_csi[0][0] + std::conj(d_csi[1][1])*d_csi[1][0]; + gr_complex d = std::norm(d_csi[0][1]) + std::norm(d_csi[1][1]) + (1/d_snr[1]); + gr_complex e = 1/(a*d-b*c); + + d_mimo_equalizer[0][0] = (std::conj(d_csi[0][0])*d - std::conj(d_csi[0][1])*b)*e; + d_mimo_equalizer[0][1] = (std::conj(d_csi[1][0])*d - std::conj(d_csi[1][1])*b)*e; + d_mimo_equalizer[1][0] = (std::conj(d_csi[0][1])*a - std::conj(d_csi[0][0])*c)*e; + d_mimo_equalizer[1][1] = (std::conj(d_csi[1][1])*a - std::conj(d_csi[1][0])*c)*e; + break; + } + default: { + + } } + } else{ + // The selected equalizer type is not existing in this implementation. } } diff --git a/gr-digital/lib/vblast_decoder_cc_impl.h b/gr-digital/lib/vblast_decoder_cc_impl.h index 36fe7f8a415..3990b323410 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.h +++ b/gr-digital/lib/vblast_decoder_cc_impl.h @@ -32,20 +32,22 @@ namespace gr { { private: uint16_t d_num_inputs; /*!< Number of inputs ports. This equals the interpolation rate.*/ + std::string d_equalizer_type; /*!< Equalization technique zero forcing 'ZF' or + * minimum mean squared error 'MMSE'. */ std::vector tags; /*!< Vector that stores the tags in input buffer. */ static const std::string s; /*!< String that matches the key of the CSI tags. */ static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ pmt::pmt_t d_pmt_csi; std::vector > d_csi; /*!< Current channel matrix. */ - std::vector > d_mimo_equalizer; + std::vector d_snr; void update_mimo_equalizer(); void equalize_symbol(gr_vector_const_void_star input, gr_complex* out, uint32_t offset, uint32_t length); public: - vblast_decoder_cc_impl(uint16_t num_inputs); + vblast_decoder_cc_impl(uint16_t num_inputs, std::string equalizer_type); ~vblast_decoder_cc_impl(); // Where all the action really happens diff --git a/gr-digital/python/digital/qa_vblast_decoder_cc.py b/gr-digital/python/digital/qa_vblast_decoder_cc.py index 35615d70f90..309b078df46 100755 --- a/gr-digital/python/digital/qa_vblast_decoder_cc.py +++ b/gr-digital/python/digital/qa_vblast_decoder_cc.py @@ -59,13 +59,16 @@ def dice_csi_tags(self, data, num_inputs, num_tags, tag_pos): expected_result[tag_pos[i]*num_inputs::] = np.reshape(np.transpose(np.dot(np.linalg.inv(csi), data[::, tag_pos[i]::])), (np.size(data, 0)*(np.size(data,1)-tag_pos[i]))) return tags, expected_result -# 5 tests validating the correct output of the decoder with random input data. + ''' + 5 tests validating the correct output of the decoder with random input data, ZF equalizer + and 2x2 MIMO scheme. ''' def test_001_t(self): # Define test params. data_length = 20 repetitions = 5 num_tags = 4 num_inputs = 2 + equalizer_type = 'ZF' for i in range(repetitions): # Generate random input data. @@ -88,7 +91,7 @@ def test_001_t(self): for n in range(1, num_inputs): src.append(blocks.vector_source_c(data=data[n], repeat=False)) - vblast_decoder = digital.vblast_decoder_cc(num_inputs) + vblast_decoder = digital.vblast_decoder_cc(num_inputs, equalizer_type) sink = blocks.vector_sink_c() self.tb.connect(src[0], vblast_decoder, sink) for n in range(1, num_inputs): From 01ed4849b1a8889de1792bf902cd6e64d2857659 Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 19 Jun 2018 11:13:08 +0200 Subject: [PATCH 05/22] VBLAST: Add qa test for 2x2 MMSE case. --- gr-digital/lib/vblast_decoder_cc_impl.cc | 37 ++++++++----- gr-digital/lib/vblast_decoder_cc_impl.h | 2 +- .../python/digital/qa_vblast_decoder_cc.py | 55 ++++++++++++++++++- 3 files changed, 78 insertions(+), 16 deletions(-) diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index 14feb9bb3ca..406d06742e1 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -57,7 +57,7 @@ namespace gr { // Init CSI array and mimo_equalizer. d_csi = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); d_mimo_equalizer = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); - d_snr = std::vector(num_inputs, 1.0); + d_snr = std::vector(num_inputs, 1.0); } /* @@ -98,16 +98,16 @@ namespace gr { break; } case 2: { - gr_complex a = std::norm(d_csi[0][0]) + std::norm(d_csi[1][0]) + (1/d_snr[0]); + gr_complex a = std::norm(d_csi[0][0]) + std::norm(d_csi[1][0]) + 1./d_snr[0]; gr_complex b = std::conj(d_csi[0][0])*d_csi[0][1] + std::conj(d_csi[1][0])*d_csi[1][1]; gr_complex c = std::conj(d_csi[0][1])*d_csi[0][0] + std::conj(d_csi[1][1])*d_csi[1][0]; - gr_complex d = std::norm(d_csi[0][1]) + std::norm(d_csi[1][1]) + (1/d_snr[1]); - gr_complex e = 1/(a*d-b*c); + gr_complex d = std::norm(d_csi[0][1]) + std::norm(d_csi[1][1]) + 1./d_snr[1]; + gr_complex e = a*d-b*c; - d_mimo_equalizer[0][0] = (std::conj(d_csi[0][0])*d - std::conj(d_csi[0][1])*b)*e; - d_mimo_equalizer[0][1] = (std::conj(d_csi[1][0])*d - std::conj(d_csi[1][1])*b)*e; - d_mimo_equalizer[1][0] = (std::conj(d_csi[0][1])*a - std::conj(d_csi[0][0])*c)*e; - d_mimo_equalizer[1][1] = (std::conj(d_csi[1][1])*a - std::conj(d_csi[1][0])*c)*e; + d_mimo_equalizer[0][0] = (std::conj(d_csi[0][0])*d - std::conj(d_csi[0][1])*b)/e; + d_mimo_equalizer[0][1] = (std::conj(d_csi[1][0])*d - std::conj(d_csi[1][1])*b)/e; + d_mimo_equalizer[1][0] = (std::conj(d_csi[0][1])*a - std::conj(d_csi[0][0])*c)/e; + d_mimo_equalizer[1][1] = (std::conj(d_csi[1][1])*a - std::conj(d_csi[1][0])*c)/e; break; } default: { @@ -143,8 +143,8 @@ namespace gr { gr_complex *out = (gr_complex *) output_items[0]; uint16_t nprocessed = 0; // Number of read and written items. - // Collect all tags of the input buffer with key "csi" in the vector 'tags'. - get_tags_in_window(tags, 0, 0, noutput_items, d_key); + // Collect all tags of the input buffer in the vector 'tags'. + get_tags_in_window(tags, 0, 0, noutput_items); uint16_t symbol_length; // Number of items in the current symbol. @@ -165,16 +165,25 @@ namespace gr { for (unsigned int i = 0; i < tags.size(); ++i) { // Calculate the number of items before the next tag. if (i < tags.size() - 1) { + // This is not the last tag. symbol_length = (tags[i + 1].offset - tags[i].offset)*d_num_inputs; } else { + // This is the last tag. symbol_length = noutput_items - (tags[i].offset - nitems_read(0))*d_num_inputs; } - // Get CSI from tag. - for (unsigned j = 0; j < pmt::length(tags[i].value); ++j) { + // Check the key of the tag. + if (pmt::symbol_to_string(tags[i].key).compare("csi") == 0) { + // Get CSI from 'csi' tag. + for (unsigned j = 0; j < pmt::length(tags[i].value); ++j) { d_csi[j] = pmt::c32vector_elements(pmt::vector_ref(tags[i].value, j)); + } + // Calculate the weighting vector for the next symbol with the received CSI. + update_mimo_equalizer(); + } else if (pmt::symbol_to_string(tags[i].key).compare("snr") == 0 && d_equalizer_type.compare("MMSE") == 0) { + // 'snr' tag: Recalculate the weighting vector for the next symbol with the updated snr. + d_snr = pmt::f32vector_elements(tags[i].value); + update_mimo_equalizer(); } - // Calculate the weighting vector for the next symbol with the received CSI. - update_mimo_equalizer(); // Process the symbol with the calculated weighting vector. equalize_symbol(input_items, &out[nprocessed], nprocessed, symbol_length); nprocessed += symbol_length; diff --git a/gr-digital/lib/vblast_decoder_cc_impl.h b/gr-digital/lib/vblast_decoder_cc_impl.h index 3990b323410..2e51d3891f0 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.h +++ b/gr-digital/lib/vblast_decoder_cc_impl.h @@ -40,7 +40,7 @@ namespace gr { pmt::pmt_t d_pmt_csi; std::vector > d_csi; /*!< Current channel matrix. */ std::vector > d_mimo_equalizer; - std::vector d_snr; + std::vector d_snr; void update_mimo_equalizer(); diff --git a/gr-digital/python/digital/qa_vblast_decoder_cc.py b/gr-digital/python/digital/qa_vblast_decoder_cc.py index 309b078df46..8c1a962e0ee 100755 --- a/gr-digital/python/digital/qa_vblast_decoder_cc.py +++ b/gr-digital/python/digital/qa_vblast_decoder_cc.py @@ -35,9 +35,17 @@ def setUp (self): def tearDown (self): self.tb = None - def dice_csi_tags(self, data, num_inputs, num_tags, tag_pos): + def dice_csi_tags(self, data, type, num_inputs, num_tags, tag_pos): tags = [] expected_result = np.empty([np.size(data, 0)*np.size(data,1)], dtype=complex) + + if type == 'MMSE': + # Add an SNR tag at the start of the stream for MMSE. + tags.append(gr.tag_utils.python_to_tag((0, + pmt.string_to_symbol("snr"), + pmt.make_f32vector(num_inputs, 1e8), + pmt.from_long(0)))) + for i in range(0, num_tags): # Randomly generate CSI for one symbol. csi = (np.random.randn(num_inputs, num_inputs) + 1j * np.random.randn(num_inputs, num_inputs)) @@ -79,6 +87,7 @@ def test_001_t(self): tag_pos = np.sort(tag_pos) # Calculate expected result. tags, expected_result = self.dice_csi_tags(data, + 'ZF', num_inputs, num_tags, tag_pos) @@ -102,6 +111,50 @@ def test_001_t(self): # Check if the expected result equals the actual result. self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 4) + ''' + 5 tests validating the correct output of the decoder with random input data, MMSE equalizer + and 2x2 MIMO scheme and an extremely high SNR regime (1/snr -> 0). ''' + def test_002_t(self): + # Define test params. + data_length = 20 + repetitions = 5 + num_tags = 4 + num_inputs = 2 + equalizer_type = 'MMSE' + + for i in range(repetitions): + # Generate random input data. + data = np.random.randn(num_inputs, data_length) + 1j * np.random.randn(num_inputs, data_length) + # Generate random tag positions. + tag_pos = np.random.randint(low=0, high=data_length, size=num_tags) + tag_pos[0] = 0 + tag_pos = np.sort(tag_pos) + # Calculate expected result. + tags, expected_result = self.dice_csi_tags(data, + 'MMSE', + num_inputs, + num_tags, + tag_pos) + + # Build up the test flowgraph. + src = [] + src.append(blocks.vector_source_c(data=data[0], + repeat=False, + tags=tags)) + for n in range(1, num_inputs): + src.append(blocks.vector_source_c(data=data[n], + repeat=False)) + vblast_decoder = digital.vblast_decoder_cc(num_inputs, equalizer_type) + sink = blocks.vector_sink_c() + self.tb.connect(src[0], vblast_decoder, sink) + for n in range(1, num_inputs): + self.tb.connect(src[n], (vblast_decoder, n)) + # Run flowgraph. + self.tb.run() + + # Check if the expected result equals the actual result. + self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 2) + if __name__ == '__main__': gr_unittest.run(qa_vblast_decoder_cc, "qa_vblast_decoder_cc.xml") From 64dba722cbff04b69df802ff0c70a7dd6cb06171 Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 19 Jun 2018 15:58:54 +0200 Subject: [PATCH 06/22] VBLAST: Add MxM ZF case with Eigen inverse calculation and qa test. --- gr-digital/lib/vblast_decoder_cc_impl.cc | 12 +++++ .../python/digital/qa_vblast_decoder_cc.py | 45 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index 406d06742e1..54a3f236bb3 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -28,6 +28,7 @@ #include #include #include "vblast_decoder_cc_impl.h" +#include using namespace boost; @@ -86,7 +87,18 @@ namespace gr { break; } default: { + // Map CSI 2-dimensional std::vector to Eigen MatrixXcf. + Eigen::MatrixXcf csi_matrix(d_num_inputs, d_num_inputs); + for (int i = 0; i < d_num_inputs; ++i) { + csi_matrix.row(i) = Eigen::VectorXcf::Map(&d_csi[i][0], d_csi[i].size()); + } + // Calculate the inverse of the CSI matrix. + Eigen::MatrixXcf csi_inverse = csi_matrix.inverse(); + // Map the inverse of the Eigen MatrixXcf to the 2-dim equalizer std::vector. + for (int i = 0; i < d_num_inputs; ++i) { + Eigen::VectorXcf::Map(&d_mimo_equalizer[i][0], csi_matrix.row(i).size()) = csi_inverse.row(i); + } } } } else if (d_equalizer_type.compare("MMSE") == 0){ diff --git a/gr-digital/python/digital/qa_vblast_decoder_cc.py b/gr-digital/python/digital/qa_vblast_decoder_cc.py index 8c1a962e0ee..6adc885ca06 100755 --- a/gr-digital/python/digital/qa_vblast_decoder_cc.py +++ b/gr-digital/python/digital/qa_vblast_decoder_cc.py @@ -155,6 +155,51 @@ def test_002_t(self): # Check if the expected result equals the actual result. self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 2) + ''' + 5 tests validating the correct output of the decoder with random input data, ZF equalizer + and MxM MIMO scheme with M in [3, 16]. ''' + def test_003_t(self): + # Define test params. + data_length = 20 + repetitions = 50 + num_tags = 4 + equalizer_type = 'ZF' + + for i in range(repetitions): + # Generate random number of inputs. + num_inputs = np.random.randint(low=3, high=17) + # Generate random input data. + data = np.random.randn(num_inputs, data_length) + 1j * np.random.randn(num_inputs, data_length) + # Generate random tag positions. + tag_pos = np.random.randint(low=0, high=data_length, size=num_tags) + tag_pos[0] = 0 + tag_pos = np.sort(tag_pos) + # Calculate expected result. + tags, expected_result = self.dice_csi_tags(data, + 'ZF', + num_inputs, + num_tags, + tag_pos) + + # Build up the test flowgraph. + src = [] + src.append(blocks.vector_source_c(data=data[0], + repeat=False, + tags=tags)) + for n in range(1, num_inputs): + src.append(blocks.vector_source_c(data=data[n], + repeat=False)) + vblast_decoder = digital.vblast_decoder_cc(num_inputs, equalizer_type) + sink = blocks.vector_sink_c() + self.tb.connect(src[0], vblast_decoder, sink) + for n in range(1, num_inputs): + self.tb.connect(src[n], (vblast_decoder, n)) + # Run flowgraph. + self.tb.run() + + # Check if the expected result equals the actual result. + self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 2) + if __name__ == '__main__': gr_unittest.run(qa_vblast_decoder_cc, "qa_vblast_decoder_cc.xml") From d5f6179c43313b894eb592ce3d95e8ab365173d8 Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 19 Jun 2018 16:33:54 +0200 Subject: [PATCH 07/22] VBLAST: Add ZF and MMSE equalizer for MIMO 1x1 scheme (SISO case). --- gr-digital/lib/vblast_decoder_cc_impl.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index 54a3f236bb3..b080c55a936 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -75,7 +75,7 @@ namespace gr { switch (d_num_inputs) { case 1: { // SISO case. - + d_mimo_equalizer[0][0] = (gr_complex) 1./d_csi[0][0]; break; } case 2: { @@ -106,7 +106,7 @@ namespace gr { switch (d_num_inputs) { case 1: { // SISO case. - + d_mimo_equalizer[0][0] = std::conj(d_csi[0][0]) / (std::norm(d_csi[0][0])+(gr_complex) 1./d_snr[0]); break; } case 2: { From d6339eead24be5719b57478629b249c702d7a7f3 Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 19 Jun 2018 16:34:23 +0200 Subject: [PATCH 08/22] VBLAST: Wrap up qa test for decoder. --- .../python/digital/qa_vblast_decoder_cc.py | 150 +++++++----------- 1 file changed, 60 insertions(+), 90 deletions(-) diff --git a/gr-digital/python/digital/qa_vblast_decoder_cc.py b/gr-digital/python/digital/qa_vblast_decoder_cc.py index 6adc885ca06..7cf9b3fc085 100755 --- a/gr-digital/python/digital/qa_vblast_decoder_cc.py +++ b/gr-digital/python/digital/qa_vblast_decoder_cc.py @@ -67,18 +67,10 @@ def dice_csi_tags(self, data, type, num_inputs, num_tags, tag_pos): expected_result[tag_pos[i]*num_inputs::] = np.reshape(np.transpose(np.dot(np.linalg.inv(csi), data[::, tag_pos[i]::])), (np.size(data, 0)*(np.size(data,1)-tag_pos[i]))) return tags, expected_result - ''' - 5 tests validating the correct output of the decoder with random input data, ZF equalizer - and 2x2 MIMO scheme. ''' - def test_001_t(self): - # Define test params. - data_length = 20 - repetitions = 5 - num_tags = 4 - num_inputs = 2 - equalizer_type = 'ZF' - + def build_and_run_flowgraph(self, repetitions, data_length, num_inputs_min, num_inputs_max, num_tags, equalizer_type): for i in range(repetitions): + # Generate random number of inputs. + num_inputs = np.random.randint(low=num_inputs_min, high=num_inputs_max+1) # Generate random input data. data = np.random.randn(num_inputs, data_length) + 1j * np.random.randn(num_inputs, data_length) # Generate random tag positions. @@ -87,7 +79,7 @@ def test_001_t(self): tag_pos = np.sort(tag_pos) # Calculate expected result. tags, expected_result = self.dice_csi_tags(data, - 'ZF', + equalizer_type, num_inputs, num_tags, tag_pos) @@ -109,96 +101,74 @@ def test_001_t(self): self.tb.run() # Check if the expected result equals the actual result. - self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 4) + self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 2) + + ''' + 5 tests validating the correct output of the decoder with random input data, ZF equalizer + and 1x1 MIMO scheme. ''' + def test_001_t(self): + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=1, + num_inputs_max=1, + num_tags=4, + equalizer_type='ZF') ''' 5 tests validating the correct output of the decoder with random input data, MMSE equalizer - and 2x2 MIMO scheme and an extremely high SNR regime (1/snr -> 0). ''' + and 1x1 MIMO scheme. ''' def test_002_t(self): - # Define test params. - data_length = 20 - repetitions = 5 - num_tags = 4 - num_inputs = 2 - equalizer_type = 'MMSE' - - for i in range(repetitions): - # Generate random input data. - data = np.random.randn(num_inputs, data_length) + 1j * np.random.randn(num_inputs, data_length) - # Generate random tag positions. - tag_pos = np.random.randint(low=0, high=data_length, size=num_tags) - tag_pos[0] = 0 - tag_pos = np.sort(tag_pos) - # Calculate expected result. - tags, expected_result = self.dice_csi_tags(data, - 'MMSE', - num_inputs, - num_tags, - tag_pos) + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=1, + num_inputs_max=1, + num_tags=4, + equalizer_type='MMSE') - # Build up the test flowgraph. - src = [] - src.append(blocks.vector_source_c(data=data[0], - repeat=False, - tags=tags)) - for n in range(1, num_inputs): - src.append(blocks.vector_source_c(data=data[n], - repeat=False)) - vblast_decoder = digital.vblast_decoder_cc(num_inputs, equalizer_type) - sink = blocks.vector_sink_c() - self.tb.connect(src[0], vblast_decoder, sink) - for n in range(1, num_inputs): - self.tb.connect(src[n], (vblast_decoder, n)) - # Run flowgraph. - self.tb.run() + ''' + 5 tests validating the correct output of the decoder with random input data, ZF equalizer + and 2x2 MIMO scheme. ''' + def test_003_t(self): + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=2, + num_inputs_max=2, + num_tags=4, + equalizer_type='ZF') - # Check if the expected result equals the actual result. - self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 2) + ''' + 5 tests validating the correct output of the decoder with random input data, MMSE equalizer + and 2x2 MIMO scheme and an extremely high SNR regime (1/snr -> 0). ''' + def test_004_t(self): + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=2, + num_inputs_max=2, + num_tags=4, + equalizer_type='MMSE') ''' 5 tests validating the correct output of the decoder with random input data, ZF equalizer and MxM MIMO scheme with M in [3, 16]. ''' - def test_003_t(self): - # Define test params. - data_length = 20 - repetitions = 50 - num_tags = 4 - equalizer_type = 'ZF' - - for i in range(repetitions): - # Generate random number of inputs. - num_inputs = np.random.randint(low=3, high=17) - # Generate random input data. - data = np.random.randn(num_inputs, data_length) + 1j * np.random.randn(num_inputs, data_length) - # Generate random tag positions. - tag_pos = np.random.randint(low=0, high=data_length, size=num_tags) - tag_pos[0] = 0 - tag_pos = np.sort(tag_pos) - # Calculate expected result. - tags, expected_result = self.dice_csi_tags(data, - 'ZF', - num_inputs, - num_tags, - tag_pos) + def test_005_t(self): + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=3, + num_inputs_max=16, + num_tags=4, + equalizer_type='ZF') - # Build up the test flowgraph. - src = [] - src.append(blocks.vector_source_c(data=data[0], - repeat=False, - tags=tags)) - for n in range(1, num_inputs): - src.append(blocks.vector_source_c(data=data[n], - repeat=False)) - vblast_decoder = digital.vblast_decoder_cc(num_inputs, equalizer_type) - sink = blocks.vector_sink_c() - self.tb.connect(src[0], vblast_decoder, sink) - for n in range(1, num_inputs): - self.tb.connect(src[n], (vblast_decoder, n)) - # Run flowgraph. - self.tb.run() + ''' + 5 tests validating the correct output of the decoder with random input data, MMSE equalizer + and MxM MIMO scheme with M in [3, 16]. ''' - # Check if the expected result equals the actual result. - self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 2) + def test_006_t(self): + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=3, + num_inputs_max=16, + num_tags=4, + equalizer_type='MMSE') if __name__ == '__main__': From 4a127c0faf7cf338c96f2c8bcb8569c88cd1f144 Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 20 Jun 2018 10:38:15 +0200 Subject: [PATCH 09/22] VBLAST: Add MxM MMSE case with Eigen pseudo-inverse calculation. --- gr-digital/lib/vblast_decoder_cc_impl.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index b080c55a936..4ba3df95962 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -123,7 +123,22 @@ namespace gr { break; } default: { + // Map CSI 2-dimensional std::vector to Eigen MatrixXcf. + Eigen::MatrixXcf csi_matrix(d_num_inputs, d_num_inputs); + for (int i = 0; i < d_num_inputs; ++i) { + csi_matrix.row(i) = Eigen::VectorXcf::Map(&d_csi[i][0], d_csi[i].size()); + } + // Calculate the pseudo-inverse fo the CSI matrix. + Eigen::MatrixXcf snr_matrix = Eigen::MatrixXcf::Zero(d_num_inputs, d_num_inputs); + for (int j = 0; j < d_num_inputs; ++j) { + snr_matrix.coeffRef(j, j) = (gr_complex)(1.0 / d_snr[j]); + } + Eigen::MatrixXcf csi_pseudo_inverse = (csi_matrix.adjoint()*csi_matrix + snr_matrix).inverse() * csi_matrix.adjoint(); + // Map the inverse of the Eigen MatrixXcf to the 2-dim equalizer std::vector. + for (int i = 0; i < d_num_inputs; ++i) { + Eigen::VectorXcf::Map(&d_mimo_equalizer[i][0], csi_matrix.row(i).size()) = csi_pseudo_inverse.row(i); + } } } } else{ From 896870a261d68ca10edb651251eb5c7114c2ccf1 Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 20 Jun 2018 12:08:45 +0200 Subject: [PATCH 10/22] VBLAST: Add loopback test for ZF and MMSE. --- .../python/digital/qa_vblast_loopback.py | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 gr-digital/python/digital/qa_vblast_loopback.py diff --git a/gr-digital/python/digital/qa_vblast_loopback.py b/gr-digital/python/digital/qa_vblast_loopback.py new file mode 100644 index 00000000000..29e54f5285a --- /dev/null +++ b/gr-digital/python/digital/qa_vblast_loopback.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2018 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 gnuradio import gr, gr_unittest +from gnuradio import blocks +import digital_swig as digital +import numpy as np +import pmt + +class qa_vblast_loopback (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def build_and_run_flowgraph(self, repetitions, data_length, num_inputs_min, num_inputs_max, equalizer_type): + for n in range(repetitions): + num_inputs = np.random.randint(low=num_inputs_min, high=num_inputs_max+1) + # Generate random input data. + data = np.random.randn(data_length*num_inputs) + 1j * np.random.randn(data_length*num_inputs) + + # Randomly generate CSI for one symbol. + csi = (np.random.randn(num_inputs, num_inputs) + 1j * np.random.randn(num_inputs, num_inputs)) + # Assign the CSI vector to a PMT vector. + csi_pmt = pmt.make_vector(num_inputs, pmt.make_c32vector(num_inputs, 1.0)) + for k, rx in enumerate(csi): + line_vector_pmt = pmt.make_c32vector(num_inputs, csi[k][0]) + for l, tx in enumerate(csi[k]): + pmt.c32vector_set(v=line_vector_pmt, k=l, x=csi[k][l]) + pmt.vector_set(csi_pmt, k, line_vector_pmt) + + # Append stream tags with CSI to data stream. + + tags = [(gr.tag_utils.python_to_tag((0, + pmt.string_to_symbol("csi"), + csi_pmt, + pmt.from_long(0))))] + + if equalizer_type == 'MMSE': + # Add an SNR tag at the start of the stream for MMSE. + tags.append(gr.tag_utils.python_to_tag((0, + pmt.string_to_symbol("snr"), + pmt.make_f32vector(num_inputs, 1e8), + pmt.from_long(0)))) + + # Build up the test flowgraph. + src = blocks.vector_source_c(data=data, + repeat=False, + tags=tags) + vblast_encoder = digital.vblast_encoder_cc(num_inputs) + # Simulate channel with matrix multiplication. + channel = blocks.multiply_matrix_cc_make(csi) + vblast_decoder = digital.vblast_decoder_cc(num_inputs, equalizer_type) + sink = blocks.vector_sink_c() + self.tb.connect(src, vblast_encoder) + for n in range(0, num_inputs): + self.tb.connect((vblast_encoder, n), (channel, n)) + self.tb.connect((channel, n), (vblast_decoder, n)) + self.tb.connect(vblast_decoder, sink) + # Run flowgraph. + self.tb.run() + + ''' + Check if the expected result (=the data itself, because + we do a loopback) equals the actual result. ''' + self.assertComplexTuplesAlmostEqual(data, sink.data(), 2) + + ''' + 2 tests validating the correct output of the loopback with random input data, ZF equalizer + and 1x1 MIMO scheme. ''' + + def test_001_t(self): + self.build_and_run_flowgraph(repetitions=2, + data_length=20, + num_inputs_min=1, + num_inputs_max=1, + equalizer_type='ZF') + + ''' + 2 tests validating the correct output of the loopback with random input data, MMSE equalizer + and 1x1 MIMO scheme. ''' + + def test_002_t(self): + self.build_and_run_flowgraph(repetitions=2, + data_length=20, + num_inputs_min=1, + num_inputs_max=1, + equalizer_type='MMSE') + + ''' + 2 tests validating the correct output of the loopback with random input data, ZF equalizer + and 2x2 MIMO scheme. ''' + + def test_003_t(self): + self.build_and_run_flowgraph(repetitions=2, + data_length=20, + num_inputs_min=2, + num_inputs_max=2, + equalizer_type='ZF') + + ''' + 2 tests validating the correct output of the loopback with random input data, MMSE equalizer + and 2x2 MIMO scheme and an extremely high SNR regime (1/snr -> 0). ''' + + def test_004_t(self): + self.build_and_run_flowgraph(repetitions=2, + data_length=20, + num_inputs_min=2, + num_inputs_max=2, + equalizer_type='MMSE') + + ''' + 5 tests validating the correct output of the loopback with random input data, ZF equalizer + and MxM MIMO scheme with M in [3, 16]. ''' + + def test_005_t(self): + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=3, + num_inputs_max=16, + equalizer_type='ZF') + + ''' + 5 tests validating the correct output of the loopback with random input data, MMSE equalizer + and MxM MIMO scheme with M in [3, 16]. ''' + + def test_006_t(self): + self.build_and_run_flowgraph(repetitions=5, + data_length=20, + num_inputs_min=3, + num_inputs_max=16, + equalizer_type='MMSE') + +if __name__ == '__main__': + gr_unittest.run(qa_vblast_loopback, "qa_vblast_loopback.xml") From 6d1fba3af4cd22a5e7e301600c27dff6b151e31a Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 20 Jun 2018 14:18:34 +0200 Subject: [PATCH 11/22] VBLAST: Wrap up code and doxygen docu. --- .../gnuradio/digital/vblast_decoder_cc.h | 20 ++++++--- .../gnuradio/digital/vblast_encoder_cc.h | 12 +++--- gr-digital/lib/vblast_decoder_cc_impl.cc | 2 +- gr-digital/lib/vblast_decoder_cc_impl.h | 35 ++++++++++++++-- gr-digital/lib/vblast_encoder_cc_impl.h | 1 + .../python/digital/qa_vblast_decoder_cc.py | 41 +++++++++---------- .../python/digital/qa_vblast_encoder_cc.py | 3 ++ .../python/digital/qa_vblast_loopback.py | 20 ++++----- 8 files changed, 86 insertions(+), 48 deletions(-) diff --git a/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h b/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h index 862b8165224..9b119d1e710 100644 --- a/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h +++ b/gr-digital/include/gnuradio/digital/vblast_decoder_cc.h @@ -30,11 +30,21 @@ namespace gr { namespace digital { - /*! - * \brief <+description of block+> - * \ingroup digital - * - */ +/*! \brief VBLAST decoder. + * Decodes incoming MxM MIMO data by equalizing the received signals in order to extract the + * M different transmission signals. For the equalization, you can choose between a + * zero forcing (key='ZF') and a minimum mean squared error (key='MMSE') scheme. Both schemes require + * channel state information and MMSE additionally needs SNR information. + * + * The CSI and SNR information is transported via stream tags with key='csi' or 'snr', respectively. + * Initially the CSI is set to 1.0 + 0j for both branches and are updated + * with each incoming CSI. The SNR is initially set to 1.0e6; in this case is the MMSE equalizer equal + * to the ZF equalizer. The CSI and SNR tags are processed separately and they can therefore arrive + * at different positions of the stream and occur in different frequencies. + * + * For 1x1 and 2x2 MIMO schemes, the equalizer is calculated internally. For MxM schemes with M > 2, + * the C++ template library Eigen is used for the linear algebra operations. + */ class DIGITAL_API vblast_decoder_cc : virtual public gr::sync_interpolator { public: diff --git a/gr-digital/include/gnuradio/digital/vblast_encoder_cc.h b/gr-digital/include/gnuradio/digital/vblast_encoder_cc.h index 8b9cdd5b5a2..7acec1be618 100644 --- a/gr-digital/include/gnuradio/digital/vblast_encoder_cc.h +++ b/gr-digital/include/gnuradio/digital/vblast_encoder_cc.h @@ -30,13 +30,11 @@ namespace gr { namespace digital { - /*! - * \brief VBLAST encoder. - * \ingroup digital - * Encodes a serial input data stream into a vertical transmission vector - * which is transmitted at once over multiple antennas. - * - */ +/*! \brief VBLAST encoder. + * Encodes a serial input data stream into a vertical transmission vector + * which is transmitted at once over multiple antennas. + * The functionality equals a multiplexing of the data. + */ class DIGITAL_API vblast_encoder_cc : virtual public gr::sync_decimator { public: diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index 4ba3df95962..7a33d36af1a 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -58,7 +58,7 @@ namespace gr { // Init CSI array and mimo_equalizer. d_csi = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); d_mimo_equalizer = std::vector >(num_inputs, std::vector (num_inputs, 1.0)); - d_snr = std::vector(num_inputs, 1.0); + d_snr = std::vector(num_inputs, 1.0e6); } /* diff --git a/gr-digital/lib/vblast_decoder_cc_impl.h b/gr-digital/lib/vblast_decoder_cc_impl.h index 2e51d3891f0..2aced425097 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.h +++ b/gr-digital/lib/vblast_decoder_cc_impl.h @@ -27,7 +27,21 @@ namespace gr { namespace digital { - +/*! \brief VBLAST decoder. + * Decodes incoming MxM MIMO data by equalizing the received signals in order to extract the + * M different transmission signals. For the equalization, you can choose between a + * zero forcing (key='ZF') and a minimum mean squared error (key='MMSE') scheme. Both schemes require + * channel state information and MMSE additionally needs SNR information. + * + * The CSI and SNR information is transported via stream tags with key='csi' or 'snr', respectively. + * Initially the CSI is set to 1.0 + 0j for both branches and are updated + * with each incoming CSI. The SNR is initially set to 1.0e6; in this case is the MMSE equalizer equal + * to the ZF equalizer. The CSI and SNR tags are processed separately and they can therefore arrive + * at different positions of the stream and occur in different frequencies. + * + * For 1x1 and 2x2 MIMO schemes, the equalizer is calculated internally. For MxM schemes with M > 2, + * the C++ template library Eigen is used for the linear algebra operations. + */ class vblast_decoder_cc_impl : public vblast_decoder_cc { private: @@ -37,13 +51,26 @@ namespace gr { std::vector tags; /*!< Vector that stores the tags in input buffer. */ static const std::string s; /*!< String that matches the key of the CSI tags. */ static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ - pmt::pmt_t d_pmt_csi; std::vector > d_csi; /*!< Current channel matrix. */ - std::vector > d_mimo_equalizer; - std::vector d_snr; + std::vector > d_mimo_equalizer; /*!< Equalizer matrix. + * The left-sided matrix multiplication of the mimo_equalizer with the reception vector produces + * the decoded data vector. + */ + std::vector d_snr; /*!< Vector that stores the current SNR values of the M receivers.*/ + /*!< \brief Equalizes the input data with the d_mimo_equalizer matrix. */ void update_mimo_equalizer(); + /*!< \brief Calculates the new d_mimo_equalizer matrix with the updated CSI or SNR info. + * The equalization schemes zero forcing ('ZF') or minimum mean squared error ('MMSE') can be chosen. + * For 1x1 and 2x2 MIMO schemes, the equalizer is calculated internally. For MxM schemes with M > 2, + * the C++ template library Eigen is used for the linear algebra operations. + * + * @param input Vector of pointers to the input items, one entry per input stream. + * @param out Pointer to the start of unwritten output items. + * @param offset Number of already written input items. The number of already read items per stream is offset/d_num_inputs. + * @param length Number of items of the current symbol in the current buffer. + */ void equalize_symbol(gr_vector_const_void_star input, gr_complex* out, uint32_t offset, uint32_t length); public: diff --git a/gr-digital/lib/vblast_encoder_cc_impl.h b/gr-digital/lib/vblast_encoder_cc_impl.h index 98100819752..9b9a768ec56 100644 --- a/gr-digital/lib/vblast_encoder_cc_impl.h +++ b/gr-digital/lib/vblast_encoder_cc_impl.h @@ -30,6 +30,7 @@ namespace gr { /*! \brief VBLAST encoder. * Encodes a serial input data stream into a vertical transmission vector * which is transmitted at once over multiple antennas. + * The functionality equals a multiplexing of the data. */ class vblast_encoder_cc_impl : public vblast_encoder_cc { diff --git a/gr-digital/python/digital/qa_vblast_decoder_cc.py b/gr-digital/python/digital/qa_vblast_decoder_cc.py index 7cf9b3fc085..87f503517a2 100755 --- a/gr-digital/python/digital/qa_vblast_decoder_cc.py +++ b/gr-digital/python/digital/qa_vblast_decoder_cc.py @@ -87,8 +87,8 @@ def build_and_run_flowgraph(self, repetitions, data_length, num_inputs_min, num_ # Build up the test flowgraph. src = [] src.append(blocks.vector_source_c(data=data[0], - repeat=False, - tags=tags)) + repeat=False, + tags=tags)) for n in range(1, num_inputs): src.append(blocks.vector_source_c(data=data[n], repeat=False)) @@ -104,72 +104,71 @@ def build_and_run_flowgraph(self, repetitions, data_length, num_inputs_min, num_ self.assertComplexTuplesAlmostEqual(expected_result, sink.data(), 2) ''' - 5 tests validating the correct output of the decoder with random input data, ZF equalizer + 2 tests validating the correct output of the decoder with random input data, ZF equalizer and 1x1 MIMO scheme. ''' def test_001_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=1, num_inputs_max=1, num_tags=4, equalizer_type='ZF') ''' - 5 tests validating the correct output of the decoder with random input data, MMSE equalizer + 2 tests validating the correct output of the decoder with random input data, MMSE equalizer and 1x1 MIMO scheme. ''' def test_002_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=1, num_inputs_max=1, num_tags=4, equalizer_type='MMSE') ''' - 5 tests validating the correct output of the decoder with random input data, ZF equalizer + 2 tests validating the correct output of the decoder with random input data, ZF equalizer and 2x2 MIMO scheme. ''' def test_003_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=2, num_inputs_max=2, num_tags=4, equalizer_type='ZF') ''' - 5 tests validating the correct output of the decoder with random input data, MMSE equalizer + 2 tests validating the correct output of the decoder with random input data, MMSE equalizer and 2x2 MIMO scheme and an extremely high SNR regime (1/snr -> 0). ''' def test_004_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=2, num_inputs_max=2, num_tags=4, equalizer_type='MMSE') ''' - 5 tests validating the correct output of the decoder with random input data, ZF equalizer + 2 tests validating the correct output of the decoder with random input data, ZF equalizer and MxM MIMO scheme with M in [3, 16]. ''' def test_005_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=3, num_inputs_max=16, num_tags=4, equalizer_type='ZF') ''' - 5 tests validating the correct output of the decoder with random input data, MMSE equalizer + 2 tests validating the correct output of the decoder with random input data, MMSE equalizer and MxM MIMO scheme with M in [3, 16]. ''' def test_006_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=3, num_inputs_max=16, num_tags=4, equalizer_type='MMSE') - if __name__ == '__main__': gr_unittest.run(qa_vblast_decoder_cc, "qa_vblast_decoder_cc.xml") diff --git a/gr-digital/python/digital/qa_vblast_encoder_cc.py b/gr-digital/python/digital/qa_vblast_encoder_cc.py index 036a0683d2c..36121e0e96f 100755 --- a/gr-digital/python/digital/qa_vblast_encoder_cc.py +++ b/gr-digital/python/digital/qa_vblast_encoder_cc.py @@ -42,6 +42,9 @@ def encode_vblast(self, input, M): output[m] = input[m::M] return output + ''' + 5 tests validating the correct output of the encoder with random input data + and random number of antennas M. ''' def test_001_t (self): # Define test params. data_length = 20 diff --git a/gr-digital/python/digital/qa_vblast_loopback.py b/gr-digital/python/digital/qa_vblast_loopback.py index 29e54f5285a..cecec128804 100644 --- a/gr-digital/python/digital/qa_vblast_loopback.py +++ b/gr-digital/python/digital/qa_vblast_loopback.py @@ -93,7 +93,7 @@ def build_and_run_flowgraph(self, repetitions, data_length, num_inputs_min, num_ def test_001_t(self): self.build_and_run_flowgraph(repetitions=2, - data_length=20, + data_length=10, num_inputs_min=1, num_inputs_max=1, equalizer_type='ZF') @@ -104,7 +104,7 @@ def test_001_t(self): def test_002_t(self): self.build_and_run_flowgraph(repetitions=2, - data_length=20, + data_length=10, num_inputs_min=1, num_inputs_max=1, equalizer_type='MMSE') @@ -115,7 +115,7 @@ def test_002_t(self): def test_003_t(self): self.build_and_run_flowgraph(repetitions=2, - data_length=20, + data_length=10, num_inputs_min=2, num_inputs_max=2, equalizer_type='ZF') @@ -126,29 +126,29 @@ def test_003_t(self): def test_004_t(self): self.build_and_run_flowgraph(repetitions=2, - data_length=20, + data_length=10, num_inputs_min=2, num_inputs_max=2, equalizer_type='MMSE') ''' - 5 tests validating the correct output of the loopback with random input data, ZF equalizer + 2 tests validating the correct output of the loopback with random input data, ZF equalizer and MxM MIMO scheme with M in [3, 16]. ''' def test_005_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=3, num_inputs_max=16, equalizer_type='ZF') ''' - 5 tests validating the correct output of the loopback with random input data, MMSE equalizer + 2 tests validating the correct output of the loopback with random input data, MMSE equalizer and MxM MIMO scheme with M in [3, 16]. ''' def test_006_t(self): - self.build_and_run_flowgraph(repetitions=5, - data_length=20, + self.build_and_run_flowgraph(repetitions=2, + data_length=10, num_inputs_min=3, num_inputs_max=16, equalizer_type='MMSE') From 5e0328ffcdb7c27b195715952a47f07b7f59b8e8 Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 20 Jun 2018 14:58:32 +0200 Subject: [PATCH 12/22] VBLAST: Add xml files for GRC. --- gr-digital/grc/digital_vblast_decoder_cc.xml | 36 ++++++++++++++++++++ gr-digital/grc/digital_vblast_encoder_cc.xml | 7 ++-- 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 gr-digital/grc/digital_vblast_decoder_cc.xml diff --git a/gr-digital/grc/digital_vblast_decoder_cc.xml b/gr-digital/grc/digital_vblast_decoder_cc.xml new file mode 100644 index 00000000000..7a354f03d29 --- /dev/null +++ b/gr-digital/grc/digital_vblast_decoder_cc.xml @@ -0,0 +1,36 @@ + + V-BLAST Decoder + digital_vblast_decoder_cc + [MIMO] + from gnuradio import digital + digital.vblast_decoder_cc($num_inputs, $equalizer_type) + + Number of Inputs + num_inputs + 2 + int + + + Equalizer Type + equalizer_type + "ZF" + string + + + + + in + complex + $num_inputs + + + out + complex + + diff --git a/gr-digital/grc/digital_vblast_encoder_cc.xml b/gr-digital/grc/digital_vblast_encoder_cc.xml index 431ec66abb5..546aa59ad0e 100644 --- a/gr-digital/grc/digital_vblast_encoder_cc.xml +++ b/gr-digital/grc/digital_vblast_encoder_cc.xml @@ -1,5 +1,5 @@ - VBLAST encoder + V-BLAST Encoder digital_vblast_encoder_cc [MIMO] from gnuradio import digital @@ -7,7 +7,8 @@ Number of Outputs num_outputs - raw + 2 + int in @@ -16,6 +17,6 @@ out complex - num_outputs + $num_outputs From a4d1e217d31d3c3443922071c5746ea3ee986988 Mon Sep 17 00:00:00 2001 From: luca Date: Thu, 21 Jun 2018 17:49:26 +0200 Subject: [PATCH 13/22] VBLAST: Find package Eigen3 in CMake and replace Eigen code with runtime error if Eigen3 not found. --- gr-digital/CMakeLists.txt | 11 +++++++++++ gr-digital/lib/vblast_decoder_cc_impl.cc | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/gr-digital/CMakeLists.txt b/gr-digital/CMakeLists.txt index 322b50868f1..fc341b12fd3 100644 --- a/gr-digital/CMakeLists.txt +++ b/gr-digital/CMakeLists.txt @@ -52,6 +52,17 @@ SET(GR_PKG_DIGITAL_EXAMPLES_DIR ${GR_PKG_DATA_DIR}/examples/digital) ######################################################################## if(ENABLE_GR_DIGITAL) +######################################################################## +# Find package Eigen3 (for optional MIMO features) +######################################################################## +find_package(Eigen3) +add_definitions(-DEIGEN3_ENABLED=${Eigen3_FOUND}) +if(Eigen3_FOUND) + message([STATUS] "Eigen3 found.") +else() + message([STATUS] "Eigen3 not found. Eigen3 is required for MxM MIMO schemes with M>2.") +endif(Eigen3_FOUND) + ######################################################################## # Setup CPack components ######################################################################## diff --git a/gr-digital/lib/vblast_decoder_cc_impl.cc b/gr-digital/lib/vblast_decoder_cc_impl.cc index 7a33d36af1a..90752b09e10 100644 --- a/gr-digital/lib/vblast_decoder_cc_impl.cc +++ b/gr-digital/lib/vblast_decoder_cc_impl.cc @@ -28,7 +28,10 @@ #include #include #include "vblast_decoder_cc_impl.h" + +#if (EIGEN3_ENABLED) #include +#endif using namespace boost; @@ -87,6 +90,7 @@ namespace gr { break; } default: { +#if (EIGEN3_ENABLED) // Map CSI 2-dimensional std::vector to Eigen MatrixXcf. Eigen::MatrixXcf csi_matrix(d_num_inputs, d_num_inputs); for (int i = 0; i < d_num_inputs; ++i) { @@ -99,6 +103,9 @@ namespace gr { for (int i = 0; i < d_num_inputs; ++i) { Eigen::VectorXcf::Map(&d_mimo_equalizer[i][0], csi_matrix.row(i).size()) = csi_inverse.row(i); } +#else + throw std::runtime_error("Required library Eigen3 for MxM MIMO schemes with M>2 not installed."); +#endif } } } else if (d_equalizer_type.compare("MMSE") == 0){ @@ -123,6 +130,7 @@ namespace gr { break; } default: { +#if (EIGEN3_ENABLED) // Map CSI 2-dimensional std::vector to Eigen MatrixXcf. Eigen::MatrixXcf csi_matrix(d_num_inputs, d_num_inputs); for (int i = 0; i < d_num_inputs; ++i) { @@ -139,6 +147,9 @@ namespace gr { for (int i = 0; i < d_num_inputs; ++i) { Eigen::VectorXcf::Map(&d_mimo_equalizer[i][0], csi_matrix.row(i).size()) = csi_pseudo_inverse.row(i); } +#else + throw std::runtime_error("Required library Eigen3 for MxM MIMO schemes with M>2 not installed."); +#endif } } } else{ From 25d618489c8a06c1ebe0bed2f1ce6319e80aae8e Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 27 Jun 2018 21:08:58 +0200 Subject: [PATCH 14/22] mimo_channel_est: Init channel estimation C++ block with precalculated 1x1 and 2x2 MIMO scheme. --- .../include/gnuradio/digital/CMakeLists.txt | 3 +- .../digital/mimo_channel_estimator_cc.h | 58 ++++++ gr-digital/lib/CMakeLists.txt | 1 + .../lib/mimo_channel_estimator_cc_impl.cc | 185 ++++++++++++++++++ .../lib/mimo_channel_estimator_cc_impl.h | 66 +++++++ .../digital/qa_mimo_channel_estimator_cc.py | 43 ++++ gr-digital/swig/digital_swig0.i | 3 + 7 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h create mode 100644 gr-digital/lib/mimo_channel_estimator_cc_impl.cc create mode 100644 gr-digital/lib/mimo_channel_estimator_cc_impl.h create mode 100755 gr-digital/python/digital/qa_mimo_channel_estimator_cc.py diff --git a/gr-digital/include/gnuradio/digital/CMakeLists.txt b/gr-digital/include/gnuradio/digital/CMakeLists.txt index d9161bdc42a..e753fa46618 100644 --- a/gr-digital/include/gnuradio/digital/CMakeLists.txt +++ b/gr-digital/include/gnuradio/digital/CMakeLists.txt @@ -123,6 +123,7 @@ install(FILES alamouti_decoder_cc.h diversity_combiner_cc.h vblast_encoder_cc.h - vblast_decoder_cc.h DESTINATION ${GR_INCLUDE_DIR}/gnuradio/digital + vblast_decoder_cc.h + mimo_channel_estimator_cc.h DESTINATION ${GR_INCLUDE_DIR}/gnuradio/digital COMPONENT "digital_devel" ) diff --git a/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h b/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h new file mode 100644 index 00000000000..c08e1b3648e --- /dev/null +++ b/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h @@ -0,0 +1,58 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + + +#ifndef INCLUDED_DIGITAL_MIMO_CHANNEL_ESTIMATOR_CC_H +#define INCLUDED_DIGITAL_MIMO_CHANNEL_ESTIMATOR_CC_H + +#include +#include + +namespace gr { + namespace digital { + + /*! + * \brief <+description of block+> + * \ingroup digital + * + */ + class DIGITAL_API mimo_channel_estimator_cc : virtual public gr::block + { + public: + typedef boost::shared_ptr sptr; + + /*! + * \brief Return a shared_ptr to a new instance of digital::mimo_channel_estimator_cc. + * + * To avoid accidental use of raw pointers, digital::mimo_channel_estimator_cc's + * constructor is in a private implementation + * class. digital::mimo_channel_estimator_cc::make is the public interface for + * creating new instances. + */ + static sptr make(uint16_t num_inputs, std::vector > training_sequence); + }; + + } // namespace digital +} // namespace gr + +#endif /* INCLUDED_DIGITAL_MIMO_CHANNEL_ESTIMATOR_CC_H */ + diff --git a/gr-digital/lib/CMakeLists.txt b/gr-digital/lib/CMakeLists.txt index 97c9276abd8..ca678c31322 100644 --- a/gr-digital/lib/CMakeLists.txt +++ b/gr-digital/lib/CMakeLists.txt @@ -137,6 +137,7 @@ list(APPEND digital_sources diversity_combiner_cc_impl.cc vblast_encoder_cc_impl.cc vblast_decoder_cc_impl.cc + mimo_channel_estimator_cc_impl.cc ) #Add Windows DLL resource file if using MSVC diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc new file mode 100644 index 00000000000..a46daed7c1c --- /dev/null +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc @@ -0,0 +1,185 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "mimo_channel_estimator_cc_impl.h" + +#define _USE_MATH_DEFINES +#include + +#if (EIGEN3_ENABLED) +#include +#endif + +namespace gr { + namespace digital { + + const pmt::pmt_t mimo_channel_estimator_cc_impl::d_key = pmt::string_to_symbol("pilot"); + + mimo_channel_estimator_cc::sptr + mimo_channel_estimator_cc::make(uint16_t num_inputs, std::vector > training_sequence) + { + return gnuradio::get_initial_sptr + (new mimo_channel_estimator_cc_impl(num_inputs, training_sequence)); + } + + /* + * The private constructor + */ + mimo_channel_estimator_cc_impl::mimo_channel_estimator_cc_impl(uint16_t num_inputs, std::vector > training_sequence) + : gr::block("mimo_channel_estimator_cc", + gr::io_signature::make(num_inputs, num_inputs, sizeof(gr_complex)), + gr::io_signature::make(num_inputs, num_inputs, sizeof(gr_complex))), + d_num_inputs(num_inputs), + d_training_sequence(training_sequence) + { + d_training_length = d_training_sequence[0].size(); + } + + /* + * Our virtual destructor. + */ + mimo_channel_estimator_cc_impl::~mimo_channel_estimator_cc_impl() + { + } + + void + mimo_channel_estimator_cc_impl::forecast (int noutput_items, gr_vector_int &ninput_items_required) + { + ninput_items_required[0] = noutput_items; + } + + void + mimo_channel_estimator_cc_impl::copy_symbols(gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items, + uint32_t symbol_length, + uint32_t reading_offset, + uint32_t writing_offset) { + for (int i = 0; i < d_num_inputs; ++i) { + const gr_complex *in = (const gr_complex *) input_items[i]; + gr_complex *out = (gr_complex *) output_items[i]; + memcpy(&out[writing_offset], &in[reading_offset], symbol_length); + } + } + + void + mimo_channel_estimator_cc_impl::estimate_channel(gr_vector_const_void_star &input_items, + uint32_t reading_offset) { + switch (d_num_inputs) { + case 1: { + // Init CSI vector. + std::vector > csi (1, std::vector (1, 0.0)); + // Fill CSI vector with precalculated ML channel estimation. + for (int i = 0; i < d_training_length; ++i) { + csi[0][0] += ((const gr_complex *) input_items[0])[i]; + } + csi[0][0] *= 1./ d_training_length; + break; + } + case 2: { + // Init CSI vector. + std::vector > csi (2, std::vector (2, 0.0)); + // Fill CSI vector with precalculated ML channel estimation. + for (int i = 0; i < d_training_length; ++i) { + csi[0][0] += ((const gr_complex *) input_items[0])[i]; + csi[0][1] += ((const gr_complex *) input_items[0])[i] * (gr_complex) std::polar(1.0, 2*M_PI*i / d_training_length); + csi[1][0] += ((const gr_complex *) input_items[1])[i]; + csi[1][1] += ((const gr_complex *) input_items[1])[i] * (gr_complex) std::polar(1.0, 2*M_PI*i / d_training_length); + } + // Multiply with factor. + // TODO Integrate SNR in factor!!! + csi[0][0] *= M_SQRT2 / d_training_length; + csi[0][1] *= M_SQRT2 / d_training_length; + csi[1][0] *= M_SQRT2 / d_training_length; + csi[1][1] *= M_SQRT2 / d_training_length; + break; + } + default: { + + } + } + } + + int + mimo_channel_estimator_cc_impl::general_work (int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + // Collect all tags of the input buffer with key "csi" in the vector 'tags'. + get_tags_in_window(tags, 0, 0, noutput_items, d_key); + + uint32_t nconsumed = 0; // Number of read items. + uint32_t nwritten = 0; // Number of written items. + uint32_t symbol_length; // Number of items in the current symbol. + + if(tags.size() == 0){ // Input buffer includes no tags at all. + // Handle all samples in buffer as they belong to the current symbol. + symbol_length = noutput_items; + copy_symbols(input_items, output_items, symbol_length, 0, 0); + nconsumed = noutput_items; + nwritten = noutput_items; + } else { // Input buffer includes tags. + if (tags[0].offset - nitems_read(0) > 0){ + /* There are items in the input buffer, before the first tag arrives, ++ * which belong to the previous symbol. */ + symbol_length = tags[0].offset - nitems_read(0); + copy_symbols(input_items, output_items, symbol_length, 0, 0); + nconsumed += symbol_length; + nwritten += symbol_length - d_training_length; + } + // Iterate over tags in buffer. + for (unsigned int i = 0; i < tags.size(); ++i) { + // Calculate the number of items before the next tag. + if (i < tags.size() - 1) { + // This is not the last tag in the buffer. + symbol_length = tags[i + 1].offset - nitems_read(0) - nwritten; + } else { + // This is the last tag in the buffer. + symbol_length = noutput_items - nwritten; + } + // Copy symbols to output. + copy_symbols(input_items, output_items, symbol_length, nconsumed, nwritten); + // Estimate MIMO channel and write CSI tag to stream. + // TODO handel other previous training symbols (Schmidl & Cox) via pilot offset or dumping + estimate_channel(input_items, nconsumed); + + nconsumed += symbol_length; + nwritten += symbol_length - d_training_length; + } + } + + // Tell runtime system how many input items we consumed on + // each input stream. + consume_each (noutput_items); + + // Tell runtime system how many output items we produced. + return noutput_items; + } + + } /* namespace digital */ +} /* namespace gr */ + diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.h b/gr-digital/lib/mimo_channel_estimator_cc_impl.h new file mode 100644 index 00000000000..70c3b234e1b --- /dev/null +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.h @@ -0,0 +1,66 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 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. + */ + +#ifndef INCLUDED_DIGITAL_MIMO_CHANNEL_ESTIMATOR_CC_IMPL_H +#define INCLUDED_DIGITAL_MIMO_CHANNEL_ESTIMATOR_CC_IMPL_H + +#include + +namespace gr { + namespace digital { + + class mimo_channel_estimator_cc_impl : public mimo_channel_estimator_cc + { + private: + uint16_t d_num_inputs; + std::vector > d_training_sequence; + uint16_t d_training_length; + std::vector tags; /*!< Vector that stores the tags in input buffer. */ + static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ + + void copy_symbols(gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items, + uint32_t symbol_length, + uint32_t reading_offset, + uint32_t writing_offset); + + void estimate_channel(gr_vector_const_void_star &input_items, + uint32_t reading_offset); + + public: + mimo_channel_estimator_cc_impl(uint16_t num_inputs, std::vector > training_sequence); + ~mimo_channel_estimator_cc_impl(); + + // Where all the action really happens + void forecast (int noutput_items, gr_vector_int &ninput_items_required); + + int general_work(int noutput_items, + gr_vector_int &ninput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + }; + + } // namespace digital +} // namespace gr + +#endif /* INCLUDED_DIGITAL_MIMO_CHANNEL_ESTIMATOR_CC_IMPL_H */ + diff --git a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py new file mode 100755 index 00000000000..ecac622eab0 --- /dev/null +++ b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2018 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 gnuradio import gr, gr_unittest +from gnuradio import blocks +import digital_swig as digital + +class qa_mimo_channel_estimator_cc (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001_t (self): + # set up fg + self.tb.run () + # check data + + +if __name__ == '__main__': + gr_unittest.run(qa_mimo_channel_estimator_cc, "qa_mimo_channel_estimator_cc.xml") diff --git a/gr-digital/swig/digital_swig0.i b/gr-digital/swig/digital_swig0.i index 7242db2e02d..c93ee070430 100644 --- a/gr-digital/swig/digital_swig0.i +++ b/gr-digital/swig/digital_swig0.i @@ -76,6 +76,7 @@ #include "gnuradio/digital/diversity_combiner_cc.h" #include "gnuradio/digital/vblast_encoder_cc.h" #include "gnuradio/digital/vblast_decoder_cc.h" +#include "gnuradio/digital/mimo_channel_estimator_cc.h" %} %include "gnuradio/digital/binary_slicer_fb.h" @@ -117,6 +118,7 @@ %include "gnuradio/digital/diversity_combiner_cc.h" %include "gnuradio/digital/vblast_encoder_cc.h" %include "gnuradio/digital/vblast_decoder_cc.h" +%include "gnuradio/digital/mimo_channel_estimator_cc.h" GR_SWIG_BLOCK_MAGIC2(digital, binary_slicer_fb); GR_SWIG_BLOCK_MAGIC2(digital, cma_equalizer_cc); @@ -152,6 +154,7 @@ GR_SWIG_BLOCK_MAGIC2(digital, alamouti_decoder_cc); GR_SWIG_BLOCK_MAGIC2(digital, diversity_combiner_cc); GR_SWIG_BLOCK_MAGIC2(digital, vblast_encoder_cc); GR_SWIG_BLOCK_MAGIC2(digital, vblast_decoder_cc); +GR_SWIG_BLOCK_MAGIC2(digital, mimo_channel_estimator_cc); GR_SWIG_BLOCK_MAGIC_FACTORY(digital, cpmmod_bc, gmskmod_bc); From e8995e64668622a8529c21ce1bc6146fe5fb0964 Mon Sep 17 00:00:00 2001 From: luca Date: Mon, 2 Jul 2018 15:02:56 +0200 Subject: [PATCH 15/22] mimo_channel_est: Expand estimation to a general MxN MIMO scheme, using the Eigen library. --- .../digital/mimo_channel_estimator_cc.h | 2 +- .../lib/mimo_channel_estimator_cc_impl.cc | 57 +++++++++++++++---- .../lib/mimo_channel_estimator_cc_impl.h | 6 +- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h b/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h index c08e1b3648e..d2044a90420 100644 --- a/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h +++ b/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h @@ -48,7 +48,7 @@ namespace gr { * class. digital::mimo_channel_estimator_cc::make is the public interface for * creating new instances. */ - static sptr make(uint16_t num_inputs, std::vector > training_sequence); + static sptr make(uint16_t M, uint16_t N, std::vector > training_sequence); }; } // namespace digital diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc index a46daed7c1c..112d85fb48f 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc @@ -26,7 +26,7 @@ #include #include "mimo_channel_estimator_cc_impl.h" - +#include #define _USE_MATH_DEFINES #include @@ -40,21 +40,20 @@ namespace gr { const pmt::pmt_t mimo_channel_estimator_cc_impl::d_key = pmt::string_to_symbol("pilot"); mimo_channel_estimator_cc::sptr - mimo_channel_estimator_cc::make(uint16_t num_inputs, std::vector > training_sequence) + mimo_channel_estimator_cc::make(uint16_t M, uint16_t N, std::vector > training_sequence) { return gnuradio::get_initial_sptr - (new mimo_channel_estimator_cc_impl(num_inputs, training_sequence)); + (new mimo_channel_estimator_cc_impl(M, N, training_sequence)); } /* * The private constructor */ - mimo_channel_estimator_cc_impl::mimo_channel_estimator_cc_impl(uint16_t num_inputs, std::vector > training_sequence) + mimo_channel_estimator_cc_impl::mimo_channel_estimator_cc_impl(uint16_t M, uint16_t N, std::vector > training_sequence) : gr::block("mimo_channel_estimator_cc", - gr::io_signature::make(num_inputs, num_inputs, sizeof(gr_complex)), - gr::io_signature::make(num_inputs, num_inputs, sizeof(gr_complex))), - d_num_inputs(num_inputs), - d_training_sequence(training_sequence) + gr::io_signature::make(N, N, sizeof(gr_complex)), + gr::io_signature::make(N, N, sizeof(gr_complex))), + d_M(M), d_N(N), d_training_sequence(training_sequence) { d_training_length = d_training_sequence[0].size(); } @@ -78,7 +77,7 @@ namespace gr { uint32_t symbol_length, uint32_t reading_offset, uint32_t writing_offset) { - for (int i = 0; i < d_num_inputs; ++i) { + for (int i = 0; i < d_N; ++i) { const gr_complex *in = (const gr_complex *) input_items[i]; gr_complex *out = (gr_complex *) output_items[i]; memcpy(&out[writing_offset], &in[reading_offset], symbol_length); @@ -88,7 +87,7 @@ namespace gr { void mimo_channel_estimator_cc_impl::estimate_channel(gr_vector_const_void_star &input_items, uint32_t reading_offset) { - switch (d_num_inputs) { + switch (d_N) { case 1: { // Init CSI vector. std::vector > csi (1, std::vector (1, 0.0)); @@ -96,6 +95,7 @@ namespace gr { for (int i = 0; i < d_training_length; ++i) { csi[0][0] += ((const gr_complex *) input_items[0])[i]; } + // TODO Integrate SNR in factor!!! csi[0][0] *= 1./ d_training_length; break; } @@ -118,7 +118,30 @@ namespace gr { break; } default: { +#if (EIGEN3_ENABLED) + // TODO calculate pseudo-inverse in constructor + // Map training sequence and received sequence 2-dimensional std::vector to Eigen MatrixXcf. + Eigen::MatrixXcf training_matrix(d_M, d_training_length); + Eigen::MatrixXcf received_training_matrix(d_N, d_training_length); + for (int i = 0; i < d_M; ++i) { + training_matrix.row(i) = Eigen::VectorXcf::Map(&d_training_sequence[i][0], d_training_length); + } + for (int i = 0; i < d_N; ++i) { + received_training_matrix.row(i) = Eigen::VectorXcf::Map((const gr_complex *) input_items[i], d_training_length); + } + // Calculate the Maximum Likelihood estimation for the channel matrix. + Eigen::MatrixXcf csi = received_training_matrix * training_matrix.adjoint() * (training_matrix*training_matrix.adjoint()).inverse(); + + // Map the CSI Eigen MatrixXcf to a 2-dim std::vector. + for (int i = 0; i < d_N; ++i) { + Eigen::VectorXcf::Map(&d_csi[i][0], d_M) = csi.row(i); + } + + +#else + throw std::runtime_error("Required library Eigen3 for MxM MIMO schemes with M>2 not installed."); +#endif } } } @@ -163,10 +186,22 @@ namespace gr { } // Copy symbols to output. copy_symbols(input_items, output_items, symbol_length, nconsumed, nwritten); + // Estimate MIMO channel and write CSI tag to stream. - // TODO handel other previous training symbols (Schmidl & Cox) via pilot offset or dumping estimate_channel(input_items, nconsumed); + // Assign the CSI vector to a PMT vector. + pmt::pmt_t csi_pmt = pmt::make_vector(d_N, pmt::make_c32vector(d_M, d_csi[0][0])); + for (int i = 0; i < d_N; ++i){ + pmt::pmt_t csi_line_vector = pmt::make_c32vector(d_M, d_csi[i][0]); + for (int j = 0; j < d_M; ++j) { + pmt::c32vector_set(csi_line_vector, i, d_csi[i][j]); + } + pmt::vector_set(csi_pmt, i, csi_line_vector); + } + // Append stream tags with CSI to data stream. + add_item_tag(0, nitems_written(0) + nconsumed, pmt::mp("csi"), csi_pmt); +// TODO handel other previous training symbols (Schmidl & Cox) via pilot offset or dumping nconsumed += symbol_length; nwritten += symbol_length - d_training_length; } diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.h b/gr-digital/lib/mimo_channel_estimator_cc_impl.h index 70c3b234e1b..d0491318804 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.h +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.h @@ -31,11 +31,13 @@ namespace gr { class mimo_channel_estimator_cc_impl : public mimo_channel_estimator_cc { private: - uint16_t d_num_inputs; + uint16_t d_M; + uint16_t d_N; std::vector > d_training_sequence; uint16_t d_training_length; std::vector tags; /*!< Vector that stores the tags in input buffer. */ static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ + std::vector > d_csi; void copy_symbols(gr_vector_const_void_star &input_items, gr_vector_void_star &output_items, @@ -47,7 +49,7 @@ namespace gr { uint32_t reading_offset); public: - mimo_channel_estimator_cc_impl(uint16_t num_inputs, std::vector > training_sequence); + mimo_channel_estimator_cc_impl(uint16_t M, uint16_t N, std::vector > training_sequence); ~mimo_channel_estimator_cc_impl(); // Where all the action really happens From 1b2f71b36df43ab3386f48abd9e5d57fa4958f0f Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 3 Jul 2018 13:03:36 +0200 Subject: [PATCH 16/22] mimo_channel_est: Add python qa test for 2x2 scheme. --- .../lib/mimo_channel_estimator_cc_impl.cc | 33 +++++++----- .../digital/qa_mimo_channel_estimator_cc.py | 50 +++++++++++++++++-- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc index 112d85fb48f..44f7324c8ef 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc @@ -34,6 +34,9 @@ #include #endif +#include +using namespace boost; + namespace gr { namespace digital { @@ -56,6 +59,7 @@ namespace gr { d_M(M), d_N(N), d_training_sequence(training_sequence) { d_training_length = d_training_sequence[0].size(); + d_csi = std::vector >(N, std::vector (M, 1.0)); } /* @@ -95,12 +99,11 @@ namespace gr { for (int i = 0; i < d_training_length; ++i) { csi[0][0] += ((const gr_complex *) input_items[0])[i]; } - // TODO Integrate SNR in factor!!! csi[0][0] *= 1./ d_training_length; break; } case 2: { - // Init CSI vector. + // Init csi vector. std::vector > csi (2, std::vector (2, 0.0)); // Fill CSI vector with precalculated ML channel estimation. for (int i = 0; i < d_training_length; ++i) { @@ -110,11 +113,12 @@ namespace gr { csi[1][1] += ((const gr_complex *) input_items[1])[i] * (gr_complex) std::polar(1.0, 2*M_PI*i / d_training_length); } // Multiply with factor. - // TODO Integrate SNR in factor!!! - csi[0][0] *= M_SQRT2 / d_training_length; - csi[0][1] *= M_SQRT2 / d_training_length; - csi[1][0] *= M_SQRT2 / d_training_length; - csi[1][1] *= M_SQRT2 / d_training_length; + csi[0][0] *= 1./ d_training_length; + csi[0][1] *= 1./ d_training_length; + csi[1][0] *= 1./ d_training_length; + csi[1][1] *= 1./ d_training_length; + // Write local vector to class member. + d_csi = csi; break; } default: { @@ -140,7 +144,7 @@ namespace gr { #else - throw std::runtime_error("Required library Eigen3 for MxM MIMO schemes with M>2 not installed."); + throw std::runtime_error("Required library Eigen3 for MxN MIMO schemes with M,N>2 not installed."); #endif } } @@ -159,6 +163,8 @@ namespace gr { uint32_t nwritten = 0; // Number of written items. uint32_t symbol_length; // Number of items in the current symbol. + + if(tags.size() == 0){ // Input buffer includes no tags at all. // Handle all samples in buffer as they belong to the current symbol. symbol_length = noutput_items; @@ -192,15 +198,16 @@ namespace gr { // Assign the CSI vector to a PMT vector. pmt::pmt_t csi_pmt = pmt::make_vector(d_N, pmt::make_c32vector(d_M, d_csi[0][0])); - for (int i = 0; i < d_N; ++i){ - pmt::pmt_t csi_line_vector = pmt::make_c32vector(d_M, d_csi[i][0]); - for (int j = 0; j < d_M; ++j) { - pmt::c32vector_set(csi_line_vector, i, d_csi[i][j]); + for (int n = 0; n < d_N; ++n){ + pmt::pmt_t csi_line_vector = pmt::make_c32vector(d_M, d_csi[n][0]); + for (int m = 0; m < d_M; ++m) { + pmt::c32vector_set(csi_line_vector, m, d_csi[n][m]); } - pmt::vector_set(csi_pmt, i, csi_line_vector); + pmt::vector_set(csi_pmt, n, csi_line_vector); } // Append stream tags with CSI to data stream. add_item_tag(0, nitems_written(0) + nconsumed, pmt::mp("csi"), csi_pmt); + // TODO handel other previous training symbols (Schmidl & Cox) via pilot offset or dumping nconsumed += symbol_length; nwritten += symbol_length - d_training_length; diff --git a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py index ecac622eab0..8cab1d19460 100755 --- a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py +++ b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py @@ -24,6 +24,8 @@ from gnuradio import gr, gr_unittest from gnuradio import blocks import digital_swig as digital +import numpy as np +import pmt class qa_mimo_channel_estimator_cc (gr_unittest.TestCase): @@ -33,10 +35,52 @@ def setUp (self): def tearDown (self): self.tb = None + def produce_training_seq(self, M, length): + seq = np.empty([M, length], dtype=complex) + for (m, i), value in np.ndenumerate(seq): + seq[m][i] = np.exp(-1j*2*np.pi*m*i/length) + return seq + def test_001_t (self): - # set up fg - self.tb.run () - # check data + M = 2 + N = 2 + training_length = 9 + + training_sequence = self.produce_training_seq(M, training_length) + csi = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) + + print 'channel' + print csi + print 'training sequence' + print training_sequence + + # Build flowgraph. + tags = [gr.tag_utils.python_to_tag((0, + pmt.string_to_symbol("pilot"), + pmt.from_long(0), + pmt.from_long(0)))] + src = [blocks.vector_source_c(data=training_sequence[0], + repeat=False, + tags=tags)] + sink = [blocks.tag_debug_make(gr.sizeof_gr_complex, "debug1")] + for m in range(1, M): + src.append(blocks.vector_source_c(training_sequence[m])) + sink.append(blocks.null_sink_make(gr.sizeof_gr_complex)) + channel = blocks.multiply_matrix_cc_make(csi) + estimator = digital.mimo_channel_estimator_cc(M, N, training_sequence) + for m in range(0, M): + self.tb.connect(src[m], (channel, m), (estimator, m), sink[m]) + self.tb.run() + + + estimation = pmt.to_python(sink[0].current_tags()[0].value) + + # Check if the expected result equals the actual result. + for n in range(0, N): + self.assertComplexTuplesAlmostEqual(csi[n], estimation[n], 2) + + + if __name__ == '__main__': From 25a6cc33b5aa8742ed6b99ca65ec81e4d612911e Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 3 Jul 2018 15:49:14 +0200 Subject: [PATCH 17/22] mimo_channel_est: Generalize 1x1 and 2x2 MIMO scheme to 1xN and 2xN. --- .../lib/mimo_channel_estimator_cc_impl.cc | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc index 44f7324c8ef..769fee9cb2d 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc @@ -91,32 +91,37 @@ namespace gr { void mimo_channel_estimator_cc_impl::estimate_channel(gr_vector_const_void_star &input_items, uint32_t reading_offset) { - switch (d_N) { + switch (d_M) { case 1: { // Init CSI vector. - std::vector > csi (1, std::vector (1, 0.0)); + std::vector > csi (d_N, std::vector (1, 0.0)); // Fill CSI vector with precalculated ML channel estimation. - for (int i = 0; i < d_training_length; ++i) { - csi[0][0] += ((const gr_complex *) input_items[0])[i]; + for (int n = 0; n < d_N; ++n) { + const gr_complex* in = (const gr_complex *) input_items[n]; + for (int i = 0; i < d_training_length; ++i) { + csi[n][0] += in[i]; + } + // Multiply elements with factor. + csi[n][0] *= 1./ d_training_length; } - csi[0][0] *= 1./ d_training_length; + // Write local vector to class member. + d_csi = csi; break; } case 2: { // Init csi vector. - std::vector > csi (2, std::vector (2, 0.0)); + std::vector > csi (d_N, std::vector (2, 0.0)); // Fill CSI vector with precalculated ML channel estimation. - for (int i = 0; i < d_training_length; ++i) { - csi[0][0] += ((const gr_complex *) input_items[0])[i]; - csi[0][1] += ((const gr_complex *) input_items[0])[i] * (gr_complex) std::polar(1.0, 2*M_PI*i / d_training_length); - csi[1][0] += ((const gr_complex *) input_items[1])[i]; - csi[1][1] += ((const gr_complex *) input_items[1])[i] * (gr_complex) std::polar(1.0, 2*M_PI*i / d_training_length); + for (int n = 0; n < d_N; ++n) { + const gr_complex* in = (const gr_complex *) input_items[n]; + for (int i = 0; i < d_training_length; ++i) { + csi[n][0] += in[i]; + csi[n][1] += in[i] * (gr_complex) std::polar(1.0, 2 * M_PI * i / d_training_length); + } + // Multiply elements with factor. + csi[n][0] *= 1./ d_training_length; + csi[n][1] *= 1./ d_training_length; } - // Multiply with factor. - csi[0][0] *= 1./ d_training_length; - csi[0][1] *= 1./ d_training_length; - csi[1][0] *= 1./ d_training_length; - csi[1][1] *= 1./ d_training_length; // Write local vector to class member. d_csi = csi; break; From 482e5e4ff8a3164c77372182526705d84653082c Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 3 Jul 2018 16:56:52 +0200 Subject: [PATCH 18/22] mimo_channel_est: Wrap up QA test. --- .../lib/mimo_channel_estimator_cc_impl.cc | 32 ++--- .../digital/qa_mimo_channel_estimator_cc.py | 109 +++++++++++++----- 2 files changed, 96 insertions(+), 45 deletions(-) diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc index 769fee9cb2d..9da538849ff 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc @@ -72,7 +72,9 @@ namespace gr { void mimo_channel_estimator_cc_impl::forecast (int noutput_items, gr_vector_int &ninput_items_required) { - ninput_items_required[0] = noutput_items; + for (int i = 0; i < d_N; ++i) { + ninput_items_required[i] = noutput_items; + } } void @@ -145,9 +147,6 @@ namespace gr { for (int i = 0; i < d_N; ++i) { Eigen::VectorXcf::Map(&d_csi[i][0], d_M) = csi.row(i); } - - - #else throw std::runtime_error("Required library Eigen3 for MxN MIMO schemes with M,N>2 not installed."); #endif @@ -183,24 +182,24 @@ namespace gr { symbol_length = tags[0].offset - nitems_read(0); copy_symbols(input_items, output_items, symbol_length, 0, 0); nconsumed += symbol_length; - nwritten += symbol_length - d_training_length; + nwritten += symbol_length; } // Iterate over tags in buffer. for (unsigned int i = 0; i < tags.size(); ++i) { // Calculate the number of items before the next tag. if (i < tags.size() - 1) { // This is not the last tag in the buffer. - symbol_length = tags[i + 1].offset - nitems_read(0) - nwritten; + symbol_length = tags[i + 1].offset - nitems_read(0) - nconsumed - d_training_length; } else { // This is the last tag in the buffer. - symbol_length = noutput_items - nwritten; + symbol_length = noutput_items - nconsumed - d_training_length; } - // Copy symbols to output. - copy_symbols(input_items, output_items, symbol_length, nconsumed, nwritten); - // Estimate MIMO channel and write CSI tag to stream. estimate_channel(input_items, nconsumed); + // Consume training symbols. + nconsumed += d_training_length; + // Assign the CSI vector to a PMT vector. pmt::pmt_t csi_pmt = pmt::make_vector(d_N, pmt::make_c32vector(d_M, d_csi[0][0])); for (int n = 0; n < d_N; ++n){ @@ -210,21 +209,22 @@ namespace gr { } pmt::vector_set(csi_pmt, n, csi_line_vector); } + + // Copy symbols to output. + copy_symbols(input_items, output_items, symbol_length, nconsumed, nwritten); // Append stream tags with CSI to data stream. - add_item_tag(0, nitems_written(0) + nconsumed, pmt::mp("csi"), csi_pmt); + add_item_tag(0, nitems_written(0) + nwritten, pmt::string_to_symbol(std::string("csi")), csi_pmt); -// TODO handel other previous training symbols (Schmidl & Cox) via pilot offset or dumping nconsumed += symbol_length; - nwritten += symbol_length - d_training_length; + nwritten += symbol_length; } } // Tell runtime system how many input items we consumed on // each input stream. - consume_each (noutput_items); - + consume_each (nconsumed); // Tell runtime system how many output items we produced. - return noutput_items; + return nwritten; } } /* namespace digital */ diff --git a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py index 8cab1d19460..80e0be34873 100755 --- a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py +++ b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py @@ -41,47 +41,98 @@ def produce_training_seq(self, M, length): seq[m][i] = np.exp(-1j*2*np.pi*m*i/length) return seq - def test_001_t (self): - M = 2 - N = 2 - training_length = 9 - - training_sequence = self.produce_training_seq(M, training_length) - csi = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) - - print 'channel' - print csi - print 'training sequence' - print training_sequence - - # Build flowgraph. + def run_flowgraph(self, M, N, training_sequence, channel_matrix): + # Produce tags. tags = [gr.tag_utils.python_to_tag((0, pmt.string_to_symbol("pilot"), pmt.from_long(0), pmt.from_long(0)))] - src = [blocks.vector_source_c(data=training_sequence[0], - repeat=False, - tags=tags)] - sink = [blocks.tag_debug_make(gr.sizeof_gr_complex, "debug1")] + # Build GNU Radio blocks. + data = np.random.randn(1) + 1j * np.random.randn(1) + src = [blocks.vector_source_c(data=np.append(training_sequence[0], data), + repeat=False, + tags=tags)] + sink = [blocks.vector_sink_c_make()] for m in range(1, M): - src.append(blocks.vector_source_c(training_sequence[m])) + src.append(blocks.vector_source_c(np.append(training_sequence[m], data))) + for n in range(1, N): sink.append(blocks.null_sink_make(gr.sizeof_gr_complex)) - channel = blocks.multiply_matrix_cc_make(csi) + channel = blocks.multiply_matrix_cc_make(channel_matrix) estimator = digital.mimo_channel_estimator_cc(M, N, training_sequence) + # Connect everything. for m in range(0, M): - self.tb.connect(src[m], (channel, m), (estimator, m), sink[m]) + self.tb.connect(src[m], (channel, m)) + for n in range(0, N): + self.tb.connect((channel, n), (estimator, n), sink[n]) + # Run flowgraph. self.tb.run() - - - estimation = pmt.to_python(sink[0].current_tags()[0].value) - - # Check if the expected result equals the actual result. + # Read channel estimation and write from pmt to numpy array. + csi = np.empty(shape=[N, M], dtype=complex) for n in range(0, N): - self.assertComplexTuplesAlmostEqual(csi[n], estimation[n], 2) - - + for m in range(0, M): + csi[n][m] = pmt.c32vector_ref(pmt.vector_ref(sink[0].tags()[0].value, n), m) + return csi + ''' + 2 tests validating the correct estimation of the channel coefficients with a random channel + and 1xN MIMO scheme. ''' + def test_001_t (self): + # Test parameters. + M = 1 + repetitions = 2 + for i in range(0, repetitions): + N = np.random.randint(1, 65) + training_length = M + # Produce training sequence and random channel coefficients. + training_sequence = self.produce_training_seq(M, training_length) + channel_matrix = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) + + estimation = self.run_flowgraph(M, N, training_sequence, channel_matrix) + + # Check if the expected result equals the actual result. + for n in range(0, N): + self.assertComplexTuplesAlmostEqual(channel_matrix[n], estimation[n], 2) + + ''' + 2 tests validating the correct estimation of the channel coefficients with a random channel + and 2xN MIMO scheme. ''' + def test_002_t (self): + # Test parameters. + M = 2 + repetitions = 2 + for i in range(0, repetitions): + N = np.random.randint(1, 64) + training_length = M#np.random.randint(M, 5) + # Produce training sequence and random channel coefficients. + training_sequence = self.produce_training_seq(M, training_length) + channel_matrix = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) + + estimation = self.run_flowgraph(M, N, training_sequence, channel_matrix) + + # Check if the expected result equals the actual result. + for n in range(0, N): + self.assertComplexTuplesAlmostEqual(channel_matrix[n], estimation[n], 2) + + ''' + 2 tests validating the correct estimation of the channel coefficients with a random channel + and MxN MIMO scheme (M>2). ''' + def test_003_t (self): + # Test parameters. + M = 3#np.random.randint(5, 6) + repetitions = 2 + for i in range(0, repetitions): + N = np.random.randint(1, 64) + training_length = M#np.random.randint(M, 10) + # Produce training sequence and random channel coefficients. + training_sequence = self.produce_training_seq(M, training_length) + channel_matrix = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) + + estimation = self.run_flowgraph(M, N, training_sequence, channel_matrix) + + # Check if the expected result equals the actual result. + for n in range(0, N): + self.assertComplexTuplesAlmostEqual(channel_matrix[n], estimation[n], 2) if __name__ == '__main__': gr_unittest.run(qa_mimo_channel_estimator_cc, "qa_mimo_channel_estimator_cc.xml") From 6b4cf6464497a6bd794739109453afb11c0653dc Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 10 Jul 2018 11:07:54 +0200 Subject: [PATCH 19/22] mimo_channel_est: Avoid extension of training sequence over multiple buffers. --- .../lib/mimo_channel_estimator_cc_impl.cc | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc index 9da538849ff..039544d8274 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc @@ -60,6 +60,8 @@ namespace gr { { d_training_length = d_training_sequence[0].size(); d_csi = std::vector >(N, std::vector (M, 1.0)); + // Set the minimum size for the input buffer to the training length. + set_min_noutput_items(d_training_length); } /* @@ -189,16 +191,32 @@ namespace gr { // Calculate the number of items before the next tag. if (i < tags.size() - 1) { // This is not the last tag in the buffer. - symbol_length = tags[i + 1].offset - nitems_read(0) - nconsumed - d_training_length; + symbol_length = tags[i + 1].offset - nitems_read(0) - nconsumed; + // Check if the symbol length is smaller than the training length + if (symbol_length < d_training_length){ + // We have to ignore this symbol in this case. + nconsumed += symbol_length; + GR_LOG_INFO(d_logger, format("The symbol length (%d) is smaller than the training length (%d)." + "No estimation possible. Dumping symbol.") + %symbol_length %d_training_length); + continue; + } } else { // This is the last tag in the buffer. - symbol_length = noutput_items - nconsumed - d_training_length; + symbol_length = noutput_items - nconsumed; + // Check if the training sequence of this last symbol is completely in this buffer. + + if (symbol_length < d_training_length){ + // We consume the whole training sequence in the next buffer and don't consume it now. + break; + } } // Estimate MIMO channel and write CSI tag to stream. estimate_channel(input_items, nconsumed); // Consume training symbols. nconsumed += d_training_length; + symbol_length -= d_training_length; // Assign the CSI vector to a PMT vector. pmt::pmt_t csi_pmt = pmt::make_vector(d_N, pmt::make_c32vector(d_M, d_csi[0][0])); @@ -219,9 +237,7 @@ namespace gr { nwritten += symbol_length; } } - - // Tell runtime system how many input items we consumed on - // each input stream. + // Tell runtime system how many input items we consumed on each input stream. consume_each (nconsumed); // Tell runtime system how many output items we produced. return nwritten; From 05f4083526ca3144b9746febc9db214c3863202c Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 10 Jul 2018 11:10:09 +0200 Subject: [PATCH 20/22] mimo_channel_est: Insert head block in qa test to avoid infinite work() loops for terminated input data. --- .../digital/qa_mimo_channel_estimator_cc.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py index 80e0be34873..487bb23d9b4 100755 --- a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py +++ b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py @@ -41,14 +41,14 @@ def produce_training_seq(self, M, length): seq[m][i] = np.exp(-1j*2*np.pi*m*i/length) return seq - def run_flowgraph(self, M, N, training_sequence, channel_matrix): + def run_flowgraph(self, M, N, training_sequence, training_length, channel_matrix): # Produce tags. tags = [gr.tag_utils.python_to_tag((0, pmt.string_to_symbol("pilot"), pmt.from_long(0), pmt.from_long(0)))] # Build GNU Radio blocks. - data = np.random.randn(1) + 1j * np.random.randn(1) + data = np.random.randn(training_length) + 1j * np.random.randn(training_length) src = [blocks.vector_source_c(data=np.append(training_sequence[0], data), repeat=False, tags=tags)] @@ -59,10 +59,12 @@ def run_flowgraph(self, M, N, training_sequence, channel_matrix): sink.append(blocks.null_sink_make(gr.sizeof_gr_complex)) channel = blocks.multiply_matrix_cc_make(channel_matrix) estimator = digital.mimo_channel_estimator_cc(M, N, training_sequence) + head = blocks.head_make(gr.sizeof_gr_complex, training_length) # Connect everything. for m in range(0, M): self.tb.connect(src[m], (channel, m)) - for n in range(0, N): + self.tb.connect((channel, 0), (estimator, 0), head, sink[0]) + for n in range(1, N): self.tb.connect((channel, n), (estimator, n), sink[n]) # Run flowgraph. self.tb.run() @@ -74,7 +76,7 @@ def run_flowgraph(self, M, N, training_sequence, channel_matrix): return csi - ''' + ''' 2 tests validating the correct estimation of the channel coefficients with a random channel and 1xN MIMO scheme. ''' def test_001_t (self): @@ -83,12 +85,12 @@ def test_001_t (self): repetitions = 2 for i in range(0, repetitions): N = np.random.randint(1, 65) - training_length = M + training_length = np.random.randint(M, 65) # Produce training sequence and random channel coefficients. training_sequence = self.produce_training_seq(M, training_length) channel_matrix = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) - estimation = self.run_flowgraph(M, N, training_sequence, channel_matrix) + estimation = self.run_flowgraph(M, N, training_sequence, training_length, channel_matrix) # Check if the expected result equals the actual result. for n in range(0, N): @@ -102,13 +104,13 @@ def test_002_t (self): M = 2 repetitions = 2 for i in range(0, repetitions): - N = np.random.randint(1, 64) - training_length = M#np.random.randint(M, 5) + N = np.random.randint(1, 65) + training_length = np.random.randint(M, 65) # Produce training sequence and random channel coefficients. training_sequence = self.produce_training_seq(M, training_length) channel_matrix = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) - estimation = self.run_flowgraph(M, N, training_sequence, channel_matrix) + estimation = self.run_flowgraph(M, N, training_sequence, training_length, channel_matrix) # Check if the expected result equals the actual result. for n in range(0, N): @@ -119,16 +121,16 @@ def test_002_t (self): and MxN MIMO scheme (M>2). ''' def test_003_t (self): # Test parameters. - M = 3#np.random.randint(5, 6) + M = np.random.randint(3, 65) repetitions = 2 for i in range(0, repetitions): - N = np.random.randint(1, 64) - training_length = M#np.random.randint(M, 10) + N = np.random.randint(1, 65) + training_length = np.random.randint(M, 65) # Produce training sequence and random channel coefficients. training_sequence = self.produce_training_seq(M, training_length) channel_matrix = (np.random.randn(N, M) + 1j * np.random.randn(N, M)) - estimation = self.run_flowgraph(M, N, training_sequence, channel_matrix) + estimation = self.run_flowgraph(M, N, training_sequence, training_length, channel_matrix) # Check if the expected result equals the actual result. for n in range(0, N): From 37a3a63b52ab628295c3057c06c34b8c79e205de Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 11 Jul 2018 15:09:41 +0200 Subject: [PATCH 21/22] mimo_channel_est: Wrap up and docu. --- .../grc/digital_mimo_channel_estimator_cc.xml | 32 +++++++++++++++++++ .../digital/mimo_channel_estimator_cc.h | 21 ++++++++++-- .../lib/mimo_channel_estimator_cc_impl.cc | 20 ++++++------ .../lib/mimo_channel_estimator_cc_impl.h | 30 ++++++++++++++--- .../digital/qa_mimo_channel_estimator_cc.py | 2 ++ 5 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 gr-digital/grc/digital_mimo_channel_estimator_cc.xml diff --git a/gr-digital/grc/digital_mimo_channel_estimator_cc.xml b/gr-digital/grc/digital_mimo_channel_estimator_cc.xml new file mode 100644 index 00000000000..43b9d74c3f3 --- /dev/null +++ b/gr-digital/grc/digital_mimo_channel_estimator_cc.xml @@ -0,0 +1,32 @@ + + MIMO Channel estimator + digital_mimo_channel_estimator_cc + [MIMO] + from gnuraido import digital + digital.mimo_channel_estimator_cc($M, $N, $training_sequence) + + M + M + int + + + N + N + int + + + Training Sequence + training_sequence + raw + + + in + complex + N + + + out + complex + N + + diff --git a/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h b/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h index d2044a90420..e5a66d23c3d 100644 --- a/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h +++ b/gr-digital/include/gnuradio/digital/mimo_channel_estimator_cc.h @@ -30,10 +30,25 @@ namespace gr { namespace digital { - /*! - * \brief <+description of block+> - * \ingroup digital + /*! \brief Estimates a MIMO channel matrix. * + * The block estimates the channel matrix of a MIMO scheme with the help of + * training sequences. The training sequence for each transmitting antenna + * equals one subvector/row of the 2-dimensional vector training_sequence. + * The training sequence should be appended as a pilot to the data at the transmitter. + * The beginning of the pilot must be tagged at the receiver with the key 'pilot'. + * This tag can be set from a preceding sync block, for example. + * + * This block has N inports and N outports. It estimates the channel matrix which each incoming + * 'pilot' tag, dumps the pilot symbol of each stream and passes the rest of the data through without + * changing anything. A tag is set at the beginning of each symbol (= a data sequence between to training sequences) + * with the key 'csi' that contains the estimated MxN channel matrix. + * + * For 1xN and 2xN MIMO schemes, the equalizer is calculated internally. For MxN schemes with M > 2, + * the C++ template library Eigen is used for the linear algebra operations + * and is a requirement in this case. + * The training_length is not bounded above, but the minimum length is M + * (to avoid an underdetermined equation system). */ class DIGITAL_API mimo_channel_estimator_cc : virtual public gr::block { diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc index 039544d8274..3e597ad51ae 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.cc +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.cc @@ -105,7 +105,7 @@ namespace gr { for (int i = 0; i < d_training_length; ++i) { csi[n][0] += in[i]; } - // Multiply elements with factor. + // Normalize elements referring the training length. csi[n][0] *= 1./ d_training_length; } // Write local vector to class member. @@ -122,7 +122,7 @@ namespace gr { csi[n][0] += in[i]; csi[n][1] += in[i] * (gr_complex) std::polar(1.0, 2 * M_PI * i / d_training_length); } - // Multiply elements with factor. + // Normalize elements referring the training length. csi[n][0] *= 1./ d_training_length; csi[n][1] *= 1./ d_training_length; } @@ -169,11 +169,10 @@ namespace gr { uint32_t nwritten = 0; // Number of written items. uint32_t symbol_length; // Number of items in the current symbol. - - if(tags.size() == 0){ // Input buffer includes no tags at all. // Handle all samples in buffer as they belong to the current symbol. symbol_length = noutput_items; + // Copy the data to the output buffer. copy_symbols(input_items, output_items, symbol_length, 0, 0); nconsumed = noutput_items; nwritten = noutput_items; @@ -182,6 +181,7 @@ namespace gr { /* There are items in the input buffer, before the first tag arrives, + * which belong to the previous symbol. */ symbol_length = tags[0].offset - nitems_read(0); + // Copy the data to the output buffer. copy_symbols(input_items, output_items, symbol_length, 0, 0); nconsumed += symbol_length; nwritten += symbol_length; @@ -194,7 +194,7 @@ namespace gr { symbol_length = tags[i + 1].offset - nitems_read(0) - nconsumed; // Check if the symbol length is smaller than the training length if (symbol_length < d_training_length){ - // We have to ignore this symbol in this case. + // We ignore this unfinished training sequence and dump it without any estimation. nconsumed += symbol_length; GR_LOG_INFO(d_logger, format("The symbol length (%d) is smaller than the training length (%d)." "No estimation possible. Dumping symbol.") @@ -205,16 +205,16 @@ namespace gr { // This is the last tag in the buffer. symbol_length = noutput_items - nconsumed; // Check if the training sequence of this last symbol is completely in this buffer. - if (symbol_length < d_training_length){ - // We consume the whole training sequence in the next buffer and don't consume it now. + /* We consume the whole training sequence in the next buffer and don't consume it now + * in order to do the estimation at once. */ break; } } // Estimate MIMO channel and write CSI tag to stream. estimate_channel(input_items, nconsumed); - // Consume training symbols. + // Consume training symbols. We don't copy them to the output buffer. nconsumed += d_training_length; symbol_length -= d_training_length; @@ -228,11 +228,9 @@ namespace gr { pmt::vector_set(csi_pmt, n, csi_line_vector); } - // Copy symbols to output. + // Copy symbols to output and append stream tags with CSI to the beginning of this symbol. copy_symbols(input_items, output_items, symbol_length, nconsumed, nwritten); - // Append stream tags with CSI to data stream. add_item_tag(0, nitems_written(0) + nwritten, pmt::string_to_symbol(std::string("csi")), csi_pmt); - nconsumed += symbol_length; nwritten += symbol_length; } diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.h b/gr-digital/lib/mimo_channel_estimator_cc_impl.h index d0491318804..b0cf783739a 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.h +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.h @@ -27,17 +27,37 @@ namespace gr { namespace digital { - + /*! \brief Estimates a MIMO channel matrix. + * + * The block estimates the channel matrix of a MIMO scheme with the help of + * training sequences. The training sequence for each transmitting antenna + * equals one subvector/row of the 2-dimensional vector training_sequence. + * The training sequence should be appended as a pilot to the data at the transmitter. + * The beginning of the pilot must be tagged at the receiver with the key 'pilot'. + * This tag can be set from a preceding sync block, for example. + * + * This block has N inports and N outports. It estimates the channel matrix which each incoming + * 'pilot' tag, dumps the pilot symbol of each stream and passes the rest of the data through without + * changing anything. A tag is set at the beginning of each symbol (= a data sequence between to training sequences) + * with the key 'csi' that contains the estimated MxN channel matrix. + * + * For 1xN and 2xN MIMO schemes, the equalizer is calculated internally. For MxN schemes with M > 2, + * the C++ template library Eigen is used for the linear algebra operations + * and is a requirement in this case. + * The training_length is not bounded above, but the minimum length is M + * (to avoid an underdetermined equation system). + */ class mimo_channel_estimator_cc_impl : public mimo_channel_estimator_cc { private: - uint16_t d_M; - uint16_t d_N; + uint16_t d_M; /*!< Number of transmitting antennas. */ + uint16_t d_N; /*!< Number of receiving antennas. */ std::vector > d_training_sequence; - uint16_t d_training_length; + /*!< Training matrix: Each subvector/row is sent through one of the M transmit antennas. */ + uint16_t d_training_length; /*!< Length of the training sequence. */ std::vector tags; /*!< Vector that stores the tags in input buffer. */ static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ - std::vector > d_csi; + std::vector > d_csi; /*!< Currently estimated CSI. */ void copy_symbols(gr_vector_const_void_star &input_items, gr_vector_void_star &output_items, diff --git a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py index 487bb23d9b4..cc381ed8c39 100755 --- a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py +++ b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py @@ -57,8 +57,10 @@ def run_flowgraph(self, M, N, training_sequence, training_length, channel_matrix src.append(blocks.vector_source_c(np.append(training_sequence[m], data))) for n in range(1, N): sink.append(blocks.null_sink_make(gr.sizeof_gr_complex)) + # Use a matrix multiplier as a static channel model. channel = blocks.multiply_matrix_cc_make(channel_matrix) estimator = digital.mimo_channel_estimator_cc(M, N, training_sequence) + # Use a head block to terminate the flowgraph. head = blocks.head_make(gr.sizeof_gr_complex, training_length) # Connect everything. for m in range(0, M): From 69ee3e0ef626c7bffd771cbe8445827d2fe0f0e8 Mon Sep 17 00:00:00 2001 From: luca Date: Wed, 8 Aug 2018 17:04:57 +0200 Subject: [PATCH 22/22] mimo_channel_est: Change data type of training_sequence from uint16_t to uint32_t. --- gr-digital/lib/mimo_channel_estimator_cc_impl.h | 2 +- gr-digital/python/digital/qa_mimo_channel_estimator_cc.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gr-digital/lib/mimo_channel_estimator_cc_impl.h b/gr-digital/lib/mimo_channel_estimator_cc_impl.h index b0cf783739a..bfd218a7799 100644 --- a/gr-digital/lib/mimo_channel_estimator_cc_impl.h +++ b/gr-digital/lib/mimo_channel_estimator_cc_impl.h @@ -54,7 +54,7 @@ namespace gr { uint16_t d_N; /*!< Number of receiving antennas. */ std::vector > d_training_sequence; /*!< Training matrix: Each subvector/row is sent through one of the M transmit antennas. */ - uint16_t d_training_length; /*!< Length of the training sequence. */ + uint32_t d_training_length; /*!< Length of the training sequence. */ std::vector tags; /*!< Vector that stores the tags in input buffer. */ static const pmt::pmt_t d_key; /*!< PMT stores the key of the CSI tag. */ std::vector > d_csi; /*!< Currently estimated CSI. */ diff --git a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py index cc381ed8c39..0c3216f6dcb 100755 --- a/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py +++ b/gr-digital/python/digital/qa_mimo_channel_estimator_cc.py @@ -81,7 +81,7 @@ def run_flowgraph(self, M, N, training_sequence, training_length, channel_matrix ''' 2 tests validating the correct estimation of the channel coefficients with a random channel and 1xN MIMO scheme. ''' - def test_001_t (self): + def test_1xN_scheme (self): # Test parameters. M = 1 repetitions = 2 @@ -101,7 +101,7 @@ def test_001_t (self): ''' 2 tests validating the correct estimation of the channel coefficients with a random channel and 2xN MIMO scheme. ''' - def test_002_t (self): + def test_2xN_scheme (self): # Test parameters. M = 2 repetitions = 2 @@ -121,7 +121,7 @@ def test_002_t (self): ''' 2 tests validating the correct estimation of the channel coefficients with a random channel and MxN MIMO scheme (M>2). ''' - def test_003_t (self): + def test_MxN_scheme (self): # Test parameters. M = np.random.randint(3, 65) repetitions = 2