Converting a Python GNU Radio block into C++

Converting a Python GNU Radio block into C++ is very doable, but it’s not a mechanical translation — you’re moving from a runtime-defined block to a compiled, typed one that plugs into GNU Radio’s C++ scheduler.

Below is the clean, modern GNU Radio (3.9–3.10+) way, with emphasis on signal-processing blocks rather than message-only helpers.


Big picture: what actually changes

Python blockC++ block
gr.sync_block / basic_blockgr::sync_block / gr::block
Dynamic typingStatic typing
Python work()C++ work()
Installed via .pyBuilt via CMake
Easy iterationMuch faster execution

You normally do not “convert” line-by-line — you re-implement the algorithm.


Step 1: Identify what kind of block you wrote

Look at your Python class inheritance:

  • gr.sync_block → C++ sync_block
  • gr.decim_block → C++ decim_block
  • gr.interp_block → C++ interp_block
  • gr.basic_block → C++ block

Most custom DSP blocks map cleanly to sync_block.


Step 2: Create a new C++ OOT module (recommended)

Do not edit GNU Radio core.

gr_modtool newmod mymodule
cd gr-mymodule

Create your block:

gr_modtool add -t sync my_block

This generates:

  • Header (include/…/my_block.h)
  • Implementation (lib/my_block_impl.cc)
  • Python bindings
  • CMake glue

Step 3: Understand the generated C++ skeleton

Header (my_block.h)

class MYMODULE_API my_block : virtual public gr::sync_block
{
public:
    typedef std::shared_ptr<my_block> sptr;
 
    static sptr make(int param);
};

Implementation (my_block_impl.cc)

my_block::sptr
my_block::make(int param)
{
    return gnuradio::make_block_sptr<my_block_impl>(param);
}

Step 4: Translate the Python __init__

Python

gr.sync_block.__init__(
    self,
    name="my_block",
    in_sig=[np.float32],
    out_sig=[np.float32],
)
self.gain = gain

C++

my_block_impl::my_block_impl(float gain)
  : gr::sync_block(
        "my_block",
        gr::io_signature::make(1, 1, sizeof(float)),
        gr::io_signature::make(1, 1, sizeof(float))),
    d_gain(gain)
{
}

Member variables go in the class private section:

float d_gain;

Step 5: Translate work()

Python

def work(self, input_items, output_items):
    out = output_items[0]
    inp = input_items[0]
    out[:] = inp * self.gain
    return len(out)

C++

int
my_block_impl::work(int noutput_items,
                    gr_vector_const_void_star& input_items,
                    gr_vector_void_star& output_items)
{
    const float* in = (const float*)input_items[0];
    float* out = (float*)output_items[0];
 
    for (int i = 0; i < noutput_items; i++)
        out[i] = in[i] * d_gain;
 
    return noutput_items;
}

Step 6: Handle state, history, and tags

Python equivalents map cleanly:

PythonC++
self.set_history(n)set_history(n)
self.add_item_tag()add_item_tag()
self.get_tags_in_range()get_tags_in_range()

C++ tags example:

add_item_tag(0, nitems_written(0),
             pmt::string_to_symbol("key"),
             pmt::from_double(value));

Step 7: Build and install

mkdir build && cd build
cmake ..
make -j
sudo make install
sudo ldconfig

Your block will now appear in:

  • GNU Radio Companion
  • Python via import mymodule

Step 8: Test against your Python block

Before deleting the Python version:

  • Feed identical test vectors
  • Compare outputs numerically
  • Verify tag behavior
  • Check threading assumptions

Common traps (important)

  1. Python slicing ≠ C++ You must loop or use volk_* kernels.
  2. Scheduler differences Don’t assume fixed buffer sizes.
  3. Memory alignment Use volk_malloc if doing heavy SIMD work.
  4. Exceptions Don’t throw from work().

When not to convert

Keep Python if:

  • Control-plane logic only
  • Message-based blocks
  • Low data rates
  • Rapid experimentation

Convert to C++ when:

  • Heavy DSP
  • Correlation / FFT / accumulation
  • Real-time interferometry (very relevant to your RSPduo work)

By Admin

Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.