Skip to content
Snippets Groups Projects
Commit d18f99df authored by Glenn Bradford's avatar Glenn Bradford
Browse files

add constellation decoder block

parent 183e3bfc
No related branches found
No related tags found
No related merge requests found
......@@ -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
)
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
......@@ -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
)
/* -*- 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 */
......@@ -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)
......
/* -*- 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 */
/* -*- 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 */
......@@ -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)
......@@ -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
......
/*
* 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));
}
/*
* 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";
......@@ -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
#!/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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment