diff options
author | Johnathan Corgan | 2009-11-12 10:50:03 -0800 |
---|---|---|
committer | Johnathan Corgan | 2009-11-12 10:50:03 -0800 |
commit | b3858bfe3b559722bd788e7c32159d4963422c01 (patch) | |
tree | 570dabd2bf93584af5ecab429089783eb24903b8 /docs | |
parent | 5d80acab66a91a6c7bc476761f857d5e876e8bcc (diff) | |
download | gnuradio-b3858bfe3b559722bd788e7c32159d4963422c01.tar.gz gnuradio-b3858bfe3b559722bd788e7c32159d4963422c01.tar.bz2 gnuradio-b3858bfe3b559722bd788e7c32159d4963422c01.zip |
howto: move limbo'd docs into toplevel doc dir for preservation
Diffstat (limited to 'docs')
-rw-r--r-- | docs/howto-write-a-block/.gitignore | 17 | ||||
-rw-r--r-- | docs/howto-write-a-block/Makefile.am | 81 | ||||
-rw-r--r-- | docs/howto-write-a-block/README | 4 | ||||
-rw-r--r-- | docs/howto-write-a-block/howto-write-a-block.xml | 959 | ||||
-rw-r--r-- | docs/howto-write-a-block/howto_1.i | 29 | ||||
-rwxr-xr-x | docs/howto-write-a-block/make_numbered_listing.py | 45 | ||||
-rwxr-xr-x | docs/howto-write-a-block/qa_howto_1.py | 27 | ||||
-rw-r--r-- | docs/howto-write-a-block/src_lib_Makefile_1.am | 25 | ||||
-rw-r--r-- | docs/howto-write-a-block/src_lib_Makefile_2.am | 86 |
9 files changed, 1270 insertions, 3 deletions
diff --git a/docs/howto-write-a-block/.gitignore b/docs/howto-write-a-block/.gitignore index 719ea5d4f..f65ab6cf7 100644 --- a/docs/howto-write-a-block/.gitignore +++ b/docs/howto-write-a-block/.gitignore @@ -1 +1,18 @@ +/Makefile +/Makefile.in +/.deps +/.libs +/*.la +/*.lo +/autom4te.cache +/*.cache /howto-write-a-block.html +/gr_block.h.xml +/howto_1.i.xml +/howto_square_ff.cc.xml +/howto_square_ff.h.xml +/qa_howto_1.py.xml +/src_lib_Makefile_1.am.xml +/src_lib_Makefile_2.am.xml +/howto_square2_ff.cc.xml +/howto_square2_ff.h.xml diff --git a/docs/howto-write-a-block/Makefile.am b/docs/howto-write-a-block/Makefile.am new file mode 100644 index 000000000..5f58a21e9 --- /dev/null +++ b/docs/howto-write-a-block/Makefile.am @@ -0,0 +1,81 @@ +# +# Copyright 2004,2005,2007 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +TARGETS = howto-write-a-block.html + +# To avoid build problems for folks who don't have xmlto installed, we +# don't build the docs by default. + +# html: $(TARGETS) +all: $(TARGETS) + + +EXTRA_DIST = \ + README \ + howto-write-a-block.xml \ + howto_1.i \ + make_numbered_listing.py \ + qa_howto_1.py \ + src_lib_Makefile_1.am \ + src_lib_Makefile_2.am + + +BUILT_XML_FILES = \ + gr_block.h.xml \ + howto_1.i.xml \ + howto_square_ff.cc.xml \ + howto_square_ff.h.xml \ + howto_square2_ff.cc.xml \ + howto_square2_ff.h.xml \ + qa_howto_1.py.xml \ + src_lib_Makefile_1.am.xml \ + src_lib_Makefile_2.am.xml + + +howto-write-a-block.html : howto-write-a-block.xml $(BUILT_XML_FILES) + + +gr_block.h.xml: $(GNURADIO_CORE_INCLUDEDIR)/gr_block.h make_numbered_listing.py + $(PYTHON) ./make_numbered_listing.py $(GNURADIO_CORE_INCLUDEDIR)/gr_block.h + +howto_square_ff.cc.xml: $(top_srcdir)/src/lib/howto_square_ff.cc make_numbered_listing.py + $(PYTHON) ./make_numbered_listing.py $(top_srcdir)/src/lib/howto_square_ff.cc + +howto_square_ff.h.xml: $(top_srcdir)/src/lib/howto_square_ff.h make_numbered_listing.py + $(PYTHON) ./make_numbered_listing.py $(top_srcdir)/src/lib/howto_square_ff.h + +howto_square2_ff.cc.xml: $(top_srcdir)/src/lib/howto_square2_ff.cc make_numbered_listing.py + $(PYTHON) ./make_numbered_listing.py $(top_srcdir)/src/lib/howto_square2_ff.cc + +howto_square2_ff.h.xml: $(top_srcdir)/src/lib/howto_square2_ff.h make_numbered_listing.py + $(PYTHON) ./make_numbered_listing.py $(top_srcdir)/src/lib/howto_square2_ff.h + + +# ---------------------------------------------------------------- + +clean: + -${RM} -f $(TARGETS) $(BUILT_XML_FILES) + +%.html : %.xml + xmlto html-nochunks $< + +%.xml : % make_numbered_listing.py + $(PYTHON) ./make_numbered_listing.py $< diff --git a/docs/howto-write-a-block/README b/docs/howto-write-a-block/README index 73b8e6028..ff3b75e57 100644 --- a/docs/howto-write-a-block/README +++ b/docs/howto-write-a-block/README @@ -1,3 +1 @@ -If you're looking for howto-write-a-block, it was moved into it's own -tarball: gr-howto-write-a-block. It includes the document source -and a full build tree, Makefiles, examples, etc. +The contents of this directory are obsolete. diff --git a/docs/howto-write-a-block/howto-write-a-block.xml b/docs/howto-write-a-block/howto-write-a-block.xml new file mode 100644 index 000000000..f8027b456 --- /dev/null +++ b/docs/howto-write-a-block/howto-write-a-block.xml @@ -0,0 +1,959 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "docbookx.dtd" [ + <!ENTITY gnuradio "<application>GNU Radio</application>"> + <!ENTITY SWIG "<application>SWIG</application>"> + <!ENTITY gr_block "<classname>gr_block</classname>"> + <!ENTITY square "<classname>howto_square_ff</classname>"> + + <!ENTITY were "we're"> + <!ENTITY well "we'll"> + <!ENTITY thats "that's"> + <!ENTITY its "it's"> + <!ENTITY lets "let's"> + <!ENTITY youre "you're"> + + <!ENTITY gr_block_listing SYSTEM "gr_block.h.xml"> + <!ENTITY qa_howto_1_listing SYSTEM "qa_howto_1.py.xml"> + <!ENTITY howto_square_ff_h_listing SYSTEM "howto_square_ff.h.xml"> + <!ENTITY howto_square_ff_cc_listing SYSTEM "howto_square_ff.cc.xml"> + <!ENTITY howto_square2_ff_h_listing SYSTEM "howto_square2_ff.h.xml"> + <!ENTITY howto_square2_ff_cc_listing SYSTEM "howto_square2_ff.cc.xml"> + <!ENTITY howto_1_i_listing SYSTEM "howto_1.i.xml"> + <!ENTITY src_lib_Makefile_1_am_listing SYSTEM "src_lib_Makefile_1.am.xml"> + <!ENTITY src_lib_Makefile_2_am_listing SYSTEM "src_lib_Makefile_2.am.xml"> + +]> + +<article> + +<articleinfo> +<title>How to Write a Signal Processing Block</title> +<author> + <firstname>Eric</firstname> + <surname>Blossom</surname> + <affiliation> + <address> + <email>eb@comsec.com</email> + </address> + </affiliation> +</author> + +<revhistory> + <revision> + <revnumber>0.1</revnumber> + <date>2005-01-20</date> + </revision> + <revision> + <revnumber>0.2</revnumber> + <date>2005-02-02</date> + <revremark>Updated for SWIG 1.3.24</revremark> + </revision> + <revision> + <revnumber>0.3</revnumber> + <date>2006-07-21</date> + <revremark>Clarification of 1:1 fixed rate vs item size</revremark> + </revision> +</revhistory> + +<copyright> + <year>2004</year> + <year>2005</year> + <holder>Free Software Foundation, Inc.</holder> +</copyright> + +<abstract><para>This article explains how to write signal +processing blocks for <application>GNU Radio</application>. +</para></abstract> + +</articleinfo> + +<sect1 id="prereqs"><title>Prerequisites</title> +<para>This article assumes that the reader has basic familiarity with +GNU Radio and has read and understood +<ulink url="http://www.gnu.org/software/gnuradio/doc/exploring-gnuradio.html"> +<citetitle>Exploring GNU Radio</citetitle></ulink>. +</para> + +<para>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 <ulink +url="ftp://ftp.gnu.org/gnu/gnuradio"> +ftp://ftp.gnu.org/gnu/gnuradio</ulink> or one of the mirrors. The +file you want is +<filename>gr-howto-write-a-block-X.Y.tar.gz</filename>. Pick the one +with the highest version number. +See <ulink url="http://comsec.com/wiki?CvsAccess"> +http://comsec.com/wiki?CvsAccess</ulink> for CVS Access. +</para> + + +</sect1> + +<sect1 id="intro"><title>Introduction</title> +<para>&gnuradio; 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++.</para> + +<para>From the Python point of view, &gnuradio; provides a data flow +abstraction. The fundamental concepts are signal processing +blocks and the connections between them. This abstraction is +implemented by the Python <classname>gr.flow_graph</classname> 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 +<classname>float</classname> and <classname>gr_complex</classname> +(equivalent to std::complex<float>), though other types are used, +including those representing structures, arrays or other types of +packetized data.</para> + +<para>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.</para> + +</sect1> + +<sect1 id="overview"><title>The View from 30,000 Feet</title> + +<para>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.</para> + +<para>The example blocks will be built in the style of all &gnuradio; +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.</para> + +</sect1> + + +<sect1 id="gr_block"><title></title> + +<para>The C++ class &gr_block; is the base of all signal processing +blocks in &gnuradio;. 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.</para> + +<para>Our first examples will derive directly from &gr_block;. Later +we will look at some other subclasses that simplify the process for +common cases.</para> + +</sect1><!-- end gr_block sect1 --> + + + +<!-- ================================================================ --> + +<sect1 id="autotools"><title>Autotools, Makefiles, and Directory Layout</title> + +<para>Before we dive into the code, &lets; talk a bit about the +overall build environment and the directory structure that &well; +be using.</para> + +<para>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 +<application>autoconf</application>, +<application>automake</application>, and +<application>libtool</application> 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.)</para> + +<variablelist> + +<varlistentry><term>automake</term> + +<listitem><para>automake and configure work together to generate GNU +compliant Makefiles from a much higher level description contained in +the corresponding Makefile.am file. <filename>Makefile.am</filename> +specifies the libraries and programs to build and the source files +that compose each. Automake reads <filename>Makefile.am</filename> +and produces <filename>Makefile.in</filename>. Configure reads +<filename>Makefile.in</filename> and produces +<filename>Makefile</filename>. 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 +<filename>Makefile</filename> to be 5 or 6 times larger than +<filename>Makefile.am</filename>.</para> + +</listitem> +</varlistentry> + +<varlistentry><term>autoconf</term> +<listitem><para>autoconf reads <filename>configure.ac</filename> +and produces the <filename>configure</filename> shell +script. <filename>configure</filename> 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.</para> +</listitem> +</varlistentry> + +<varlistentry><term>libtool</term> +<listitem><para>libtool works behind the scenes and provides the magic +to construct shared libraries on a wide variety of systems.</para> +</listitem> +</varlistentry> + +</variablelist> + +<para><xref linkend="dir-layout"/> shows the directory layout and +common files &well; be using. After renaming the +<replaceable>topdir</replaceable> directory, use it in your projects +too. We'll talk about particular files as they come up later.</para> + + +<table id="dir-layout"><title>Directory Layout</title> +<tgroup cols="2"> + +<thead><row> +<entry>File/Dir Name</entry> +<entry>Comment</entry> +</row> +</thead> + +<tbody> + +<row> +<entry><replaceable>topdir</replaceable>/Makefile.am</entry> +<entry>Top level Makefile.am</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/Makefile.common</entry> +<entry>Common fragment included in sub-Makefiles</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/bootstrap</entry> +<entry>Runs autoconf, automake, libtool first time through</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/config</entry> +<entry>Directory of m4 macros used by configure.ac</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/configure.ac</entry> +<entry>Input to autoconf</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/src</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/src/lib</entry> +<entry>C++ code goes here</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/src/lib/Makefile.am</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/src/python</entry> +<entry>Python code goes here</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/src/python/Makefile.am</entry> +</row> +<row> +<entry><replaceable>topdir</replaceable>/src/python/run_tests</entry> +<entry>Script to run tests in the build tree</entry> +</row> + +</tbody> +</tgroup> +</table> + +</sect1> + +<!-- ================================================================ --> + +<sect1 id="naming"><title>Naming Conventions</title> + +<para>&gnuradio; uses a set of naming conventions to assist in +comprehending the code base and gluing C++ and Python together. +Please follow them.</para> + +<sect2 id="camel-case"><title><emphasis>Death to CamelCaseNames!</emphasis></title> + +<para>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.</para> + +<para>With the exception of macros and other constant values, all +identifiers shall be lower case with <literal>words_separated_like_this</literal>.</para> + +<para>Macros and constant values (e.g., enumerated values, +<literal>static const int FOO = 23</literal>) shall be in <literal>UPPER_CASE</literal>.</para> + +</sect2> + +<sect2 id="global_names"><title>Global Names</title> + +<para>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 <literal>gr_open_file (...)</literal>.</para> + +<para>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.</para> + +</sect2> + +<sect2 id="package_prefixes"><title>Package Prefixes</title> + +<para>These are the current package prefixes: + +<variablelist> + +<varlistentry><term>gr_</term> +<listitem><para>Almost everything.</para></listitem> +</varlistentry> + +<varlistentry><term>gri_</term> +<listitem><para> +Implementation primitives. Sometimes we +have both a gr_<replaceable>foo</replaceable> and a gri_<replaceable>foo</replaceable>. In that case, +gr_<replaceable>foo</replaceable> would be derived from gr_block and gri_<replaceable>foo</replaceable> +would be the low level guts of the function.</para></listitem> +</varlistentry> + +<varlistentry><term>atsc_</term> +<listitem><para>Code related to the Advanced Television Standards Committee HDTV implementation +</para></listitem> +</varlistentry> + +<varlistentry><term>usrp_</term> +<listitem><para>Universal Software Radio Peripheral.</para></listitem> +</varlistentry> + +<varlistentry><term>qa_</term> +<listitem><para>Quality Assurance (Test code.)</para></listitem> +</varlistentry> + +</variablelist> + +</para> +</sect2> + +<sect2 id="class-data-members"><title>Class Data Members (instance variables)</title> + +<para>All class data members shall begin with d_<replaceable>foo</replaceable>.</para> + +<para>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_. </para> + +<literallayout> +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) + { + ... + } + ... +}; +</literallayout> + +</sect2> + +<sect2 id="static-data-members"><title>Class Static Data Members (class variables)</title> + +<para> +All class static data members shall begin with s_<replaceable>foo</replaceable>. +</para> + +</sect2> + +<sect2 id="file-names"><title>File Names</title> + +<para>Each significant class shall be contained in its own file. The +declaration of class <classname>gr_foo</classname> shall be in +<filename>gr_foo.h</filename> and the definition in +<filename>gr_foo.cc</filename>.</para> +</sect2> + + +<sect2><title>Suffixes</title> + +<para>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.</para> + +<para>These are the suffix characters and their interpretations: +<itemizedlist> +<listitem><para>f - single precision floating point</para></listitem> +<listitem><para>c - complex<float></para></listitem> +<listitem><para>s - short (16-bit integer)</para></listitem> +<listitem><para>i - integer (32-bit integer)</para></listitem> +</itemizedlist> +</para> + +<para>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 +<classname>gr_fft_vcc</classname>. The FFT block takes a vector of +complex numbers on its input and produces a vector of complex +numbers on its output.</para> + +</sect2> + +</sect1> + + + + +<sect1 id="square"><title>First Block: □</title> + +<para>For our first example &well; 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.</para> + +<para>Following the naming conventions, &well; use +<literal>howto</literal> as our package prefix, and the block will +be called <classname>howto_square_ff</classname>.</para> + +<para>We are going to arrange that this block, as well as the others +that we write in this article, end up in the +<literal>gnuradio.howto</literal> Python module. This will allow us +to access it from Python like this: +<programlisting> +from gnuradio import howto +sqr = howto.square_ff () +</programlisting> +</para> + + +<sect2 id="test_driven"><title>Test Driven Programming</title> + +<para>We could just start banging out the C++ code, but being highly +evolved modern programmers, &were; 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.</para> + +<para>How hard could this be? Turns out that this is easy! Check out +<xref linkend="qa_howto_1.py"/>.</para> + +<example id="qa_howto_1.py"> +<title><filename>qa_howto.py</filename> (first version)</title> +&qa_howto_1_listing; +</example> + +<para> +<classname>gr_unittest</classname> is an extension to the standard +python module <classname>unittest</classname>. +<classname>gr_unittest</classname> 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 +<methodname>test_</methodname> and runs them. Unittest wraps each call +to <methodname>test_*</methodname> with matching calls to +<methodname>setUp</methodname> and <methodname>tearDown</methodname>. +See the python <ulink url="http://docs.python.org/lib/module-unittest.html"> +unittest</ulink> documentation for details. +</para> + +<para>When we run the test, +gr_unittest.main is going to invoke +<methodname>setUp</methodname>, +<methodname>test_001_square_ff</methodname>, and +<methodname>tearDown</methodname>.</para> +<para> +<methodname>test_001_square_ff</methodname> 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 &its; finished. howto.square_ff is the block +&were; testing. gr.vector_sink_f gathers the output of +howto.square_ff.</para> + +<para>The <methodname>run</methodname> 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. +</para> + +</sect2> + +<sect2 id="build_vs_install"><title>Build Tree vs. Install Tree</title> + +<para>The build tree is everything from <replaceable>topdir</replaceable> +(the one containing configure.ac) down. The path to the install tree is +<filename> +<replaceable>prefix</replaceable>/lib/python<replaceable>version</replaceable>/site-packages</filename>, +where <replaceable>prefix</replaceable> is the <literal>--prefix</literal> +argument to configure (default <filename>/usr/local</filename>) and +<replaceable>version</replaceable> is the installed version of +python. A typical value is +<filename>/usr/local/lib/python2.3/site-packages</filename>.</para> + + +<para>We normally set our PYTHONPATH environment variable to point at +the install tree, and do this in <filename>~/.bash_profile</filename> +or <filename>~/.profile</filename>. +This allows our python apps to access all the standard python +libraries, plus our locally installed stuff like GNU Radio.</para> + +<para>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.</para> + +</sect2> + +<sect2 id="make_check"><title>make check</title> + + +<para>We use <command>make check</command> to run our tests. +Make check invokes the <command>run_tests</command> 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 <filename>qa_*.py</filename> and reports +the overall success or failure.</para> + +<para>There is quite a bit of behind-the-scenes action required to use +the non-installed versions of our code (look at +<filename>runtest</filename> for a cheap thrill.)</para> + +<para>Finally, running <command>make check</command> in the python +directory produces this result: +<literallayout> + [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]$ +</literallayout> +Excellent! Our test failed, just as we expected. The ImportError +indicates that it can't find the module named +<classname>howto</classname>. No surprise, since we haven't written it yet. +</para> + +</sect2> + +<sect2><title>The C++ code</title> +<para>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 <classname>gr_block</classname> or +one of its subclasses. Let's take a look at +<xref linkend="gr_block.h"/>.</para> + +<example id="gr_block.h"> +<title><filename>gr_block.h</filename></title> +&gr_block_listing; +</example> + +<para>A quick scan of <filename>gr_block.h</filename> reveals that +since <methodname>general_work</methodname> is pure virtual, we +definitely need to override that. +<methodname>general_work</methodname> is the method that does the +actual signal processing. For our squaring example we'll +need to override <methodname>general_work</methodname> and provide a +constructor and destructor and a bit of stuff to take advantage of +the <ulink url="http://www.boost.org">boost</ulink> +<ulink url="http://www.boost.org/libs/smart_ptr/smart_ptr.htm"> +<classname>shared_ptr</classname>s.</ulink> + +</para> + + +<para><xref linkend="howto_square_ff.h"/> +and <xref linkend="howto_square_ff.cc"/> are the header and c++ +source.</para> + +<example id="howto_square_ff.h"> +<title><filename>howto_square_ff.h</filename></title> +&howto_square_ff_h_listing; +</example> + +<example id="howto_square_ff.cc"> +<title><filename>howto_square_ff.cc</filename></title> +&howto_square_ff_cc_listing; +</example> + +<para>Now we need a Makefile.am to get all this to build. +<xref linkend="src_lib_Makefile_1"/> +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.</para> + +<example id="src_lib_Makefile_1"> +<title><filename>src/lib/Makefile.am</filename> (no &SWIG;)</title> +&src_lib_Makefile_1_am_listing; +</example> + +</sect2> + + +<!-- ============================== + +<sect2 id="io_sig"><title><classname>gr_io_signature</classname></title> +<para></para> +</sect2> + +<sect2 id="forecast"><title><methodname>forecast</methodname></title> +<para></para> +</sect2> + +<sect2 id="output_multiple"> +<title><methodname>set_output_multiple</methodname></title> +<para></para> +</sect2> + + ============================== --> + + +<sect2 id="swig"><title>The &SWIG; .i file</title> + +<para>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 &well; want to +access from Python.</para> + +<para>We're going to call the .i file +<filename>howto.i</filename>, and use it to hold the &SWIG; +declarations for all classes from <literal>howto</literal> that will +be accessible from python. It's quite small: +&howto_1_i_listing; +</para> + +</sect2> + +<sect2><title>Putting it all together</title> +<para> +Now we need to modify <filename>src/lib/Makefile.am</filename> +to run &SWIG; and to add the glue it generates to the shared library.</para> + +<example id="src_lib_Makefile_2"> +<title><filename>src/lib/Makefile.am</filename> (with &SWIG;)</title> +&src_lib_Makefile_2_am_listing; +</example> + +<para><command>make</command> now builds everything successfully. We get a +few warnings, but &thats; OK.</para> + +<para>Changing directories back to the python directory we try +<command>make check</command> again: +<literallayout> + [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]$ +</literallayout> +<emphasis>Victory! Our new block works!</emphasis> +</para> + +</sect2> + +</sect1><!-- end First Block: square --> + +<sect1 id="additional_methods"><title>Additional gr_block methods</title> + +<para>In our <classname>howto_square_ff</classname> example above, we only +had to override the <methodname>general_work</methodname> method to +accomplish our goal. <classname>gr_block</classname> provides a few other +methods that are sometimes useful.</para> + +<sect2 id="forecast"><title>forecast</title> + +<para>Looking at <methodname>general_work</methodname> you may +have wondered how the system knows how much data it needs to +ensure is valid in each of the input arrays. The +<methodname>forecast</methodname> method provides this +information.</para> + +<para>The default implementation of <methodname>forecast</methodname> +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 +<classname>gr_io_signature</classname>s in the constructor of +<classname>gr_block</classname>. The sizes of the input and output items +can of course differ; this still qualifies as a 1:1 relationship. +<programlisting> + // 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; + } +</programlisting> +</para> + +<para>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 +<classname>gr_sync_block</classname>, +<classname>gr_sync_interpolator</classname> or +<classname>gr_sync_decimator</classname> instead of +<classname>gr_block</classname>, you can often avoid +implementing <methodname>forecast</methodname>.</para> + +</sect2> + +<sect2 id="set_output_multiple"><title>set_output_multiple</title> + +<para>When implementing your <methodname>general_work</methodname> +routine, &its; 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 <methodname>set_output_multiple</methodname> in your constructor +to specify this requirement. The default output multiple is 1.</para> + +</sect2> + +</sect1> + + +<sect1 id="common_patterns"> +<title>Subclasses for common patterns</title> + +<para><classname>gr_block</classname> allows tremendous flexibility +with regard to the consumption of input streams and the production of +output streams. Adroit use of <methodname>forecast</methodname> and +<methodname>consume</methodname> 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.</para> + +<para>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.</para> + +<para>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".</para> + +<sect2 id="gr_sync_block"><title><classname>gr_sync_block</classname></title> + +<para> +<ulink url="http://www.gnu.org/software/gnuradio/doc/classgr__sync__block.html"> +<classname>gr_sync_block</classname></ulink> +is derived from +<ulink url="http://www.gnu.org/software/gnuradio/doc/classgr__block.html"> +<classname>gr_block</classname></ulink> +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 +<methodname>work</methodname> method instead of +<methodname>general_work</methodname>. <methodname>work</methodname> +has a slightly different calling sequence; +It omits the unnecessary ninput_items parameter, and arranges for +<methodname>consume_each</methodname> to be called on our +behalf.</para> +<programlisting> + /*! + * \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; +</programlisting> + +<para>This gives us fewer things to worry about, and less code to +write. If the block requires history greater than 1, call +<methodname>set_history</methodname> in the constructor, or any time +the requirement changes.</para> + +<para><classname>gr_sync_block</classname> provides a +version of <methodname>forecast</methodname> that handles the +history requirement.</para> + +</sect2> + +<sect2 id="gr_sync_decimator"><title><classname>gr_sync_decimator</classname></title> + +<para> +<ulink url="http://www.gnu.org/software/gnuradio/doc/classgr__sync__decimator.html"> +<classname>gr_sync_decimator</classname></ulink> +is derived from +<ulink url="http://www.gnu.org/software/gnuradio/doc/classgr__sync__block.html"> +<classname>gr_sync_block</classname></ulink> +and implements a N:1 block with optional history. +</para> + +</sect2> + +<sect2 id="gr_sync_interpolator"><title><classname>gr_sync_interpolator</classname></title> + +<para> +<ulink url="http://www.gnu.org/software/gnuradio/doc/classgr__sync__interpolator.html"> +<classname>gr_sync_interpolator</classname></ulink> +is derived from +<ulink url="http://www.gnu.org/software/gnuradio/doc/classgr__sync__block.html"> +<classname>gr_sync_block</classname></ulink> +and implements a 1:N block with optional history. +</para> + +</sect2> + + +</sect1> + +<sect1 id="square2"> +<title>Second Block: <classname>howto_square2_ff</classname></title> + +<para>Given that we now know about +<classname>gr_sync_block</classname>, the way +<classname>howto_square_ff</classname> should really be implemented is +by subclassing <classname>gr_sync_block</classname>.</para> + +<para>Here are the revised sources: <xref +linkend="howto_square2_ff.h"/>, +<xref linkend="howto_square2_ff.cc"/>. +The accompanying files contain the additional test code. +</para> + +<example id="howto_square2_ff.h"> +<title><filename>howto_square2_ff.h</filename></title> +&howto_square2_ff_h_listing; +</example> + +<example id="howto_square2_ff.cc"> +<title><filename>howto_square2_ff.cc</filename></title> +&howto_square2_ff_cc_listing; +</example> + +</sect1> + +<sect1 id="where_to"><title>Where to from Here?</title> + +<para>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 <ulink +url="http://www.gnu.org/software/gnuradio/doc/hierarchy.html"> class +hierarchy</ulink> is a useful way to find things that might interest +you.</para> + +</sect1> + + +<sect1 id="tips"><title>Miscellaneous Tips</title> + +<sect2 id="sources_and_sinks"><title>Sources and Sinks</title> + +<para>Sources and sinks are derived from +<classname>gr_sync_block</classname>. The only thing different about +them is that sources have no inputs and sinks have no outputs. This +is reflected in the <classname>gr_io_signature</classname>s that are +passed to the <classname>gr_sync_block</classname> constructor. +Take a look at <filename>gr_file_source.{h,cc}</filename> and +<filename>gr_file_sink.{h,cc}</filename> for some very straight-forward examples. +</para> + +</sect2> + +<sect2 id="debugging"> +<title>Debugging with <application>gdb</application></title> + +<para>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 <application>gdb</application> to debug it. The trick of course +is that all of &gnuradio;, including your new block, is dynamically +loaded into python for execution.</para> + +<para>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.</para> + +<programlisting> + #!/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... +</programlisting> + +<para>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.</para> + +</sect2> + +<sect2 id="oprofile"> +<title>Performance Measurement with <application>oprofile</application></title> +<para>Oprofile is your friend. +See <ulink url="http://oprofile.sourceforge.net">http://oprofile.sourceforge.net</ulink>. +</para> +</sect2> + +</sect1><!-- end tips --> + +<sect1 id="futures"><title>Coming Attractions</title> +<para></para> + +<sect2 id="types"><title>Improved Type System</title> +<para></para> +</sect2> + +<sect2 id="hierarchy"><title>Hierarchical Blocks</title> +<para></para> +</sect2> + +</sect1><!-- end Coming Attractions --> + +</article> diff --git a/docs/howto-write-a-block/howto_1.i b/docs/howto-write-a-block/howto_1.i new file mode 100644 index 000000000..640d0897b --- /dev/null +++ b/docs/howto-write-a-block/howto_1.i @@ -0,0 +1,29 @@ +/* -*- c++ -*- */ + +%include "exception.i" +%import "gnuradio.i" // the common stuff + +%{ +#include "gnuradio_swig_bug_workaround.h" // mandatory bug fix +#include "howto_square_ff.h" +#include <stdexcept> +%} + +// ---------------------------------------------------------------- + +/* + * First arg is the package prefix. + * Second arg is the name of the class minus the prefix. + * + * This does some behind-the-scenes magic so we can + * access howto_square_ff from python as howto.square_ff + */ +GR_SWIG_BLOCK_MAGIC(howto,square_ff); + +howto_square_ff_sptr howto_make_square_ff (); + +class howto_square_ff : public gr_block +{ +private: + howto_square_ff (); +}; diff --git a/docs/howto-write-a-block/make_numbered_listing.py b/docs/howto-write-a-block/make_numbered_listing.py new file mode 100755 index 000000000..889c2d78d --- /dev/null +++ b/docs/howto-write-a-block/make_numbered_listing.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +import sys +import os, os.path +from optparse import OptionParser + +def quote_line (line): + line = line.replace ('&', '&') + line = line.replace ('<', '<') + line = line.replace ('>', '>') + line = line.replace ("'", ''') + line = line.replace ('"', '"') + return line + +def generate_listing (input_filename, title=None): + inf = open (input_filename, "r") + output_filename = os.path.basename (input_filename) + '.xml' + outf = open (output_filename, "w") + outf.write ('<?xml version="1.0" encoding="ISO-8859-1"?>\n') + # outf.write ('<example id="%s">\n' % (input_filename,)) + # if not title: + # title = input_filename + # outf.write ('<title>') + # outf.write (title) + # outf.write ('</title>\n') + outf.write ('<programlisting>\n'); + + lineno = 0 + for line in inf: + line = line.expandtabs (8) + line = quote_line (line) + lineno = lineno + 1 + outf.write ('%3d %s' % (lineno, line)) + + outf.write ('</programlisting>\n') + # outf.write ('</example>\n') + + +def main (): + for file in sys.argv[1:]: + generate_listing (file) + +if __name__ == '__main__': + main () + diff --git a/docs/howto-write-a-block/qa_howto_1.py b/docs/howto-write-a-block/qa_howto_1.py new file mode 100755 index 000000000..3173110f8 --- /dev/null +++ b/docs/howto-write-a-block/qa_howto_1.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +from gnuradio import gr, gr_unittest +import howto + +class qa_howto (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001_square_ff (self): + src_data = (-3, 4, -5.5, 2, 3) + expected_result = (9, 16, 30.25, 4, 9) + src = gr.vector_source_f (src_data) + sqr = howto.square_ff () + dst = gr.vector_sink_f () + self.tb.connect (src, sqr) + self.tb.connect (sqr, dst) + self.tb.run () + result_data = dst.data () + self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6) + +if __name__ == '__main__': + gr_unittest.main () diff --git a/docs/howto-write-a-block/src_lib_Makefile_1.am b/docs/howto-write-a-block/src_lib_Makefile_1.am new file mode 100644 index 000000000..e97d70d1c --- /dev/null +++ b/docs/howto-write-a-block/src_lib_Makefile_1.am @@ -0,0 +1,25 @@ +include $(top_srcdir)/Makefile.common + +# Install this stuff so that it ends up as the gnuradio.howto module +# This usually ends up at: +# ${prefix}/lib/python${python_version}/site-packages/gnuradio + +ourpythondir = $(grpythondir) +ourlibdir = $(grpyexecdir) + +INCLUDES = $(STD_DEFINES_AND_INCLUDES) $(PYTHON_CPPFLAGS) $(WITH_INCLUDES) + +ourlib_LTLIBRARIES = _howto.la + +# These are the source files that go into the shared library +_howto_la_SOURCES = \ + howto_square_ff.cc + +# magic flags +_howto_la_LDFLAGS = -module -avoid-version + +# These headers get installed in ${prefix}/include/gnuradio +grinclude_HEADERS = \ + howto_square_ff.h + +MOSTLYCLEANFILES = $(BUILT_SOURCES) *.pyc diff --git a/docs/howto-write-a-block/src_lib_Makefile_2.am b/docs/howto-write-a-block/src_lib_Makefile_2.am new file mode 100644 index 000000000..dca236e20 --- /dev/null +++ b/docs/howto-write-a-block/src_lib_Makefile_2.am @@ -0,0 +1,86 @@ +# +# Copyright 2004,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/Makefile.common + +# Install this stuff so that it ends up as the gnuradio.howto module +# This usually ends up at: +# ${prefix}/lib/python${python_version}/site-packages/gnuradio + +ourpythondir = $(grpythondir) +ourlibdir = $(grpyexecdir) + +INCLUDES = $(STD_DEFINES_AND_INCLUDES) $(PYTHON_CPPFLAGS) $(WITH_INCLUDES) + +SWIGCPPPYTHONARGS = -noruntime -c++ -python $(PYTHON_CPPFLAGS) \ + -I$(swigincludedir) -I$(grincludedir) $(WITH_SWIG_INCLUDES) + +ALL_IFILES = \ + $(LOCAL_IFILES) \ + $(NON_LOCAL_IFILES) + +NON_LOCAL_IFILES = \ + $(GNURADIO_CORE_INCLUDEDIR)/swig/gnuradio.i + + +LOCAL_IFILES = \ + howto.i + +# These files are built by SWIG. The first is the C++ glue. +# The second is the python wrapper that loads the _howto shared library +# and knows how to call our extensions. + +BUILT_SOURCES = \ + howto.cc \ + howto.py + +# This gets howto.py installed in the right place +ourpython_PYTHON = \ + howto.py + +ourlib_LTLIBRARIES = _howto.la + +# These are the source files that go into the shared library +_howto_la_SOURCES = \ + howto.cc \ + howto_square_ff.cc + +# magic flags +_howto_la_LDFLAGS = -module -avoid-version + +# link the library against some comon swig runtime code and the +# c++ standard library +_howto_la_LIBADD = \ + -lgrswigrunpy \ + -lstdc++ + +howto.cc howto.py: howto.i $(ALL_IFILES) + $(SWIG) $(SWIGCPPPYTHONARGS) -module howto -o howto.cc $< + +# These headers get installed in ${prefix}/include/gnuradio +grinclude_HEADERS = \ + howto_square_ff.h + +# These swig headers get installed in ${prefix}/include/gnuradio/swig +swiginclude_HEADERS = \ + $(LOCAL_IFILES) + +MOSTLYCLEANFILES = $(BUILT_SOURCES) *.pyc |