BlocksCodingGuide
Version 7 (Johnathan Corgan, 06/22/2012 10:41 pm)
| 1 | 6 | Tom Rondeau | h1. Blocks Coding Style |
|---|---|---|---|
| 2 | 1 | {{toc}} |
|
| 3 | 1 | ||
| 4 | 1 | h2. Terminology |
|
| 5 | 1 | ||
| 6 | 1 | | Block | A functional processing unit with inputs and outputs | |
|
| 7 | 1 | | Port | A single input or output of a block | |
|
| 8 | 1 | | Source | A producer of data | |
|
| 9 | 1 | | Sink | A consumer of data | |
|
| 10 | 1 | | Connection | A flow of data from output port to input port| |
|
| 11 | 1 | | Flow graph | A collection of blocks and connections | |
|
| 12 | 1 | | Item | A unit of data. Ex: baseband sample, fft vector, matrix... | |
|
| 13 | 1 | | Stream | A continuous flow of consecutive items | |
|
| 14 | 1 | |IO signature | A description of a blocks input and output ports | |
|
| 15 | 1 | ||
| 16 | 1 | ---- |
|
| 17 | 1 | ||
| 18 | 6 | Tom Rondeau | h2. Coding Structure |
| 19 | 1 | ||
| 20 | 6 | Tom Rondeau | h3. Public Header Files |
| 21 | 6 | Tom Rondeau | |
| 22 | 6 | Tom Rondeau | The public header files are defined in *include/foo* and get installed into *$prefix/include/foo*. |
| 23 | 6 | Tom Rondeau | |
| 24 | 6 | Tom Rondeau | The accessors (set/get) functions that are to be exported are defined a pure virtual functions in this header. |
| 25 | 6 | Tom Rondeau | |
| 26 | 6 | Tom Rondeau | A skeleton of the a common public header file looks like: |
| 27 | 6 | Tom Rondeau | |
| 28 | 6 | Tom Rondeau | <pre> |
| 29 | 6 | Tom Rondeau | #ifndef INCLUDED_FOO_BAR_H |
| 30 | 6 | Tom Rondeau | #define INCLUDED_FOO_BAR_H |
| 31 | 6 | Tom Rondeau | |
| 32 | 6 | Tom Rondeau | #include <foo/api.h> |
| 33 | 6 | Tom Rondeau | #include <gr_sync_block.h> |
| 34 | 6 | Tom Rondeau | |
| 35 | 6 | Tom Rondeau | namespace gr { |
| 36 | 6 | Tom Rondeau | namespace foo { |
| 37 | 6 | Tom Rondeau | |
| 38 | 6 | Tom Rondeau | class FOO_API bar : virtual public gr_sync_block |
| 39 | 6 | Tom Rondeau | { |
| 40 | 6 | Tom Rondeau | public: |
| 41 | 6 | Tom Rondeau | |
| 42 | 6 | Tom Rondeau | // gr::foo::bar::sptr |
| 43 | 6 | Tom Rondeau | typedef boost::shared_ptr<bar> sptr; |
| 44 | 6 | Tom Rondeau | |
| 45 | 6 | Tom Rondeau | /*! |
| 46 | 6 | Tom Rondeau | * \class bar |
| 47 | 6 | Tom Rondeau | * \brief A brief description of what foo::bar does |
| 48 | 6 | Tom Rondeau | * |
| 49 | 6 | Tom Rondeau | * \ingroup <some group>_blk |
| 50 | 6 | Tom Rondeau | * |
| 51 | 6 | Tom Rondeau | * A more detailed description of the block. |
| 52 | 6 | Tom Rondeau | * |
| 53 | 6 | Tom Rondeau | * \param var explanation of argument var. |
| 54 | 6 | Tom Rondeau | */ |
| 55 | 7 | Johnathan Corgan | static sptr make(dtype var); |
| 56 | 6 | Tom Rondeau | |
| 57 | 6 | Tom Rondeau | virtual void set_var(dtype var) = 0; |
| 58 | 6 | Tom Rondeau | virtual dtype var() = 0; |
| 59 | 6 | Tom Rondeau | }; |
| 60 | 6 | Tom Rondeau | |
| 61 | 6 | Tom Rondeau | } /* namespace foo */ |
| 62 | 6 | Tom Rondeau | } /* namespace gr */ |
| 63 | 6 | Tom Rondeau | |
| 64 | 6 | Tom Rondeau | #endif /* INCLUDED_FOO_BAR_H */ |
| 65 | 6 | Tom Rondeau | </pre> |
| 66 | 6 | Tom Rondeau | |
| 67 | 6 | Tom Rondeau | h3. Implementation Header File |
| 68 | 6 | Tom Rondeau | |
| 69 | 6 | Tom Rondeau | The private implementation header files are defined in *lib* and do not get installed. We normally define these files to use the same name as the public file and class with a '_impl' suffix to indicate that this is the implementation file for the class. |
| 70 | 6 | Tom Rondeau | |
| 71 | 6 | Tom Rondeau | In some cases, this file might be specific to a very particular implementation and multiple implementations might be available for a given block but with the same public API. A good example is the use of the FFTW library for implementing the *fft_filter* blocks. This is only one of many possible ways to implement an FFT, and so the implementation was named *fft_filter_ccc_fftw*. Another library that implements an FFT specific to a platform or purpose could then be slotted in as a new implementation like *fft_filter_ccc_myfft*. |
| 72 | 6 | Tom Rondeau | |
| 73 | 6 | Tom Rondeau | All member variables are declared private and use the prefix 'd_'. As much as possible, all variables should have a set and get function. The set function looks like *void set_var(dtype var)*, and the get function looks like *dtype var()*. It does not always make sense to have a set or get for a particular variable, but all efforts should be made to accommodate it. |
| 74 | 6 | Tom Rondeau | |
| 75 | 6 | Tom Rondeau | The Doxygen comments that will be included in the manual are defined in the public header file. There is no need for Doxygen markup in the private files, but of course, any comments or documentation that make sense should always be used. |
| 76 | 6 | Tom Rondeau | |
| 77 | 6 | Tom Rondeau | A skeleton of the a common private header file looks like: |
| 78 | 6 | Tom Rondeau | |
| 79 | 6 | Tom Rondeau | <pre> |
| 80 | 6 | Tom Rondeau | #ifndef INCLUDED_FOO_BAR_IMPL_H |
| 81 | 6 | Tom Rondeau | #define INCLUDED_FOO_BAR_IMPL_H |
| 82 | 6 | Tom Rondeau | |
| 83 | 6 | Tom Rondeau | #include <foo/bar.h> |
| 84 | 6 | Tom Rondeau | |
| 85 | 6 | Tom Rondeau | namespace gr { |
| 86 | 6 | Tom Rondeau | namespace foo { |
| 87 | 6 | Tom Rondeau | |
| 88 | 6 | Tom Rondeau | class FOO_API bar_impl : public bar |
| 89 | 6 | Tom Rondeau | { |
| 90 | 6 | Tom Rondeau | private: |
| 91 | 6 | Tom Rondeau | dtype d_var; |
| 92 | 6 | Tom Rondeau | |
| 93 | 6 | Tom Rondeau | public: |
| 94 | 6 | Tom Rondeau | bar_impl(dtype var); |
| 95 | 6 | Tom Rondeau | |
| 96 | 6 | Tom Rondeau | ~bar_impl(); |
| 97 | 6 | Tom Rondeau | |
| 98 | 6 | Tom Rondeau | void set_var(dtype var); |
| 99 | 6 | Tom Rondeau | dtype var(); |
| 100 | 6 | Tom Rondeau | |
| 101 | 6 | Tom Rondeau | int work(int noutput_items, |
| 102 | 6 | Tom Rondeau | gr_vector_const_void_star &input_items, |
| 103 | 6 | Tom Rondeau | gr_vector_void_star &output_items); |
| 104 | 6 | Tom Rondeau | }; |
| 105 | 6 | Tom Rondeau | |
| 106 | 6 | Tom Rondeau | } /* namespace foo */ |
| 107 | 6 | Tom Rondeau | } /* namespace gr */ |
| 108 | 6 | Tom Rondeau | |
| 109 | 6 | Tom Rondeau | #endif /* INCLUDED_FOO_BAR_IMPL_H */ |
| 110 | 6 | Tom Rondeau | </pre> |
| 111 | 6 | Tom Rondeau | |
| 112 | 6 | Tom Rondeau | h3. Implementation Source File |
| 113 | 6 | Tom Rondeau | |
| 114 | 6 | Tom Rondeau | The source file is *lib/bar.cc* and implements the actual code for the class. |
| 115 | 6 | Tom Rondeau | |
| 116 | 6 | Tom Rondeau | This file defines the *make* function for the public class. This is a member of the class, which means that we can, if necessary, do interesting things, define multiple factor functions, etc. Most of the time, this simply returns an sptr to the implementation class. |
| 117 | 6 | Tom Rondeau | |
| 118 | 6 | Tom Rondeau | <pre> |
| 119 | 6 | Tom Rondeau | #ifdef HAVE_CONFIG_H |
| 120 | 6 | Tom Rondeau | #include "config.h" |
| 121 | 6 | Tom Rondeau | #endif |
| 122 | 6 | Tom Rondeau | |
| 123 | 6 | Tom Rondeau | #include "bar_impl.h" |
| 124 | 6 | Tom Rondeau | #include <gr_io_signature.h> |
| 125 | 6 | Tom Rondeau | |
| 126 | 6 | Tom Rondeau | namespace gr { |
| 127 | 6 | Tom Rondeau | namespace foo { |
| 128 | 6 | Tom Rondeau | |
| 129 | 6 | Tom Rondeau | bar::sptr bar::make(dtype var) |
| 130 | 6 | Tom Rondeau | { |
| 131 | 6 | Tom Rondeau | return gnuradio::get_initial_sptr(new bar_impl(var)); |
| 132 | 6 | Tom Rondeau | } |
| 133 | 6 | Tom Rondeau | |
| 134 | 6 | Tom Rondeau | bar_impl::bar_impl(dtype var) |
| 135 | 6 | Tom Rondeau | : gr_sync_block("bar", |
| 136 | 6 | Tom Rondeau | gr_make_io_signature(1, 1, sizeof(in_type)), |
| 137 | 6 | Tom Rondeau | gr_make_io_signature(1, 1, sizeof(out_type))) |
| 138 | 6 | Tom Rondeau | { |
| 139 | 6 | Tom Rondeau | set_var(var); |
| 140 | 6 | Tom Rondeau | } |
| 141 | 6 | Tom Rondeau | |
| 142 | 6 | Tom Rondeau | bar_impl::~bar_impl() |
| 143 | 6 | Tom Rondeau | { |
| 144 | 6 | Tom Rondeau | // any cleanup code here |
| 145 | 6 | Tom Rondeau | } |
| 146 | 6 | Tom Rondeau | |
| 147 | 6 | Tom Rondeau | dtype |
| 148 | 6 | Tom Rondeau | bar_impl::var() |
| 149 | 6 | Tom Rondeau | { |
| 150 | 6 | Tom Rondeau | return d_var; |
| 151 | 6 | Tom Rondeau | } |
| 152 | 6 | Tom Rondeau | |
| 153 | 6 | Tom Rondeau | void |
| 154 | 6 | Tom Rondeau | bar_impl::set_var(dtype var) |
| 155 | 6 | Tom Rondeau | { |
| 156 | 6 | Tom Rondeau | d_var = var; |
| 157 | 6 | Tom Rondeau | } |
| 158 | 6 | Tom Rondeau | |
| 159 | 6 | Tom Rondeau | int |
| 160 | 6 | Tom Rondeau | bar_impl::work(int noutput_items, |
| 161 | 6 | Tom Rondeau | gr_vector_const_void_star &input_items, |
| 162 | 6 | Tom Rondeau | gr_vector_void_star &output_items) |
| 163 | 6 | Tom Rondeau | { |
| 164 | 6 | Tom Rondeau | const in_type *in = (const in_type*)input_items[0]; |
| 165 | 6 | Tom Rondeau | out_type *out = (out_type*)output_items[0]; |
| 166 | 6 | Tom Rondeau | |
| 167 | 6 | Tom Rondeau | // Perform work; read from in, write to out. |
| 168 | 6 | Tom Rondeau | |
| 169 | 6 | Tom Rondeau | return noutput_items; |
| 170 | 6 | Tom Rondeau | } |
| 171 | 6 | Tom Rondeau | |
| 172 | 6 | Tom Rondeau | } /* namespace foo */ |
| 173 | 6 | Tom Rondeau | } /* namespace gr */ |
| 174 | 6 | Tom Rondeau | </pre> |
| 175 | 6 | Tom Rondeau | |
| 176 | 6 | Tom Rondeau | h3. SWIG Interface File |
| 177 | 6 | Tom Rondeau | |
| 178 | 6 | Tom Rondeau | Because of the use of the public header file to describe what we want accessible publicly, we can simple include the headers in the main interface file. So in the directory *swig* is a single interface file *foo_swig.i*: |
| 179 | 6 | Tom Rondeau | |
| 180 | 6 | Tom Rondeau | <pre> |
| 181 | 6 | Tom Rondeau | #define FOO_API |
| 182 | 6 | Tom Rondeau | |
| 183 | 6 | Tom Rondeau | %include "gnuradio.i" |
| 184 | 6 | Tom Rondeau | |
| 185 | 6 | Tom Rondeau | //load generated python docstrings |
| 186 | 6 | Tom Rondeau | %include "foo_swig_doc.i" |
| 187 | 6 | Tom Rondeau | |
| 188 | 6 | Tom Rondeau | %{ |
| 189 | 6 | Tom Rondeau | #include "foo/bar.h" |
| 190 | 6 | Tom Rondeau | %} |
| 191 | 6 | Tom Rondeau | |
| 192 | 6 | Tom Rondeau | %include "foo/bar.h" |
| 193 | 6 | Tom Rondeau | |
| 194 | 6 | Tom Rondeau | GR_SWIG_BLOCK_MAGIC2(foo, bar); |
| 195 | 6 | Tom Rondeau | </pre> |
| 196 | 6 | Tom Rondeau | |
| 197 | 6 | Tom Rondeau | *NOTE*: We are using "GR_SWIG_BLOCK_MAGIC2" for the definitions now. When we are completely converted over, this will be replaced by "GR_SWIG_BLOCK_MAGIC". |
| 198 | 6 | Tom Rondeau | |
| 199 | 6 | Tom Rondeau | ---- |
| 200 | 6 | Tom Rondeau | |
| 201 | 6 | Tom Rondeau | h2. Block Structure |
| 202 | 6 | Tom Rondeau | |
| 203 | 6 | Tom Rondeau | h3. The *work* function |
| 204 | 6 | Tom Rondeau | |
| 205 | 1 | To implement processing, the user must write a "work" routine that reads inputs, processes, and writes outputs. |
|
| 206 | 5 | Tom Rondeau | |
| 207 | 5 | Tom Rondeau | An example work function implementing an adder in c++ |
| 208 | 5 | Tom Rondeau | <pre> |
| 209 | 5 | Tom Rondeau | int work(int noutput_items, |
| 210 | 1 | gr_vector_const_void_star &input_items, |
|
| 211 | 5 | Tom Rondeau | gr_vector_void_star &output_items) |
| 212 | 5 | Tom Rondeau | { |
| 213 | 1 | //cast buffers |
|
| 214 | 1 | const float* in0 = reinterpret_cast<const float *>(input_items[0]); |
|
| 215 | 1 | const float* in1 = reinterpret_cast<const float *>(input_items[1]); |
|
| 216 | 1 | float* out = reinterpret_cast<float *>(output_items[0]); |
|
| 217 | 1 | ||
| 218 | 1 | //process data |
|
| 219 | 1 | for(size_t i = 0; i < noutput_items; i++) { |
|
| 220 | 1 | out[i] = in0[i] + in1[i]; |
|
| 221 | 1 | } |
|
| 222 | 1 | ||
| 223 | 1 | //return produced |
|
| 224 | 1 | return noutput_items; |
|
| 225 | 1 | } |
|
| 226 | 1 | </pre> |
|
| 227 | 2 | Josh Blum | |
| 228 | 2 | Josh Blum | Parameter definitions: |
| 229 | 1 | * **noutput_items:** total number of items in each output buffer |
|
| 230 | 1 | * **input_items:** vector of input buffers, where each element corresponds to an input port |
|
| 231 | 1 | * **output_items:** vector of output buffers, where each element corresponds to an output port |
|
| 232 | 1 | ||
| 233 | 1 | Some observations: |
|
| 234 | 1 | * Each buffer must be cast from a void* pointer into a usable data type. |
|
| 235 | 1 | * The number of items in each input buffer is implied by noutput_items |
|
| 236 | 1 | ** More information on this in later sections |
|
| 237 | 1 | * The number of items produced is returned, this can be less than noutput_items |
|
| 238 | 1 | ||
| 239 | 6 | Tom Rondeau | h3. IO signatures |
| 240 | 1 | ||
| 241 | 1 | When creating a block, the user must communicate the following to the block: |
|
| 242 | 1 | ||
| 243 | 1 | * The number of input ports |
|
| 244 | 1 | * The number of output ports |
|
| 245 | 1 | * The item size of each port |
|
| 246 | 1 | ||
| 247 | 1 | An IO signature describes the number of ports a block may have and the size of each item in bytes. Each block has 2 IO signatures: an input signature, and an output signature. |
|
| 248 | 1 | ||
| 249 | 1 | Some example signatures in c++ |
|
| 250 | 1 | <pre> |
|
| 251 | 1 | ||
| 252 | 1 | -- A block with 2 inputs and 1 output -- |
|
| 253 | 1 | ||
| 254 | 1 | gr_sync_block("my adder", gr_make_io_signature(2, 2, sizeof(float)), gr_make_io_signature(1, 1, sizeof(float))) |
|
| 255 | 1 | ||
| 256 | 1 | -- A block with no inputs and 1 output -- |
|
| 257 | 1 | ||
| 258 | 1 | gr_sync_block("my source", gr_make_io_signature(0, 0, 0), gr_make_io_signature(1, 1, sizeof(float))) |
|
| 259 | 1 | ||
| 260 | 1 | -- A block with 2 inputs (float and double) and 1 output -- |
|
| 261 | 1 | ||
| 262 | 1 | std::vector<int> input_sizes; |
|
| 263 | 1 | input_sizes.push_back(sizeof(float)); |
|
| 264 | 1 | input_sizes.push_back(sizeof(double)); |
|
| 265 | 1 | ||
| 266 | 1 | gr_sync_block("my block", gr_make_io_signaturev(2, 2, input_sizes), gr_make_io_signature(1, 1, sizeof(float))) |
|
| 267 | 1 | </pre> |
|
| 268 | 1 | ||
| 269 | 1 | Some observations: |
|
| 270 | 1 | * Use gr_make_io_signature for blocks where all ports are homogenous in size |
|
| 271 | 1 | * Use gr_make_io_signaturev for blocks that have heterogeneous port sizes |
|
| 272 | 6 | Tom Rondeau | The first two parameters are min and max number of ports, this allows blocks to have a selectable number of ports at runtime |
| 273 | 1 | ||
| 274 | 6 | Tom Rondeau | |
| 275 | 1 | ---- |
|
| 276 | 1 | ||
| 277 | 1 | h2. Block types |
|
| 278 | 1 | ||
| 279 | 1 | To take advantage of the gnuradio framework, users will create various blocks to implement the desired data processing. There are several types of blocks to choose from: |
|
| 280 | 1 | ||
| 281 | 6 | Tom Rondeau | * Synchronous Blocks (1:1) |
| 282 | 6 | Tom Rondeau | * Decimation Blocks (N:1) |
| 283 | 6 | Tom Rondeau | * Interpolation Blocks (1:M) |
| 284 | 6 | Tom Rondeau | * General Blocks (N:M) |
| 285 | 6 | Tom Rondeau | |
| 286 | 5 | Tom Rondeau | h3. Synchronous Block |
| 287 | 5 | Tom Rondeau | |
| 288 | 1 | 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. |
|
| 289 | 5 | Tom Rondeau | |
| 290 | 5 | Tom Rondeau | An example sync block in c++ |
| 291 | 5 | Tom Rondeau | <pre> |
| 292 | 5 | Tom Rondeau | #include <gr_sync_block.h> |
| 293 | 5 | Tom Rondeau | |
| 294 | 5 | Tom Rondeau | class my_sync_block : public gr_sync_block |
| 295 | 1 | { |
|
| 296 | 5 | Tom Rondeau | public: |
| 297 | 5 | Tom Rondeau | my_sync_block(...): |
| 298 | 5 | Tom Rondeau | gr_sync_block("my block", |
| 299 | 5 | Tom Rondeau | gr_make_io_signature(1, 1, sizeof(int32_t)), |
| 300 | 5 | Tom Rondeau | gr_make_io_signature(1, 1, sizeof(int32_t))) |
| 301 | 5 | Tom Rondeau | { |
| 302 | 5 | Tom Rondeau | //constructor stuff |
| 303 | 1 | } |
|
| 304 | 1 | ||
| 305 | 1 | int work(int noutput_items, |
|
| 306 | 1 | gr_vector_const_void_star &input_items, |
|
| 307 | 1 | gr_vector_void_star &output_items) |
|
| 308 | 1 | { |
|
| 309 | 1 | //work stuff... |
|
| 310 | 1 | return noutput_items; |
|
| 311 | 1 | } |
|
| 312 | 1 | }; |
|
| 313 | 1 | </pre> |
|
| 314 | 1 | ||
| 315 | 1 | Some observations: |
|
| 316 | 1 | * noutput_items is the length in items of all input and output buffers |
|
| 317 | 1 | * an input siganture of gr_make_io_signature(0, 0, 0) makes this a source block |
|
| 318 | 1 | * an output siganture of gr_make_io_signature(0, 0, 0) makes this a sink block |
|
| 319 | 5 | Tom Rondeau | |
| 320 | 5 | Tom Rondeau | h3. Decimation Block |
| 321 | 1 | ||
| 322 | 5 | Tom Rondeau | 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. |
| 323 | 5 | Tom Rondeau | |
| 324 | 5 | Tom Rondeau | An example decimation block in c++ |
| 325 | 5 | Tom Rondeau | <pre> |
| 326 | 5 | Tom Rondeau | #include <gr_sync_decimator.h> |
| 327 | 5 | Tom Rondeau | |
| 328 | 5 | Tom Rondeau | class my_decim_block : public gr_sync_decimator |
| 329 | 5 | Tom Rondeau | { |
| 330 | 1 | public: |
|
| 331 | 5 | Tom Rondeau | my_decim_block(...): |
| 332 | 1 | gr_sync_decimator("my decim block", |
|
| 333 | 1 | in_sig, |
|
| 334 | 1 | out_sig, |
|
| 335 | 1 | decimation) |
|
| 336 | 1 | { |
|
| 337 | 1 | //constructor stuff |
|
| 338 | 1 | } |
|
| 339 | 1 | ||
| 340 | 1 | //work function here... |
|
| 341 | 1 | }; |
|
| 342 | 1 | </pre> |
|
| 343 | 1 | ||
| 344 | 1 | Some observations: |
|
| 345 | 1 | * The gr_sync_decimator constructor takes a 4th parameter, the decimation factor |
|
| 346 | 1 | * The user should assume that the number of input items = noutput_items*decimation |
|
| 347 | 5 | Tom Rondeau | |
| 348 | 5 | Tom Rondeau | h3. Interpolation Block |
| 349 | 1 | ||
| 350 | 5 | Tom Rondeau | 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. |
| 351 | 5 | Tom Rondeau | |
| 352 | 5 | Tom Rondeau | An example interpolation block in c++ |
| 353 | 5 | Tom Rondeau | <pre> |
| 354 | 5 | Tom Rondeau | #include <gr_sync_interpolator.h> |
| 355 | 5 | Tom Rondeau | |
| 356 | 5 | Tom Rondeau | class my_interp_block : public gr_sync_interpolator |
| 357 | 5 | Tom Rondeau | { |
| 358 | 1 | public: |
|
| 359 | 5 | Tom Rondeau | my_interp_block(...): |
| 360 | 1 | gr_sync_interpolator("my interp block", |
|
| 361 | 1 | in_sig, |
|
| 362 | 1 | out_sig, |
|
| 363 | 1 | interpolation) |
|
| 364 | 1 | { |
|
| 365 | 1 | //constructor stuff |
|
| 366 | 1 | } |
|
| 367 | 1 | ||
| 368 | 1 | //work function here... |
|
| 369 | 1 | }; |
|
| 370 | 1 | </pre> |
|
| 371 | 1 | ||
| 372 | 1 | Some observations: |
|
| 373 | 1 | * The gr_sync_interpolator constructor takes a 4th parameter, the interpolation factor |
|
| 374 | 1 | * The user should assume that the number of input items = noutput_items/interpolation |
|
| 375 | 1 | ||
| 376 | 1 | h3. Basic Block |
|
| 377 | 1 | ||
| 378 | 1 | The basic block provides no relation between the number of input items and the number of output items. All other blocks are just simplifications of the basic block. Users should choose to inherit from basic block when the other blocks are not suitable. |
|
| 379 | 1 | ||
| 380 | 1 | The adder revisited as a basic block in c++ |
|
| 381 | 1 | <pre> |
|
| 382 | 1 | #include <gr_block.h> |
|
| 383 | 5 | Tom Rondeau | |
| 384 | 5 | Tom Rondeau | class my_basic_block : public gr_block |
| 385 | 1 | { |
|
| 386 | 5 | Tom Rondeau | public: |
| 387 | 5 | Tom Rondeau | my_basic_adder_block(...): |
| 388 | 5 | Tom Rondeau | gr_block("another adder block", |
| 389 | 5 | Tom Rondeau | in_sig, |
| 390 | 5 | Tom Rondeau | out_sig) |
| 391 | 5 | Tom Rondeau | { |
| 392 | 5 | Tom Rondeau | //constructor stuff |
| 393 | 1 | } |
|
| 394 | 5 | Tom Rondeau | |
| 395 | 5 | Tom Rondeau | int general_work(int noutput_items, |
| 396 | 5 | Tom Rondeau | gr_vector_int &ninput_items, |
| 397 | 5 | Tom Rondeau | gr_vector_const_void_star &input_items, |
| 398 | 5 | Tom Rondeau | gr_vector_void_star &output_items) |
| 399 | 5 | Tom Rondeau | { |
| 400 | 5 | Tom Rondeau | //cast buffers |
| 401 | 5 | Tom Rondeau | const float* in0 = reinterpret_cast<const float *>(input_items[0]); |
| 402 | 5 | Tom Rondeau | const float* in1 = reinterpret_cast<const float *>(input_items[1]); |
| 403 | 1 | float* out = reinterpret_cast<float *>(output_items[0]); |
|
| 404 | 5 | Tom Rondeau | |
| 405 | 5 | Tom Rondeau | //process data |
| 406 | 5 | Tom Rondeau | for(size_t i = 0; i < noutput_items; i++) { |
| 407 | 5 | Tom Rondeau | out[i] = in0[i] + in1[i]; |
| 408 | 1 | } |
|
| 409 | 5 | Tom Rondeau | |
| 410 | 5 | Tom Rondeau | //consume the inputs |
| 411 | 5 | Tom Rondeau | this->consume(0, noutput_items); //consume port 0 input |
| 412 | 5 | Tom Rondeau | this->consume(1, noutput_items); //consume port 1 input |
| 413 | 1 | //this->consume_each(noutput_items); //or shortcut to consume on all inputs |
|
| 414 | 5 | Tom Rondeau | |
| 415 | 5 | Tom Rondeau | //return produced |
| 416 | 5 | Tom Rondeau | return noutput_items; |
| 417 | 1 | } |
|
| 418 | 1 | }; |
|
| 419 | 1 | ||
| 420 | 1 | </pre> |
|
| 421 | 1 | ||
| 422 | 1 | Some observations: |
|
| 423 | 1 | * This class overloads the general_work() method, not work() |
|
| 424 | 1 | * The general work has a parameter: ninput_items |
|
| 425 | 1 | ** ninput_items is a vector describing the length of each input buffer |
|
| 426 | 1 | * Before return, general_work must manually consume the used inputs |
|
| 427 | 1 | * The number of items in the input buffers is assumed to be noutput_items |
|
| 428 | 1 | ** Users may alter this behaviour by overloading the forecast() method |
|
| 429 | 1 | ||
| 430 | 1 | ---- |
|
| 431 | 1 | ||
| 432 | 6 | Tom Rondeau | h2. Other Types of Blocks |
| 433 | 6 | Tom Rondeau | |
| 434 | 6 | Tom Rondeau | h3. Hierarchical Block |
| 435 | 6 | Tom Rondeau | |
| 436 | 6 | Tom Rondeau | 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. |
| 437 | 6 | Tom Rondeau | |
| 438 | 6 | Tom Rondeau | 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): |
| 439 | 6 | Tom Rondeau | |
| 440 | 6 | Tom Rondeau | @self.connect((self, i), <block>)@ |
| 441 | 6 | Tom Rondeau | |
| 442 | 6 | Tom Rondeau | Similarly, to send the signal out of the block on output stream *o*: |
| 443 | 6 | Tom Rondeau | |
| 444 | 6 | Tom Rondeau | @self.connect(<block>, (self, o))@ |
| 445 | 6 | Tom Rondeau | |
| 446 | 6 | Tom Rondeau | |
| 447 | 6 | Tom Rondeau | h3. Top Block |
| 448 | 6 | Tom Rondeau | |
| 449 | 6 | Tom Rondeau | The top block is the main data structure of a GNU Radio flowgraph. All blocks are connected under this block. The top block has the functions that control the running of the flowgraph. Generally, we create a class that inherits from a top block: |
| 450 | 6 | Tom Rondeau | |
| 451 | 6 | Tom Rondeau | <pre> |
| 452 | 6 | Tom Rondeau | class my_topblock(gr.top_block): |
| 453 | 6 | Tom Rondeau | def __init__(self, <args>): |
| 454 | 6 | Tom Rondeau | gr.top_block.__init__(self) |
| 455 | 6 | Tom Rondeau | |
| 456 | 6 | Tom Rondeau | <create and connect blocks> |
| 457 | 6 | Tom Rondeau | |
| 458 | 6 | Tom Rondeau | def main(): |
| 459 | 6 | Tom Rondeau | tb = mytb(<args>) |
| 460 | 6 | Tom Rondeau | tb.run() |
| 461 | 6 | Tom Rondeau | </pre> |
| 462 | 6 | Tom Rondeau | |
| 463 | 6 | Tom Rondeau | The top block has a few main member functions: |
| 464 | 6 | Tom Rondeau | |
| 465 | 6 | Tom Rondeau | * start(N): starts the flow graph running with N as the maximum noutput_items any block can receive. |
| 466 | 6 | Tom Rondeau | * stop(): stops the top block |
| 467 | 6 | Tom Rondeau | * wait(): blocks until top block is finished |
| 468 | 6 | Tom Rondeau | * run(N): a blocking start(N) (calls start then wait) |
| 469 | 6 | Tom Rondeau | * lock(): locks the flowgraph so we can reconfigure it |
| 470 | 6 | Tom Rondeau | * unlock(): unlocks and resetarts the flowgraph |
| 471 | 6 | Tom Rondeau | |
| 472 | 6 | Tom Rondeau | The N concept allows us to adjust the latency of a flowgraph. By default, N is large and blocks pass large chunks of items between eachothre. This is designed to maximize throughput and efficiency. Since large chunks of items incurs latency, we can force these chunks to a maximum size to control the overall latency at the expense of efficiency. A *set_max_noutput_items(N)* method is defined for a top block to change this number, but it only takes effect during a lock/unlock procedure. |
| 473 | 6 | Tom Rondeau | |
| 474 | 6 | Tom Rondeau | ---- |
| 475 | 6 | Tom Rondeau | |
| 476 | 1 | h2. Stream Tags |
|
| 477 | 1 | ||
| 478 | 1 | A tag decorates a stream with metadata. A tag is associated with a particular item in a stream. An item may have more than one tag associated with it. The association of an item and tag is made through an absolute count. Every item in a stream has an absolute count. Tags use this count to identify which item in a stream to which they are associated. |
|
| 479 | 1 | ||
| 480 | 1 | A tag has the following members: |
|
| 481 | 1 | * **offset:** the unique item count |
|
| 482 | 1 | * **key:** a PMT key unique to the type of contents |
|
| 483 | 1 | * **value:** a PMT holding the contents of this tag |
|
| 484 | 1 | * **srcid:** a PMT id unique to the producer of the tag (optional) |
|
| 485 | 1 | ||
| 486 | 1 | A PMT is a special data type in gnuradio to serialize arbitrary data. To learn more about PMTs see gruel/pmt.h |
|
| 487 | 1 | ||
| 488 | 1 | h3. Reading stream tags |
|
| 489 | 1 | ||
| 490 | 1 | Tags can be read from the work function using get_tags_in_range. Each input port/stream can have associated tags. |
|
| 491 | 1 | ||
| 492 | 1 | Example reading tags in c++ |
|
| 493 | 5 | Tom Rondeau | <pre> |
| 494 | 5 | Tom Rondeau | int work(int noutput_items, |
| 495 | 5 | Tom Rondeau | gr_vector_const_void_star &input_items, |
| 496 | 5 | Tom Rondeau | gr_vector_void_star &output_items) |
| 497 | 5 | Tom Rondeau | { |
| 498 | 5 | Tom Rondeau | std::vector<gr_tag_t> tags; |
| 499 | 5 | Tom Rondeau | const uint64_t nread = this->nitems_read(0); //number of items read on port 0 |
| 500 | 1 | const size_t ninput_items = noutput_items; //assumption for sync block, this can change |
|
| 501 | 5 | Tom Rondeau | |
| 502 | 5 | Tom Rondeau | //read all tags associated with port 0 for items in this work function |
| 503 | 1 | this->get_tags_in_range(tags, 0, nread, nread+ninput_items); |
|
| 504 | 5 | Tom Rondeau | |
| 505 | 1 | //work stuff here... |
|
| 506 | 2 | Josh Blum | } |
| 507 | 2 | Josh Blum | </pre> |
| 508 | 1 | ||
| 509 | 1 | h3. Writing stream tags |
|
| 510 | 1 | ||
| 511 | 1 | Tags can be written from the work function using add_item_tag. Each output port/stream can have associated tags. |
|
| 512 | 1 | ||
| 513 | 1 | Example writing tags in c++ |
|
| 514 | 5 | Tom Rondeau | <pre> |
| 515 | 5 | Tom Rondeau | int work(int noutput_items, |
| 516 | 5 | Tom Rondeau | gr_vector_const_void_star &input_items, |
| 517 | 1 | gr_vector_void_star &output_items) |
|
| 518 | 5 | Tom Rondeau | { |
| 519 | 5 | Tom Rondeau | const size_t item_index = ? //which output item gets the tag? |
| 520 | 5 | Tom Rondeau | const uint64_t offset = this->nitems_written(0) + item_index; |
| 521 | 5 | Tom Rondeau | pmt::pmt_t key = pmt::pmt_string_to_symbol("example_key"); |
| 522 | 5 | Tom Rondeau | pmt::pmt_t value = pmt::pmt_string_to_symbol("example_value"); |
| 523 | 1 | ||
| 524 | 5 | Tom Rondeau | //write at tag to output port 0 with given absolute item offset |
| 525 | 5 | Tom Rondeau | this->add_item_tag(0, offset, key, value); |
| 526 | 1 | ||
| 527 | 5 | Tom Rondeau | //work stuff here... |
| 528 | 1 | } |
|
| 529 | 1 | </pre> |
|
| 530 | 1 | ||
| 531 | 1 | ---- |
|
| 532 | 1 | ||
| 533 | 6 | Tom Rondeau | h2. Tips and Tricks |
| 534 | 1 | ||
| 535 | 1 | This is the part of the guide where we give tips and tricks for making blocks that work robustly with the scheduler. |
|
| 536 | 1 | ||
| 537 | 1 | h3. Blocking calls |
|
| 538 | 1 | ||
| 539 | 1 | If a work function contains a blocking call, it must be written in such a way that it can be interrupted by boost threads. When the flow graph is stopped, all worker threads will be interrupted. Thread interruption occurs when the user calls unlock() or stop() on the flow graph. Therefore, it is only acceptable to block indefinitely on a boost thread call such a sleep or condition variable, or something that uses these boost thread calls internally such as pop_msg_queue(). If you need to block on a resource such as a file descriptor or socket, the work routine should always call into the blocking routine with a timeout. When the operation times out, the work routine should call a boost thread interruption point or check boost thread interrupted and exit it true. |
|
| 540 | 1 | ||
| 541 | 1 | h3. Saving state |
|
| 542 | 1 | ||
| 543 | 1 | Because work functions can be interrupted, the block's state variables may be indeterminate next time the flow graph is run. To make blocks robust against indeterminate state, users should overload the blocks start() and stop() functions. The start() routine is called when the flow graph is started before the work() thread is spawned. The stop() routine is called when the flow graph is stopped after the work thread has been joined and exited. Users should ensure that the state variables of the block are initialized property in the start() routine. |