diff --git a/blocklib/digital/chunks_to_symbols/.gitignore b/blocklib/digital/chunks_to_symbols/.gitignore new file mode 100644 index 00000000..25d053a5 --- /dev/null +++ b/blocklib/digital/chunks_to_symbols/.gitignore @@ -0,0 +1 @@ +meson.build \ No newline at end of file diff --git a/blocklib/digital/chunks_to_symbols/chunks_to_symbols.yml b/blocklib/digital/chunks_to_symbols/chunks_to_symbols.yml new file mode 100644 index 00000000..285ab41b --- /dev/null +++ b/blocklib/digital/chunks_to_symbols/chunks_to_symbols.yml @@ -0,0 +1,68 @@ +module: digital +block: chunks_to_symbols +label: Chunks to Symbols +blocktype: block + +doc: + brief: |- + Map a stream of unpacked symbol indexes to stream of + float or complex constellation points in D dimensions (D = 1 by + default) + detail: |- + \li input: stream of IN_T + \li output: stream of OUT_T + \li out[n D + k] = symbol_table[in[n] D + k], k=0,1,...,D-1 + The combination of gr::blocks::packed_to_unpacked_XX followed + by gr::digital::chunks_to_symbols_XY handles the general case + of mapping from a stream of bytes or shorts into arbitrary + float or complex symbols. + +typekeys: + - id: IN_T + type: class + options: + - ru8 + - ru16 + - ru32 + - id: OUT_T + type: class + options: + - cf32 + - rf32 + +parameters: +- id: symbol_table + label: Symbol Table + dtype: OUT_T + settable: true + container: vector +- id: D + label: Dimension + dtype: size_t + settable: false + default: 1 + +# - id: num_ports +# label: Num Ports +# dtype: size_t +# default: 1 +# grc: +# hide: part + +# Example Ports +ports: +- domain: stream + id: in + direction: input + type: typekeys/IN_T + +- domain: stream + id: out + direction: output + type: typekeys/OUT_T + +implementations: +- id: cpu +# - id: cuda + +file_format: 1 \ No newline at end of file diff --git a/blocklib/digital/chunks_to_symbols/chunks_to_symbols_cpu.cc b/blocklib/digital/chunks_to_symbols/chunks_to_symbols_cpu.cc new file mode 100644 index 00000000..94d3859c --- /dev/null +++ b/blocklib/digital/chunks_to_symbols/chunks_to_symbols_cpu.cc @@ -0,0 +1,70 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 FIXME + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "chunks_to_symbols_cpu.h" +#include "chunks_to_symbols_cpu_gen.h" + +namespace gr { +namespace digital { + +template +chunks_to_symbols_cpu::chunks_to_symbols_cpu( + const typename chunks_to_symbols::block_args& args) + : INHERITED_CONSTRUCTORS(IN_T, OUT_T) +{ + this->set_output_multiple(args.D); +} + +template +work_return_code_t +chunks_to_symbols_cpu::work(std::vector& work_input, + std::vector& work_output) +{ + auto in = work_input[0]->items(); + auto out = work_output[0]->items(); + + auto noutput = work_output[0]->n_items; + auto ninput = work_input[0]->n_items; + + auto d_D = pmtf::get_as(*this->param_D); + + // number of inputs to consume + auto in_count = std::min(ninput, noutput / d_D); + if (in_count < 1) { + return work_return_code_t::WORK_INSUFFICIENT_OUTPUT_ITEMS; + } + + auto d_symbol_table = pmtf::get_as>(*this->param_symbol_table); + + if (d_D == 1) { + for (size_t i = 0; i < in_count; i++) { + auto key = static_cast(*in); + *out = d_symbol_table[key]; + ++out; + ++in; + } + } + else { // the multi-dimensional case + for (size_t i = 0; i < in_count; i++) { + auto key = static_cast(*in) * d_D; + for (size_t idx = 0; idx < d_D; ++idx) { + *out = d_symbol_table[key + idx]; + ++out; + } + ++in; + } + } + + this->consume_each(in_count, work_input); + this->produce_each(in_count * d_D, work_output); + return work_return_code_t::WORK_OK; +} +} // namespace digital +} // namespace gr diff --git a/blocklib/digital/chunks_to_symbols/chunks_to_symbols_cpu.h b/blocklib/digital/chunks_to_symbols/chunks_to_symbols_cpu.h new file mode 100644 index 00000000..bfc1ec1c --- /dev/null +++ b/blocklib/digital/chunks_to_symbols/chunks_to_symbols_cpu.h @@ -0,0 +1,33 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 FIXME + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +namespace gr { +namespace digital { + +template +class chunks_to_symbols_cpu : public chunks_to_symbols +{ +public: + chunks_to_symbols_cpu(const typename chunks_to_symbols::block_args& args); + + virtual work_return_code_t work(std::vector& work_input, + std::vector& work_output) override; + +private: + // Declare private variables here +}; + + +} // namespace digital +} // namespace gr diff --git a/blocklib/digital/crc_append/.gitignore b/blocklib/digital/crc_append/.gitignore new file mode 100644 index 00000000..25d053a5 --- /dev/null +++ b/blocklib/digital/crc_append/.gitignore @@ -0,0 +1 @@ +meson.build \ No newline at end of file diff --git a/blocklib/digital/crc_append/crc_append.yml b/blocklib/digital/crc_append/crc_append.yml new file mode 100644 index 00000000..0f451367 --- /dev/null +++ b/blocklib/digital/crc_append/crc_append.yml @@ -0,0 +1,61 @@ +module: digital +block: crc_append +label: CRC Append +blocktype: block + +parameters: +- id: num_bits + label: CRC Size (bits) + dtype: size_t + grc: + default: 32 +- id: poly + label: CRC Polynomial + dtype: uint64_t + grc: + default: 0x4C11DB7 +- id: initial_value + label: Initial Register Value + dtype: uint64_t + grc: + default: 0xFFFFFFFF +- id: final_xor + label: Final XOR Value + dtype: uint64_t + grc: + default: 0xFFFFFFFF +- id: input_reflected + label: LSB-first Input + dtype: bool + grc: + default: True +- id: result_reflected + label: LSB-first Result + dtype: bool + grc: + default: True +- id: swap_endianness + label: LSB CRC in PDU + dtype: bool + grc: + default: False +- id: skip_header_bytes + label: Header Bytes to Skip + dtype: size_t + default: 0 + +ports: +- domain: message + id: in + direction: input + +- domain: message + id: out + direction: output + optional: true + +implementations: +- id: cpu +# - id: cuda + +file_format: 1 diff --git a/blocklib/digital/crc_append/crc_append_cpu.cc b/blocklib/digital/crc_append/crc_append_cpu.cc new file mode 100644 index 00000000..5e250482 --- /dev/null +++ b/blocklib/digital/crc_append/crc_append_cpu.cc @@ -0,0 +1,36 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 FIXME + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "crc_append_cpu.h" +#include "crc_append_cpu_gen.h" + +namespace gr { +namespace digital { + +crc_append_cpu::crc_append_cpu(block_args args) + : INHERITED_CONSTRUCTORS, + d_num_bits(args.num_bits), + d_swap_endianness(args.swap_endianness), + d_crc(kernel::digital::crc(args.num_bits, + args.poly, + args.initial_value, + args.final_xor, + args.input_reflected, + args.result_reflected)), + d_header_bytes(args.skip_header_bytes) +{ + if (args.num_bits % 8 != 0) { + throw std::runtime_error("CRC number of bits must be divisible by 8"); + } +} + + +} // namespace digital +} // namespace gr \ No newline at end of file diff --git a/blocklib/digital/crc_append/crc_append_cpu.h b/blocklib/digital/crc_append/crc_append_cpu.h new file mode 100644 index 00000000..59a439aa --- /dev/null +++ b/blocklib/digital/crc_append/crc_append_cpu.h @@ -0,0 +1,70 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 FIXME + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +#include +#include + +#include + +namespace gr { +namespace digital { + +class crc_append_cpu : public virtual crc_append +{ +public: + crc_append_cpu(block_args args); + +private: + unsigned d_num_bits; + bool d_swap_endianness; + kernel::digital::crc d_crc; + unsigned d_header_bytes; + + virtual void handle_msg_in(pmtf::pmt msg) override + { + auto meta = pmtf::get_as>( + pmtf::map(msg)["meta"]); + auto samples = pmtf::get_as>( + pmtf::map(msg)["data"]); + + const auto size = samples.size(); + if (size <= d_header_bytes) { + d_logger->warn("PDU too short; dropping"); + return; + } + + uint64_t crc = d_crc.compute(&samples[d_header_bytes], size - d_header_bytes); + + unsigned num_bytes = d_num_bits / 8; + if (d_swap_endianness) { + for (unsigned i = 0; i < num_bytes; ++i) { + samples.push_back(crc & 0xff); + crc >>= 8; + } + } + else { + for (unsigned i = 0; i < num_bytes; ++i) { + samples.push_back((crc >> (d_num_bits - 8 * (i + 1))) & 0xff); + } + } + + meta["packet_len"] = pmtf::get_as(meta["packet_len"]) + num_bytes; + auto pdu = pmtf::map({ { "data", samples }, { "meta", meta } }); + + this->get_message_port("out")->post(pdu); + } +}; + +} // namespace digital +} // namespace gr \ No newline at end of file diff --git a/blocklib/digital/examples/ofdm_tx.grc b/blocklib/digital/examples/ofdm_tx.grc new file mode 100644 index 00000000..83426fa5 --- /dev/null +++ b/blocklib/digital/examples/ofdm_tx.grc @@ -0,0 +1,308 @@ +options: + parameters: + author: josh + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: '' + gen_cmake: 'On' + gen_linking: dynamic + generate_options: no_gui + hier_block_src_path: '.:' + id: ofdm_tx + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: run + sizing_mode: fixed + thread_safe_setters: '' + title: Not titled yet + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: header_mod + id: variable + parameters: + comment: '' + value: digitalk.constellation_bpsk() + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [496, 36.0] + rotation: 0 + state: true +- name: samp_rate + id: variable + parameters: + comment: '' + value: '32000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [184, 12] + rotation: 0 + state: enabled +- name: blocks_message_debug_0 + id: blocks_message_debug + parameters: + affinity: '' + alias: '' + comment: '' + en_uvec: 'False' + impl: cpu + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + showports: 'False' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1024, 44.0] + rotation: 0 + state: disabled +- name: blocks_vector_source_0 + id: blocks_vector_source + parameters: + T: float + affinity: '' + alias: '' + comment: '' + data: numpy.random.randint(0, 255, 96*10) + impl: cpu + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + repeat: 'False' + showports: 'False' + tags: '[]' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [248, 300.0] + rotation: 0 + state: true +- name: digital_crc_append_0 + id: digital_crc_append + parameters: + affinity: '' + alias: '' + comment: '' + final_xor: '4294967295' + impl: cpu + initial_value: '4294967295' + input_reflected: 'True' + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + num_bits: '32' + poly: '79764919' + result_reflected: 'True' + showports: 'False' + skip_header_bytes: '0' + swap_endianness: 'False' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [728, 36.0] + rotation: 0 + state: disabled +- name: import_0 + id: import + parameters: + alias: '' + comment: '' + imports: import numpy + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [304, 36.0] + rotation: 0 + state: true +- name: import_0_0 + id: import + parameters: + alias: '' + comment: '' + imports: from gnuradio.kernel import digital as digitalk + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [312, 108.0] + rotation: 0 + state: true +- name: math_multiply_const_0 + id: math_multiply_const + parameters: + T: float + affinity: '' + alias: '' + comment: '' + impl: cpu + k: '1.0' + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'True' + showports: 'False' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 316.0] + rotation: 0 + state: true +- name: math_multiply_const_0_0 + id: math_multiply_const + parameters: + T: float + affinity: '' + alias: '' + comment: '' + impl: cpu + k: '1.0' + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + showports: 'False' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [760, 452.0] + rotation: 0 + state: true +- name: pdu_pdu_to_stream_0 + id: pdu_pdu_to_stream + parameters: + T: float + affinity: '' + alias: '' + comment: '' + impl: cpu + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + showports: 'False' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1040, 332.0] + rotation: 0 + state: true +- name: pdu_stream_to_pdu_0 + id: pdu_stream_to_pdu + parameters: + T: float + affinity: '' + alias: '' + comment: '' + impl: cpu + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + packet_len: '96' + showports: 'False' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [512, 324.0] + rotation: 0 + state: true +- name: snippet_0 + id: snippet + parameters: + alias: '' + code: 'from matplotlib import pyplot as plt + + + plt.plot(fg.snk1.data()) + + plt.plot(fg.snk2.data(), ''r:'') + + plt.show()' + comment: '' + priority: '0' + section: main_after_stop + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1384, 116.0] + rotation: 0 + state: true +- name: snk1 + id: blocks_vector_sink + parameters: + T: float + affinity: '' + alias: '' + comment: '' + impl: cpu + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + reserve_items: '1024' + showports: 'False' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1248, 436.0] + rotation: 0 + state: true +- name: snk2 + id: blocks_vector_sink + parameters: + T: float + affinity: '' + alias: '' + comment: '' + impl: cpu + maxoutbuf: '0' + minoutbuf: '0' + msg_work: 'False' + reserve_items: '1024' + showports: 'False' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1256, 316.0] + rotation: 0 + state: true + +connections: +- [blocks_vector_source_0, '0', math_multiply_const_0_0, '0'] +- [blocks_vector_source_0, '0', pdu_stream_to_pdu_0, '0'] +- [math_multiply_const_0, pdus_out, pdu_pdu_to_stream_0, pdus] +- [math_multiply_const_0_0, '0', snk1, '0'] +- [pdu_pdu_to_stream_0, '0', snk2, '0'] +- [pdu_stream_to_pdu_0, pdus, math_multiply_const_0, pdus_in] + +metadata: + file_format: 1 diff --git a/blocklib/digital/examples/ofdm_tx.py b/blocklib/digital/examples/ofdm_tx.py new file mode 100755 index 00000000..bce8cce2 --- /dev/null +++ b/blocklib/digital/examples/ofdm_tx.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# +# SPDX-License-Identifier: GPL-3.0 +# +# GNU Radio Python Flow Graph +# Title: Not titled yet +# Author: josh +# GNU Radio version: 0.2.0 + +from gnuradio import blocks +from gnuradio import gr +#from gnuradio.filter import firdes +#from gnuradio.fft import window +import sys +import signal +from argparse import ArgumentParser +#from gnuradio.eng_arg import eng_float, intx +#from gnuradio import eng_notation +from gnuradio import math +from gnuradio import pdu +from gnuradio.kernel import digital as digitalk +import numpy + + +def snipfcn_snippet_0(fg, rt=None): + from matplotlib import pyplot as plt + + plt.plot(fg.snk1.data()) + plt.plot(fg.snk2.data(), 'r:') + plt.show() + + +def snippets_main_after_stop(fg, rt=None): + snipfcn_snippet_0(fg, rt) + + +class ofdm_tx(gr.flowgraph): + + def __init__(self): + gr.flowgraph.__init__(self, "Not titled yet") + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate = 32000 + self.header_mod = header_mod = digitalk.constellation_bpsk() + + ################################################## + # Blocks + ################################################## + self.snk2 = blocks.vector_sink_f( + 1,1024, impl=blocks.vector_sink_f.cpu) + self.snk1 = blocks.vector_sink_f( + 1,1024, impl=blocks.vector_sink_f.cpu) + self.pdu_stream_to_pdu_0 = pdu.stream_to_pdu_f( + 96, impl=pdu.stream_to_pdu_f.cpu) + self.pdu_pdu_to_stream_0 = pdu.pdu_to_stream_f( + impl=pdu.pdu_to_stream_f.cpu) + self.math_multiply_const_0_0 = math.multiply_const_ff( + 1.0,1, impl=math.multiply_const_ff.cpu) + self.math_multiply_const_0 = math.multiply_const_ff( + 1.0,1, impl=math.multiply_const_ff.cpu) + self.math_multiply_const_0.set_work_mode(gr.work_mode_t.PDU) + self.blocks_vector_source_0 = blocks.vector_source_f( + numpy.random.randint(0, 255, 96*10),False,1,[], impl=blocks.vector_source_f.cpu) + + + + ################################################## + # Connections + ################################################## + self.connect((self.blocks_vector_source_0, 0), (self.math_multiply_const_0_0, 0)) + self.connect((self.blocks_vector_source_0, 0), (self.pdu_stream_to_pdu_0, 0)) + self.connect((self.math_multiply_const_0_0, 0), (self.snk1, 0)) + self.connect((self.pdu_pdu_to_stream_0, 0), (self.snk2, 0)) + self.msg_connect((self.math_multiply_const_0, 'pdus_out'), (self.pdu_pdu_to_stream_0, 'pdus')) + self.msg_connect((self.pdu_stream_to_pdu_0, 'pdus'), (self.math_multiply_const_0, 'pdus_in')) + + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + + def get_header_mod(self): + return self.header_mod + + def set_header_mod(self, header_mod): + self.header_mod = header_mod + + + + +def main(flowgraph_cls=ofdm_tx, options=None): + fg = flowgraph_cls() + rt = gr.runtime() + + + rt.initialize(fg) + + rt.start() + + try: + rt.wait() + except KeyboardInterrupt: + rt.stop() + rt.wait() + snippets_main_after_stop(fg, rt) + +if __name__ == '__main__': + main() diff --git a/blocklib/digital/meson.build b/blocklib/digital/meson.build index fa60cdb5..3a12bf45 100644 --- a/blocklib/digital/meson.build +++ b/blocklib/digital/meson.build @@ -1,19 +1,29 @@ +#autogenerated +# This file has been automatically generated and will be overwritten by meson build +# Remove the #autogenerated comment at the top if you wish for the build scripts to leave +# this file alone + subdir('include/gnuradio/digital') digital_sources = [] digital_cu_sources = [] +digital_pure_python_sources = [] digital_pybind_sources = [] digital_pybind_names = [] digital_deps = [] + + # Individual block subdirectories +subdir('crc_append') +subdir('chunks_to_symbols') subdir('lib') -# if (get_option('enable_python')) -# subdir('python/digital') -# endif +if (get_option('enable_python')) + subdir('python/digital') +endif -# if (get_option('enable_testing')) -# subdir('test') -# endif \ No newline at end of file +if (get_option('enable_testing')) + subdir('test') +endif \ No newline at end of file diff --git a/blocklib/digital/test/meson.build b/blocklib/digital/test/meson.build index 7939bfe1..eaaa839f 100644 --- a/blocklib/digital/test/meson.build +++ b/blocklib/digital/test/meson.build @@ -3,7 +3,7 @@ ################################################### if get_option('enable_testing') - # test('qa_agc', find_program('qa_agc.py'), env: TEST_ENV) + test('qa_chunks_to_symbols', find_program('qa_chunks_to_symbols.py'), env: TEST_ENV) # if (cuda_available and get_option('enable_cuda')) # test('qa_cufft', find_program('qa_cufft.py'), env: TEST_ENV) # endif diff --git a/blocklib/digital/test/qa_chunks_to_symbols.py b/blocklib/digital/test/qa_chunks_to_symbols.py new file mode 100644 index 00000000..68f0edd7 --- /dev/null +++ b/blocklib/digital/test/qa_chunks_to_symbols.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# + + +# import pmt +from gnuradio import gr, gr_unittest, digital, blocks + + +class test_chunks_to_symbols(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_bc_001(self): + const = [1 + 0j, 0 + 1j, + -1 + 0j, 0 - 1j] + src_data = (0, 1, 2, 3, 3, 2, 1, 0) + expected_result = [1 + 0j, 0 + 1j, -1 + 0j, 0 - 1j, + 0 - 1j, -1 + 0j, 0 + 1j, 1 + 0j] + + src = blocks.vector_source_b(src_data) + op = digital.chunks_to_symbols_bc(const) + + dst = blocks.vector_sink_c() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_bf_002(self): + const = [-3, -1, 1, 3] + src_data = (0, 1, 2, 3, 3, 2, 1, 0) + expected_result = [-3, -1, 1, 3, + 3, 1, -1, -3] + + src = blocks.vector_source_b(src_data) + op = digital.chunks_to_symbols_bf(const) + + dst = blocks.vector_sink_f() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_bf_2d(self): + maxval = 4 + dimensions = 2 + const = list(range(maxval * dimensions)) + src_data = [v * 13 % maxval for v in range(maxval)] + expected_result = [] + for data in src_data: + for i in range(dimensions): + expected_result += [const[data * dimensions + i]] + + self.assertEqual(len(src_data) * dimensions, len(expected_result)) + src = blocks.vector_source_b(src_data) + op = digital.chunks_to_symbols_bf(const, dimensions) + + dst = blocks.vector_sink_f() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_bf_3d(self): + maxval = 48 + dimensions = 3 + const = list(range(maxval * dimensions)) + src_data = [v * 13 % maxval for v in range(maxval)] + expected_result = [] + for data in src_data: + for i in range(dimensions): + expected_result += [const[data * dimensions + i]] + + self.assertEqual(len(src_data) * dimensions, len(expected_result)) + src = blocks.vector_source_b(src_data) + op = digital.chunks_to_symbols_bf(const, dimensions) + + dst = blocks.vector_sink_f() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_ic_003(self): + const = [1 + 0j, 0 + 1j, + -1 + 0j, 0 - 1j] + src_data = (0, 1, 2, 3, 3, 2, 1, 0) + expected_result = [1 + 0j, 0 + 1j, -1 + 0j, 0 - 1j, + 0 - 1j, -1 + 0j, 0 + 1j, 1 + 0j] + + src = blocks.vector_source_i(src_data) + op = digital.chunks_to_symbols_ic(const) + + dst = blocks.vector_sink_c() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_if_004(self): + const = [-3, -1, 1, 3] + src_data = (0, 1, 2, 3, 3, 2, 1, 0) + expected_result = [-3, -1, 1, 3, + 3, 1, -1, -3] + + src = blocks.vector_source_i(src_data) + op = digital.chunks_to_symbols_if(const) + + dst = blocks.vector_sink_f() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_sc_005(self): + const = [1 + 0j, 0 + 1j, + -1 + 0j, 0 - 1j] + src_data = (0, 1, 2, 3, 3, 2, 1, 0) + expected_result = [1 + 0j, 0 + 1j, -1 + 0j, 0 - 1j, + 0 - 1j, -1 + 0j, 0 + 1j, 1 + 0j] + + src = blocks.vector_source_s(src_data) + op = digital.chunks_to_symbols_sc(const) + + dst = blocks.vector_sink_c() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_sf_006(self): + const = [-3, -1, 1, 3] + src_data = (0, 1, 2, 3, 3, 2, 1, 0) + expected_result = [-3, -1, 1, 3, + 3, 1, -1, -3] + + src = blocks.vector_source_s(src_data) + op = digital.chunks_to_symbols_sf(const) + + dst = blocks.vector_sink_f() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_sf_callback(self): + constA = [-3, -1, 1, 3] + constB = [12, -12, 6, -6] + src_data = [0, 1, 2, 3, 3, 2, 1, 0] + expected_result = [12, -12, 6, -6, -6, 6, -12, 12] + + src = blocks.vector_source_s(src_data, False, 1, []) + op = digital.chunks_to_symbols_sf(constA) + op.set_symbol_table(constB) + dst = blocks.vector_sink_f() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + def test_sc_callback(self): + constA = [-3.0 + 1j, -1.0 - 1j, 1.0 + 1j, 3 - 1j] + constB = [12.0 + 1j, -12.0 - 1j, 6.0 + 1j, -6 - 1j] + src_data = [0, 1, 2, 3, 3, 2, 1, 0] + expected_result = [12.0 + 1j, -12.0 - 1j, 6.0 + + 1j, -6 - 1j, -6 - 1j, 6 + 1j, -12 - 1j, 12 + 1j] + + src = blocks.vector_source_s(src_data, False, 1, []) + op = digital.chunks_to_symbols_sc(constA) + op.set_symbol_table(constB) + dst = blocks.vector_sink_c() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + actual_result = dst.data() + self.assertEqual(expected_result, actual_result) + + # def test_sf_tag(self): + # constA = [-3.0, -1.0, 1.0, 3] + # constB = [12.0, -12.0, 6.0, -6] + # src_data = (0, 1, 2, 3, 3, 2, 1, 0) + # expected_result = [-3, -1, 1, 3, + # -6, 6, -12, 12] + # first_tag = gr.tag_t() + # first_tag.key = pmt.intern("set_symbol_table") + # first_tag.value = pmt.init_f32vector(len(constA), constA) + # first_tag.offset = 0 + # second_tag = gr.tag_t() + # second_tag.key = pmt.intern("set_symbol_table") + # second_tag.value = pmt.init_f32vector(len(constB), constB) + # second_tag.offset = 4 + + # src = blocks.vector_source_s( + # src_data, False, 1, [ + # first_tag, second_tag]) + # op = digital.chunks_to_symbols_sf(constB) + + # dst = blocks.vector_sink_f() + # self.tb.connect(src, op) + # self.tb.connect(op, dst) + # self.tb.run() + + # actual_result = dst.data() + # self.assertEqual(expected_result, actual_result) + + # def test_sc_tag(self): + # constA = [-3.0 + 1j, -1.0 - 1j, 1.0 + 1j, 3 - 1j] + # constB = [12.0 + 1j, -12.0 - 1j, 6.0 + 1j, -6 - 1j] + # src_data = (0, 1, 2, 3, 3, 2, 1, 0) + # expected_result = [-3 + 1j, -1 - 1j, 1 + 1j, 3 - 1j, + # -6 - 1j, 6 + 1j, -12 - 1j, 12 + 1j] + # first_tag = gr.tag_t() + # first_tag.key = pmt.intern("set_symbol_table") + # first_tag.value = pmt.init_c32vector(len(constA), constA) + # first_tag.offset = 0 + # second_tag = gr.tag_t() + # second_tag.key = pmt.intern("set_symbol_table") + # second_tag.value = pmt.init_c32vector(len(constB), constB) + # second_tag.offset = 4 + + # src = blocks.vector_source_s( + # src_data, False, 1, [ + # first_tag, second_tag]) + # op = digital.chunks_to_symbols_sc(constB) + + # dst = blocks.vector_sink_c() + # self.tb.connect(src, op) + # self.tb.connect(op, dst) + # self.tb.run() + + # actual_result = dst.data() + # self.assertEqual(expected_result, actual_result) + + +if __name__ == '__main__': + gr_unittest.run(test_chunks_to_symbols) diff --git a/blocklib/meson.build b/blocklib/meson.build index 2796b17b..dca6e601 100644 --- a/blocklib/meson.build +++ b/blocklib/meson.build @@ -40,4 +40,6 @@ if (ENABLE_GR_SOAPY) endif if get_option('enable_gr_audio') subdir('audio') -endif \ No newline at end of file +endif + +subdir('pdu') \ No newline at end of file diff --git a/blocklib/pdu/.gitignore b/blocklib/pdu/.gitignore new file mode 100644 index 00000000..25d053a5 --- /dev/null +++ b/blocklib/pdu/.gitignore @@ -0,0 +1 @@ +meson.build \ No newline at end of file diff --git a/blocklib/pdu/include/gnuradio/pdu/.gitignore b/blocklib/pdu/include/gnuradio/pdu/.gitignore new file mode 100644 index 00000000..01ecb66f --- /dev/null +++ b/blocklib/pdu/include/gnuradio/pdu/.gitignore @@ -0,0 +1 @@ +!meson.build \ No newline at end of file diff --git a/blocklib/pdu/include/gnuradio/pdu/meson.build b/blocklib/pdu/include/gnuradio/pdu/meson.build new file mode 100644 index 00000000..e0f0f14a --- /dev/null +++ b/blocklib/pdu/include/gnuradio/pdu/meson.build @@ -0,0 +1,3 @@ +headers = [] + +install_headers(headers, subdir : 'gnuradio/pdu') \ No newline at end of file diff --git a/blocklib/pdu/lib/.gitignore b/blocklib/pdu/lib/.gitignore new file mode 100644 index 00000000..01ecb66f --- /dev/null +++ b/blocklib/pdu/lib/.gitignore @@ -0,0 +1 @@ +!meson.build \ No newline at end of file diff --git a/blocklib/pdu/lib/meson.build b/blocklib/pdu/lib/meson.build new file mode 100644 index 00000000..e215e8de --- /dev/null +++ b/blocklib/pdu/lib/meson.build @@ -0,0 +1,35 @@ +pdu_sources += [] +pdu_deps += [gnuradio_gr_dep, volk_dep, fmt_dep, pmtf_dep, python3_embed_dep] + +block_cpp_args = ['-DHAVE_CPU'] + +# if cuda_dep.found() and get_option('enable_cuda') +# block_cpp_args += '-DHAVE_CUDA' + +# gnuradio_blocklib_pdu_cu = library('gnuradio-blocklib-pdu-cu', +# pdu_cu_sources, +# include_directories : incdir, +# install : true, +# dependencies : [cuda_dep]) + +# gnuradio_blocklib_pdu_cu_dep = declare_dependency(include_directories : incdir, +# link_with : gnuradio_blocklib_pdu_cu, +# dependencies : cuda_dep) + +# pdu_deps += [gnuradio_blocklib_pdu_cu_dep, cuda_dep] + +# endif + +incdir = include_directories(['../include/gnuradio/pdu','../include']) +gnuradio_blocklib_pdu_lib = library('gnuradio-blocklib-pdu', + pdu_sources, + include_directories : incdir, + install : true, + link_language: 'cpp', + dependencies : pdu_deps, + link_args : ['-lgnuradio-runtime'], + cpp_args : block_cpp_args) + +gnuradio_blocklib_pdu_dep = declare_dependency(include_directories : incdir, + link_with : gnuradio_blocklib_pdu_lib, + dependencies : pdu_deps) diff --git a/blocklib/pdu/pdu_to_stream/.gitignore b/blocklib/pdu/pdu_to_stream/.gitignore new file mode 100644 index 00000000..25d053a5 --- /dev/null +++ b/blocklib/pdu/pdu_to_stream/.gitignore @@ -0,0 +1 @@ +meson.build \ No newline at end of file diff --git a/blocklib/pdu/pdu_to_stream/pdu_to_stream.yml b/blocklib/pdu/pdu_to_stream/pdu_to_stream.yml new file mode 100644 index 00000000..cddc37e7 --- /dev/null +++ b/blocklib/pdu/pdu_to_stream/pdu_to_stream.yml @@ -0,0 +1,31 @@ +module: pdu +block: pdu_to_stream +label: PDU to Stream +blocktype: sync_block + +typekeys: + - id: T + type: class + options: + - cf32 + - rf32 + - ru32 + - ru16 + - ru8 + +ports: + - domain: message + id: pdus + direction: input + optional: true + + - domain: stream + id: in + direction: output + type: typekeys/T + +implementations: + - id: cpu +# - id: cuda + +file_format: 1 diff --git a/blocklib/pdu/pdu_to_stream/pdu_to_stream_cpu.cc b/blocklib/pdu/pdu_to_stream/pdu_to_stream_cpu.cc new file mode 100644 index 00000000..e0fe5786 --- /dev/null +++ b/blocklib/pdu/pdu_to_stream/pdu_to_stream_cpu.cc @@ -0,0 +1,72 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 Josh Morman + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "pdu_to_stream_cpu.h" +#include "pdu_to_stream_cpu_gen.h" + +namespace gr { +namespace pdu { + +template +pdu_to_stream_cpu::pdu_to_stream_cpu(const typename pdu_to_stream::block_args& args) + : INHERITED_CONSTRUCTORS(T) +{ +} + +template +work_return_code_t +pdu_to_stream_cpu::work(std::vector& work_input, + std::vector& work_output) +{ + auto out = work_output[0]->items(); + auto noutput_items = work_output[0]->n_items; + + // fill up the output buffer with the data from the pdus + size_t i = 0; + while (i < noutput_items) { + if (!d_vec_ready && !d_pmt_queue.empty()) { + auto data = pmtf::map(d_pmt_queue.front())["data"]; + d_vec = pmtf::vector(data); + d_pmt_queue.pop(); + d_vec_idx = 0; + d_vec_ready = true; + } + + if (d_vec_ready) { + auto num_in_this_pmt = std::min(noutput_items - i, d_vec.size() - d_vec_idx); + + std::copy(d_vec.data() + d_vec_idx, + d_vec.data() + d_vec_idx + num_in_this_pmt, + out + i); + i += num_in_this_pmt; + d_vec_idx += num_in_this_pmt; + + if (d_vec_idx >= d_vec.size()) { + d_vec_ready = false; + } + } + else { + break; + } + } + + this->produce_each(i, work_output); + return work_return_code_t::WORK_OK; +} + +template +void pdu_to_stream_cpu::handle_msg_pdus(pmtf::pmt msg) +{ + d_pmt_queue.push(msg); + this->notify_scheduler_output(); +} + +} /* namespace pdu */ +} /* namespace gr */ diff --git a/blocklib/pdu/pdu_to_stream/pdu_to_stream_cpu.h b/blocklib/pdu/pdu_to_stream/pdu_to_stream_cpu.h new file mode 100644 index 00000000..d7a23f7b --- /dev/null +++ b/blocklib/pdu/pdu_to_stream/pdu_to_stream_cpu.h @@ -0,0 +1,41 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 Josh Morman + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include +#include + +#include + +namespace gr { +namespace pdu { + +template +class pdu_to_stream_cpu : public pdu_to_stream +{ +public: + pdu_to_stream_cpu(const typename pdu_to_stream::block_args& args); + + virtual work_return_code_t work(std::vector& work_input, + std::vector& work_output) override; + +private: + std::queue d_pmt_queue; + pmtf::vector d_vec; + bool d_vec_ready = false; + size_t d_vec_idx = 0; + + void handle_msg_pdus(pmtf::pmt msg) override; +}; + + +} // namespace pdu +} // namespace gr diff --git a/blocklib/pdu/python/pdu/.gitignore b/blocklib/pdu/python/pdu/.gitignore new file mode 100644 index 00000000..25d053a5 --- /dev/null +++ b/blocklib/pdu/python/pdu/.gitignore @@ -0,0 +1 @@ +meson.build \ No newline at end of file diff --git a/blocklib/pdu/python/pdu/__init__.py b/blocklib/pdu/python/pdu/__init__.py new file mode 100644 index 00000000..fb5f9269 --- /dev/null +++ b/blocklib/pdu/python/pdu/__init__.py @@ -0,0 +1,9 @@ + +import os + +try: + from .pdu_python import * +except ImportError: + dirname, filename = os.path.split(os.path.abspath(__file__)) + __path__.append(os.path.join(dirname, "bindings")) + from .pdu_python import * diff --git a/blocklib/pdu/stream_to_pdu/.gitignore b/blocklib/pdu/stream_to_pdu/.gitignore new file mode 100644 index 00000000..25d053a5 --- /dev/null +++ b/blocklib/pdu/stream_to_pdu/.gitignore @@ -0,0 +1 @@ +meson.build \ No newline at end of file diff --git a/blocklib/pdu/stream_to_pdu/stream_to_pdu.yml b/blocklib/pdu/stream_to_pdu/stream_to_pdu.yml new file mode 100644 index 00000000..b1db535b --- /dev/null +++ b/blocklib/pdu/stream_to_pdu/stream_to_pdu.yml @@ -0,0 +1,40 @@ +module: pdu +block: stream_to_pdu +label: Stream to PDU +blocktype: sync_block + +typekeys: + - id: T + type: class + options: + - cf32 + - rf32 + - ru32 + - ru16 + - ru8 + +# Example Parameters +parameters: +- id: packet_len + label: Packet Length + dtype: size_t + grc: + default: 96 + +# Example Ports +ports: +- domain: stream + id: in + direction: input + type: typekeys/T + +- domain: message + id: pdus + direction: output + optional: true + +implementations: +- id: cpu +# - id: cuda + +file_format: 1 \ No newline at end of file diff --git a/blocklib/pdu/stream_to_pdu/stream_to_pdu_cpu.cc b/blocklib/pdu/stream_to_pdu/stream_to_pdu_cpu.cc new file mode 100644 index 00000000..535c08c3 --- /dev/null +++ b/blocklib/pdu/stream_to_pdu/stream_to_pdu_cpu.cc @@ -0,0 +1,50 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 Josh Morman + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "stream_to_pdu_cpu.h" +#include "stream_to_pdu_cpu_gen.h" + +namespace gr { +namespace pdu { + +template +stream_to_pdu_cpu::stream_to_pdu_cpu(const typename stream_to_pdu::block_args& args) + : INHERITED_CONSTRUCTORS(T), d_packet_len(args.packet_len) +{ + this->set_output_multiple(args.packet_len); +} + +template +work_return_code_t +stream_to_pdu_cpu::work(std::vector& work_input, + std::vector& work_output) +{ + + auto n_pdu = work_input[0]->n_items / d_packet_len; + auto in = work_input[0]->items(); + + for (size_t n = 0; n < n_pdu; n++) { + auto samples = + pmtf::vector(in + n * d_packet_len, in + (n + 1) * d_packet_len); + auto d = pmtf::map({ + { "packet_len", d_packet_len }, + }); + + auto pdu = pmtf::map({ { "data", samples }, { "meta", d } }); + + this->get_message_port("pdus")->post(pdu); + } + + this->consume_each(n_pdu * d_packet_len, work_input); + return work_return_code_t::WORK_OK; +} + +} /* namespace pdu */ +} /* namespace gr */ diff --git a/blocklib/pdu/stream_to_pdu/stream_to_pdu_cpu.h b/blocklib/pdu/stream_to_pdu/stream_to_pdu_cpu.h new file mode 100644 index 00000000..81a87e5c --- /dev/null +++ b/blocklib/pdu/stream_to_pdu/stream_to_pdu_cpu.h @@ -0,0 +1,33 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 Josh Morman + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +namespace gr { +namespace pdu { + +template +class stream_to_pdu_cpu : public stream_to_pdu +{ +public: + stream_to_pdu_cpu(const typename stream_to_pdu::block_args& args); + + virtual work_return_code_t work(std::vector& work_input, + std::vector& work_output) override; + +private: + size_t d_packet_len; +}; + + +} // namespace pdu +} // namespace gr diff --git a/blocklib/pdu/test/.gitignore b/blocklib/pdu/test/.gitignore new file mode 100644 index 00000000..01ecb66f --- /dev/null +++ b/blocklib/pdu/test/.gitignore @@ -0,0 +1 @@ +!meson.build \ No newline at end of file diff --git a/blocklib/pdu/test/meson.build b/blocklib/pdu/test/meson.build new file mode 100644 index 00000000..3b9163af --- /dev/null +++ b/blocklib/pdu/test/meson.build @@ -0,0 +1,7 @@ +################################################### +# QA +################################################### + +if get_option('enable_testing') + +endif diff --git a/gr/include/gnuradio/block.h b/gr/include/gnuradio/block.h index 2d109a9d..fed41fef 100644 --- a/gr/include/gnuradio/block.h +++ b/gr/include/gnuradio/block.h @@ -1,10 +1,10 @@ #pragma once #include +#include #include #include #include -#include #include #include @@ -18,6 +18,8 @@ namespace gr { +enum class block_work_mode_t { DEFAULT, PDU }; + class pyblock_detail; /** * @brief The abstract base class for all signal processing blocks in the GR @@ -45,9 +47,12 @@ class GR_RUNTIME_API block : public gr::node std::map d_param_str_map; std::map d_str_param_map; message_port_sptr _msg_param_update; + message_port_sptr _msg_work; + message_port_sptr _msg_work_out; message_port_sptr _msg_system; std::shared_ptr d_pyblock_detail; bool d_finished = false; + block_work_mode_t _work_mode = block_work_mode_t::DEFAULT; void notify_scheduler(); void notify_scheduler_input(); @@ -87,8 +92,8 @@ class GR_RUNTIME_API block : public gr::node { throw std::runtime_error("work function has been called but not implemented"); } - using work_t = std::function&, - std::vector&)>; + using work_t = std::function&, std::vector&)>; /** * @brief Wrapper for work to perform special checks and take care of special * cases for certain types of blocks, e.g. sync_block, decim_block @@ -124,7 +129,8 @@ class GR_RUNTIME_API block : public gr::node virtual void on_parameter_change(param_action_sptr action); virtual void on_parameter_query(param_action_sptr action); static void consume_each(size_t num, std::vector& work_input); - static void produce_each(size_t num, std::vector& work_output); + static void produce_each(size_t num, + std::vector& work_output); void set_output_multiple(size_t multiple); size_t output_multiple() const { return d_output_multiple; } bool output_multiple_set() const { return d_output_multiple_set; } @@ -144,6 +150,14 @@ class GR_RUNTIME_API block : public gr::node virtual void handle_msg_system(pmtf::pmt msg); + // Every block can have a "work" message handler that + // will take a pmt, run it through the work method and output + // to the work output port + virtual void handle_msg_work(pmtf::pmt msg); + + block_work_mode_t work_mode() { return _work_mode; } + void set_work_mode(block_work_mode_t work_mode_) { _work_mode = work_mode_; } + static pmtf::pmt deserialize_param_to_pmt(const std::string& param_value); static sptr cast(node_sptr n) { return std::static_pointer_cast(n); } }; diff --git a/gr/include/gnuradio/buffer_pdu.h b/gr/include/gnuradio/buffer_pdu.h new file mode 100644 index 00000000..2606c958 --- /dev/null +++ b/gr/include/gnuradio/buffer_pdu.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace gr { + +class buffer_pdu_reader; + +/** + * @brief PDU based buffer + * + * @details Contrived buffer that wraps a PDU for passing to work function + * + */ + +class buffer_pdu : public buffer +{ +private: + uint8_t* _buffer; + pmtf::pmt _pdu; + +public: + using sptr = std::shared_ptr; + buffer_pdu(size_t num_items, size_t itemsize, uint8_t* items, pmtf::pmt pdu) + + : buffer(num_items, itemsize, std::make_shared()) + { + _pdu = pdu; // hold the pdu -- do i need to?? + _buffer = items; + } + + static buffer_sptr + make(size_t num_items, size_t itemsize, uint8_t* items, pmtf::pmt pdu) + { + return buffer_sptr(new buffer_pdu(num_items, itemsize, items, pdu)); + + } + + void* read_ptr(size_t index) override { return _buffer; } + void* write_ptr() override { return _buffer; } + + void post_write(int num_items) override; + + std::shared_ptr + add_reader(std::shared_ptr buf_props, size_t itemsize) override + { + // do nothing because readers are detached from the writer + return nullptr; + } +}; + +class buffer_pdu_reader : public buffer_reader +{ +private: + uint8_t* _buffer; + pmtf::pmt _pdu; + size_t _n_items; + +public: + static buffer_reader_sptr + make(size_t num_items, size_t itemsize, uint8_t* items, pmtf::pmt pdu) + { + // // decompose the PDU + // auto meta = pmtf::map(pdu)["meta"]; + // auto data = pmtf::map(pdu)["data"]; + + // _pdu = pdu; // hold the pdu -- do i need to?? + // _buffer = items; + + return buffer_reader_sptr(new buffer_pdu_reader(num_items, itemsize, items, pdu)); + } + + buffer_pdu_reader(size_t num_items, size_t itemsize, uint8_t* items, pmtf::pmt pdu) + : buffer_reader(nullptr, std::make_shared(), itemsize, 0) + { + _pdu = pdu; // hold the pdu -- do i need to?? + _buffer = items; + _n_items = num_items; + } + + void* read_ptr() override { return _buffer; } + void post_read(int num_items) override; +}; + +} // namespace gr diff --git a/gr/include/gnuradio/concurrent_queue.h b/gr/include/gnuradio/concurrent_queue.h index d05507b0..009ef2fe 100644 --- a/gr/include/gnuradio/concurrent_queue.h +++ b/gr/include/gnuradio/concurrent_queue.h @@ -1,5 +1,6 @@ #pragma once +#if 0 #include namespace gr { @@ -39,3 +40,66 @@ class concurrent_queue moodycamel::BlockingConcurrentQueue q; }; } // namespace gr +#else + +#include +#include +#include +#include + +namespace gr { + +/** + * @brief Blocking Multi-producer Single-consumer Queue class + * + * @tparam T Data type of items in queue + */ +template +class concurrent_queue +{ +public: + bool push(const T& msg) + { + std::unique_lock l(_mutex); + _queue.push_back(msg); + l.unlock(); + _cond.notify_all(); + + return true; + } + + // Non-blocking + bool try_pop(T& msg) + { + std::unique_lock l(_mutex); + if (!_queue.empty()) { + msg = _queue.front(); + _queue.pop_front(); + return true; + } + else { + return false; + } + } + bool pop(T& msg) + { + std::unique_lock l(_mutex); + _cond.wait(l, + [this] { return !_queue.empty(); }); // TODO - replace with a waitfor + msg = _queue.front(); + _queue.pop_front(); + return true; + } + void clear() + { + std::unique_lock l(_mutex); + _queue.clear(); + } + +private: + std::deque _queue; + std::mutex _mutex; + std::condition_variable _cond; +}; +} // namespace gr +#endif diff --git a/gr/lib/block.cc b/gr/lib/block.cc index d353d1ea..6ef46144 100644 --- a/gr/lib/block.cc +++ b/gr/lib/block.cc @@ -8,6 +8,9 @@ #include #include + +#include + namespace gr { block::block(const std::string& name, const std::string& module) @@ -25,6 +28,12 @@ block::block(const std::string& name, const std::string& module) _msg_system->register_callback( [this](pmtf::pmt msg) { this->handle_msg_system(msg); }); add_port(_msg_system); + + _msg_work = message_port::make("pdus_in", port_direction_t::INPUT); + _msg_work->register_callback([this](pmtf::pmt msg) { this->handle_msg_work(msg); }); + add_port(_msg_work); + _msg_work_out = message_port::make("pdus_out", port_direction_t::OUTPUT); + add_port(_msg_work_out); } void block::set_pyblock_detail(std::shared_ptr p) @@ -111,6 +120,193 @@ void block::handle_msg_param_update(pmtf::pmt msg) request_parameter_change(get_param_id(id), value, false); } +void block::handle_msg_work(pmtf::pmt msg) +{ + + // only considering 1 input and 1 output for now + // FIXME: need checks elsewhere to enforce this + + // prepare the input buffer + // Interpret the data based on the port that it represents + auto input_port = this->get_port(0, port_type_t::STREAM, port_direction_t::INPUT); + auto output_port = this->get_port(0, port_type_t::STREAM, port_direction_t::OUTPUT); + + // The PDU must match the input port + auto meta = pmtf::map(msg)["meta"]; + auto data = pmtf::map(msg)["data"]; + + // data should be a vector of some sort + uint8_t* input_items = nullptr; + size_t num_input_items = 0; + size_t input_itemsize = input_port->itemsize(); + switch (input_port->data_type()) { + case param_type_t::FLOAT: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::DOUBLE: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::CFLOAT: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::CDOUBLE: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::INT8: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::INT16: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::INT32: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::INT64: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::UINT8: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::UINT16: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::UINT32: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + case param_type_t::UINT64: { + auto vec = pmtf::vector(data); + input_items = reinterpret_cast(vec.data()); + num_input_items = vec.size(); + } break; + default: + break; + } + + auto br = buffer_pdu_reader::make(num_input_items, input_itemsize, input_items, msg); + + + // data should be a vector of some sort + uint8_t* output_items = nullptr; + size_t output_itemsize = output_port->itemsize(); + + size_t num_output_items = static_cast(num_input_items * this->relative_rate()); + pmtf::pmt output_vec; + + switch (output_port->data_type()) { + case param_type_t::FLOAT: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::DOUBLE: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::CFLOAT: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::CDOUBLE: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::INT8: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::INT16: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::INT32: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::INT64: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::UINT8: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::UINT16: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::UINT32: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::UINT64: { + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + case param_type_t::UNTYPED: { + // FIXME: there is no way untyped ports will work with this ... + auto vec = pmtf::vector(num_output_items); + output_items = reinterpret_cast(vec.data()); + output_vec = vec; + } break; + default: + break; + } + + auto bw = buffer_pdu::make(num_output_items, output_itemsize, output_items, output_vec); + + std::vector work_input; + std::vector work_output; + work_input.push_back(std::make_shared(num_input_items, br)); + work_output.push_back(std::make_shared(num_output_items, bw)); + + auto code = work(work_input, work_output); + + if (code == work_return_code_t::WORK_OK) + { + // validate the n_produced + auto pdu = pmtf::map({ { "data", output_vec }, { "meta", meta } }); + _msg_work_out->post(pdu); + } + else + { + // TODO: have a better call here + throw std::runtime_error("Generic PDU handling on work port unable to handle this work call"); + } +} + void block::handle_msg_system(pmtf::pmt msg) { auto str_msg = pmtf::get_as(msg); diff --git a/gr/lib/buffer_management.cc b/gr/lib/buffer_management.cc index ea495f07..cc039df2 100644 --- a/gr/lib/buffer_management.cc +++ b/gr/lib/buffer_management.cc @@ -60,7 +60,8 @@ void buffer_manager::initialize_buffers(flat_graph_sptr fg, for (auto p : input_ports) { edge_vector_t ed = fg->find_edge(p); if (ed.empty()) { - throw std::runtime_error("Edge associated with input port not found"); + ///throw std::runtime_error("Edge associated with input port not found"); + continue; } // TODO: more robust way of ensuring readers don't get double-added diff --git a/gr/lib/buffer_pdu.cc b/gr/lib/buffer_pdu.cc new file mode 100644 index 00000000..e55faa90 --- /dev/null +++ b/gr/lib/buffer_pdu.cc @@ -0,0 +1,15 @@ +#include + +namespace gr { + +void buffer_pdu::post_write(int num_items) +{ + _total_written += num_items; +} + +void buffer_pdu_reader::post_read(int num_items) +{ + _total_read += num_items; +} + +} // namespace gr diff --git a/gr/lib/flowgraph.cc b/gr/lib/flowgraph.cc index 3e46a557..e8c0f688 100644 --- a/gr/lib/flowgraph.cc +++ b/gr/lib/flowgraph.cc @@ -39,7 +39,8 @@ void flowgraph::check_connections(const graph_sptr& g) // Are all non-optional ports connected to something for (auto& node : g->calc_used_nodes()) { for (auto& port : node->output_ports()) { - if (!port->optional() && port->connected_ports().empty()) { + if (!port->optional() && port->connected_ports().empty() && + node->get_message_port("pdus_out")->connected_ports().empty()) { throw std::runtime_error("Nothing connected to " + node->name() + ": " + port->name()); } @@ -49,7 +50,8 @@ void flowgraph::check_connections(const graph_sptr& g) if (port->type() == port_type_t::STREAM) { - if (port->connected_ports().empty()) { + if (port->connected_ports().empty() && + node->get_message_port("pdus_in")->connected_ports().empty()) { throw std::runtime_error("Nothing connected to " + node->name() + ": " + port->name()); } diff --git a/gr/lib/graph.cc b/gr/lib/graph.cc index d8526a8f..2de31dfa 100644 --- a/gr/lib/graph.cc +++ b/gr/lib/graph.cc @@ -4,7 +4,6 @@ namespace gr { edge_sptr graph::connect(const node_endpoint& src, const node_endpoint& dst) { - std::cout << "graph connect 1" << std::endl; if (src.port() && dst.port() && src.port()->itemsize() != dst.port()->itemsize() && src.port()->itemsize() > 0 && dst.port()->itemsize() > 0) { std::stringstream msg; diff --git a/gr/lib/meson.build b/gr/lib/meson.build index 41ba3e3f..4d38ca6c 100644 --- a/gr/lib/meson.build +++ b/gr/lib/meson.build @@ -34,6 +34,7 @@ runtime_sources = [ 'buffer_management.cc', 'buffer_cpu_simple.cc', 'buffer_sm.cc', + 'buffer_pdu.cc', 'realtime.cc', 'runtime.cc', 'runtime_monitor.cc', diff --git a/gr/python/gr/bindings/block_pybind.cc b/gr/python/gr/bindings/block_pybind.cc index ae7a1e3a..41de8607 100644 --- a/gr/python/gr/bindings/block_pybind.cc +++ b/gr/python/gr/bindings/block_pybind.cc @@ -23,6 +23,12 @@ void bind_block(py::module& m) { using block = ::gr::block; + py::enum_(m, "work_mode_t") + .value("DEFAULT", gr::block_work_mode_t::DEFAULT) + .value("PDU", + gr::block_work_mode_t::PDU) + .export_values(); + py::class_>(m, "block") .def("work", &block::work, @@ -43,5 +49,6 @@ void bind_block(py::module& m) py::overload_cast( &block::request_parameter_change)) .def_static("deserialize_param_to_pmt", &block::deserialize_param_to_pmt) - .def("to_json", &block::to_json); + .def("to_json", &block::to_json) + .def("set_work_mode", &block::set_work_mode); } diff --git a/kernel/include/gnuradio/kernel/digital/crc.h b/kernel/include/gnuradio/kernel/digital/crc.h new file mode 100644 index 00000000..b14a9c93 --- /dev/null +++ b/kernel/include/gnuradio/kernel/digital/crc.h @@ -0,0 +1,85 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 Daniel Estevez + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include + +#include +#include +#include + +namespace gr { +namespace kernel { +namespace digital { + +/*! + * \brief Calculates a CRC + * \ingroup packet_operators_blk + * + * \details + * This class calculates a CRC with configurable parameters. + * A table-driven byte-by-byte approach is used in the CRC + * computation. + */ +class DIGITAL_API crc +{ +public: + /*! + * \brief Construct a CRC calculator instance. + * + * \param num_bits CRC size in bits + * \param poly CRC polynomial, in MSB-first notation + * \param initial_value Initial register value + * \param final_xor Final XOR value + * \param input_reflected true if the input is LSB-first, false if not + * \param result_reflected true if the output is LSB-first, false if not + */ + crc(unsigned num_bits, + uint64_t poly, + uint64_t initial_value, + uint64_t final_xor, + bool input_reflected, + bool result_reflected); + ~crc(); + + /*! + * \brief Computes a CRC + * + * \param data the input data for the CRC calculation + * \param len the length in bytes of the data + */ + uint64_t compute(const uint8_t* data, std::size_t len); + + /*! + * \brief Computes a CRC + * + * \param data the input data for the CRC calculation + */ + uint64_t compute(std::vector const& data) + { + return compute(data.data(), data.size()); + } + +private: + std::array d_table; + unsigned d_num_bits; + uint64_t d_mask; + uint64_t d_initial_value; + uint64_t d_final_xor; + bool d_input_reflected; + bool d_result_reflected; + + uint64_t reflect(uint64_t word) const; +}; + +} // namespace digital +} +} // namespace gr diff --git a/kernel/include/gnuradio/kernel/digital/meson.build b/kernel/include/gnuradio/kernel/digital/meson.build index add59f0c..03b04f29 100644 --- a/kernel/include/gnuradio/kernel/digital/meson.build +++ b/kernel/include/gnuradio/kernel/digital/meson.build @@ -1,6 +1,7 @@ headers = [ 'constellation.h', - 'metric_type.h' + 'metric_type.h', + 'crc.h' ] install_headers(headers, subdir : 'gnuradio/kernel/digital') \ No newline at end of file diff --git a/kernel/lib/digital/crc.cc b/kernel/lib/digital/crc.cc new file mode 100644 index 00000000..15f8f3b7 --- /dev/null +++ b/kernel/lib/digital/crc.cc @@ -0,0 +1,116 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 Daniel Estevez + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + + +#include +#include + +namespace gr { +namespace kernel { +namespace digital { + +crc::crc(unsigned num_bits, + uint64_t poly, + uint64_t initial_value, + uint64_t final_xor, + bool input_reflected, + bool result_reflected) + : d_num_bits(num_bits), + d_mask(num_bits == 64 ? ~static_cast(0) + : (static_cast(1) << num_bits) - 1), + d_initial_value(initial_value & d_mask), + d_final_xor(final_xor & d_mask), + d_input_reflected(input_reflected), + d_result_reflected(result_reflected) +{ + if ((num_bits < 8) || (num_bits > 64)) { + throw std::runtime_error("CRC number of bits must be between 8 and 64"); + } + + d_table[0] = 0; + if (d_input_reflected) { + poly = reflect(poly); + uint64_t crc = 1; + int i = 128; + do { + if (crc & 1) { + crc = (crc >> 1) ^ poly; + } + else { + crc >>= 1; + } + for (int j = 0; j < 256; j += 2 * i) { + d_table[i + j] = (crc ^ d_table[j]) & d_mask; + } + i >>= 1; + } while (i > 0); + } + else { + const uint64_t msb = static_cast(1) << (num_bits - 1); + uint64_t crc = msb; + int i = 1; + do { + if (crc & msb) { + crc = (crc << 1) ^ poly; + } + else { + crc <<= 1; + } + for (int j = 0; j < i; ++j) { + d_table[i + j] = (crc ^ d_table[j]) & d_mask; + } + i <<= 1; + } while (i < 256); + } +} + +crc::~crc() {} + +uint64_t crc::compute(const uint8_t* data, std::size_t len) +{ + uint64_t rem = d_initial_value; + + if (d_input_reflected) { + for (std::size_t i = 0; i < len; ++i) { + uint8_t byte = data[i]; + uint8_t idx = (rem ^ byte) & 0xff; + rem = d_table[idx] ^ (rem >> 8); + } + } + else { + for (std::size_t i = 0; i < len; ++i) { + uint8_t byte = data[i]; + uint8_t idx = ((rem >> (d_num_bits - 8)) ^ byte) & 0xff; + rem = (d_table[idx] ^ (rem << 8)) & d_mask; + } + } + + if (d_input_reflected != d_result_reflected) { + rem = reflect(rem); + } + + rem = rem ^ d_final_xor; + return rem; +} + +uint64_t crc::reflect(uint64_t word) const +{ + uint64_t ret; + ret = word & 1; + for (unsigned i = 1; i < d_num_bits; ++i) { + word >>= 1; + ret = (ret << 1) | (word & 1); + } + return ret; +} + +} /* namespace digital */ +} // namespace kernel +} /* namespace gr */ diff --git a/kernel/lib/meson.build b/kernel/lib/meson.build index f07836e6..12aefd77 100644 --- a/kernel/lib/meson.build +++ b/kernel/lib/meson.build @@ -16,7 +16,8 @@ kernel_sources = [ 'math/fast_atan2f.cc', 'math/fxpt.cc', 'math/random.cc', - 'digital/constellation.cc' + 'digital/constellation.cc', + 'digital/crc.cc' ] compiler = meson.get_compiler('cpp') diff --git a/schedulers/nbt/lib/graph_executor.cc b/schedulers/nbt/lib/graph_executor.cc index e4c08e46..f399d753 100644 --- a/schedulers/nbt/lib/graph_executor.cc +++ b/schedulers/nbt/lib/graph_executor.cc @@ -19,7 +19,11 @@ graph_executor::run_one_iteration(std::vector blocks) blocks = d_blocks; } + // This is only for streaming blocks for (auto const& b : blocks) { // TODO - order the blocks + if (b->work_mode() != block_work_mode_t::DEFAULT) { + continue; + } std::vector work_input; //(num_input_ports); std::vector work_output; //(num_output_ports); diff --git a/utils/blockbuilder/templates/blockname.grc.j2 b/utils/blockbuilder/templates/blockname.grc.j2 index 6b0e145b..085d0cc4 100644 --- a/utils/blockbuilder/templates/blockname.grc.j2 +++ b/utils/blockbuilder/templates/blockname.grc.j2 @@ -101,6 +101,12 @@ parameters: dtype: bool default: False hide: 'part' +- id: msg_work + label: PDU Operation + category: Advanced + dtype: bool + default: False + hide: 'part' inputs: {% for port in ports -%}{%- if port['direction'] == 'input'%} @@ -113,13 +119,19 @@ inputs: {%if 'shape' in port %}vlen: {{get_linked_value(port['shape'])}}{% endif %} {%if 'multiplicity' in port %}multiplicity: {{get_linked_value(port['multiplicity'])}}{% endif %} label: {{port['id']}} - optional: {{ port['optional'] if 'optional' in port else 'false' }} + optional: ${ msg_work } or {{ port['optional'] if 'optional' in port else 'false' }} + hide: ${ msg_work } {% endif %}{% endfor %} - domain: message id: param_update label: param optional: true hide: ${ not showports } +- domain: message + id: pdus_in + label: pdus_in + optional: true #${ not msg_work } + hide: ${ not msg_work } outputs: {% for port in ports -%} @@ -133,9 +145,15 @@ outputs: {%if 'shape' in port %}vlen: {{get_linked_value(port['shape'])}}{% endif %} {%if 'multiplicity' in port %}multiplicity: {{get_linked_value(port['multiplicity'])}}{% endif %} label: {{port['id']}} - optional: {{ port['optional'] if 'optional' in port else 'false' }} + optional: ${ msg_work } or {{ port['optional'] if 'optional' in port else 'false' }} + hide: ${ msg_work } {% endif -%} {% endfor -%} +- domain: message + id: pdus_out + label: pdus_out + optional: true #${ not msg_work } + hide: ${ not msg_work } {% if grc -%} templates: @@ -149,13 +167,17 @@ templates: {%- if type_inst %} make: {{module}}.{{block}}{% if typekeys -%}_{{'${type_inst}'}}{%endif%}( {%- else %} - make: {{module}}.{{block}}{% if typekeys -%}_{% for key in typekeys %}{{'${' + key['id'] + '.fcn}'}}{% endfor %}{%endif%}( + make: | + {{module}}.{{block}}{% if typekeys -%}_{% for key in typekeys %}{{'${' + key['id'] + '.fcn}'}}{% endfor %}{%endif%}( {%- endif %} {% if parameters %}{%- for param in parameters -%} {%- if 'cotr' not in param or param['cotr'] -%} {{'${'}}{{param['id']}}{{'}'}}, {%- endif -%} {%-endfor-%}{% endif %} impl={{module}}.{{block}}{%if type_inst%}_${type_inst}{% elif typekeys -%}_{% for key in typekeys %}{{'${' + key['id'] + '.fcn}'}}{% endfor %}{%endif%}.${impl}) + % if context.get('msg_work')(): + self.${id}.set_work_mode(gr.work_mode_t.PDU) + % endif {% endif -%} file_format: 1 {# TODO - should this increment for GR 4.0 #} diff --git a/utils/blockbuilder/templates/macros.j2 b/utils/blockbuilder/templates/macros.j2 index 3975ae0b..5c533b90 100644 --- a/utils/blockbuilder/templates/macros.j2 +++ b/utils/blockbuilder/templates/macros.j2 @@ -240,6 +240,9 @@ Ports: {%- elif sigmf_type | trim() == 'ri32'-%}{%- set cpp_type = 'std::int32_t' -%} {%- elif sigmf_type | trim() == 'ri16'-%}{%- set cpp_type = 'std::int16_t' -%} {%- elif sigmf_type | trim() == 'ri8'-%}{%- set cpp_type = 'std::int8_t' -%} +{%- elif sigmf_type | trim() == 'ru64'-%}{%- set cpp_type = 'std::uint64_t' -%} +{%- elif sigmf_type | trim() == 'ru32'-%}{%- set cpp_type = 'std::uint32_t' -%} +{%- elif sigmf_type | trim() == 'ru16'-%}{%- set cpp_type = 'std::uint16_t' -%} {%- elif sigmf_type | trim() == 'ru8'-%}{%- set cpp_type = 'std::uint8_t' -%} {%- endif -%}{{cpp_type}} {%- endmacro -%} @@ -253,6 +256,8 @@ Ports: {%- elif type | trim() == 'ri32'-%}{%- set suffix = 'i' -%} {%- elif type | trim() == 'ri16'-%}{%- set suffix = 's' -%} {%- elif type | trim() == 'ri8'-%}{%- set suffix = 'b' -%} +{%- elif type | trim() == 'ru32'-%}{%- set suffix = 'i' -%} +{%- elif type | trim() == 'ru16'-%}{%- set suffix = 's' -%} {%- elif type | trim() == 'ru8'-%}{%- set suffix = 'b' -%} {%- endif -%}{{suffix}} {%- endmacro -%}