Copyright © 2004, 2005 Free Software Foundation, Inc.
Revision History | |
---|---|
Revision 0.1 | 2005-01-20 |
Revision 0.2 | 2005-02-02 |
Updated for SWIG 1.3.24 | |
Revision 0.3 | 2006-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
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.
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.
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.
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 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 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 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 Name | Comment |
---|---|
topdir /Makefile.am | Top level Makefile.am |
topdir /Makefile.common | Common fragment included in sub-Makefiles |
topdir /bootstrap | Runs autoconf, automake, libtool first time through |
topdir /config | Directory of m4 macros used by configure.ac |
topdir /configure.ac | Input to autoconf |
topdir /src | |
topdir /src/lib | C++ code goes here |
topdir /src/lib/Makefile.am | |
topdir /src/python | Python code goes here |
topdir /src/python/Makefile.am | |
topdir /src/python/run_tests | Script to run tests in the build tree |
GNU Radio uses a set of naming conventions to assist in comprehending the code base and gluing C++ and Python together. Please follow them.
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
.
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.
These are the current package prefixes:
Almost everything.
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.
Code related to the Advanced Television Standards Committee HDTV implementation
Universal Software Radio Peripheral.
Quality Assurance (Test code.)
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)
{
...
}
...
};
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
.
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.
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 ()
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.
The build tree is everything from topdir
(the one containing configure.ac) down. The path to the install tree is
,
where prefix
/lib/pythonversion
/site-packagesprefix
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.
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.
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_ptr
s.
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
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 };
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!
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.
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_signature
s 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
.
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.
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
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
is derived from
gr_sync_block
and implements a N:1 block with optional history.
gr_sync_interpolator
is derived from
gr_sync_block
and implements a 1:N block with optional history.
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 }
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.
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_signature
s 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.
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.
Oprofile is your friend. See http://oprofile.sourceforge.net.