diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 7bd50626ef9ac1668c7b5c76b8132e59618f230f..43e25723a8be312b24349ae63d471357a71f9f6e 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -13,5 +13,6 @@ install(FILES elen90089_header_format_cdc.block.yml elen90089_packet_phy_tx.block.yml elen90089_packet_mac_tx.block.yml + elen90089_constellation_decoder_cf.block.yml DESTINATION share/gnuradio/grc/blocks ) diff --git a/grc/elen90089_constellation_decoder_cf.block.yml b/grc/elen90089_constellation_decoder_cf.block.yml new file mode 100644 index 0000000000000000000000000000000000000000..b510360a5ce4e3fa85766047a0c0c4e1545c9fb6 --- /dev/null +++ b/grc/elen90089_constellation_decoder_cf.block.yml @@ -0,0 +1,31 @@ +id: elen90089_constellation_decoder_cf +label: CDC Constellation Decoder +category: '[elen90089]' + +templates: + imports: import elen90089 + make: elen90089.constellation_decoder_cf(${bps}, ${soft_decisions}, ${length_tag_name}) + +parameters: +- id: bps + label: Bits per sym + dtype: int +- id: soft_decisions + label: Soft Decisions + default: True + dtype: bool +- id: length_tag_name + label: Length Tag Name + default: '' + dtype: string + +inputs: +- label: in + domain: stream + dtype: complex +outputs: +- label: out + domain: stream + dtype: float + +file_format: 1 diff --git a/include/elen90089/CMakeLists.txt b/include/elen90089/CMakeLists.txt index 01411424550ddb7307cb581024c5b8d59e98eb84..a43d267181d68f69e89d71b393f515f5d2026269 100644 --- a/include/elen90089/CMakeLists.txt +++ b/include/elen90089/CMakeLists.txt @@ -15,5 +15,6 @@ install(FILES moe_symbol_sync_cc.h symbol_mapper_c.h header_format_cdc.h + constellation_decoder_cf.h DESTINATION include/elen90089 ) diff --git a/include/elen90089/constellation_decoder_cf.h b/include/elen90089/constellation_decoder_cf.h new file mode 100644 index 0000000000000000000000000000000000000000..779e6ae624a2c99815969dc8bbfed2100fc81b7c --- /dev/null +++ b/include/elen90089/constellation_decoder_cf.h @@ -0,0 +1,83 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 University of Melbourne. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_ELEN90089_CONSTELLATION_DECODER_CF_H +#define INCLUDED_ELEN90089_CONSTELLATION_DECODER_CF_H + +#include <elen90089/api.h> +#include <gnuradio/sync_interpolator.h> + +namespace gr { +namespace elen90089 { + +/*! + * \brief CDC soft or hard decision constellation decoder + * \ingroup elen90089 + * + * \details + * Decode a constellation's points from a complex space to either hard bits or + * soft bits from soft decision LUT. The default constellation is set by + * the bps parameter in the constructor. The constellation used is updated + * from input stream tags with key 'bps' and value of the bits per symbol of + * the desired constellation. The following constellations are supported: + * + * \li BPSK bps = 1 + * \li QPSK bps = 2 + * \li 8PSK bps = 3 + * \li 16QAM bps = 4 + * + * Both hard and soft decision making is supported. Tags will be propagated + * on the correct sample based on the interpolation factor of the current + * constellation, e.g., the bps. If a length tag name is provided, the value + * of this tag will be scaled by the current bps. + */ +class ELEN90089_API constellation_decoder_cf : virtual public gr::sync_interpolator +{ +public: + typedef std::shared_ptr<constellation_decoder_cf> sptr; + + /*! + * \brief Make a CDC constellation decoder block. + * + * \param bps (int) Bits per symbol of constellation + * \param soft_decisions (bool) Use soft decision making + * \length_tag_name (string) Tag key identifying length of symbol + * blocks + */ + static sptr make(int bps, + bool soft_decisions = true, + const std::string& length_tag_name = ""); + + /*! + * \brief Returns current bits per symbol of constellation. + */ + virtual int bps() const = 0; + + /*! + * \brief Set bits per symbol of constellation. + * + * \param bps (int) Bits per symbol of constellation + */ + virtual void set_bps(int bps) = 0; + + /*! + * \brief Returns whether soft decision making is used. + */ + virtual bool soft_decisions() const = 0; + + /*! + * \brief Set whether soft decision making is used. + * + * \param soft_decisions (bool) Use soft decision making. + */ + virtual void set_soft_decisions(bool soft_decisions) = 0; +}; + +} // namespace elen90089 +} // namespace gr + +#endif /* INCLUDED_ELEN90089_CONSTELLATION_DECODER_CF_H */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 505cbddc305651cced6842d7a1cbb6d1eb7b500a..e6353a612f8d78d98a47f9b7e6cf2cb9a3015eee 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -16,6 +16,7 @@ list(APPEND elen90089_sources moe_symbol_sync_cc_impl.cc symbol_mapper_c_impl.cc header_format_cdc.cc + constellation_decoder_cf_impl.cc ) set(elen90089_sources "${elen90089_sources}" PARENT_SCOPE) diff --git a/lib/constellation_decoder_cf_impl.cc b/lib/constellation_decoder_cf_impl.cc new file mode 100644 index 0000000000000000000000000000000000000000..dc22feea4eb440888965189110706cc196313754 --- /dev/null +++ b/lib/constellation_decoder_cf_impl.cc @@ -0,0 +1,146 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 University of Melbourne. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <gnuradio/io_signature.h> +#include "constellation_decoder_cf_impl.h" + + +namespace gr { +namespace elen90089 { + +constellation_decoder_cf::sptr constellation_decoder_cf::make(int bps, + bool soft_decisions, + const std::string& length_tag_name) +{ + return gnuradio::make_block_sptr<constellation_decoder_cf_impl>( + bps, soft_decisions, length_tag_name); +} + +constellation_decoder_cf_impl::constellation_decoder_cf_impl(int bps, + bool soft_decisions, + const std::string& length_tag_name) + : gr::sync_interpolator("constellation_decoder_cf", + gr::io_signature::make(1, 1, sizeof(gr_complex)), + gr::io_signature::make(1, 1, sizeof(float)), + bps), + d_soft_decisions(soft_decisions), + d_length_tag_key(pmt::PMT_NIL), + d_bps_new(-1) +{ + if(!length_tag_name.empty()) { + d_length_tag_key = pmt::intern(length_tag_name); + } + + // manually propagate tags + set_tag_propagation_policy(TPP_DONT); + + // create constellation maps + d_constel[1] = digital::constellation_bpsk::make(); + d_constel[2] = digital::constellation_calcdist::make( // normalized QPSK + {gr_complex(-1, -1), gr_complex( 1, -1), + gr_complex(-1, 1), gr_complex( 1, 1)}, + {0x0, 0x1, 0x2, 0x3}, 4, 1, + digital::constellation::POWER_NORMALIZATION); + d_constel[3] = digital::constellation_8psk::make(); + d_constel[4] = digital::constellation_16qam::make(); +} + +constellation_decoder_cf_impl::~constellation_decoder_cf_impl() { } + +int constellation_decoder_cf_impl::bps() const +{ + return interpolation(); +} + +void constellation_decoder_cf_impl::set_bps(int bps) +{ + set_interpolation(bps); +} + +bool constellation_decoder_cf_impl::soft_decisions() const +{ + return d_soft_decisions; +} + +void constellation_decoder_cf_impl::set_soft_decisions(bool soft_decisions) +{ + d_soft_decisions = soft_decisions; +} + +int constellation_decoder_cf_impl::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + auto in = static_cast<const gr_complex*>(input_items[0]); + auto out = static_cast<float*>(output_items[0]); + + // update bps from last time as needed + if (d_bps_new > 0) { + set_interpolation(d_bps_new); + d_bps_new = -1; + } + int bps = interpolation(); + int nin = noutput_items / bps; + + // get bps tags in window + std::vector<gr::tag_t> tags; + get_tags_in_window(tags, 0, 0, nin, pmt::mp("bps")); + + // look for change in bps in this window + int nread = nitems_read(0); + for(gr::tag_t tag : tags) { + int value = pmt::to_long(tag.value); + assert(value > 0 && value < 5); + if(value != bps) { + d_bps_new = value; + nin = tag.offset - nread; + break; + } + } + tags.clear(); + + // decoding with current constellation + digital::constellation_sptr constel = d_constel[bps]; + if (d_soft_decisions) { + std::vector<float> bits; + for(int i = 0; i < nin; i++) { + bits = constel->soft_decision_maker(in[i]); + for (size_t j = 0; j < bits.size(); j++) { + out[bps*i + j] = bits[j]; + } + } + } else { // hard decisions + for(int i = 0; i < nin; i++) { + int bits = constel->decision_maker(&in[i]); + for (size_t j = 0; j < bps; j++) { + int bit = 0x1 & (bits >> (bps - j - 1)); + out[bps*i + j] = (float)bit; + } + } + } + + // manually propagate tags in window to safely handle changes in + // interpolation rate (bps) + get_tags_in_window(tags, 0, 0, nin); + int nwritten = nitems_written(0); + for(gr::tag_t tag : tags) { + int offset = bps*(tag.offset - nread) + nwritten; + + pmt::pmt_t value = tag.value; + if(tag.key == d_length_tag_key) { + int length = pmt::to_long(value); + value = pmt::from_long(bps*length); + } + + add_item_tag(0, offset, tag.key, value, tag.srcid); + } + + return bps*nin; +} + +} /* namespace elen90089 */ +} /* namespace gr */ diff --git a/lib/constellation_decoder_cf_impl.h b/lib/constellation_decoder_cf_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..7c8d93aaa63dad2ae3109406c473cd4b73b9b327 --- /dev/null +++ b/lib/constellation_decoder_cf_impl.h @@ -0,0 +1,50 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 University of Melbourne. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_ELEN90089_CONSTELLATION_DECODER_CF_IMPL_H +#define INCLUDED_ELEN90089_CONSTELLATION_DECODER_CF_IMPL_H + +#include <elen90089/constellation_decoder_cf.h> +#include <gnuradio/digital/constellation.h> +#include <pmt/pmt.h> + +namespace gr { +namespace elen90089 { + +class constellation_decoder_cf_impl : public constellation_decoder_cf +{ +private: + bool d_soft_decisions; + pmt::pmt_t d_length_tag_key; + + int d_bps_new; + std::map<int, digital::constellation_sptr> d_constel; + +public: + constellation_decoder_cf_impl(int bps, + bool soft_decisions = true, + const std::string& length_tag_name = ""); + + ~constellation_decoder_cf_impl(); + + int bps() const override; + + void set_bps(int bps) override; + + bool soft_decisions() const override; + + void set_soft_decisions(bool soft_decisions) override; + + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); +}; + +} // namespace elen90089 +} // namespace gr + +#endif /* INCLUDED_ELEN90089_CONSTELLATION_DECODER_CF_IMPL_H */ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index eb5106e643b31c808e9bdee3704037c6cb311451..a429794c9327c34a43dfb52092ab68d6673cf1a9 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -44,3 +44,4 @@ add_custom_target( GR_ADD_TEST(qa_corr_est_cc ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_corr_est_cc.py) GR_ADD_TEST(qa_moe_symbol_sync_cc ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_moe_symbol_sync_cc.py) GR_ADD_TEST(qa_symbol_mapper_c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_symbol_mapper_c.py) +GR_ADD_TEST(qa_constellation_decoder_cf ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_constellation_decoder_cf.py) diff --git a/python/bindings/CMakeLists.txt b/python/bindings/CMakeLists.txt index 95bcf56bf41e4fec2d69113d490e0eee00815ebd..b5b89a83c55c18fb648b72d65b05b5e5d31c773c 100644 --- a/python/bindings/CMakeLists.txt +++ b/python/bindings/CMakeLists.txt @@ -33,6 +33,7 @@ list(APPEND elen90089_python_files moe_symbol_sync_cc_python.cc symbol_mapper_c_python.cc header_format_cdc_python.cc + constellation_decoder_cf_python.cc python_bindings.cc) GR_PYBIND_MAKE_OOT(elen90089 diff --git a/python/bindings/constellation_decoder_cf_python.cc b/python/bindings/constellation_decoder_cf_python.cc new file mode 100644 index 0000000000000000000000000000000000000000..77684fd5dcecdd71682e5a4e8d58789aae9de293 --- /dev/null +++ b/python/bindings/constellation_decoder_cf_python.cc @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(0) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(constellation_decoder_cf.h) */ +/* BINDTOOL_HEADER_FILE_HASH(6676402190669fcc53daed62a6ba41cc) */ +/***********************************************************************************/ + +#include <pybind11/complex.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + +namespace py = pybind11; + +#include <elen90089/constellation_decoder_cf.h> +// pydoc.h is automatically generated in the build directory +#include <constellation_decoder_cf_pydoc.h> + +void bind_constellation_decoder_cf(py::module& m) +{ + using constellation_decoder_cf = ::gr::elen90089::constellation_decoder_cf; + + py::class_<constellation_decoder_cf, + gr::sync_interpolator, + std::shared_ptr<constellation_decoder_cf>>(m, "constellation_decoder_cf", D(constellation_decoder_cf)) + + .def(py::init(&constellation_decoder_cf::make), + py::arg("bps"), + py::arg("soft_decisions") = true, + py::arg("length_tag_name") = "", + D(constellation_decoder_cf,make)) + + .def("bps", + &constellation_decoder_cf::bps, + D(constellation_decoder_cf, bps)) + + .def("set_bps", + &constellation_decoder_cf::set_bps, + py::arg("bps"), + D(constellation_decoder_cf, set_bps)) + + .def("soft_decisions", + &constellation_decoder_cf::soft_decisions, + D(constellation_decoder_cf, soft_decisions)) + + .def("set_soft_decisions", + &constellation_decoder_cf::set_soft_decisions, + py::arg("soft_decisions"), + D(constellation_decoder_cf, set_soft_decisions)); +} diff --git a/python/bindings/docstrings/constellation_decoder_cf_pydoc_template.h b/python/bindings/docstrings/constellation_decoder_cf_pydoc_template.h new file mode 100644 index 0000000000000000000000000000000000000000..8e2509b324bed38074185a0429798b46d3f8c6f3 --- /dev/null +++ b/python/bindings/docstrings/constellation_decoder_cf_pydoc_template.h @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr,elen90089, __VA_ARGS__ ) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ + + + + static const char *__doc_gr_elen90089_constellation_decoder_cf = R"doc()doc"; + + + static const char *__doc_gr_elen90089_constellation_decoder_cf_constellation_decoder_cf_0 = R"doc()doc"; + + + static const char *__doc_gr_elen90089_constellation_decoder_cf_constellation_decoder_cf_1 = R"doc()doc"; + + + static const char *__doc_gr_elen90089_constellation_decoder_cf_make = R"doc()doc"; + + + static const char *__doc_gr_elen90089_constellation_decoder_cf_bps = R"doc()doc"; + + + static const char *__doc_gr_elen90089_constellation_decoder_cf_set_bps = R"doc()doc"; + + + static const char *__doc_gr_elen90089_constellation_decoder_cf_soft_decisions = R"doc()doc"; + + + static const char *__doc_gr_elen90089_constellation_decoder_cf_set_soft_decisions = R"doc()doc"; + + diff --git a/python/bindings/python_bindings.cc b/python/bindings/python_bindings.cc index d46dcdfb7afb3b71c81cfa25552e3060e5807ed1..05ab0ceb79ab46242dbb907dc22920580d4342ee 100644 --- a/python/bindings/python_bindings.cc +++ b/python/bindings/python_bindings.cc @@ -25,6 +25,7 @@ namespace py = pybind11; void bind_moe_symbol_sync_cc(py::module& m); void bind_symbol_mapper_c(py::module& m); void bind_header_format_cdc(py::module& m); + void bind_constellation_decoder_cf(py::module& m); // ) END BINDING_FUNCTION_PROTOTYPES @@ -58,5 +59,6 @@ PYBIND11_MODULE(elen90089_python, m) bind_moe_symbol_sync_cc(m); bind_symbol_mapper_c(m); bind_header_format_cdc(m); + bind_constellation_decoder_cf(m); // ) END BINDING_FUNCTION_CALLS -} +} \ No newline at end of file diff --git a/python/qa_constellation_decoder_cf.py b/python/qa_constellation_decoder_cf.py new file mode 100755 index 0000000000000000000000000000000000000000..768fde1a857c781c3c4eb93b4558f4c3b022adaf --- /dev/null +++ b/python/qa_constellation_decoder_cf.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2022 University of Melbourne. +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from gnuradio import gr, gr_unittest +from gnuradio import blocks, digital +import pmt +import numpy as np +import time +try: + from elen90089 import constellation_decoder_cf +except ImportError: + import os + import sys + dirname, filename = os.path.split(os.path.abspath(__file__)) + sys.path.append(os.path.join(dirname, "bindings")) + from elen90089 import constellation_decoder_cf + + +def modulate_u8vector(cnst, vec): + bits = np.unpackbits(vec) + bps = cnst.bits_per_symbol() + + symb = [] + for ii in range(0, len(bits), bps): + x = np.flip(bits[ii:ii+bps]) + s = np.packbits(x, bitorder='little')[0] + symb.append(cnst.points()[s]) + + return np.array(symb, dtype=np.complex) + +class qa_constellation_decoder_cf(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_instance(self): + instance = constellation_decoder_cf(1, True, "payload symbols") + + def test_001_constel_change(self): + # test parameters + default_bps = 3 + soft_decisions = True + length_tag = "payload symbols" + nbytes = 16 + constel = [digital.constellation_calcdist( # normalized qpsk + [-1-1j, 1-1j, -1+1j, 1+1j], + [0, 1, 2, 3], 4, 1, + digital.constellation.normalization.POWER_NORMALIZATION), + digital.constellation_bpsk(), + digital.constellation_16qam()] + + # set up flowgraph + src = blocks.pdu_to_tagged_stream(blocks.complex_t, length_tag) + dec = constellation_decoder_cf(default_bps, soft_decisions, length_tag) + dst = blocks.vector_sink_f() + self.tb.connect(src, dec, dst) + dbg = blocks.tag_debug(gr.sizeof_float, "", length_tag) + dbg.set_save_all(True) + dbg.set_display(False) + self.tb.connect(dec, dbg) + + # create test pdus + data = np.array([], dtype=np.uint8) + for c in constel: + # create modulated vector + d = np.random.randint(256, size=nbytes, dtype=np.uint8) + x = modulate_u8vector(c, d) + # convert to pdu + bps = pmt.from_long(c.bits_per_symbol()) + meta = pmt.make_dict() + meta = pmt.dict_add(meta, pmt.intern('bps'), bps) + pdu = pmt.cons(meta, pmt.init_c32vector(len(x), x)) + # pass to flowgraph + src.to_basic_block()._post(pmt.intern('pdus'), pdu) + data = np.concatenate((data, d)) + + # run fg + self.tb.start() + time.sleep(1) + self.tb.stop() + + # check soft bits + results = np.array(dst.data()) + vec = np.packbits(results > 0) + self.assertTupleEqual(tuple(data), tuple(vec)) + + # check length tag is updated + tags = dbg.current_tags() + self.assertEqual(len(tags), len(constel)) + offset = 0 + for tag in tags: + value = pmt.to_python(tag.value) + self.assertEqual(offset, tag.offset) + self.assertEqual(8*nbytes, value) + offset += value + + +if __name__ == '__main__': + gr_unittest.run(qa_constellation_decoder_cf)