Difference between revisions of "Guided Tutorial GNU Radio in C++"

From GNU Radio
Jump to: navigation, search
Line 32: Line 32:
 
This is a standard question that every one of us needs to consider before starting our own OOT module. Tutorial 3 already addressed ways to select the programming language of choice for the blocks in OOT module using <code>gr_modtool</code>
 
This is a standard question that every one of us needs to consider before starting our own OOT module. Tutorial 3 already addressed ways to select the programming language of choice for the blocks in OOT module using <code>gr_modtool</code>
  
<pre>$ gr_modtool add -t sync -l python</pre>
+
$ gr_modtool add -t sync -l python
 
or
 
or
  
<pre>$ gr_modtool add -t sync -l cpp # This is the default</pre>
+
$ gr_modtool add -t sync -l cpp # This is the default
 +
 
 
Apart from the compiler and interpreter, there are many differences out there. To decide upon the choice of the programming language, it is important that we limit the differences from the GNU Radio perspective. Primarily, it depends more on the objective of the OOT module. As far as the performance is concerned, implementing the blocks in C++ makes more sense, and if the performance of the OOT module is the not main issue Python would be a good choice, as it is concise yet simple. Moreover, Python allows faster prototyping as we don't have to compile to test the modules.
 
Apart from the compiler and interpreter, there are many differences out there. To decide upon the choice of the programming language, it is important that we limit the differences from the GNU Radio perspective. Primarily, it depends more on the objective of the OOT module. As far as the performance is concerned, implementing the blocks in C++ makes more sense, and if the performance of the OOT module is the not main issue Python would be a good choice, as it is concise yet simple. Moreover, Python allows faster prototyping as we don't have to compile to test the modules.
  
Line 69: Line 70:
 
== 4.2.2 Step 1: Create an OOT module <code>gr-tutorial</code> ==
 
== 4.2.2 Step 1: Create an OOT module <code>gr-tutorial</code> ==
  
<pre>xyz@comp:mydir$ gr_modtool nm tutorial
+
<pre>
 +
xyz@comp:mydir$ gr_modtool nm tutorial
 
Creating out-of-tree module in ./gr-tutorial... Done.
 
Creating out-of-tree module in ./gr-tutorial... Done.
 
Use 'gr_modtool add' to add a new block to this currently empty module.
 
Use 'gr_modtool add' to add a new block to this currently empty module.
Line 76: Line 78:
 
Have a look into the dir structure of our <code>gr-tutorial</code>
 
Have a look into the dir structure of our <code>gr-tutorial</code>
  
<pre>xyz@comp:mydir$ cd gr-tutorial
+
<pre>
 +
xyz@comp:mydir$ cd gr-tutorial
 
xyz@comp:mydir/gr-tutorial$ ls
 
xyz@comp:mydir/gr-tutorial$ ls
 
apps  cmake  CMakeLists.txt  docs  examples  grc  include  lib  python  swig</pre>
 
apps  cmake  CMakeLists.txt  docs  examples  grc  include  lib  python  swig</pre>
Line 83: Line 86:
 
Again using <code>gr_modtool</code>, inside <code>gr-tutorial</code>, we create our <code>my_qpsk_demod</code> block:
 
Again using <code>gr_modtool</code>, inside <code>gr-tutorial</code>, we create our <code>my_qpsk_demod</code> block:
  
<pre>xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb
+
<pre>
 +
xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb
 
GNU Radio module name identified: tutorial
 
GNU Radio module name identified: tutorial
 
Enter code type: general
 
Enter code type: general
Line 101: Line 105:
 
Unlike when creating an OOT module, creating a block using <code>gr_modtool</code> demand inputs from the user. To follow the command line user interaction, let's decompose the information above.
 
Unlike when creating an OOT module, creating a block using <code>gr_modtool</code> demand inputs from the user. To follow the command line user interaction, let's decompose the information above.
  
<pre>xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb</pre>
+
xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb
 
<code>my_qpsk_demod_cb</code> represents class name of the block, where the suffix, 'cb' is added to the block name, which conform to the GNU Radio nomenclature. 'cb' states the block established that takes complex data as input and spits byte as output.
 
<code>my_qpsk_demod_cb</code> represents class name of the block, where the suffix, 'cb' is added to the block name, which conform to the GNU Radio nomenclature. 'cb' states the block established that takes complex data as input and spits byte as output.
  
<pre>Enter code type: general</pre>
+
Enter code type: general
 
In GNU Radio, there exist different kinds of blocks: general, sync, interpolator/decimator, source/sink, Hierarchical, etc. Depending on the choice of our block, <code>gr_modtool</code> adds the corresponding code and functions. As illustrated, for <code>my_qpsk_demod_cb</code> block, we opt for a general block. The following [http://gnuradio.org/redmine/projects/gnuradio/wiki/Guided_Tutorial_GNU_Radio_in_C++#432-Specific-block-categories section] will discuss the purpose of the specific blocks in detail.<br />
 
In GNU Radio, there exist different kinds of blocks: general, sync, interpolator/decimator, source/sink, Hierarchical, etc. Depending on the choice of our block, <code>gr_modtool</code> adds the corresponding code and functions. As illustrated, for <code>my_qpsk_demod_cb</code> block, we opt for a general block. The following [http://gnuradio.org/redmine/projects/gnuradio/wiki/Guided_Tutorial_GNU_Radio_in_C++#432-Specific-block-categories section] will discuss the purpose of the specific blocks in detail.<br />
 
In many cases, the block demands a user interface. For <code>my_qpsk_demod_cb</code>, gray_code is selected to be &quot;default arguments&quot;.
 
In many cases, the block demands a user interface. For <code>my_qpsk_demod_cb</code>, gray_code is selected to be &quot;default arguments&quot;.
  
<pre>Enter valid argument list, including default arguments:  bool gray_code</pre>
+
Enter valid argument list, including default arguments:  bool gray_code
 
Moreover, GNU Radio provides an option of writing test cases. This provides quality assurance to the code written. If selected, the <code>gr_modtool</code> adds the quality assurance files corresponding to python and C++.
 
Moreover, GNU Radio provides an option of writing test cases. This provides quality assurance to the code written. If selected, the <code>gr_modtool</code> adds the quality assurance files corresponding to python and C++.
  
<pre>Add Python QA code? [Y/n]  
+
<pre>
 +
Add Python QA code? [Y/n]  
 
Add C++ QA code? [y/N] y</pre>
 
Add C++ QA code? [y/N] y</pre>
 
With this, we have already established the GNU Radio semantics for our block coupled with the OOT module. In the following sections, we will focus on the implementation of our block.<br />
 
With this, we have already established the GNU Radio semantics for our block coupled with the OOT module. In the following sections, we will focus on the implementation of our block.<br />
Line 120: Line 125:
 
The next step is to implement the logic for our block. This is done inside the work function which is defined in the source file <code>my_qpsk_demod_cb_impl.cc</code> inside the <code>lib/</code> folder. The skeleton of the <code>my_qpsk_demod_cb_impl.cc</code> has the following structure:
 
The next step is to implement the logic for our block. This is done inside the work function which is defined in the source file <code>my_qpsk_demod_cb_impl.cc</code> inside the <code>lib/</code> folder. The skeleton of the <code>my_qpsk_demod_cb_impl.cc</code> has the following structure:
  
<pre>    /*!
+
<syntaxhighlight lang="cpp" line="line">
 +
   /*!
 
     * The private constructor
 
     * The private constructor
 
     */
 
     */
 
     my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)
 
     my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)
 
       : gr::block(&quot;my_qpsk_demod_cb&quot;,
 
       : gr::block(&quot;my_qpsk_demod_cb&quot;,
               gr::io_signature::make(&lt;+MIN_IN+&gt;, &lt;+MAX_IN+&gt;, sizeof(&lt;+ITYPE+&gt;)),
+
               gr::io_signature::make(<+MIN_IN+>, <+MAX_IN+>, sizeof(<+ITYPE+>)),
               gr::io_signature::make(&lt;+MIN_OUT+&gt;, &lt;+MAX_OUT+&gt;, sizeof(&lt;+OTYPE+&gt;)))
+
               gr::io_signature::make(<+MIN_OUT+>, <+MAX_OUT+>, sizeof(<+OTYPE+>)))
     {}</pre>
+
     {}
 +
</syntaxhighlight>
 
* <code>my_qpsk_demod_cb_impl()</code> is the constructor of the block <code>my_qpsk_demod</code>. <code>my_qpsk_demod_cb_impl()</code> calls the constructor of the base class block <code>gr::block(...)</code> defined [http://gnuradio.org/doc/doxygen/basic__block_8h_source.html here].
 
* <code>my_qpsk_demod_cb_impl()</code> is the constructor of the block <code>my_qpsk_demod</code>. <code>my_qpsk_demod_cb_impl()</code> calls the constructor of the base class block <code>gr::block(...)</code> defined [http://gnuradio.org/doc/doxygen/basic__block_8h_source.html here].
  
Line 139: Line 146:
 
Next, we need to modify the constructor. After modification, it looks like this:
 
Next, we need to modify the constructor. After modification, it looks like this:
  
<pre>    /*!
+
<syntaxhighlight lang="cpp" line="line">
 +
   /*!
 
     * The private constructor
 
     * The private constructor
 
     */
 
     */
Line 147: Line 155:
 
               gr::io_signature::make(1, 1, sizeof(char))),
 
               gr::io_signature::make(1, 1, sizeof(char))),
 
         d_gray_code(gray_code)
 
         d_gray_code(gray_code)
     {}</pre>
+
     {}
 +
</syntaxhighlight>
 
The option <code>gray_code</code> is copied to the class attribute <code>d_gray_code</code>. Note that we need<br />
 
The option <code>gray_code</code> is copied to the class attribute <code>d_gray_code</code>. Note that we need<br />
 
to declare this a private member of the class in the header file <code>my_qpsk_demod_cb_impl.h</code>,
 
to declare this a private member of the class in the header file <code>my_qpsk_demod_cb_impl.h</code>,
  
<pre>    private:
+
<syntaxhighlight lang="cpp" line="line">
       bool d_gray_code;</pre>
+
     private:
 +
       bool d_gray_code;
 +
</syntaxhighlight>
 
Also inside this class is the method <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. After running <code>gr_modtool</code>,<br />
 
Also inside this class is the method <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. After running <code>gr_modtool</code>,<br />
 
the skeleton version of this function will look something like this:
 
the skeleton version of this function will look something like this:
  
<pre>int
+
<syntaxhighlight lang="cpp" line="line">
 +
int
 
my_qpsk_demod_cb_impl::general_work (int noutput_items,
 
my_qpsk_demod_cb_impl::general_work (int noutput_items,
 
                   gr_vector_int &amp;ninput_items,
 
                   gr_vector_int &amp;ninput_items,
Line 172: Line 184:
 
     // Tell runtime system how many output items we produced.
 
     // Tell runtime system how many output items we produced.
 
     return noutput_items;
 
     return noutput_items;
}</pre>
+
}
 +
</syntaxhighlight>
 
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which processes the items in the input buffer and copies them to the output buffer. Once the demodulation logic is implemented, the structure of the work function has the form
 
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which processes the items in the input buffer and copies them to the output buffer. Once the demodulation logic is implemented, the structure of the work function has the form
  
<pre>   int
+
<syntaxhighlight lang="cpp" line="line">
 +
    int
 
     my_qpsk_demod_cb_impl::general_work (int noutput_items,
 
     my_qpsk_demod_cb_impl::general_work (int noutput_items,
 
                       gr_vector_int &amp;ninput_items,
 
                       gr_vector_int &amp;ninput_items,
Line 197: Line 211:
 
         // Tell runtime system how many output items we produced.
 
         // Tell runtime system how many output items we produced.
 
         return noutput_items;
 
         return noutput_items;
     }</pre>
+
     }
 +
</syntaxhighlight>
 
This work function calls another function <code>get_minimum_distances(const gr_complex &amp;sample)</code>, which we also need to add:
 
This work function calls another function <code>get_minimum_distances(const gr_complex &amp;sample)</code>, which we also need to add:
  
<pre>   unsigned char
+
<syntaxhighlight lang="cpp" line="line">
 +
    unsigned char
 
     my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &amp;sample)
 
     my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &amp;sample)
 
     {
 
     {
Line 230: Line 246:
 
         }
 
         }
 
       }
 
       }
     }</pre>
+
     }
 +
</syntaxhighlight>
 +
 
 
Note the function declaration also needs to be added to the class header (<code>my_qpsk_demod_cb_impl.h</code>).<br />
 
Note the function declaration also needs to be added to the class header (<code>my_qpsk_demod_cb_impl.h</code>).<br />
 
The function <code>get_minimum_distances</code> is a maximum likelihood decoder for the QPSK demodulater. Theoretically, the function should compute the distance from each ideal QPSK symbol to the received symbol (It is mathematically equivalent to determining the Voronoi regions of the received sample). For a QPSK signal, these Voronoi regions are simply four quadrants in the complex plane. Hence, to decode the sample into bits, it makes sense to map the received sample to these quadrants.
 
The function <code>get_minimum_distances</code> is a maximum likelihood decoder for the QPSK demodulater. Theoretically, the function should compute the distance from each ideal QPSK symbol to the received symbol (It is mathematically equivalent to determining the Voronoi regions of the received sample). For a QPSK signal, these Voronoi regions are simply four quadrants in the complex plane. Hence, to decode the sample into bits, it makes sense to map the received sample to these quadrants.
Line 238: Line 256:
 
The default implementation of <code>forecast()</code> 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 <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!
 
The default implementation of <code>forecast()</code> 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 <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!
  
<pre> // default implementation:  1:1
+
<syntaxhighlight lang="cpp" line="line">
 +
  // default implementation:  1:1
 
   void
 
   void
 
   gr::block::forecast(int noutput_items,
 
   gr::block::forecast(int noutput_items,
Line 246: Line 265:
 
     for(unsigned i = 0; i &lt; ninputs; i++)
 
     for(unsigned i = 0; i &lt; ninputs; i++)
 
       ninput_items_required[i] = noutput_items;
 
       ninput_items_required[i] = noutput_items;
   }</pre>
+
   }
 +
</syntaxhighlight>
 
Although the 1:1 implementation worked for <code>my_qpsk_demod_cb</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from <code>gr::sync_block</code>, <code>gr::sync_interpolator</code> or <code>gr::sync_decimator</code> instead of <code>gr::block</code>, you can often avoid implementing <code>forecast</code>.
 
Although the 1:1 implementation worked for <code>my_qpsk_demod_cb</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from <code>gr::sync_block</code>, <code>gr::sync_interpolator</code> or <code>gr::sync_decimator</code> instead of <code>gr::block</code>, you can often avoid implementing <code>forecast</code>.
  
Line 362: Line 382:
 
{{collapse(sequence:)
 
{{collapse(sequence:)
  
<pre>xyz@comp:mydir/gr-tutorial$ mkdir build
+
<pre>
 +
xyz@comp:mydir/gr-tutorial$ mkdir build
 
xyz@comp:mydir/gr-tutorial$ cd build
 
xyz@comp:mydir/gr-tutorial$ cd build
 
xyz@comp:mydir/gr-tutorial/build$ cmake ..
 
xyz@comp:mydir/gr-tutorial/build$ cmake ..
Line 416: Line 437:
 
-- Up-to-date: /usr/local/share/gnuradio/grc/blocks/tutorial_my_qpsk_cb.xml
 
-- Up-to-date: /usr/local/share/gnuradio/grc/blocks/tutorial_my_qpsk_cb.xml
  
xyz@comp:mydir/gr-qpsk_demod/build$ sudo ldconfig</pre>
+
xyz@comp:mydir/gr-qpsk_demod/build$ sudo ldconfig
 +
</pre>
 
}}
 
}}
  
Line 431: Line 453:
 
{{collapse(Full QA code)
 
{{collapse(Full QA code)
  
<pre>from gnuradio import gr, gr_unittest
+
<syntaxhighlight lang="python" line="line">
 +
from gnuradio import gr, gr_unittest
 
from gnuradio import blocks
 
from gnuradio import blocks
 
import tutorial_swig as tutorial
 
import tutorial_swig as tutorial
Line 495: Line 518:
  
 
if __name__ == '__main__':
 
if __name__ == '__main__':
     gr_unittest.run(qa_qpsk_demod, &quot;qa_qpsk_demod.xml&quot;)</pre>
+
     gr_unittest.run(qa_qpsk_demod, &quot;qa_qpsk_demod.xml&quot;)
 +
</syntaxhighlight>
 
}}
 
}}
  
Line 502: Line 526:
 
So lets gather a bit of know how on how to write test cases for the block. Okay, lets consider the header part first:
 
So lets gather a bit of know how on how to write test cases for the block. Okay, lets consider the header part first:
  
<pre>from gnuradio import gr, gr_unittest
+
<syntaxhighlight lang="python">
 +
from gnuradio import gr, gr_unittest
 
from gnuradio import blocks
 
from gnuradio import blocks
 
import tutorial_swig as tutorial
 
import tutorial_swig as tutorial
from numpy import array</pre>
+
from numpy import array
 +
</syntaxhighlight>
 
<code>from gnuradio import gr, gr_unittest</code> and <code>from gnuradio import blocks</code> are the standard lines that includes gr, gr_unittest functionality in the <code>qa_</code> file. <code>import tutorial_swig as tutorial</code> import the python bidden version of our module, which provides an access our block <code>my_qpsk_demod_cb</code>. Finally, <code>from numpy import array</code> includes array.
 
<code>from gnuradio import gr, gr_unittest</code> and <code>from gnuradio import blocks</code> are the standard lines that includes gr, gr_unittest functionality in the <code>qa_</code> file. <code>import tutorial_swig as tutorial</code> import the python bidden version of our module, which provides an access our block <code>my_qpsk_demod_cb</code>. Finally, <code>from numpy import array</code> includes array.
  
<pre>if __name__ == '__main__':
+
<syntaxhighlight lang="python">
     gr_unittest.run(qa_qpsk_demod, &quot;qa_qpsk_demod.xml&quot;)</pre>
+
if __name__ == '__main__':
 +
     gr_unittest.run(qa_qpsk_demod, "qa_qpsk_demod.xml")
 +
</syntaxhighlight>
 
The <code>qa_</code> file execution start by calling this function. The <code>gr_unittest</code> automatically calls the functions in a specific order <code>def setUp (self)</code> for creating the top block at the start, <code>tearDown (self)</code> for deleting the top block at the end. In between the <code>setUp</code> and <code>tearDown</code> the test cases defined are executed. The methods starting with prefix <code>test_</code> are recognized as test cases by <code>gr_unittest</code>. We have defined two test cases <code>test_001_gray_code_enabled</code> and <code>test_002_gray_code_disabled</code>. The usual structure of a test cases comprises of a known input data and the expected output. A flowgraph is created to include the source (input data), block to be tested (processor) and sink (resulted output data). In the end the expected output is compared with the resulted output data.
 
The <code>qa_</code> file execution start by calling this function. The <code>gr_unittest</code> automatically calls the functions in a specific order <code>def setUp (self)</code> for creating the top block at the start, <code>tearDown (self)</code> for deleting the top block at the end. In between the <code>setUp</code> and <code>tearDown</code> the test cases defined are executed. The methods starting with prefix <code>test_</code> are recognized as test cases by <code>gr_unittest</code>. We have defined two test cases <code>test_001_gray_code_enabled</code> and <code>test_002_gray_code_disabled</code>. The usual structure of a test cases comprises of a known input data and the expected output. A flowgraph is created to include the source (input data), block to be tested (processor) and sink (resulted output data). In the end the expected output is compared with the resulted output data.
  
 
Finally, the statements in the test cases
 
Finally, the statements in the test cases
  
<pre>self.assertTupleEqual(expected_result, result_data)
+
<syntaxhighlight lang="python">
self.assertEqual(len(expected_result), len(result_data))</pre>
+
self.assertTupleEqual(expected_result, result_data)
 +
self.assertEqual(len(expected_result), len(result_data))
 +
</syntaxhighlight>
 
determine the result of test cases as passed or failed. The test cases are executed before installation of the module by running <code>make test</code>:<br />
 
determine the result of test cases as passed or failed. The test cases are executed before installation of the module by running <code>make test</code>:<br />
 
{{collapse(Output)
 
{{collapse(Output)
  
<pre>xyz@comp:mydir/gr-tutorial/build$ make test
+
<pre>
 +
xyz@comp:mydir/gr-tutorial/build$ make test
 
Running tests...
 
Running tests...
 
Test project /home/kaushik/src/gr-tutorial/build
 
Test project /home/kaushik/src/gr-tutorial/build
Line 558: Line 589:
 
{{collapse(example)
 
{{collapse(example)
  
<pre>test::test(const std::string &amp;name,
+
<syntaxhighlight lang="cpp">
 +
test::test(const std::string &amp;name,
 
             int min_inputs, int max_inputs,
 
             int min_inputs, int max_inputs,
 
             unsigned int sizeof_input_item,
 
             unsigned int sizeof_input_item,
Line 585: Line 617:
 
     set_relative_rate(relative_rate);
 
     set_relative_rate(relative_rate);
 
     set_fixed_rate(fixed_rate);
 
     set_fixed_rate(fixed_rate);
   }</pre>
+
   }
 +
</syntaxhighlight>
 
}}
 
}}
  
Line 597: Line 630:
 
The <code>set_history()</code> is defined in <code>gnuradio/gnuradio-runtime/block.cc</code>
 
The <code>set_history()</code> is defined in <code>gnuradio/gnuradio-runtime/block.cc</code>
  
<pre>void block::set_history(unsigned history)
+
<syntaxhighlight lang="cpp">
  {
+
void block::set_history(unsigned history)
 +
{
 
     d_history = history;
 
     d_history = history;
  }</pre>
+
}
 +
</syntaxhighlight>
 
=== 4.3.1.2 set_output_multiple() ===
 
=== 4.3.1.2 set_output_multiple() ===
  
Line 607: Line 642:
 
{{collapse(code)
 
{{collapse(code)
  
<pre>test::test(const std::string &amp;name,
+
<syntaxhighlight lang="cpp">
 +
test::test(const std::string &amp;name,
 
             int min_inputs, int max_inputs,
 
             int min_inputs, int max_inputs,
 
             unsigned int sizeof_input_item,
 
             unsigned int sizeof_input_item,
Line 629: Line 665:
 
     d_min_produce(0),
 
     d_min_produce(0),
 
     d_max_produce(0)
 
     d_max_produce(0)
  {
+
{
 
     set_history(history);
 
     set_history(history);
 
     set_output_multiple(output_multiple);
 
     set_output_multiple(output_multiple);
 
     set_relative_rate(relative_rate);
 
     set_relative_rate(relative_rate);
 
     set_fixed_rate(fixed_rate);
 
     set_fixed_rate(fixed_rate);
  }</pre>
+
}
 +
</syntaxhighlight>
 
}}
 
}}
  
Line 643: Line 680:
 
The definition of <code>set_output_multiple</code> can be found in gnuradio/gnuradio-runtime/block.cc
 
The definition of <code>set_output_multiple</code> can be found in gnuradio/gnuradio-runtime/block.cc
  
<pre>void gr_block::set_output_multiple (int multiple)
+
<syntaxhighlight lang="cpp">void gr_block::set_output_multiple (int multiple)
 
{
 
{
 
   if (multiple &lt; 1)
 
   if (multiple &lt; 1)
Line 650: Line 687:
 
   d_output_multiple_set = true;
 
   d_output_multiple_set = true;
 
   d_output_multiple = multiple;
 
   d_output_multiple = multiple;
}</pre>
+
}</syntaxhighlight>
 
== 4.3.2 Specific block categories ==
 
== 4.3.2 Specific block categories ==
  
Line 679: Line 716:
 
=== 4.3.2.1 General ===
 
=== 4.3.2.1 General ===
  
<pre>howto_square_ff::howto_square_ff ()
+
<syntaxhighlight lang="cpp">howto_square_ff::howto_square_ff ()
 
: gr::block(&quot;square_ff&quot;,
 
: gr::block(&quot;square_ff&quot;,
 
gr::io_signature::make(MIN_IN, MAX_IN, sizeof (float)),
 
gr::io_signature::make(MIN_IN, MAX_IN, sizeof (float)),
Line 685: Line 722:
 
{
 
{
 
// nothing else required in this example
 
// nothing else required in this example
}</pre>
+
}
 +
</syntaxhighlight>
 
=== 4.3.2.2 Source and Sinks ===
 
=== 4.3.2.2 Source and Sinks ===
  
Line 692: Line 730:
 
An example of source block in C++
 
An example of source block in C++
  
<pre>usrp_source_impl::usrp_source_impl(const ::uhd::device_addr_t &amp;device_addr,
+
<syntaxhighlight lang="cpp">
 +
usrp_source_impl::usrp_source_impl(const ::uhd::device_addr_t &amp;device_addr,
 
                                   const ::uhd::stream_args_t &amp;stream_args):
 
                                   const ::uhd::stream_args_t &amp;stream_args):
 
       sync_block(&quot;gr uhd usrp source&quot;,
 
       sync_block(&quot;gr uhd usrp source&quot;,
Line 702: Line 741:
 
       _tag_now(false),
 
       _tag_now(false),
 
       _start_time_set(false)
 
       _start_time_set(false)
</pre>
+
 
 +
</syntaxhighlight>
 
Some observations:
 
Some observations:
  
Line 712: Line 752:
 
An example of the sink block in C++
 
An example of the sink block in C++
  
<pre>usrp_sink_impl::usrp_sink_impl(const ::uhd::device_addr_t &amp;device_addr,
+
<syntaxhighlight lang="cpp">
 +
usrp_sink_impl::usrp_sink_impl(const ::uhd::device_addr_t &amp;device_addr,
 
                                   const ::uhd::stream_args_t &amp;stream_args)
 
                                   const ::uhd::stream_args_t &amp;stream_args)
 
       : sync_block(&quot;gr uhd usrp sink&quot;,
 
       : sync_block(&quot;gr uhd usrp sink&quot;,
Line 721: Line 762:
 
         _stream_now(_nchan == 1),
 
         _stream_now(_nchan == 1),
 
         _start_time_set(false)
 
         _start_time_set(false)
</pre>
+
 
 +
</syntaxhighlight>
 
Some observations:
 
Some observations:
  
Line 733: Line 775:
 
An example sync block in C++:
 
An example sync block in C++:
  
<pre>#include  
+
<syntaxhighlight lang="cpp">
 +
#include  
  
 
class my_sync_block : public gr_sync_block
 
class my_sync_block : public gr_sync_block
Line 753: Line 796:
 
     return noutput_items;
 
     return noutput_items;
 
   }
 
   }
};</pre>
+
};
 +
</syntaxhighlight>
 
Some observations:
 
Some observations:
  
Line 768: Line 812:
 
An example decimation block in c++
 
An example decimation block in c++
  
<pre>#include  
+
<syntaxhighlight lang="cpp">
 +
#include  
  
 
class my_decim_block : public gr_sync_decimator
 
class my_decim_block : public gr_sync_decimator
Line 783: Line 828:
  
 
   //work function here...
 
   //work function here...
};</pre>
+
};
 +
</syntaxhighlight>
 
Some observations:
 
Some observations:
  
Line 795: Line 841:
 
An example interpolation block in c++
 
An example interpolation block in c++
  
<pre>#include  
+
<syntaxhighlight lang="cpp">
 +
#include  
  
 
class my_interp_block : public gr_sync_interpolator
 
class my_interp_block : public gr_sync_interpolator
Line 810: Line 857:
  
 
   //work function here...
 
   //work function here...
};</pre>
+
};
 +
</syntaxhighlight>
 +
 
 
Some observations:
 
Some observations:
  
Line 834: Line 883:
 
Hierarchical blocks define an input and output stream much like normal blocks. To connect input '''i''' to a hierarchical block, the source is (in Python):
 
Hierarchical blocks define an input and output stream much like normal blocks. To connect input '''i''' to a hierarchical block, the source is (in Python):
  
<code>self.connect((self, i), &lt;block&gt;)</code>
+
<syntaxhighlight lang="python">
 +
self.connect((self, i), <block>)
 +
</syntaxhighlight>
  
 
Similarly, to send the signal out of the block on output stream '''o''':
 
Similarly, to send the signal out of the block on output stream '''o''':
  
<code>self.connect(&lt;block&gt;, (self, o))</code>
+
<syntaxhighlight lang="python">
 +
self.connect(<block>, (self, o))
 +
</syntaxhighlight>
 +
 
 +
An typical example of a hierarchical block is OFDM Receiver implemented in python under <pre>gnuradio/gr-digital/python/digital</pre>
  
An typical example of a hierarchical block is OFDM Receiver implemented in python under &quot;<code>gnuradio/gr-digital/python/digital</code>&quot;.<br />
 
 
The class is defined as:
 
The class is defined as:
  
<pre>class ofdm_receiver(gr.hier_block2)</pre>
+
<syntaxhighlight lang="python">
 +
class ofdm_receiver(gr.hier_block2)
 +
</syntaxhighlight>
 +
 
 
and instantiated as
 
and instantiated as
  
<pre>gr.hier_block2.__init__(self, &quot;ofdm_receiver&quot;,
+
<syntaxhighlight lang="python">
                                gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
+
gr.hier_block2.__init__(self, &quot;ofdm_receiver&quot;,
                                gr.io_signature2(2, 2, gr.sizeof_gr_complex*occupied_tones, gr.sizeof_char)) # Output signature</pre>
+
                        gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
 +
                        gr.io_signature2(2, 2, gr.sizeof_gr_complex*occupied_tones, gr.sizeof_char)) # Output signature
 +
</syntaxhighlight>
 +
 
 
Some main tasks performed by the OFDM receiver include channel filtering, synchronization and IFFT tasks. The individual tasks are defined inside the hierarchical block.
 
Some main tasks performed by the OFDM receiver include channel filtering, synchronization and IFFT tasks. The individual tasks are defined inside the hierarchical block.
  
 
* Channel filtering
 
* Channel filtering
  
<pre>chan_coeffs = filter.firdes.low_pass (1.0,                    # gain
+
<syntaxhighlight lang="python">
 +
chan_coeffs = filter.firdes.low_pass (1.0,                    # gain
 
                                               1.0,                    # sampling rate
 
                                               1.0,                    # sampling rate
 
                                               bw+tb,                  # midpoint of trans. band
 
                                               bw+tb,                  # midpoint of trans. band
 
                                               tb,                      # width of trans. band
 
                                               tb,                      # width of trans. band
 
                                               filter.firdes.WIN_HAMMING)  # filter type
 
                                               filter.firdes.WIN_HAMMING)  # filter type
self.chan_filt = filter.fft_filter_ccc(1, chan_coeffs)</pre>
+
self.chan_filt = filter.fft_filter_ccc(1, chan_coeffs)
 +
</syntaxhighlight>
 +
 
 
* Synchronization
 
* Synchronization
  
<pre>self.chan_filt = blocks.multiply_const_cc(1.0)
+
<syntaxhighlight lang="python">
            nsymbols = 18      # enter the number of symbols per packet
+
self.chan_filt = blocks.multiply_const_cc(1.0)
            freq_offset = 0.0  # if you use a frequency offset, enter it here
+
nsymbols = 18      # enter the number of symbols per packet
            nco_sensitivity = -2.0/fft_length  # correct for fine frequency
+
freq_offset = 0.0  # if you use a frequency offset, enter it here
            self.ofdm_sync = ofdm_sync_fixed(fft_length,
+
nco_sensitivity = -2.0/fft_length  # correct for fine frequency
                                            cp_length,
+
self.ofdm_sync = ofdm_sync_fixed(fft_length,
                                            nsymbols,
+
                                cp_length,
                                            freq_offset,
+
                                nsymbols,
                                            logging)</pre>
+
                                freq_offset,
 +
                                logging)
 +
</syntaxhighlight>
 +
 
 
* ODFM demodulation
 
* ODFM demodulation
  
<pre>self.fft_demod = gr_fft.fft_vcc(fft_length, True, win, True)</pre>
+
<syntaxhighlight lang="python">
 +
self.fft_demod = gr_fft.fft_vcc(fft_length, True, win, True)
 +
</syntaxhighlight>
 +
 
 
Finally, the individual blocks along with hierarchical are connected among each to form a flow graph.
 
Finally, the individual blocks along with hierarchical are connected among each to form a flow graph.
  
 
Connection between the hierarchical block OFDM receiver to channel filter block
 
Connection between the hierarchical block OFDM receiver to channel filter block
  
<pre>self.connect(self, self.chan_filt)                            # filter the input channel</pre>
+
<syntaxhighlight lang="python">
 +
self.connect(self, self.chan_filt)                            # filter the input channel
 +
</syntaxhighlight>
 
Connection between the channel filter block to the OFDM synchronization block.
 
Connection between the channel filter block to the OFDM synchronization block.
  
<pre>self.connect(self.chan_filt, self.ofdm_sync)</pre>
+
<syntaxhighlight lang="python">
 +
self.connect(self.chan_filt, self.ofdm_sync)
 +
</syntaxhighlight>
 
and so forth.
 
and so forth.
  
Line 889: Line 962:
 
{{collapse(OFDM impl)
 
{{collapse(OFDM impl)
  
<pre>ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
+
<syntaxhighlight lang="cpp">
 +
ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
 
         : hier_block2 (&quot;ofdm_sync_sc_cfb&quot;,
 
         : hier_block2 (&quot;ofdm_sync_sc_cfb&quot;,
 
                       io_signature::make(1, 1, sizeof (gr_complex)),
 
                       io_signature::make(1, 1, sizeof (gr_complex)),
Line 946: Line 1,020:
 
       connect(delay_normalize,      0, self(),              2);
 
       connect(delay_normalize,      0, self(),              2);
 
#endif
 
#endif
     }</pre>
+
     }
 +
</syntaxhighlight>
 +
 
 
Let's understand the code piece wise. The hierarchical block is C++ is instantiated as follows:
 
Let's understand the code piece wise. The hierarchical block is C++ is instantiated as follows:
  
<pre>ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
+
<syntaxhighlight lang="cpp">
 +
ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
 
         : hier_block2 (&quot;ofdm_sync_sc_cfb&quot;,
 
         : hier_block2 (&quot;ofdm_sync_sc_cfb&quot;,
 
                       io_signature::make(1, 1, sizeof (gr_complex)),
 
                       io_signature::make(1, 1, sizeof (gr_complex)),
Line 955: Line 1,032:
 
                   io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))
 
                   io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))
 
#else
 
#else
                   io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))</pre>
+
                   io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))
 +
</syntaxhighlight>
 
}}
 
}}
  
Line 967: Line 1,045:
 
The individual blocks inside the <code>ofdm_sync_sc_cfb</code> block are defined as follows:
 
The individual blocks inside the <code>ofdm_sync_sc_cfb</code> block are defined as follows:
  
<pre>gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());</pre>
+
<syntaxhighlight lang="cpp">
 +
gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());
 +
</syntaxhighlight>
 
Finally the individual blocks are connected using:
 
Finally the individual blocks are connected using:
  
<pre>connect(normalizer_magsquare, 0, normalizer_ma, 0);</pre>
+
<syntaxhighlight lang="cpp">
 +
connect(normalizer_magsquare, 0, normalizer_ma, 0);
 +
</syntaxhighlight>
 +
 
 
= 4.4 Closing note =
 
= 4.4 Closing note =
  
Line 980: Line 1,063:
 
* If we think of porting the QPSK demodulator to a generic demodulator, does the usage of hierarchical block makes sense?
 
* If we think of porting the QPSK demodulator to a generic demodulator, does the usage of hierarchical block makes sense?
 
* How does noise affect our system?
 
* How does noise affect our system?
 
  
 
-----
 
-----

Revision as of 22:37, 14 March 2017

< Previous: Programming GNU Radio in Python > Next: Programming Topics

Tutorial: Working with GNU Radio in C++

Objectives

  • Extend our knowledge to program GNU Radio using C++.
  • An introduction to GNU Radio's C++ API, in particular:
    • types
    • generic functions
    • GNU Radio blocks
  • Learn to manage and write our own OOT modules in C++.
    • We follow our discussions based on a working example by continuing to work on our OOT module.
    • Within the tutorial module, we will build our QPSK demodulator called as My QPSK Demodulator in C++.
  • Understand the nuances of developing an OOT module.
    • The tutorial considers some advance topics that are also part of GNU Radio framework.
    • These topics find their usage for some specific implementations of the OOT module.

Prerequisites


4.1 C++ or Python?

This is a standard question that every one of us needs to consider before starting our own OOT module. Tutorial 3 already addressed ways to select the programming language of choice for the blocks in OOT module using gr_modtool

$ gr_modtool add -t sync -l python

or

$ gr_modtool add -t sync -l cpp # This is the default

Apart from the compiler and interpreter, there are many differences out there. To decide upon the choice of the programming language, it is important that we limit the differences from the GNU Radio perspective. Primarily, it depends more on the objective of the OOT module. As far as the performance is concerned, implementing the blocks in C++ makes more sense, and if the performance of the OOT module is the not main issue Python would be a good choice, as it is concise yet simple. Moreover, Python allows faster prototyping as we don't have to compile to test the modules.

4.2 Maneuvering our OOT module

Introduced in tutorial 3, we will now extend the usage of gr_modtool to create an OOT module and write our blocks in C++.

For a detailed explanation of gr_modtool commands, go here, or have a quick peek at the cheat sheet.

{{collapse(gr_modtool cheat sheet)
|Name| Alias| Description |
|help| h | Enlist the operations available|
|disable| dis | Disable block (comments out CMake entries for files)|
|info| getinfo, inf | Return information about a given module |
|remove| rm, del| Remove block (delete files and remove Makefile entries)|
|makexml| mx| Make XML file for GRC block bindings (Experimental feature!)|
|add| insert| Add block to the out-of-tree module.|
|newmod| nm,create| Create a new out-of-tree module |
}}

4.2.1 Objective

When this tutorial is complete, we will be able to build this flow graph:

Tut4_My_QPSK_demod_flowgraph.png

The flowgraph demonstrates a QPSK transceiver chain with the block My QPSK Demodulator block module under the OOT tutorial. We will be building this block using C++. All other blocks are standard GNU Radio blocks.

As in the previous tutorial, My QPSK Demodulator consumes QPSK symbols which are complex valued floats at the input and produces the alphabets as bytes at output. We will plot the binary values (from 0 through 3) as well as the transmitted complex symbols during operation.

Alright, lets get started.

4.2.2 Step 1: Create an OOT module gr-tutorial

xyz@comp:mydir$ gr_modtool nm tutorial
Creating out-of-tree module in ./gr-tutorial... Done.
Use 'gr_modtool add' to add a new block to this currently empty module.
xyz@comp:mydir$ ls
xyz@comp:mydir$ gr-tutorial

Have a look into the dir structure of our gr-tutorial

xyz@comp:mydir$ cd gr-tutorial
xyz@comp:mydir/gr-tutorial$ ls
apps  cmake  CMakeLists.txt  docs  examples  grc  include  lib  python  swig

4.2.3 Step 2: Insert My QPSK Demodulator block into the OOT module

Again using gr_modtool, inside gr-tutorial, we create our my_qpsk_demod block:

xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb
GNU Radio module name identified: tutorial
Enter code type: general
Language: C++
Block/code identifier: my_qpsk_demod_cb
Enter valid argument list, including default arguments:  bool gray_code
Add Python QA code? [Y/n] 
Add C++ QA code? [y/N] Y
Adding file 'my_qpsk_demod_cb_impl.h'...
Adding file 'my_qpsk_demod_cb_impl.cc'...
Adding file 'my_qpsk_demod_cb.h'...
Editing swig/qpsk_demod_swig.i...
Adding file 'qa_my_qpsk_demod_cb.py'...
Editing python/CMakeLists.txt...
Adding file 'qpsk_demod_my_qpsk_demod_cb.xml'...
Editing grc/CMakeLists.txt...

Unlike when creating an OOT module, creating a block using gr_modtool demand inputs from the user. To follow the command line user interaction, let's decompose the information above.

xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb

my_qpsk_demod_cb represents class name of the block, where the suffix, 'cb' is added to the block name, which conform to the GNU Radio nomenclature. 'cb' states the block established that takes complex data as input and spits byte as output.

Enter code type: general

In GNU Radio, there exist different kinds of blocks: general, sync, interpolator/decimator, source/sink, Hierarchical, etc. Depending on the choice of our block, gr_modtool adds the corresponding code and functions. As illustrated, for my_qpsk_demod_cb block, we opt for a general block. The following section will discuss the purpose of the specific blocks in detail.
In many cases, the block demands a user interface. For my_qpsk_demod_cb, gray_code is selected to be "default arguments".

Enter valid argument list, including default arguments:  bool gray_code

Moreover, GNU Radio provides an option of writing test cases. This provides quality assurance to the code written. If selected, the gr_modtool adds the quality assurance files corresponding to python and C++.

Add Python QA code? [Y/n] 
Add C++ QA code? [y/N] y

With this, we have already established the GNU Radio semantics for our block coupled with the OOT module. In the following sections, we will focus on the implementation of our block.
The detailed description of coding structure for the block can be found here

4.2.4 Step 3: Fleshing out the code

The next step is to implement the logic for our block. This is done inside the work function which is defined in the source file my_qpsk_demod_cb_impl.cc inside the lib/ folder. The skeleton of the my_qpsk_demod_cb_impl.cc has the following structure:

1    /*!
2      * The private constructor
3      */
4     my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)
5       : gr::block(&quot;my_qpsk_demod_cb&quot;,
6               gr::io_signature::make(<+MIN_IN+>, <+MAX_IN+>, sizeof(<+ITYPE+>)),
7               gr::io_signature::make(<+MIN_OUT+>, <+MAX_OUT+>, sizeof(<+OTYPE+>)))
8     {}
  • my_qpsk_demod_cb_impl() is the constructor of the block my_qpsk_demod. my_qpsk_demod_cb_impl() calls the constructor of the base class block gr::block(...) defined here.
  • The arguments inside gr::block(...) represents the block name and a call to the make function.
  • The make function gr::io_signature::make(<+MIN_IN+>, <+MAX_IN+>, sizeof(<+ITYPE+>)) and gr::io_signature::make(<+MIN_OUT+>, <+MAX_OUT+>, sizeof(<+OTYPE+>) is a member function of the class gr::io_signature that signifies the input and output port/s.
  • <MIN_OUT> and <MAX_OUT> represents the maximum and number of ports.
  • <ITYPE> and <OTYPE> indicates the datatypes for the input and output port/s which needs to be filled out manually.

Next, we need to modify the constructor. After modification, it looks like this:

1    /*!
2      * The private constructor
3      */
4     my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)
5       : gr::block(&quot;my_qpsk_demod_cb&quot;,
6               gr::io_signature::make(1, 1, sizeof(gr_complex)),
7               gr::io_signature::make(1, 1, sizeof(char))),
8         d_gray_code(gray_code)
9     {}

The option gray_code is copied to the class attribute d_gray_code. Note that we need
to declare this a private member of the class in the header file my_qpsk_demod_cb_impl.h,

1     private:
2       bool d_gray_code;

Also inside this class is the method general_work(), which is pure virtual in gr::block, so we definitely need to override that. After running gr_modtool,
the skeleton version of this function will look something like this:

 1 int
 2 my_qpsk_demod_cb_impl::general_work (int noutput_items,
 3                    gr_vector_int &amp;ninput_items,
 4                    gr_vector_const_void_star &amp;input_items,
 5                    gr_vector_void_star &amp;output_items)
 6 {
 7     const &lt;+ITYPE*&gt; *in = (const &lt;+ITYPE*&gt; *) input_items[0];
 8     &lt;+OTYPE*&gt; *out = (&lt;+OTYPE*&gt; *) output_items[0];
 9 
10     // Do &lt;+signal processing+&gt;
11     // Tell runtime system how many input items we consumed on
12     // each input stream.
13     consume_each (noutput_items);
14 
15     // Tell runtime system how many output items we produced.
16     return noutput_items;
17 }

There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which processes the items in the input buffer and copies them to the output buffer. Once the demodulation logic is implemented, the structure of the work function has the form

 1     int
 2     my_qpsk_demod_cb_impl::general_work (int noutput_items,
 3                        gr_vector_int &amp;ninput_items,
 4                        gr_vector_const_void_star &amp;input_items,
 5                        gr_vector_void_star &amp;output_items)
 6     {
 7         const gr_complex *in = (const gr_complex *) input_items[0];
 8         unsigned char *out = (unsigned char *) output_items[0];
 9         gr_complex origin = gr_complex(0,0);
10         // Perform ML decoding over the input iq data to generate alphabets
11         for(int i = 0; i &lt; noutput_items; i++)
12         {
13                 // ML decoder, determine the minimum distance from all constellation points
14                 out[i] = get_minimum_distances(in[i]);
15         }
16 
17         // Tell runtime system how many input items we consumed on
18         // each input stream.
19         consume_each (noutput_items);
20 
21         // Tell runtime system how many output items we produced.
22         return noutput_items;
23     }

This work function calls another function get_minimum_distances(const gr_complex &sample), which we also need to add:

 1     unsigned char
 2     my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &amp;sample)
 3     {
 4       if (d_gray_code) {
 5         unsigned char bit0 = 0;
 6         unsigned char bit1 = 0;
 7         // The two left quadrants (quadrature component &lt; 0) have this bit set to 1
 8         if (sample.real() &lt; 0) {
 9           bit0 = 0x01;
10         }
11         // The two lower quadrants (in-phase component &lt; 0) have this bit set to 1
12         if (sample.imag() &lt; 0) {
13           bit1 = 0x01 &lt;&lt; 1;
14         }
15         return bit0 | bit1;
16       } else {
17         // For non-gray code, we can't simply decide on signs, so we check every single quadrant.
18         if (sample.imag() &gt;= 0 and sample.real() &gt;= 0) {
19           return 0x00;
20         }
21         else if (sample.imag() &gt;= 0 and sample.real() &lt; 0) {
22           return 0x01;
23         }
24         else if (sample.imag() &lt; 0 and sample.real() &lt; 0) {
25           return 0x02;
26         }
27         else if (sample.imag() &lt; 0 and sample.real() &gt;= 0) {
28           return 0x03;
29         }
30       }
31     }

Note the function declaration also needs to be added to the class header (my_qpsk_demod_cb_impl.h).
The function get_minimum_distances is a maximum likelihood decoder for the QPSK demodulater. Theoretically, the function should compute the distance from each ideal QPSK symbol to the received symbol (It is mathematically equivalent to determining the Voronoi regions of the received sample). For a QPSK signal, these Voronoi regions are simply four quadrants in the complex plane. Hence, to decode the sample into bits, it makes sense to map the received sample to these quadrants.

Now, let's consider the forecast() function. The system needs to know how much data is required to ensure validity in each of the input arrays. As stated before, the forecast() method provides this information, and you must therefore override it anytime you write a gr::block derivative (for sync blocks, this is implicit).

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::make 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. Of course, if you had this relationship, you wouldn't want to use a gr::block!

1   // default implementation:  1:1
2   void
3   gr::block::forecast(int noutput_items,
4                      gr_vector_int &amp;ninput_items_required)
5   {
6     unsigned ninputs = ninput_items_required.size ();
7     for(unsigned i = 0; i &lt; ninputs; i++)
8       ninput_items_required[i] = noutput_items;
9   }

Although the 1:1 implementation worked for my_qpsk_demod_cb, 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.

Refilling the private constructor and overriding the general_work() and forecast() will suffice the coding structure of our block. However, in the gr::block class there exists more specific functions. These functions are covered under advanced topics section

4.2.5 Step 4: Flesh out the XML file

The .xml provides the user interface between the OOT module displayed in the GRC and the source code. Moreover, the XML file defines an interface to pass the parameters specific for the module. Hence, to access the module inside GRC, it is important to modify the .xml files manually. The XML file for our block is named as demod_my_qpsk_demod_cb.xml inside the grc/ folder. Presently, the gr_modtool's version looks like:

{{collapse(Default version:)


  my_qpsk_demod_cb
  tutorial_my_qpsk_demod_cb
  tutorial
  import tutorial
  tutorial.my_qpsk_demod_cb($gray_code)
  
  
    ...
    ...
    ...
  

  
  
    in
    
  

  
  
    out
    
  

}}

The parameter gray_code can be put under the <parameter> tag.

{{collapse(Adding parameter tag:)

  
    Gray Code
    gray_code
    True
    bool
    
      Yes
      True
    
    
      No
      False
    
  

}}

Like the work function, the datatypes for the input and output ports represented by <sink> and <source> tags should be modified.

{{collapse(Modifying source and sink tag:)

  
    in
    complex
  
  
    out
    byte
  

}}

After all the necessary modification the "tutorial_my_qpsk_demod_cb.xml" looks like this:

{{collapse(Modified version:)


  My QPSK Demodulator
  tutorial_my_qpsk_demod_cb
  tutorial
  import tutorial
  tutorial.my_qpsk_demod_cb($gray_code)
  
    Gray Code
    gray_code
    True
    bool
    
      Yes
      True
    
    
      No
      False
    
  
  
    in
    complex
  
  
    out
    byte
  

}}

4.2.6 Step 5: Install my_qpsk_demod in grc

We have finished the implementation of our block, now it's important use its functionality under GRC. So we build our OOT and install the underlying blocks. To do so, we need to execute the following commands:

{{collapse(sequence:)

xyz@comp:mydir/gr-tutorial$ mkdir build
xyz@comp:mydir/gr-tutorial$ cd build
xyz@comp:mydir/gr-tutorial/build$ cmake ..
-- Build type not specified: defaulting to release.
Checking for GNU Radio Module: RUNTIME
 * INCLUDES=/usr/local/include
 * LIBS=/usr/local/lib/libgnuradio-runtime.so;/usr/local/lib/libgnuradio-pmt.so
GNURADIO_RUNTIME_FOUND = TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: pathtomyhomefolder/mydir/gr-qpsk_demod/build
xyz@comp:mydir/gr-demod/build$make
[  7%] Built target gnuradio-tutorial
[ 23%] Built target test-tutorial
[ 23%] Built target tutorial_swig_swig_doc
[ 30%] Built target _tutorial_swig_swig_tag
[ 38%] Swig source
Scanning dependencies of target _tutorial_swig
[ 46%] Building CXX object swig/CMakeFiles/_tutorial_swig.dir/tutorial_swigPYTHON_wrap.cxx.o
Linking CXX shared module _tutorial_swig.so
[ 53%] Built target _tutorial_swig
[ 61%] Generating tutorial_swig.pyc
[ 69%] Generating tutorial_swig.pyo
[ 84%] Built target pygen_swig_2598c
[100%] Built target pygen_python_6ab2e
[100%] Built target pygen_apps_9a6dd

xyz@comp:mydir/gr-qpsk_demod/build$ sudo make install
[  7%] Built target gnuradio-tutorial
[ 23%] Built target test-tutorial
[ 23%] Built target tutorial_swig_swig_doc
[ 30%] Built target _tutorial_swig_swig_tag
[ 53%] Built target _tutorial_swig
[ 84%] Built target pygen_swig_2598c
[100%] Built target pygen_python_6ab2e
[100%] Built target pygen_apps_9a6dd
Install the project...
-- Install configuration: "Release"
-- Up-to-date: /usr/local/lib/cmake/tutorial/demodConfig.cmake
-- Up-to-date: /usr/local/include/tutorial/api.h
-- Up-to-date: /usr/local/include/tutorial/my_qpsk_demod_cb.h
-- Up-to-date: /usr/local/lib/libgnuradio-tutorial.so
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/_tutorial_swig.so
-- Removed runtime path from "/usr/local/lib/python2.7/dist-packages/tutorial/_tutorial_swig.so"
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/tutorial_swig.py
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/tutorial_swig.pyc
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/tutorial_swig.pyo
-- Up-to-date: /usr/local/include/tutorial/tutorial/swig/tutorial_swig.i
-- Installing: /usr/local/include/tutorial/tutorial/swig/tutorial_swig_doc.i
-- Up-to-date: /usr/local/lib/python2.7/dist-packages/tutorial/__init__.py
-- Up-to-date: /usr/local/lib/python2.7/dist-packages/tutorial/__init__.pyc
-- Up-to-date: /usr/local/lib/python2.7/dist-packages/tutorial/__init__.pyo
-- Up-to-date: /usr/local/share/gnuradio/grc/blocks/tutorial_my_qpsk_cb.xml

xyz@comp:mydir/gr-qpsk_demod/build$ sudo ldconfig

}}

4.2.7 Step 6: Quality Assurance (Unit Testing)

Constellation_diagram.grc

Figure above represents the constellation diagram (displayed in accordance to the Qt GUI Constellation Sink) of the QPSK modulated input fed to out OOT module. The task of our QPSK demodulator is to demodulate this complex valued input stream and to produce stream of quaternary alphabets (0,1,2,3) or simply bytes as output.

By following the steps of writing the OOT module, we did manage to produce the byte stream at the output of QPSK demodulator, still, it doesn't guarantee the correct working of our block. In this situation, it becomes significant to write unit test for our module that certifies the clean implementation of the QPSK demodulator.

Below we see the source of code of the qa_qpsk_demod.py can be found under python/

{{collapse(Full QA code)

 1 from gnuradio import gr, gr_unittest
 2 from gnuradio import blocks
 3 import tutorial_swig as tutorial
 4 from numpy import array
 5 
 6 class qa_qpsk_demod (gr_unittest.TestCase):
 7 
 8     def setUp (self):
 9         self.tb = gr.top_block ()
10 
11     def tearDown (self):
12         self.tb = None
13 
14     def test_001_gray_code_enabled (self):
15         # &quot;Construct the Iphase and Qphase components&quot;
16         Iphase = array([ 1, -1, -1,  1])
17         Qphase = array([ 1,  1, -1, -1])
18         src_data = Iphase + 1j*Qphase;
19         # &quot;Enable Gray code&quot;
20         gray_code =  True;
21         # &quot;Determine the expected result&quot;
22         expected_result = (0,1,3,2)
23         # &quot;Create a complex vector source&quot;
24         src = blocks.vector_source_c(src_data)
25         # &quot;Instantiate the test module&quot;
26         qpsk_demod = tutorial.my_qpsk_demod_cb(gray_code)
27         # &quot;Instantiate the binary sink&quot;
28         dst = blocks.vector_sink_b();
29         # &quot;Construct the flowgraph&quot;
30         self.tb.connect(src,qpsk_demod)
31         self.tb.connect(qpsk_demod,dst)
32         # &quot;Create the flow graph&quot;
33         self.tb.run ()
34         # check data
35         result_data = dst.data()
36         self.assertTupleEqual(expected_result, result_data)
37         self.assertEqual(len(expected_result), len(result_data))
38 
39     def test_002_gray_code_disabled (self):
40         # &quot;Construct the Iphase and Qphase components&quot;
41         Iphase = array([ 1, -1, -1,  1])
42         Qphase = array([ 1,  1, -1, -1])
43         src_data = Iphase + 1j*Qphase;
44         # &quot;Enable Gray code&quot;
45         gray_code =  False;
46         # &quot;Determine the expected result&quot;
47         expected_result = (0,1,2,3)
48         # &quot;Create a complex vector source&quot;
49         src = blocks.vector_source_c(src_data)
50         # &quot;Instantiate the test module&quot;
51         qpsk_demod = tutorial.my_qpsk_demod_cb(gray_code)
52         # &quot;Instantiate the binary sink&quot;
53         dst = blocks.vector_sink_b();
54         # &quot;Construct the flowgraph&quot;
55         self.tb.connect(src,qpsk_demod)
56         self.tb.connect(qpsk_demod,dst)
57         # &quot;Create the flow graph&quot;
58         self.tb.run ()
59         # check data
60         result_data = dst.data()
61         self.assertTupleEqual(expected_result, result_data)
62         self.assertEqual(len(expected_result), len(result_data))
63 
64 if __name__ == '__main__':
65     gr_unittest.run(qa_qpsk_demod, &quot;qa_qpsk_demod.xml&quot;)

}}

It can be easily noticed that the qa_qpsk_demod is implemented in python, in spite of we opted C++ in the first case for writing our blocks. This is because, GNU Radio inherits the python unittest framework to support quality assurance. And, if you remember it correctly from previous tutorials, swig as part of GNU Radio framework, provides python bindings for the C++ code. Hence, we are able to write the unit test for our block qa_qpsk_demod in python.

So lets gather a bit of know how on how to write test cases for the block. Okay, lets consider the header part first:

from gnuradio import gr, gr_unittest
from gnuradio import blocks
import tutorial_swig as tutorial
from numpy import array

from gnuradio import gr, gr_unittest and from gnuradio import blocks are the standard lines that includes gr, gr_unittest functionality in the qa_ file. import tutorial_swig as tutorial import the python bidden version of our module, which provides an access our block my_qpsk_demod_cb. Finally, from numpy import array includes array.

if __name__ == '__main__':
    gr_unittest.run(qa_qpsk_demod, "qa_qpsk_demod.xml")

The qa_ file execution start by calling this function. The gr_unittest automatically calls the functions in a specific order def setUp (self) for creating the top block at the start, tearDown (self) for deleting the top block at the end. In between the setUp and tearDown the test cases defined are executed. The methods starting with prefix test_ are recognized as test cases by gr_unittest. We have defined two test cases test_001_gray_code_enabled and test_002_gray_code_disabled. The usual structure of a test cases comprises of a known input data and the expected output. A flowgraph is created to include the source (input data), block to be tested (processor) and sink (resulted output data). In the end the expected output is compared with the resulted output data.

Finally, the statements in the test cases

self.assertTupleEqual(expected_result, result_data)
self.assertEqual(len(expected_result), len(result_data))

determine the result of test cases as passed or failed. The test cases are executed before installation of the module by running make test:
{{collapse(Output)

xyz@comp:mydir/gr-tutorial/build$ make test
Running tests...
Test project /home/kaushik/src/gr-tutorial/build
    Start 1: test_tutorial
1/3 Test #1: test_tutorial ....................   Passed    0.11 sec
    Start 2: qa_chat_sanitizer
2/3 Test #2: qa_chat_sanitizer ................***Failed    0.04 sec
    Start 3: qa_qpsk_demod
3/3 Test #3: qa_qpsk_demod ....................   Passed    2.00 sec

67% tests passed, 1 tests failed out of 3

Total Test time (real) =   2.34 sec

The following tests FAILED:
      2 - qa_chat_sanitizer (Failed)
Errors while running CTest
make: *** [test] Fehler 8

}}

In the output above, one of the test failed, however, Test 3 belonging to the qa_qpsk_demod, claims to have passed the test cases.
Congratulations, we have just finished writing our OOT module gr-tutorial and a C++ block my_qpsk_demodulator.

4.3 Advanced topics

The topics discussed until now have laid the foundation for designing the OOT module independently. However, the GNU Radio jargon extends further beyond these. Therefore, under this section, we drift from the QPSK demodulator and focus on the features that are rarely used or are more specific to the implementation.

To add physical meaning to the discussion, we have taken assistance of the existing modules. The source code excerpts are included thereof. Enthusiastic readers are suggested to open the source code in parallel and play around with their functionalities.

4.3.1 Specific functions related to block

In the last section, we managed out implementation of our block by defining functions like general_work and forecast(). But sometimes special functions need to be defined for the implementation. The list is long, but we try to discuss same of these functions in the following subsections.

4.3.1.1 set_history()

If your block needs a history (i.e., something like an FIR filter), call this in the constructor.
Here is an
{{collapse(example)

test::test(const std::string &amp;name,
             int min_inputs, int max_inputs,
             unsigned int sizeof_input_item,
             int min_outputs, int max_outputs,
             unsigned int sizeof_output_item,
             unsigned int history,
             unsigned int output_multiple,
             double relative_rate,
             bool fixed_rate,
             consume_type_t cons_type, produce_type_t prod_type)
  : block (name,
           io_signature::make(min_inputs, max_inputs, sizeof_input_item),
           io_signature::make(min_outputs, max_outputs, sizeof_output_item)),
    d_sizeof_input_item(sizeof_input_item),
    d_sizeof_output_item(sizeof_output_item),
    d_check_topology(true),
    d_consume_type(cons_type),
    d_min_consume(0),
    d_max_consume(0),
    d_produce_type(prod_type),
    d_min_produce(0),
    d_max_produce(0)
  {
    set_history(history);
    set_output_multiple(output_multiple);
    set_relative_rate(relative_rate);
    set_fixed_rate(fixed_rate);
  }

}}

GNU Radio then makes sure you have the given number of 'old' items available.

The smallest history you can have is 1, i.e., for every output item, you need 1 input item. If you choose a larger value, N, this means your output item is calculated from the current input item and from the N-1 previous input items.

The scheduler takes care of this for you. If you set the history to length N, the first N items in the input buffer include the N-1 previous ones (even though you've already consumed them).

The history is stored in the variable d_history.
The set_history() is defined in gnuradio/gnuradio-runtime/block.cc

void block::set_history(unsigned history)
{
    d_history = history;
}

4.3.1.2 set_output_multiple()

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

{{collapse(code)

test::test(const std::string &amp;name,
             int min_inputs, int max_inputs,
             unsigned int sizeof_input_item,
             int min_outputs, int max_outputs,
             unsigned int sizeof_output_item,
             unsigned int history,
             unsigned int output_multiple,
             double relative_rate,
             bool fixed_rate,
             consume_type_t cons_type, produce_type_t prod_type)
  : block (name,
           io_signature::make(min_inputs, max_inputs, sizeof_input_item),
           io_signature::make(min_outputs, max_outputs, sizeof_output_item)),
    d_sizeof_input_item(sizeof_input_item),
    d_sizeof_output_item(sizeof_output_item),
    d_check_topology(true),
    d_consume_type(cons_type),
    d_min_consume(0),
    d_max_consume(0),
    d_produce_type(prod_type),
    d_min_produce(0),
    d_max_produce(0)
{
    set_history(history);
    set_output_multiple(output_multiple);
    set_relative_rate(relative_rate);
    set_fixed_rate(fixed_rate);
}

}}

by invoking set_output_multiple, we set the value variable to d_output_multiple. The default value of d_output_multiple is 1.

Lets consider an example, say we want to generate outputs only in a 64 elements chunk, by setting d_output_multiple to 64 we can achieve this, but note that we can also get multiples of 64 i.e. 128, 256 etc

The definition of set_output_multiple can be found in gnuradio/gnuradio-runtime/block.cc

void gr_block::set_output_multiple (int multiple)
{
  if (multiple &lt; 1)
    throw std::invalid_argument (&quot;gr_block::set_output_multiple&quot;);

  d_output_multiple_set = true;
  d_output_multiple = multiple;
}

4.3.2 Specific block categories

Again the implementation of the my_qpsk_demod_cb was done using a general block. However, GNU Radio includes some blocks with special functionality. A brief overview of these blocks is described in the table.

Block Functionality
General This block a generic version of all blocks
Source/Sinks The source/sink produce/consume the input/output items
Interpolation/Decimation The interpolation/decimation block is another type of fixed rate block where the number of output/input items is a fixed multiple of the number of input/output items.
Sync The sync block allows users to write blocks that consume and produce an equal number of items per port. From the user perspective, the GNU Radio scheduler synchronizes the input and output items, it has nothing to with synchronization algorithms
Hierarchical blocks Hierarchical blocks are blocks that are made up of other blocks.

In the next subsections we discuss these blocks in detail. Again, enthusiastic readers can find these blocks in the GNU Radio source code.

4.3.2.1 General

howto_square_ff::howto_square_ff ()
: gr::block(&quot;square_ff&quot;,
gr::io_signature::make(MIN_IN, MAX_IN, sizeof (float)),
gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (float)))
{
// nothing else required in this example
}

4.3.2.2 Source and Sinks

Source

An example of source block in C++

usrp_source_impl::usrp_source_impl(const ::uhd::device_addr_t &amp;device_addr,
                                   const ::uhd::stream_args_t &amp;stream_args):
      sync_block(&quot;gr uhd usrp source&quot;,
                    io_signature::make(0, 0, 0),
                    args_to_io_sig(stream_args)),
      _stream_args(stream_args),
      _nchan(stream_args.channels.size()),
      _stream_now(_nchan == 1),
      _tag_now(false),
      _start_time_set(false)

Some observations:

  • io_signature::make(0, 0, 0) sets the input items to 0, in indicates there are no input streams.
  • Because it connected with the hardware USRP, the gr uhd usrp sink is a sub class of sync_block.

Sink

An example of the sink block in C++

usrp_sink_impl::usrp_sink_impl(const ::uhd::device_addr_t &amp;device_addr,
                                   const ::uhd::stream_args_t &amp;stream_args)
      : sync_block(&quot;gr uhd usrp sink&quot;,
                      args_to_io_sig(stream_args),
                      io_signature::make(0, 0, 0)),
        _stream_args(stream_args),
        _nchan(stream_args.channels.size()),
        _stream_now(_nchan == 1),
        _start_time_set(false)

Some observations:

  • io_signature::make(0, 0, 0) sets the output items to 0, in indicates there are no output streams.
  • Because it connected with the hardware USRP, the gr uhd usrp sink is a sub class of sync_block.

4.3.2.3 Sync

The sync block allows users to write blocks that consume and produce an equal number of items per port. A sync block may have any number of inputs or outputs. When a sync block has zero inputs, its called a source. When a sync block has zero outputs, its called a sink.

An example sync block in C++:

#include 

class my_sync_block : public gr_sync_block
{
public:
  my_sync_block(...):
    gr_sync_block(&quot;my block&quot;,
                  gr::io_signature::make(1, 1, sizeof(int32_t)),
                  gr::io_signature::make(1, 1, sizeof(int32_t)))
  {
    //constructor stuff
  }

  int work(int noutput_items,
           gr_vector_const_void_star &amp;input_items,
           gr_vector_void_star &amp;output_items)
  {
    //work stuff...
    return noutput_items;
  }
};

Some observations:

  • noutput_items is the length in items of all input and output buffers
  • an input signature of gr::io_signature::make(0, 0, 0) makes this a source block
  • an output signature of gr::io_signature::make(0, 0, 0) makes this a sink block

4.3.2.4 Rate changing blocks: Interpolators and Decimators

Decimators

The decimation block is another type of fixed rate block where the number of input items is a fixed multiple of the number of output items.

An example decimation block in c++

#include 

class my_decim_block : public gr_sync_decimator
{
public:
  my_decim_block(...):
    gr_sync_decimator(&quot;my decim block&quot;, 
                      in_sig,
                      out_sig,
                      decimation)
  {
    //constructor stuff
  }

  //work function here...
};

Some observations:

  • The gr_sync_decimator constructor takes a 4th parameter, the decimation factor
  • The user must assume that the number of input items = noutput_items*decimation. The value ninput_items is therefore implicit.

Interpolation

The interpolation block is another type of fixed rate block where the number of output items is a fixed multiple of the number of input items.

An example interpolation block in c++

#include 

class my_interp_block : public gr_sync_interpolator
{
public:
  my_interp_block(...):
    gr_sync_interpolator(&quot;my interp block&quot;,
                         in_sig,
                         out_sig,
                         interpolation)
  {
    //constructor stuff
  }

  //work function here...
};

Some observations:

  • The gr_sync_interpolator constructor takes a 4th parameter, the interpolation factor
  • The user must assume that the number of input items = noutput_items/interpolation

4.3.2.5 Hierarchical blocks

Hierarchical blocks are blocks that are made up of other blocks. They instantiate the other GNU Radio blocks (or other hierarchical blocks) and connect them together. A hierarchical block has a "connect" function for this purpose.

When to use hierarchical blocks?

Hierarchical blocks provides us modularity in our flowgraphs by abstracting simple blocks, that is hierarchical block helps us define our specific blocks at the same time provide us the flexibility to change it, example, we would like to test effect of different modulation schemes for a given channel model. However our synchronization algorithms are specific or newly published. We define our hier block as gr-my_sync that does synchronization followed equalizer and demodulation. We start with BPSK, the flowgraph looks like

gr-tx ---> gr-channel --> gr-my_sync --> gr-equalizer --> gr-bpsk_demod

Now, our flowgraph looks decent. Secondly, we abstracted the complex functionality of our synchronization. Shifting to QPSK, where the synchronization algorithm remains the same, we just replace the gr-bpsk_demod with gr-qpsk_demod

gr-tx ---> gr-channel --> gr-my_sync --> gr-equalizer --> gr-qpsk_demod

How to build hierarchical blocks in GNU Radio?

Hierarchical blocks define an input and output stream much like normal blocks. To connect input i to a hierarchical block, the source is (in Python):

self.connect((self, i), <block>)

Similarly, to send the signal out of the block on output stream o:

self.connect(<block>, (self, o))
An typical example of a hierarchical block is OFDM Receiver implemented in python under
gnuradio/gr-digital/python/digital

The class is defined as:

class ofdm_receiver(gr.hier_block2)

and instantiated as

gr.hier_block2.__init__(self, &quot;ofdm_receiver&quot;,
                        gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
                        gr.io_signature2(2, 2, gr.sizeof_gr_complex*occupied_tones, gr.sizeof_char)) # Output signature

Some main tasks performed by the OFDM receiver include channel filtering, synchronization and IFFT tasks. The individual tasks are defined inside the hierarchical block.

  • Channel filtering
chan_coeffs = filter.firdes.low_pass (1.0,                     # gain
                                              1.0,                     # sampling rate
                                              bw+tb,                   # midpoint of trans. band
                                              tb,                      # width of trans. band
                                              filter.firdes.WIN_HAMMING)   # filter type
self.chan_filt = filter.fft_filter_ccc(1, chan_coeffs)
  • Synchronization
self.chan_filt = blocks.multiply_const_cc(1.0)
nsymbols = 18      # enter the number of symbols per packet
freq_offset = 0.0  # if you use a frequency offset, enter it here
nco_sensitivity = -2.0/fft_length   # correct for fine frequency
self.ofdm_sync = ofdm_sync_fixed(fft_length,
                                 cp_length,
                                 nsymbols,
                                 freq_offset,
                                 logging)
  • ODFM demodulation
self.fft_demod = gr_fft.fft_vcc(fft_length, True, win, True)

Finally, the individual blocks along with hierarchical are connected among each to form a flow graph.

Connection between the hierarchical block OFDM receiver to channel filter block

self.connect(self, self.chan_filt)                            # filter the input channel

Connection between the channel filter block to the OFDM synchronization block.

self.connect(self.chan_filt, self.ofdm_sync)

and so forth.

Hierarchical blocks can also be nested, that is blocks defined in hierarchical blocks could also be hierarchical blocks. For example, OFDM sync block is also an hierarchical block. In this particular case it is implemented in C++. Lets have a look into it.

Underneath is instant of the hierarchical block. Don't panic by looking at its size, we just need to grab the concept behind creating hierarchical blocks.

{{collapse(OFDM impl)

ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
        : hier_block2 (&quot;ofdm_sync_sc_cfb&quot;,
                       io_signature::make(1, 1, sizeof (gr_complex)),
#ifndef SYNC_ADD_DEBUG_OUTPUT
                   io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))
#else
                   io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))
#endif
    {
      std::vector ma_taps(fft_len/2, 1.0);
      gr::blocks::delay::sptr          delay(gr::blocks::delay::make(sizeof(gr_complex), fft_len/2));
      gr::blocks::conjugate_cc::sptr   delay_conjugate(gr::blocks::conjugate_cc::make());
      gr::blocks::multiply_cc::sptr    delay_corr(gr::blocks::multiply_cc::make());
      gr::filter::fir_filter_ccf::sptr delay_ma(gr::filter::fir_filter_ccf::make(1, std::vector(fft_len/2, use_even_carriers ? 1.0 : -1.0)));
      gr::blocks::complex_to_mag_squared::sptr delay_magsquare(gr::blocks::complex_to_mag_squared::make());
      gr::blocks::divide_ff::sptr      delay_normalize(gr::blocks::divide_ff::make());

      gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());
gr::filter::fir_filter_ccf::sptr delay_ma(gr::filter::fir_filter_ccf::make(1, std::vector(fft_len/2, use_even_carriers ? 1.0 : -1.0)));
      gr::blocks::complex_to_mag_squared::sptr delay_magsquare(gr::blocks::complex_to_mag_squared::make());
      gr::blocks::divide_ff::sptr      delay_normalize(gr::blocks::divide_ff::make());

      gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());
      gr::filter::fir_filter_fff::sptr         normalizer_ma(gr::filter::fir_filter_fff::make(1, std::vector(fft_len, 0.5)));
      gr::blocks::multiply_ff::sptr            normalizer_square(gr::blocks::multiply_ff::make());

      gr::blocks::complex_to_arg::sptr         peak_to_angle(gr::blocks::complex_to_arg::make());
      gr::blocks::sample_and_hold_ff::sptr     sample_and_hold(gr::blocks::sample_and_hold_ff::make());

      gr::blocks::plateau_detector_fb::sptr    plateau_detector(gr::blocks::plateau_detector_fb::make(cp_len));

      // Delay Path
      connect(self(),               0, delay,                0);
      connect(delay,                0, delay_conjugate,      0);
      connect(delay_conjugate,      0, delay_corr,           1);
      connect(self(),               0, delay_corr,           0);
      connect(delay_corr,           0, delay_ma,             0);
      connect(delay_ma,             0, delay_magsquare,      0);
      connect(delay_magsquare,      0, delay_normalize,      0);
      // Energy Path
      connect(self(),               0, normalizer_magsquare, 0);
      connect(normalizer_magsquare, 0, normalizer_ma,        0);
      connect(normalizer_ma,        0, normalizer_square,    0);
      connect(normalizer_ma,        0, normalizer_square,    1);
      connect(normalizer_square,    0, delay_normalize,      1);
      // Fine frequency estimate (output 0)
      connect(delay_ma,             0, peak_to_angle,        0);
      connect(peak_to_angle,        0, sample_and_hold,      0);
      connect(sample_and_hold,      0, self(),               0);
      // Peak detect (output 1)
      connect(delay_normalize,      0, plateau_detector,     0);
      connect(plateau_detector,     0, sample_and_hold,      1);
      connect(plateau_detector,     0, self(),               1);
#ifdef SYNC_ADD_DEBUG_OUTPUT
      // Debugging: timing metric (output 2)
      connect(delay_normalize,      0, self(),               2);
#endif
    }

Let's understand the code piece wise. The hierarchical block is C++ is instantiated as follows:

ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
        : hier_block2 (&quot;ofdm_sync_sc_cfb&quot;,
                       io_signature::make(1, 1, sizeof (gr_complex)),
#ifndef SYNC_ADD_DEBUG_OUTPUT
                   io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))
#else
                   io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))

}}

where ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl is the constructor with parameters int fft_len, int cp_len, bool use_even_carriers and hier_block2 is the base class. The block name "ofdm_sync_sc_cfb" is defined following the GNU Radio block naming style.

io_signature::make(1, 1, sizeof (gr_complex)) defines my input items and the output items are either
io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char))) or io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))

depending on the preprocessor directive SYNC_ADD_DEBUG_OUTPUT.

The individual blocks inside the ofdm_sync_sc_cfb block are defined as follows:

gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());

Finally the individual blocks are connected using:

connect(normalizer_magsquare, 0, normalizer_ma, 0);

4.4 Closing note

At this point, we are qualified enough to write our own OOT module and include blocks within (either in Python or C++ depending on what we choose). To strengthen the things we learned in this tutorial, its time to go through a small quiz in the following section. The next tutorial, illustrates different ways, synchronous and asynchronous, of communication among the blocks.

4.5 Quiz

  • What will change if we decide to shift our modulation scheme form QPSK to 8 PSK/4-QAM/16-QAM?
  • If we think of porting the QPSK demodulator to a generic demodulator, does the usage of hierarchical block makes sense?
  • How does noise affect our system?

< Previous: Programming GNU Radio in Python > Next: Programming Topics

[1] http://radioware.nd.edu/documentation/advanced-gnuradio/writing-a-signal-processing-block-for-gnu-radio-part-i