How to Write a Signal Processing Block

Eric Blossom


      
    

Revision History
Revision 0.12005-01-20
Revision 0.22005-02-02
Updated for SWIG 1.3.24
Revision 0.32006-07-21
Clarification of 1:1 fixed rate vs item size

Abstract

This article explains how to write signal processing blocks for GNU Radio.


Table of Contents

Prerequisites
Introduction
The View from 30,000 Feet
Autotools, Makefiles, and Directory Layout
Naming Conventions
Death to CamelCaseNames!
Global Names
Package Prefixes
Class Data Members (instance variables)
Class Static Data Members (class variables)
File Names
Suffixes
First Block: howto_square_ff
Test Driven Programming
Build Tree vs. Install Tree
make check
The C++ code
The SWIG .i file
Putting it all together
Additional gr_block methods
forecast
set_output_multiple
Subclasses for common patterns
gr_sync_block
gr_sync_decimator
gr_sync_interpolator
Second Block: howto_square2_ff
Where to from Here?
Miscellaneous Tips
Sources and Sinks
Debugging with gdb
Performance Measurement with oprofile
Coming Attractions
Improved Type System
Hierarchical Blocks

Prerequisites

This article assumes that the reader has basic familiarity with GNU Radio and has read and understood Exploring GNU Radio.

There is a tarball of files that accompany this article. It includes the examples, DocBook source for the article and all the Makefiles etc it takes to make it work. Grab it at ftp://ftp.gnu.org/gnu/gnuradio or one of the mirrors. The file you want is gr-howto-write-a-block-X.Y.tar.gz. Pick the one with the highest version number. See http://comsec.com/wiki?CvsAccess for CVS Access.

Introduction

GNU Radio provides a framework for building software radios. Waveforms -- signal processing applications -- are built using a combination of Python code for high level organization, policy, GUI and other non performance-critical functions, while performance critical signal processing blocks are written in C++.

From the Python point of view, GNU Radio provides a data flow abstraction. The fundamental concepts are signal processing blocks and the connections between them. This abstraction is implemented by the Python gr.flow_graph class. Each block has a set of input ports and output ports. Each port has an associated data type. The most common port types are float and gr_complex (equivalent to std::complex<float>), though other types are used, including those representing structures, arrays or other types of packetized data.

From the high level point-of-view, infinite streams of data flow through the ports. At the C++ level, streams are dealt with in convenient sized pieces, represented as contiguous arrays of the underlying type.

The View from 30,000 Feet

This article will walk through the construction of several simple signal processing blocks, and explain the techniques and idioms used. Later sections cover debugging signal processing blocks in the mixed Python/C++ environment and performance measurement and optimization.

The example blocks will be built in the style of all GNU Radio extensions. That is, they are built outside of the gnuradio-core build tree, and are constructed as shared libraries that may be dynamically loaded into Python using the "import" mechanism. SWIG, the Simplified Wrapper and Interface Generator, is used to generate the glue that allows our code to be used from Python.

The C++ class gr_block is the base of all signal processing blocks in GNU Radio. Writing a new signal processing block involves creating 3 files: The .h and .cc files that define the new class and the .i file that tells SWIG how to generate the glue that binds the class into Python. The new class must derive from gr_block or one of it's subclasses.

Our first examples will derive directly from gr_block. Later we will look at some other subclasses that simplify the process for common cases.

Autotools, Makefiles, and Directory Layout

Before we dive into the code, let's talk a bit about the overall build environment and the directory structure that we'll be using.

To reduce the amount of Makefile hacking that we have to do, and to facilitate portability across a variety of systems, we use the GNU autoconf, automake, and libtool tools. These are collectively referred to as the autotools, and once you get over the initial shock, they will become your friends. (The good news is that we provide boilerplate that can be used pretty much as-is.)

automake

automake and configure work together to generate GNU compliant Makefiles from a much higher level description contained in the corresponding Makefile.am file. Makefile.am specifies the libraries and programs to build and the source files that compose each. Automake reads Makefile.am and produces Makefile.in. Configure reads Makefile.in and produces Makefile. The resulting Makefile contains a zillion rules that do the right right thing to build, check and install your code. It is not uncommon for the the resulting Makefile to be 5 or 6 times larger than Makefile.am.

autoconf

autoconf reads configure.ac and produces the configure shell script. configure automatically tests for features of the underlying system and sets a bunch of variables and defines that can be used in the Makefiles and your C++ code to conditionalize the build. If features are required but not found, configure will output an error message and stop.

libtool

libtool works behind the scenes and provides the magic to construct shared libraries on a wide variety of systems.

Table 1, “Directory Layout” shows the directory layout and common files we'll be using. After renaming the topdir directory, use it in your projects too. We'll talk about particular files as they come up later.

Table 1. Directory Layout

File/Dir NameComment
topdir/Makefile.amTop level Makefile.am
topdir/Makefile.commonCommon fragment included in sub-Makefiles
topdir/bootstrapRuns autoconf, automake, libtool first time through
topdir/configDirectory of m4 macros used by configure.ac
topdir/configure.acInput to autoconf
topdir/src 
topdir/src/libC++ code goes here
topdir/src/lib/Makefile.am 
topdir/src/pythonPython code goes here
topdir/src/python/Makefile.am 
topdir/src/python/run_testsScript to run tests in the build tree

Naming Conventions

GNU Radio uses a set of naming conventions to assist in comprehending the code base and gluing C++ and Python together. Please follow them.

Death to CamelCaseNames!

We've returned to a kinder, gentler era. We're now using the "STL style" naming convention with a couple of modifications since we're not using namespaces.

With the exception of macros and other constant values, all identifiers shall be lower case with words_separated_like_this.

Macros and constant values (e.g., enumerated values, static const int FOO = 23) shall be in UPPER_CASE.

Global Names

All globally visible names (types, functions, variables, consts, etc) shall begin with a "package prefix", followed by an underscore. The bulk of the code in GNU Radio belongs to the "gr" package, hence names look like gr_open_file (...).

Large coherent bodies of code may use other package prefixes, but let's try to keep them to a well thought out list. See the list below.

Package Prefixes

These are the current package prefixes:

gr_

Almost everything.

gri_

Implementation primitives. Sometimes we have both a gr_foo and a gri_foo. In that case, gr_foo would be derived from gr_block and gri_foo would be the low level guts of the function.

atsc_

Code related to the Advanced Television Standards Committee HDTV implementation

usrp_

Universal Software Radio Peripheral.

qa_

Quality Assurance (Test code.)

Class Data Members (instance variables)

All class data members shall begin with d_foo.

The big win is when you're staring at a block of code it's obvious which of the things being assigned to persist outside of the block. This also keeps you from having to be creative with parameter names for methods and constructors. You just use the same name as the instance variable, without the d_.


class gr_wonderfulness {
  std::string   d_name;
  double        d_wonderfulness_factor;

public:
  gr_wonderfulness (std::string name, double wonderfulness_factor)
    : d_name (name), d_wonderfulness_factor (wonderfulness_factor)
  {
    ...
  }
  ...
};

Class Static Data Members (class variables)

All class static data members shall begin with s_foo.

File Names

Each significant class shall be contained in its own file. The declaration of class gr_foo shall be in gr_foo.h and the definition in gr_foo.cc.

Suffixes

By convention, we encode the input and output types of signal processing blocks in their name using suffixes. The suffix is typically one or two characters long. Source and sinks have single character suffixes. Regular blocks that have both inputs and outputs have two character suffixes. The first character indicates the type of the input streams, the second indicates the type of the output streams. FIR filter blocks have a three character suffix, indicating the type of the inputs, outputs and taps, respectively.

These are the suffix characters and their interpretations:

  • f - single precision floating point

  • c - complex<float>

  • s - short (16-bit integer)

  • i - integer (32-bit integer)

In addition, for those cases where the block deals with streams of vectors, we use the character 'v' as the first character of the suffix. An example of this usage is gr_fft_vcc. The FFT block takes a vector of complex numbers on its input and produces a vector of complex numbers on its output.

First Block: howto_square_ff

For our first example we'll create a block that computes the square of its single float input. This block will accept a single float input stream and produce a single float output stream.

Following the naming conventions, we'll use howto as our package prefix, and the block will be called howto_square_ff.

We are going to arrange that this block, as well as the others that we write in this article, end up in the gnuradio.howto Python module. This will allow us to access it from Python like this:

from gnuradio import howto
sqr = howto.square_ff ()

Test Driven Programming

We could just start banging out the C++ code, but being highly evolved modern programmers, we're going to write the test code first. After all, we do have a good spec for the behavior: take a single stream of floats as the input and produce a single stream of floats as the output. The output should be the square of the input.

How hard could this be? Turns out that this is easy! Check out Example 1, “qa_howto.py (first version)”.

Example 1. qa_howto.py (first version)

  1  #!/usr/bin/env python
  2  
  3  from gnuradio import gr, gr_unittest
  4  import howto
  5  
  6  class qa_howto (gr_unittest.TestCase):
  7  
  8      def setUp (self):
  9          self.fg = gr.flow_graph ()
 10  
 11      def tearDown (self):
 12          self.fg = None
 13  
 14      def test_001_square_ff (self):
 15          src_data = (-3, 4, -5.5, 2, 3)
 16          expected_result = (9, 16, 30.25, 4, 9)
 17          src = gr.vector_source_f (src_data)
 18          sqr = howto.square_ff ()
 19          dst = gr.vector_sink_f ()
 20          self.fg.connect (src, sqr)
 21          self.fg.connect (sqr, dst)
 22          self.fg.run ()
 23          result_data = dst.data ()
 24          self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)
 25          
 26  if __name__ == '__main__':
 27      gr_unittest.main ()

gr_unittest is an extension to the standard python module unittest. gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. Unittest uses Python's reflection mechanism to find all methods that start with test_ and runs them. Unittest wraps each call to test_* with matching calls to setUp and tearDown. See the python unittest documentation for details.

When we run the test, gr_unittest.main is going to invoke setUp, test_001_square_ff, and tearDown.

test_001_square_ff builds a small graph that contains three nodes. gr.vector_source_f(src_data) will source the elements of src_data and then say that it's finished. howto.square_ff is the block we're testing. gr.vector_sink_f gathers the output of howto.square_ff.

The run method runs the graph until all the blocks indicate they are finished. Finally, we check that the result of executing square_ff on src_data matches what we expect.

Build Tree vs. Install Tree

The build tree is everything from topdir (the one containing configure.ac) down. The path to the install tree is prefix/lib/pythonversion/site-packages, where prefix is the --prefix argument to configure (default /usr/local) and version is the installed version of python. A typical value is /usr/local/lib/python2.3/site-packages.

We normally set our PYTHONPATH environment variable to point at the install tree, and do this in ~/.bash_profile or ~/.profile. This allows our python apps to access all the standard python libraries, plus our locally installed stuff like GNU Radio.

We write our applications such that they access the code and libraries in the install tree. On the other hand, we want our test code to run on the build tree, where we can detect problems before installation.

make check

We use make check to run our tests. Make check invokes the run_tests shell script which sets up the PYTHONPATH environment variable so that our tests use the build tree versions of our code and libraries. It then runs all files which have names of the form qa_*.py and reports the overall success or failure.

There is quite a bit of behind-the-scenes action required to use the non-installed versions of our code (look at runtest for a cheap thrill.)

Finally, running make check in the python directory produces this result:


  [eb@bufo python]$ make check
  make  check-TESTS
  make[1]: Entering directory `/home/eb/gr-build/gr-howto-write-a-block/src/python'
  Traceback (most recent call last):
    File "./qa_howto.py", line 24, in ?
      import howto
  ImportError: No module named howto
  Traceback (most recent call last):
    File "./qa_howto_1.py", line 24, in ?
      import howto
  ImportError: No module named howto
  FAIL: run_tests
  ===================
  1 of 1 tests failed
  ===================
  make[1]: *** [check-TESTS] Error 1
  make[1]: Leaving directory `/home/eb/gr-build/gr-howto-write-a-block/src/python'
  make: *** [check-am] Error 2
  [eb@bufo python]$

Excellent! Our test failed, just as we expected. The ImportError indicates that it can't find the module named howto. No surprise, since we haven't written it yet.

The C++ code

Now that we've got a test case written that successfully fails, let's write the C++ code. As we mentioned earlier, all signal processing blocks are derived from gr_block or one of its subclasses. Let's take a look at Example 2, “gr_block.h.

Example 2. gr_block.h

  1  /* -*- c++ -*- */
  2  /*
  3   * Copyright 2004 Free Software Foundation, Inc.
  4   * 
  5   * This file is part of GNU Radio
  6   * 
  7   * GNU Radio is free software; you can redistribute it and/or modify
  8   * it under the terms of the GNU General Public License as published by
  9   * the Free Software Foundation; either version 2, or (at your option)
 10   * any later version.
 11   * 
 12   * GNU Radio is distributed in the hope that it will be useful,
 13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   * GNU General Public License for more details.
 16   * 
 17   * You should have received a copy of the GNU General Public License
 18   * along with GNU Radio; see the file COPYING.  If not, write to
 19   * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 20   * Boston, MA 02111-1307, USA.
 21   */
 22  
 23  #ifndef INCLUDED_GR_BLOCK_H
 24  #define INCLUDED_GR_BLOCK_H
 25  
 26  #include <gr_runtime.h>
 27  #include <string>
 28  
 29  /*!
 30   * \brief The abstract base class for all signal processing blocks.
 31   * \ingroup block
 32   *
 33   * Blocks have a set of input streams and output streams.  The
 34   * input_signature and output_signature define the number of input
 35   * streams and output streams respectively, and the type of the data
 36   * items in each stream.
 37   *
 38   * Although blocks may consume data on each input stream at a
 39   * different rate, all outputs streams must produce data at the same
 40   * rate.  That rate may be different from any of the input rates.
 41   *
 42   * User derived blocks override two methods, forecast and general_work,
 43   * to implement their signal processing behavior. forecast is called
 44   * by the system scheduler to determine how many items are required on
 45   * each input stream in order to produce a given number of output
 46   * items.
 47   *
 48   * general_work is called to perform the signal processing in the block.
 49   * It reads the input items and writes the output items.
 50   */
 51  
 52  class gr_block {
 53  
 54   public:
 55    
 56    virtual ~gr_block ();
 57    
 58    std::string name () const { return d_name; }
 59    gr_io_signature_sptr input_signature () const  { return d_input_signature; }
 60    gr_io_signature_sptr output_signature () const { return d_output_signature; }
 61    long unique_id () const { return d_unique_id; }
 62  
 63    /*!
 64     * Assume block computes y_i = f(x_i, x_i-1, x_i-2, x_i-3...)
 65     * History is the number of x_i's that are examined to produce one y_i.
 66     * This comes in handy for FIR filters, where we use history to
 67     * ensure that our input contains the appropriate "history" for the
 68     * filter.   History should be equal to the number of filter taps.
 69     */
 70    unsigned history () const { return d_history; }
 71    void  set_history (unsigned history) { d_history = history; }
 72    
 73    /*!
 74     * \brief return true if this block has a fixed input to output rate
 75     *
 76     * If true, then fixed_rate_in_to_out and fixed_rate_out_to_in may be called.
 77     */
 78    bool fixed_rate() const { return d_fixed_rate; }
 79  
 80    // ----------------------------------------------------------------
 81    //            override these to define your behavior
 82    // ----------------------------------------------------------------
 83  
 84    /*!
 85     * \brief  Estimate input requirements given output request
 86     *
 87     * \param noutput_items           number of output items to produce
 88     * \param ninput_items_required   number of input items required on each input stream
 89     *
 90     * Given a request to product \p noutput_items, estimate the number of
 91     * data items required on each input stream.  The estimate doesn't have
 92     * to be exact, but should be close.
 93     */
 94    virtual void forecast (int noutput_items,
 95                           gr_vector_int &ninput_items_required);
 96  
 97    /*!
 98     * \brief compute output items from input items
 99     *
100     * \param noutput_items       number of output items to write on each output stream
101     * \param ninput_items        number of input items available on each input stream
102     * \param input_items         vector of pointers to the input items, one entry per input stream
103     * \param output_items        vector of pointers to the output items, one entry per output stream
104     *
105     * \returns number of items actually written to each output stream, or -1 on EOF.
106     * It is OK to return a value less than noutput_items.  -1 <= return value <= noutput_items
107     *
108     * general_work must call consume or consume_each to indicate how many items
109     * were consumed on each input stream.
110     */
111    virtual int general_work (int noutput_items,
112                              gr_vector_int &ninput_items,
113                              gr_vector_const_void_star &input_items,
114                              gr_vector_void_star &output_items) = 0;
115  
116    /*!
117     * \brief Confirm that ninputs and noutputs is an acceptable combination.
118     *
119     * \param ninputs     number of input streams connected
120     * \param noutputs    number of output streams connected
121     *
122     * \returns true if this is a valid configuration for this block.
123     *
124     * This function is called by the runtime system whenever the
125     * topology changes.  Most classes do not need to override this.
126     * This check is in addition to the constraints specified by the input
127     * and output gr_io_signatures.
128     */
129    virtual bool check_topology (int ninputs, int noutputs);
130  
131    /*!
132     * \brief Called to enable drivers, etc for i/o devices.
133     *
134     * This allows a block to enable an associated driver to begin
135     * transfering data just before we start to execute the scheduler.
136     * The end result is that this reduces latency in the pipeline when
137     * dealing with audio devices, usrps, etc.
138     */
139    virtual bool start();
140  
141    /*!
142     * \brief Called to disable drivers, etc for i/o devices.
143     */
144    virtual bool stop();
145  
146    // ----------------------------------------------------------------
147  
148    /*!
149     * \brief Constrain the noutput_items argument passed to forecast and general_work
150     *
151     * set_output_multiple causes the scheduler to ensure that the noutput_items
152     * argument passed to forecast and general_work will be an integer multiple
153     * of \param multiple  The default value of output multiple is 1.
154     */
155    void set_output_multiple (int multiple);
156    int  output_multiple () const { return d_output_multiple; }
157  
158    /*!
159     * \brief Tell the scheduler \p how_many_items of input stream \p which_input were consumed.
160     */
161    void consume (int which_input, int how_many_items);
162  
163    /*!
164     * \brief Tell the scheduler \p how_many_items were consumed on each input stream.
165     */
166    void consume_each (int how_many_items);
167  
168    /*!
169     * \brief Set the approximate output rate / input rate
170     *
171     * Provide a hint to the buffer allocator and scheduler.
172     * The default relative_rate is 1.0
173     *
174     * decimators have relative_rates < 1.0
175     * interpolators have relative_rates > 1.0
176     */
177    void  set_relative_rate (double relative_rate);
178  
179    /*!
180     * \brief return the approximate output rate / input rate
181     */
182    double relative_rate () const { return d_relative_rate; }
183  
184    /*
185     * The following two methods provide special case info to the
186     * scheduler in the event that a block has a fixed input to output
187     * ratio.  gr_sync_block, gr_sync_decimator and gr_sync_interpolator
188     * override these.  If you're fixed rate, subclass one of those.
189     */
190    /*!
191     * \brief Given ninput samples, return number of output samples that will be produced.
192     * N.B. this is only defined if fixed_rate returns true.
193     * Generally speaking, you don't need to override this.
194     */
195    virtual int fixed_rate_ninput_to_noutput(int ninput);
196  
197    /*!
198     * \brief Given noutput samples, return number of input samples required to produce noutput.
199     * N.B. this is only defined if fixed_rate returns true.
200     * Generally speaking, you don't need to override this.
201     */
202    virtual int fixed_rate_noutput_to_ninput(int noutput);
203  
204    // ----------------------------------------------------------------------------
205  
206   private:
207  
208    std::string           d_name;
209    gr_io_signature_sptr  d_input_signature;
210    gr_io_signature_sptr  d_output_signature;
211    int                   d_output_multiple;
212    double                d_relative_rate;        // approx output_rate / input_rate
213    gr_block_detail_sptr  d_detail;               // implementation details
214    long                  d_unique_id;            // convenient for debugging
215    unsigned              d_history;
216    bool                  d_fixed_rate;
217  
218    
219   protected:
220  
221    gr_block (const std::string &name,
222              gr_io_signature_sptr input_signature,
223              gr_io_signature_sptr output_signature);
224  
225    //! may only be called during constructor
226    void set_input_signature (gr_io_signature_sptr iosig){
227      d_input_signature = iosig;
228    }
229  
230    //! may only be called during constructor
231    void set_output_signature (gr_io_signature_sptr iosig){
232      d_output_signature = iosig;
233    }
234  
235    void set_fixed_rate(bool fixed_rate){ d_fixed_rate = fixed_rate; }
236  
237    // These are really only for internal use, but leaving them public avoids
238    // having to work up an ever-varying list of friends
239  
240   public:
241    gr_block_detail_sptr detail () const { return d_detail; }
242    void set_detail (gr_block_detail_sptr detail) { d_detail = detail; }
243  };
244  
245  long gr_block_ncurrently_allocated ();
246  
247  #endif /* INCLUDED_GR_BLOCK_H */

A quick scan of gr_block.h reveals that since general_work is pure virtual, we definitely need to override that. general_work is the method that does the actual signal processing. For our squaring example we'll need to override general_work and provide a constructor and destructor and a bit of stuff to take advantage of the boost shared_ptrs.

Example 3, “howto_square_ff.h and Example 4, “howto_square_ff.cc are the header and c++ source.

Example 3. howto_square_ff.h

  1  /* -*- c++ -*- */
  2  /*
  3   * Copyright 2004 Free Software Foundation, Inc.
  4   * 
  5   * This file is part of GNU Radio
  6   * 
  7   * GNU Radio is free software; you can redistribute it and/or modify
  8   * it under the terms of the GNU General Public License as published by
  9   * the Free Software Foundation; either version 2, or (at your option)
 10   * any later version.
 11   * 
 12   * GNU Radio is distributed in the hope that it will be useful,
 13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   * GNU General Public License for more details.
 16   * 
 17   * You should have received a copy of the GNU General Public License
 18   * along with GNU Radio; see the file COPYING.  If not, write to
 19   * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 20   * Boston, MA 02111-1307, USA.
 21   */
 22  #ifndef INCLUDED_HOWTO_SQUARE_FF_H
 23  #define INCLUDED_HOWTO_SQUARE_FF_H
 24  
 25  #include <gr_block.h>
 26  
 27  class howto_square_ff;
 28  
 29  /*
 30   * We use boost::shared_ptr's instead of raw pointers for all access
 31   * to gr_blocks (and many other data structures).  The shared_ptr gets
 32   * us transparent reference counting, which greatly simplifies storage
 33   * management issues.  This is especially helpful in our hybrid
 34   * C++ / Python system.
 35   *
 36   * See http://www.boost.org/libs/smart_ptr/smart_ptr.htm
 37   *
 38   * As a convention, the _sptr suffix indicates a boost::shared_ptr
 39   */
 40  typedef boost::shared_ptr<howto_square_ff> howto_square_ff_sptr;
 41  
 42  /*!
 43   * \brief Return a shared_ptr to a new instance of howto_square_ff.
 44   *
 45   * To avoid accidental use of raw pointers, howto_square_ff's
 46   * constructor is private.  howto_make_square_ff is the public
 47   * interface for creating new instances.
 48   */
 49  howto_square_ff_sptr howto_make_square_ff ();
 50  
 51  /*!
 52   * \brief square a stream of floats.
 53   * \ingroup block
 54   *
 55   * \sa howto_square2_ff for a version that subclasses gr_sync_block.
 56   */
 57  class howto_square_ff : public gr_block
 58  {
 59  private:
 60    // The friend declaration allows howto_make_square_ff to
 61    // access the private constructor.
 62  
 63    friend howto_square_ff_sptr howto_make_square_ff ();
 64  
 65    howto_square_ff ();   // private constructor
 66  
 67   public:
 68    ~howto_square_ff ();  // public destructor
 69  
 70    // Where all the action really happens
 71  
 72    int general_work (int noutput_items,
 73                      gr_vector_int &ninput_items,
 74                      gr_vector_const_void_star &input_items,
 75                      gr_vector_void_star &output_items);
 76  };
 77  
 78  #endif /* INCLUDED_HOWTO_SQUARE_FF_H */

Example 4. howto_square_ff.cc

  1  /* -*- c++ -*- */
  2  /*
  3   * Copyright 2004 Free Software Foundation, Inc.
  4   * 
  5   * This file is part of GNU Radio
  6   * 
  7   * GNU Radio is free software; you can redistribute it and/or modify
  8   * it under the terms of the GNU General Public License as published by
  9   * the Free Software Foundation; either version 2, or (at your option)
 10   * any later version.
 11   * 
 12   * GNU Radio is distributed in the hope that it will be useful,
 13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   * GNU General Public License for more details.
 16   * 
 17   * You should have received a copy of the GNU General Public License
 18   * along with GNU Radio; see the file COPYING.  If not, write to
 19   * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 20   * Boston, MA 02111-1307, USA.
 21   */
 22  
 23  /*
 24   * config.h is generated by configure.  It contains the results
 25   * of probing for features, options etc.  It should be the first
 26   * file included in your .cc file.
 27   */
 28  #ifdef HAVE_CONFIG_H
 29  #include "config.h"
 30  #endif
 31  
 32  #include <howto_square_ff.h>
 33  #include <gr_io_signature.h>
 34  
 35  /*
 36   * Create a new instance of howto_square_ff and return
 37   * a boost shared_ptr.  This is effectively the public constructor.
 38   */
 39  howto_square_ff_sptr 
 40  howto_make_square_ff ()
 41  {
 42    return howto_square_ff_sptr (new howto_square_ff ());
 43  }
 44  
 45  /*
 46   * Specify constraints on number of input and output streams.
 47   * This info is used to construct the input and output signatures
 48   * (2nd & 3rd args to gr_block's constructor).  The input and
 49   * output signatures are used by the runtime system to
 50   * check that a valid number and type of inputs and outputs
 51   * are connected to this block.  In this case, we accept
 52   * only 1 input and 1 output.
 53   */
 54  static const int MIN_IN = 1;    // mininum number of input streams
 55  static const int MAX_IN = 1;    // maximum number of input streams
 56  static const int MIN_OUT = 1;   // minimum number of output streams
 57  static const int MAX_OUT = 1;   // maximum number of output streams
 58  
 59  /*
 60   * The private constructor
 61   */
 62  howto_square_ff::howto_square_ff ()
 63    : gr_block ("square_ff",
 64                gr_make_io_signature (MIN_IN, MAX_IN, sizeof (float)),
 65                gr_make_io_signature (MIN_OUT, MAX_OUT, sizeof (float)))
 66  {
 67    // nothing else required in this example
 68  }
 69  
 70  /*
 71   * Our virtual destructor.
 72   */
 73  howto_square_ff::~howto_square_ff ()
 74  {
 75    // nothing else required in this example
 76  }
 77  
 78  int 
 79  howto_square_ff::general_work (int noutput_items,
 80                                 gr_vector_int &ninput_items,
 81                                 gr_vector_const_void_star &input_items,
 82                                 gr_vector_void_star &output_items)
 83  {
 84    const float *in = (const float *) input_items[0];
 85    float *out = (float *) output_items[0];
 86  
 87    for (int i = 0; i < noutput_items; i++){
 88      out[i] = in[i] * in[i];
 89    }
 90  
 91    // Tell runtime system how many input items we consumed on
 92    // each input stream.
 93  
 94    consume_each (noutput_items);
 95  
 96    // Tell runtime system how many output items we produced.
 97    return noutput_items;
 98  }

Now we need a Makefile.am to get all this to build. Example 5, “src/lib/Makefile.am (no SWIG)” is enough to build a shared library from our source file. We'll be adding additional rules to use SWIG in just a bit. If you haven't already, this is a good time to browse all the Makefile.am's in the build tree and get an idea for how it all hangs together.

Example 5. src/lib/Makefile.am (no SWIG)

  1  include $(top_srcdir)/Makefile.common
  2  
  3  # Install this stuff so that it ends up as the gnuradio.howto module
  4  # This usually ends up at:
  5  #   ${prefix}/lib/python${python_version}/site-packages/gnuradio
  6  
  7  ourpythondir = $(grpythondir)
  8  ourlibdir    = $(grpyexecdir)
  9  
 10  INCLUDES = $(STD_DEFINES_AND_INCLUDES) $(PYTHON_CPPFLAGS)
 11  
 12  ourlib_LTLIBRARIES = _howto.la
 13  
 14  # These are the source files that go into the shared library
 15  _howto_la_SOURCES =                     \
 16          howto_square_ff.cc              
 17  
 18  # magic flags
 19  _howto_la_LDFLAGS = -module -avoid-version
 20  
 21  # These headers get installed in ${prefix}/include/gnuradio
 22  grinclude_HEADERS =                     \
 23          howto_square_ff.h               
 24  
 25  MOSTLYCLEANFILES = $(BUILT_SOURCES) *.pyc

The SWIG .i file

Now that we've got something that will compile, we need to write the SWIG .i file. This is a pared-down version of the .h file, plus a bit of magic that has python work with the boost shared_ptr's. To reduce code bloat, we only declare methods that we'll want to access from Python.

We're going to call the .i file howto.i, and use it to hold the SWIG declarations for all classes from howto that will be accessible from python. It's quite small:

  1  /* -*- c++ -*- */
  2  
  3  %include "exception.i"
  4  %import "gnuradio.i"                            // the common stuff
  5  
  6  %{
  7  #include "gnuradio_swig_bug_workaround.h"       // mandatory bug fix
  8  #include "howto_square_ff.h"
  9  #include <stdexcept>
 10  %}
 11  
 12  // ----------------------------------------------------------------
 13  
 14  /*
 15   * First arg is the package prefix.
 16   * Second arg is the name of the class minus the prefix.
 17   *
 18   * This does some behind-the-scenes magic so we can
 19   * access howto_square_ff from python as howto.square_ff
 20   */
 21  GR_SWIG_BLOCK_MAGIC(howto,square_ff);
 22  
 23  howto_square_ff_sptr howto_make_square_ff ();
 24  
 25  class howto_square_ff : public gr_block
 26  {
 27  private:
 28    howto_square_ff ();
 29  };

Putting it all together

Now we need to modify src/lib/Makefile.am to run SWIG and to add the glue it generates to the shared library.

Example 6. src/lib/Makefile.am (with SWIG)

  1  #
  2  # Copyright 2004 Free Software Foundation, Inc.
  3  # 
  4  # This file is part of GNU Radio
  5  # 
  6  # GNU Radio is free software; you can redistribute it and/or modify
  7  # it under the terms of the GNU General Public License as published by
  8  # the Free Software Foundation; either version 2, or (at your option)
  9  # any later version.
 10  # 
 11  # GNU Radio is distributed in the hope that it will be useful,
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  # GNU General Public License for more details.
 15  # 
 16  # You should have received a copy of the GNU General Public License
 17  # along with GNU Radio; see the file COPYING.  If not, write to
 18  # the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 19  # Boston, MA 02111-1307, USA.
 20  # 
 21  
 22  include $(top_srcdir)/Makefile.common
 23  
 24  # Install this stuff so that it ends up as the gnuradio.howto module
 25  # This usually ends up at:
 26  #   ${prefix}/lib/python${python_version}/site-packages/gnuradio
 27  
 28  ourpythondir = $(grpythondir)
 29  ourlibdir    = $(grpyexecdir)
 30  
 31  INCLUDES = $(STD_DEFINES_AND_INCLUDES) $(PYTHON_CPPFLAGS)
 32  
 33  SWIGCPPPYTHONARGS = -noruntime -c++ -python $(PYTHON_CPPFLAGS) \
 34          -I$(swigincludedir) -I$(grincludedir)
 35  
 36  ALL_IFILES =                            \
 37          $(LOCAL_IFILES)                 \
 38          $(NON_LOCAL_IFILES)             
 39  
 40  NON_LOCAL_IFILES =                      \
 41          $(GNURADIO_CORE_INCLUDEDIR)/swig/gnuradio.i
 42  
 43  
 44  LOCAL_IFILES =                          \
 45          howto.i                         
 46  
 47  # These files are built by SWIG.  The first is the C++ glue.
 48  # The second is the python wrapper that loads the _howto shared library
 49  # and knows how to call our extensions.
 50  
 51  BUILT_SOURCES =                         \
 52          howto.cc                        \
 53          howto.py                                
 54  
 55  # This gets howto.py installed in the right place
 56  ourpython_PYTHON =                      \
 57          howto.py
 58  
 59  ourlib_LTLIBRARIES = _howto.la
 60  
 61  # These are the source files that go into the shared library
 62  _howto_la_SOURCES =                     \
 63          howto.cc                        \
 64          howto_square_ff.cc              
 65  
 66  # magic flags
 67  _howto_la_LDFLAGS = -module -avoid-version
 68  
 69  # link the library against some comon swig runtime code and the 
 70  # c++ standard library
 71  _howto_la_LIBADD =                      \
 72          -lgrswigrunpy                   \
 73          -lstdc++                        
 74  
 75  howto.cc howto.py: howto.i $(ALL_IFILES)
 76          $(SWIG) $(SWIGCPPPYTHONARGS) -module howto -o howto.cc $<
 77  
 78  # These headers get installed in ${prefix}/include/gnuradio
 79  grinclude_HEADERS =                     \
 80          howto_square_ff.h               
 81  
 82  # These swig headers get installed in ${prefix}/include/gnuradio/swig
 83  swiginclude_HEADERS =                   \
 84          $(LOCAL_IFILES)
 85  
 86  MOSTLYCLEANFILES = $(BUILT_SOURCES) *.pyc

make now builds everything successfully. We get a few warnings, but that's OK.

Changing directories back to the python directory we try make check again:


  [eb@bufo python]$ make check
  make  check-TESTS
  make[1]: Entering directory `/home/eb/gr-build/gr-howto-write-a-block/src/python'
  .
  ----------------------------------------------------------------------
  Ran 1 test in 0.004s
  
  OK
  PASS: run_tests
  ==================
  All 1 tests passed
  ==================
  make[1]: Leaving directory `/home/eb/gr-build/gr-howto-write-a-block/src/python'
  [eb@bufo python]$

Victory! Our new block works!

Additional gr_block methods

In our howto_square_ff example above, we only had to override the general_work method to accomplish our goal. gr_block provides a few other methods that are sometimes useful.

forecast

Looking at general_work you may have wondered how the system knows how much data it needs to ensure is valid in each of the input arrays. The forecast method provides this information.

The default implementation of forecast says there is a 1:1 relationship between noutput_items and the requirements for each input stream. The size of the items is defined by gr_io_signatures in the constructor of gr_block. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship.

  // default implementation:  1:1

  void
  gr_block::forecast (int noutput_items,
                      gr_vector_int &ninput_items_required)
  {
    unsigned ninputs = ninput_items_required.size ();
    for (unsigned i = 0; i < ninputs; i++)
      ninput_items_required[i] = noutput_items;
  }

Although the 1:1 implementation worked for howto_square_ff, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between noutput_items and the input requirements. That said, by deriving your classes from gr_sync_block, gr_sync_interpolator or gr_sync_decimator instead of gr_block, you can often avoid implementing forecast.

set_output_multiple

When implementing your general_work routine, it's occasionally convenient to have the run time system ensure that you are only asked to produce a number of output items that is a multiple of some particular value. This might occur if your algorithm naturally applies to a fixed sized block of data. Call set_output_multiple in your constructor to specify this requirement. The default output multiple is 1.

Subclasses for common patterns

gr_block allows tremendous flexibility with regard to the consumption of input streams and the production of output streams. Adroit use of forecast and consume allows variable rate blocks to be built. It is possible to construct blocks that consume data at different rates on each input, and produce output at a rate that is a function of the contents of the input data.

On the other hand, it is very common for signal processing blocks to have a fixed relationship between the input rate and the output rate. Many are 1:1, while others have 1:N or N:1 relationships.

Another common requirement is the need to examine more than one input sample to produce a single output sample. This is orthogonal to the relationship between input and output rate. For example, a non-decimating, non-interpolating FIR filter needs to examine N input samples for each output sample it produces, where N is the number of taps in the filter. However, it only consumes a single input sample to produce a single output. We call this concept "history", but you could also think of it as "look-ahead".

gr_sync_block

gr_sync_block is derived from gr_block and implements a 1:1 block with optional history. Given that we know the input to output rate, certain simplifications are possible. From the implementor's point-of-view, the primary change is that we define a work method instead of general_work. work has a slightly different calling sequence; It omits the unnecessary ninput_items parameter, and arranges for consume_each to be called on our behalf.

  /*!
   * \brief Just like gr_block::general_work, only this arranges to
   *  call consume_each for you.
   *
   * The user must override work to define the signal processing code
   */
  virtual int work (int noutput_items,
                    gr_vector_const_void_star &input_items,
                    gr_vector_void_star &output_items) = 0;

This gives us fewer things to worry about, and less code to write. If the block requires history greater than 1, call set_history in the constructor, or any time the requirement changes.

gr_sync_block provides a version of forecast that handles the history requirement.

gr_sync_decimator

gr_sync_decimator is derived from gr_sync_block and implements a N:1 block with optional history.

gr_sync_interpolator

gr_sync_interpolator is derived from gr_sync_block and implements a 1:N block with optional history.

Second Block: howto_square2_ff

Given that we now know about gr_sync_block, the way howto_square_ff should really be implemented is by subclassing gr_sync_block.

Here are the revised sources: Example 7, “howto_square2_ff.h, Example 8, “howto_square2_ff.cc. The accompanying files contain the additional test code.

Example 7. howto_square2_ff.h

  1  /* -*- c++ -*- */
  2  /*
  3   * Copyright 2004 Free Software Foundation, Inc.
  4   * 
  5   * This file is part of GNU Radio
  6   * 
  7   * GNU Radio is free software; you can redistribute it and/or modify
  8   * it under the terms of the GNU General Public License as published by
  9   * the Free Software Foundation; either version 2, or (at your option)
 10   * any later version.
 11   * 
 12   * GNU Radio is distributed in the hope that it will be useful,
 13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   * GNU General Public License for more details.
 16   * 
 17   * You should have received a copy of the GNU General Public License
 18   * along with GNU Radio; see the file COPYING.  If not, write to
 19   * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 20   * Boston, MA 02111-1307, USA.
 21   */
 22  #ifndef INCLUDED_HOWTO_SQUARE2_FF_H
 23  #define INCLUDED_HOWTO_SQUARE2_FF_H
 24  
 25  #include <gr_sync_block.h>
 26  
 27  class howto_square2_ff;
 28  
 29  /*
 30   * We use boost::shared_ptr's instead of raw pointers for all access
 31   * to gr_blocks (and many other data structures).  The shared_ptr gets
 32   * us transparent reference counting, which greatly simplifies storage
 33   * management issues.  This is especially helpful in our hybrid
 34   * C++ / Python system.
 35   *
 36   * See http://www.boost.org/libs/smart_ptr/smart_ptr.htm
 37   *
 38   * As a convention, the _sptr suffix indicates a boost::shared_ptr
 39   */
 40  typedef boost::shared_ptr<howto_square2_ff> howto_square2_ff_sptr;
 41  
 42  /*!
 43   * \brief Return a shared_ptr to a new instance of howto_square2_ff.
 44   *
 45   * To avoid accidental use of raw pointers, howto_square2_ff's
 46   * constructor is private.  howto_make_square2_ff is the public
 47   * interface for creating new instances.
 48   */
 49  howto_square2_ff_sptr howto_make_square2_ff ();
 50  
 51  /*!
 52   * \brief square2 a stream of floats.
 53   * \ingroup block
 54   *
 55   * This uses the preferred technique: subclassing gr_sync_block.
 56   */
 57  class howto_square2_ff : public gr_sync_block
 58  {
 59  private:
 60    // The friend declaration allows howto_make_square2_ff to
 61    // access the private constructor.
 62  
 63    friend howto_square2_ff_sptr howto_make_square2_ff ();
 64  
 65    howto_square2_ff ();          // private constructor
 66  
 67   public:
 68    ~howto_square2_ff (); // public destructor
 69  
 70    // Where all the action really happens
 71  
 72    int work (int noutput_items,
 73              gr_vector_const_void_star &input_items,
 74              gr_vector_void_star &output_items);
 75  };
 76  
 77  #endif /* INCLUDED_HOWTO_SQUARE2_FF_H */

Example 8. howto_square2_ff.cc

  1  /* -*- c++ -*- */
  2  /*
  3   * Copyright 2004 Free Software Foundation, Inc.
  4   * 
  5   * This file is part of GNU Radio
  6   * 
  7   * GNU Radio is free software; you can redistribute it and/or modify
  8   * it under the terms of the GNU General Public License as published by
  9   * the Free Software Foundation; either version 2, or (at your option)
 10   * any later version.
 11   * 
 12   * GNU Radio is distributed in the hope that it will be useful,
 13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   * GNU General Public License for more details.
 16   * 
 17   * You should have received a copy of the GNU General Public License
 18   * along with GNU Radio; see the file COPYING.  If not, write to
 19   * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 20   * Boston, MA 02111-1307, USA.
 21   */
 22  
 23  /*
 24   * config.h is generated by configure.  It contains the results
 25   * of probing for features, options etc.  It should be the first
 26   * file included in your .cc file.
 27   */
 28  #ifdef HAVE_CONFIG_H
 29  #include "config.h"
 30  #endif
 31  
 32  #include <howto_square2_ff.h>
 33  #include <gr_io_signature.h>
 34  
 35  /*
 36   * Create a new instance of howto_square2_ff and return
 37   * a boost shared_ptr.  This is effectively the public constructor.
 38   */
 39  howto_square2_ff_sptr 
 40  howto_make_square2_ff ()
 41  {
 42    return howto_square2_ff_sptr (new howto_square2_ff ());
 43  }
 44  
 45  /*
 46   * Specify constraints on number of input and output streams.
 47   * This info is used to construct the input and output signatures
 48   * (2nd & 3rd args to gr_block's constructor).  The input and
 49   * output signatures are used by the runtime system to
 50   * check that a valid number and type of inputs and outputs
 51   * are connected to this block.  In this case, we accept
 52   * only 1 input and 1 output.
 53   */
 54  static const int MIN_IN = 1;    // mininum number of input streams
 55  static const int MAX_IN = 1;    // maximum number of input streams
 56  static const int MIN_OUT = 1;   // minimum number of output streams
 57  static const int MAX_OUT = 1;   // maximum number of output streams
 58  
 59  /*
 60   * The private constructor
 61   */
 62  howto_square2_ff::howto_square2_ff ()
 63    : gr_sync_block ("square2_ff",
 64                     gr_make_io_signature (MIN_IN, MAX_IN, sizeof (float)),
 65                     gr_make_io_signature (MIN_OUT, MAX_OUT, sizeof (float)))
 66  {
 67    // nothing else required in this example
 68  }
 69  
 70  /*
 71   * Our virtual destructor.
 72   */
 73  howto_square2_ff::~howto_square2_ff ()
 74  {
 75    // nothing else required in this example
 76  }
 77  
 78  int 
 79  howto_square2_ff::work (int noutput_items,
 80                          gr_vector_const_void_star &input_items,
 81                          gr_vector_void_star &output_items)
 82  {
 83    const float *in = (const float *) input_items[0];
 84    float *out = (float *) output_items[0];
 85  
 86    for (int i = 0; i < noutput_items; i++){
 87      out[i] = in[i] * in[i];
 88    }
 89  
 90    // Tell runtime system how many output items we produced.
 91    return noutput_items;
 92  }

Where to from Here?

At this point, we've got a basic overview of how the system goes together. For more insight, I suggest that you look at the code of the system. The doxygen generated class hierarchy is a useful way to find things that might interest you.

Miscellaneous Tips

Sources and Sinks

Sources and sinks are derived from gr_sync_block. The only thing different about them is that sources have no inputs and sinks have no outputs. This is reflected in the gr_io_signatures that are passed to the gr_sync_block constructor. Take a look at gr_file_source.{h,cc} and gr_file_sink.{h,cc} for some very straight-forward examples.

Debugging with gdb

If your block isn't working, and you can't sort it out through python test cases or a few printfs in the code, you may want to use gdb to debug it. The trick of course is that all of GNU Radio, including your new block, is dynamically loaded into python for execution.

Try this: In your python test code, after the relevant imports, print out the process id and wait for a keystroke. In another window run gdb and tell it to attach to the python process with the given process id. At this point you can set breakpoints or whatever in your code. Go back to the python window and hit Enter so it'll continue.

  #!/usr/bin/env python
  from gnuradio import gr
  from gnuradio import my_buggy_module

  # insert this in your test code...
  import os
  print 'Blocked waiting for GDB attach (pid = %d)' % (os.getpid(),)
  raw_input ('Press Enter to continue: ')
  # remainder of your test code follows...

Another SNAFU you might run into is that gdb 6.2 isn't able to set breakpoints in the constructors or destructors generated by g++ 3.4. In this case, insert a call to the nop function gri_debugger_hook in the constructor and recompile. Load the code as before and set a break point on gri_debugger_hook.

Performance Measurement with oprofile

Oprofile is your friend. See http://oprofile.sourceforge.net.

Coming Attractions

Improved Type System

Hierarchical Blocks