Coding guide: Difference between revisions
No edit summary |
|||
Line 26: | Line 26: | ||
Curly brace convention is as follows. After a function name, the curly brace should follow on the next line, and the ending curly brace should be on its own line. Inside of a function, such as for an if statement or for loop, the beginning curly brace should come after the statement, while the end curly brace should be on its own line. | Curly brace convention is as follows. After a function name, the curly brace should follow on the next line, and the ending curly brace should be on its own line. Inside of a function, such as for an if statement or for loop, the beginning curly brace should come after the statement, while the end curly brace should be on its own line. | ||
<syntaxhighlight lang="c++">int | <syntaxhighlight lang="c++"> | ||
foo::bar(int arg1, int arg2) | int foo::bar(int arg1, int arg2) | ||
{ | { | ||
if(arg1 == 0) { | if(arg1 == 0) { | ||
Line 49: | Line 49: | ||
=== Standard command line options === | === Standard command line options === | ||
When writing programs that are executable from the command line, please follow these guidelines for command line argument names (short and long) and types of the arguments. We list them below using the Python optparse syntax. In general, the default value should be coded into the help string using the | When writing programs that are executable from the command line, please follow these guidelines for command line argument names (short and long) and types of the arguments. We list them below using the Python optparse syntax. In general, the default value should be coded into the help string using the "... [default=%default]" syntax. | ||
=== USRP source === | === USRP source === | ||
Any program using a USRP source (usrp.source_*) shall include: | Any program using a USRP source (usrp.source_*) shall include: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("", "--which-usrp", type="intx", default=0, | ||
help= | help="select which USRP to use [default=%default]") | ||
</ | </syntaxhighlight> | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0), | ||
help= | help="select USRP Rx side A or B [default=A]") | ||
</ | </syntaxhightlight> | ||
You are free to change the default if it makes sense in your application. | You are free to change the default if it makes sense in your application. | ||
Line 67: | Line 67: | ||
Any program using a USRP sink (usrp.sink_*) shall include: | Any program using a USRP sink (usrp.sink_*) shall include: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("", "--which-usrp", type="intx", default=0, | ||
help= | help="select which USRP to use [default=%default]") | ||
</ | </syntaxhighlight> | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-T", "--tx-subdev-spec", type="subdev", default=(0, 0), | ||
help= | help="select USRP Tx side A or B [default=A]") | ||
</ | </syntaxhighlight> | ||
You are free to change the default if it makes sense in your application. | You are free to change the default if it makes sense in your application. | ||
Line 80: | Line 80: | ||
Any program using an audio source shall include: | Any program using an audio source shall include: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-I", "--audio-input", type="string", default="", | ||
help= | help="pcm input device name. E.g., hw:0,0 or /dev/dsp") | ||
</ | </syntaxhighlight> | ||
The default must be "". This allows an audio module-dependent default<br /> | The default must be "". This allows an audio module-dependent default<br /> | ||
to be specified in the user preferences file. | to be specified in the user preferences file. | ||
=== Audio sink === | === Audio sink === | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-O", "--audio-output", type="string", default="", | ||
help= | help="pcm output device name. E.g., hw:0,0 or /dev/dsp") | ||
</ | </syntaxhighlight> | ||
The default must be | The default must be "". This allows an audio module-dependent default<br /> | ||
to be specified in the user preferences file. | to be specified in the user preferences file. | ||
=== Standard options names by parameter === | === Standard options names by parameter === | ||
Whenever you want an integer, use the | Whenever you want an integer, use the "intx" type. This allows the<br /> | ||
user to input decimal, hex or octal numbers. E.g., 10, 012, 0xa. | user to input decimal, hex or octal numbers. E.g., 10, 012, 0xa. | ||
Whenever you want a float, use the | Whenever you want a float, use the "eng_float" type. This allows the<br /> | ||
user to input numbers with SI suffixes. E.g, 10000, 10k, 10M, 10m, 92.1M | user to input numbers with SI suffixes. E.g, 10000, 10k, 10M, 10m, 92.1M | ||
Line 107: | Line 107: | ||
To specify a frequency (typically an RF center frequency) use: | To specify a frequency (typically an RF center frequency) use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-f", "--freq", type="eng_float", default=<your-default-here>, | ||
help= | help="set frequency to FREQ [default=%default]") | ||
</ | </syntaxhighlight> | ||
To specify a decimation factor use: | To specify a decimation factor use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-d", "--decim", type="intx", default=<your-default-here>, | ||
help= | help="set decimation rate to DECIM [default=%default]") | ||
</ | </syntaxhighlight> | ||
To specify an interpolation factor use: | To specify an interpolation factor use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-i", "--interp", type="intx", default=<your-default-here>, | ||
help= | help="set interpolation rate to INTERP [default=%default]") | ||
</ | </syntaxhighlight> | ||
To specify a gain setting use: | To specify a gain setting use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-g", "--gain", type="eng_float", default=<your-default-here>, | ||
help= | help="set gain in dB [default=%default]") | ||
</ | </syntaxhighlight> | ||
If your application specifies both a tx and an rx gain, use: | If your application specifies both a tx and an rx gain, use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("", "--rx-gain", type="eng_float", default=<your-default-here>, | ||
help= | help="set receive gain in dB [default=%default]") | ||
</ | </syntaxhighlight><syntaxhighlight lang="c++"> | ||
add_option( | add_option("", "--tx-gain", type="eng_float", default=<your-default-here>, | ||
help= | help="set transmit gain in dB [default=%default]") | ||
</ | </syntaxhighlight> | ||
To specify the number of channels of something use: | To specify the number of channels of something use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-n", "--nchannels", type="intx", default=1, | ||
help= | help="specify number of channels [default=%default]") | ||
</ | </syntaxhighlight> | ||
To specify an output filename use: | To specify an output filename use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-o", "--output-filename", type="string", default=<your-default-here>, | ||
help= | help="specify output-filename [default=%default]") | ||
</ | </syntaxhighlight> | ||
To specify a rate use: | To specify a rate use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-r", "--bit-rate", type="eng_float", default=<your-default-here>, | ||
help= | help="specify bit-rate [default=%default]") | ||
</ | </syntaxhighlight> | ||
or | or | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("-r", "--sample-rate", type="eng_float", default=<your-default-here>, | ||
help= | help="specify sample-rate [default=%default]") | ||
</ | </syntaxhighlight> | ||
If your application has a verbose option, use: | If your application has a verbose option, use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option('-v', '--verbose', action= | add_option('-v', '--verbose', action="store_true", default=False, | ||
help= | help="verbose output") | ||
</ | </syntaxhighlight> | ||
If your application allows the user to specify the | If your application allows the user to specify the "fast USB" options, use: | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("", "--fusb-block-size", type="intx", default=0, | ||
help= | help="specify fast usb block size [default=%default]") | ||
</ | </syntaxhighlight> | ||
< | <syntaxhighlight lang="c++"> | ||
add_option( | add_option("", "--fusb-nblocks", type="intx", default=0, | ||
help= | help="specify number of fast usb blocks [default=%default]") | ||
</ | </syntaxhighlight> | ||
== C++ == | == C++ == | ||
Line 173: | Line 173: | ||
=== Global names === | === Global names === | ||
All globally visible names (types, functions, variables, consts, etc) shall begin with a | All globally visible names (types, functions, variables, consts, etc) shall begin with a "package prefix", followed by an '_'. The bulk of the code in GNU Radio logically belongs to the "gr" package, hence names look like gr_open_file (...). | ||
Large coherent bodies of code may use other package prefixes, but let's try to keep them to a well thought out list. See the list below. | Large coherent bodies of code may use other package prefixes, but let's try to keep them to a well thought out list. See the list below. | ||
Line 194: | Line 194: | ||
* Functions and variable names should be underscore_separated_words (No CamelCase). | * Functions and variable names should be underscore_separated_words (No CamelCase). | ||
* All data members of a class should be made clear by using a | * All data members of a class should be made clear by using a "d_" prefix to them. They should also be private unless there is a very good reason not to. | ||
All data member should have a settable and gettable accessor function of the form: | All data member should have a settable and gettable accessor function of the form: | ||
Line 201: | Line 201: | ||
* type variable(); | * type variable(); | ||
These accessors should perform any required range checking and throw a | These accessors should perform any required range checking and throw a "std::out_of_range" exceptions when not met. Other conditions that are not met can throw a "std::invalid_argument" exception. | ||
* All class data members shall begin with d_<foo>. | * All class data members shall begin with d_<foo>. | ||
Line 209: | Line 209: | ||
Example: | Example: | ||
< | <syntaxhighlight lang="c++">class gr_wonderfulness { | ||
std::string d_name; | std::string d_name; | ||
double d_wonderfulness_factor; | double d_wonderfulness_factor; | ||
Line 220: | Line 220: | ||
} | } | ||
... | ... | ||
};</ | };</syntaxhighlight> | ||
'''''' All class static data members shall begin with s_<foo>. | '''''' All class static data members shall begin with s_<foo>. | ||
Line 273: | Line 273: | ||
The ''module'' is the name of the GNU Radio namespace the block belongs to. Most blocks live inside gnuradio.gr, but some, like the new digital modulation information, live elsewhere. The digital blocks, for instance, are in gnuradio.digital. These blocks are appropriately named <code>digital_foo_cf</code>. | The ''module'' is the name of the GNU Radio namespace the block belongs to. Most blocks live inside gnuradio.gr, but some, like the new digital modulation information, live elsewhere. The digital blocks, for instance, are in gnuradio.digital. These blocks are appropriately named <code>digital_foo_cf</code>. | ||
The ''name'' indicates the function of the block, such as | The ''name'' indicates the function of the block, such as "add" or "fir_filter". | ||
The ''input'' indicates the input type while ''output'' indicates the output type. Currently, the types are: | The ''input'' indicates the input type while ''output'' indicates the output type. Currently, the types are: | ||
Line 285: | Line 285: | ||
There is no differentiation between bytes and bits, even though some blocks use unpacked format where a single bit is represented in a byte. If this is the case, it should be made clear in the documentation for the block. | There is no differentiation between bytes and bits, even though some blocks use unpacked format where a single bit is represented in a byte. If this is the case, it should be made clear in the documentation for the block. | ||
GNU Radio also has a concept of vectors, where the data into or out of the block are vectors of items. The | GNU Radio also has a concept of vectors, where the data into or out of the block are vectors of items. The "v" is then used to indicate if this is the case. A block that uses vectors should indicate it by the type: | ||
<code><vector in><input><vector out><output></code> | <code><vector in><input><vector out><output></code> | ||
Line 291: | Line 291: | ||
So a block that takes vectors in but streams samples out would look like ''vcc'', but a block that does vectors for both input and output will look like ''vcvc''. | So a block that takes vectors in but streams samples out would look like ''vcc'', but a block that does vectors for both input and output will look like ''vcvc''. | ||
'''NOTE''': this is a new convention and older blocks that use vectors do not follow this. They generally only indicate that vectors are used somewhere by putting a single | '''NOTE''': this is a new convention and older blocks that use vectors do not follow this. They generally only indicate that vectors are used somewhere by putting a single "v" in front of the data type indicator. |
Revision as of 16:15, 18 March 2017
Coding Guide
General
Code Format
We want to try and make a consistent look and feel to the code in GNU Radio. Here are a few things to try to keep in mind while writing code.
All lines of code should not exceed 70 characters on a width. Often, there is a logical break in the line of code that allows it to easily span two lines, such as between arguments to the function. Sometimes not, though, so make your best judgement here. The reason for this is to make the code format easily readable in both an editor and when printed out.
As stated elsewhere, no CamelCase. All distinct words in a function or variable name should use underscores:
int foo_bar
In the header file, functions should all be written on the same line, up to the 70-character limit.
int bar(int arg1, int arg2);
In the C++ implementation file, the function should have its return data type indicated on a separate line.
int
foo::bar(int arg1, int arg2)
Curly brace convention is as follows. After a function name, the curly brace should follow on the next line, and the ending curly brace should be on its own line. Inside of a function, such as for an if statement or for loop, the beginning curly brace should come after the statement, while the end curly brace should be on its own line.
int foo::bar(int arg1, int arg2)
{
if(arg1 == 0) {
do something;
}
} else {
do something else;
}
return 0;
}
Although C++ allows single-line statements after a for or if statement to exist without curly braces surrounding them, it is recommended to always use curly braces. This may help prevent problems in the future if another line is added to the code block.
Unit testing
Build unit tests for everything non-trivial and run them after every change. Check out Extreme Programming: http://c2.com/cgi/wiki?ExtremeProgrammingRoadmap
Unit tests should also be written for all examples. This should kill off the bit rot we've been plagued with.
Unit tests can be written in Python or in C++. If possible, use Python unit tests (e.g. to test the output of signal processing blocks).
Standard command line options
When writing programs that are executable from the command line, please follow these guidelines for command line argument names (short and long) and types of the arguments. We list them below using the Python optparse syntax. In general, the default value should be coded into the help string using the "... [default=%default]" syntax.
USRP source
Any program using a USRP source (usrp.source_*) shall include:
add_option("", "--which-usrp", type="intx", default=0,
help="select which USRP to use [default=%default]")
add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0),
help="select USRP Rx side A or B [default=A]")
</syntaxhightlight>
You are free to change the default if it makes sense in your application.
=== USRP sink ===
Any program using a USRP sink (usrp.sink_*) shall include:
<syntaxhighlight lang="c++">
add_option("", "--which-usrp", type="intx", default=0,
help="select which USRP to use [default=%default]")
add_option("-T", "--tx-subdev-spec", type="subdev", default=(0, 0),
help="select USRP Tx side A or B [default=A]")
You are free to change the default if it makes sense in your application.
Audio source
Any program using an audio source shall include:
add_option("-I", "--audio-input", type="string", default="",
help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
The default must be "". This allows an audio module-dependent default
to be specified in the user preferences file.
Audio sink
add_option("-O", "--audio-output", type="string", default="",
help="pcm output device name. E.g., hw:0,0 or /dev/dsp")
The default must be "". This allows an audio module-dependent default
to be specified in the user preferences file.
Standard options names by parameter
Whenever you want an integer, use the "intx" type. This allows the
user to input decimal, hex or octal numbers. E.g., 10, 012, 0xa.
Whenever you want a float, use the "eng_float" type. This allows the
user to input numbers with SI suffixes. E.g, 10000, 10k, 10M, 10m, 92.1M
If your program allows the user to specify values for any of the
following parameters, please use these options to specify them:
To specify a frequency (typically an RF center frequency) use:
add_option("-f", "--freq", type="eng_float", default=<your-default-here>,
help="set frequency to FREQ [default=%default]")
To specify a decimation factor use:
add_option("-d", "--decim", type="intx", default=<your-default-here>,
help="set decimation rate to DECIM [default=%default]")
To specify an interpolation factor use:
add_option("-i", "--interp", type="intx", default=<your-default-here>,
help="set interpolation rate to INTERP [default=%default]")
To specify a gain setting use:
add_option("-g", "--gain", type="eng_float", default=<your-default-here>,
help="set gain in dB [default=%default]")
If your application specifies both a tx and an rx gain, use:
add_option("", "--rx-gain", type="eng_float", default=<your-default-here>,
help="set receive gain in dB [default=%default]")
add_option("", "--tx-gain", type="eng_float", default=<your-default-here>,
help="set transmit gain in dB [default=%default]")
To specify the number of channels of something use:
add_option("-n", "--nchannels", type="intx", default=1,
help="specify number of channels [default=%default]")
To specify an output filename use:
add_option("-o", "--output-filename", type="string", default=<your-default-here>,
help="specify output-filename [default=%default]")
To specify a rate use:
add_option("-r", "--bit-rate", type="eng_float", default=<your-default-here>,
help="specify bit-rate [default=%default]")
or
add_option("-r", "--sample-rate", type="eng_float", default=<your-default-here>,
help="specify sample-rate [default=%default]")
If your application has a verbose option, use:
add_option('-v', '--verbose', action="store_true", default=False,
help="verbose output")
If your application allows the user to specify the "fast USB" options, use:
add_option("", "--fusb-block-size", type="intx", default=0,
help="specify fast usb block size [default=%default]")
add_option("", "--fusb-nblocks", type="intx", default=0,
help="specify number of fast usb blocks [default=%default]")
C++
Global names
All globally visible names (types, functions, variables, consts, etc) shall begin with a "package prefix", followed by an '_'. The bulk of the code in GNU Radio logically belongs to the "gr" package, hence names look like gr_open_file (...).
Large coherent bodies of code may use other package prefixes, but let's try to keep them to a well thought out list. See the list below.
Package prefixes
These are the current package prefixes:
gr_
: Almost everything
gri_
: Implementation primitives. Sometimes we have both a gr_<foo> and a gri_<foo>. In that case, gr_<foo> would be derived from gr_block and gri_<foo> would be the low level guts of the function.
usrp_
: Universal Software Radio Peripheral
qa_
: Quality Assurance. Test code.
atsc_
: Code related to the Advanced Television Standards Committee HDTV implementation
Naming conventions
- Functions and variable names should be underscore_separated_words (No CamelCase).
- All data members of a class should be made clear by using a "d_" prefix to them. They should also be private unless there is a very good reason not to.
All data member should have a settable and gettable accessor function of the form:
- void set_variable(type var);
- type variable();
These accessors should perform any required range checking and throw a "std::out_of_range" exceptions when not met. Other conditions that are not met can throw a "std::invalid_argument" exception.
- All class data members shall begin with d_<foo>.
The big win is when you're staring at a block of code it's obvious which of the things being assigned to persist outside of the block. This also keeps you from having to be creative with parameter names for methods and constructors. You just use the same name as the instance variable, without the d_
.
Example:
class gr_wonderfulness {
std::string d_name;
double d_wonderfulness_factor;
public:
gr_wonderfulness (std::string name, double wonderfulness_factor)
: d_name (name), d_wonderfulness_factor (wonderfulness_factor)
{
...
}
...
};
' All class static data members shall begin with s_<foo>.
Accessor Functions
All signal processing blocks should have accessors to get and set values. For parameter param, the get function is param() and the set function is set_param(type new_value) for any variables that can be changed at runtime.
File names
Each significant class shall be contained in it's own file. The declaration of class gr_foo shall be in gr_foo.h, the definition in gr_foo.cc.
Storage management
Strongly consider using the boost smart pointer templates, scoped_ptr and shared_ptr. scoped_ptr should be used for locals that contain pointers to objects that we need to delete when we exit the current scope. shared_ptr implements transparent reference counting and is a major win. You never have to worry about calling delete. The right thing happens.
See http://www.boost.org/libs/smart_ptr/smart_ptr.htm
Exceptions
Use the following exceptions:
- std::runtime_error: when a runtime error has occurred
- std::out_of_range: when a variable to be set is not within a given range
- std::invalid_argument: when an argument is set to an improper value
C++ unit tests
For C++ we're using the cppunit framework. cppunit has its bad smells, but it's mostly workable. http://cppunit.sf.net
Currently each directory <dirname> contains files qa_<dirname>.{h,cc} that bring together all the qa_<foo> test suites in the directory. We ought to be able to automate this without too much trouble.
The directory gnuradio-core/src/tests contains programs that run the tests. test_all runs all of the registered C++ unit tests.
As far as I can tell, the cppunit TestFactoryRegistry maybe able to be tricked into doing what we want. As is, I don't think it's enough by itself, since there's nothing dragging the qa* files out of the library and into the program. I haven't tested out this idea.
Python
Python unit tests
We use the standard unittest package for unit testing of Python code.
Block naming conventions
Blocks should be given a name that indicates its purpose as well as its input and output data types. If a block lives in the GNU Radio core and performs the function FOO that takes complex in and produces floating points outputs, it's name should be:
gr_foo_cf
This boils down to:
<module>_<name>_<input><ouput>
The module is the name of the GNU Radio namespace the block belongs to. Most blocks live inside gnuradio.gr, but some, like the new digital modulation information, live elsewhere. The digital blocks, for instance, are in gnuradio.digital. These blocks are appropriately named digital_foo_cf
.
The name indicates the function of the block, such as "add" or "fir_filter".
The input indicates the input type while output indicates the output type. Currently, the types are:
- c: complex (32-bit floats for both I and Q)
- f: float (32-bit single precision IEEE 754 style)
- i: integer (32-bit signed integers)
- s: short (16-bit signed integers)
- b: bytes (8-bit signed integers)
There is no differentiation between bytes and bits, even though some blocks use unpacked format where a single bit is represented in a byte. If this is the case, it should be made clear in the documentation for the block.
GNU Radio also has a concept of vectors, where the data into or out of the block are vectors of items. The "v" is then used to indicate if this is the case. A block that uses vectors should indicate it by the type:
<vector in><input><vector out><output>
So a block that takes vectors in but streams samples out would look like vcc, but a block that does vectors for both input and output will look like vcvc.
NOTE: this is a new convention and older blocks that use vectors do not follow this. They generally only indicate that vectors are used somewhere by putting a single "v" in front of the data type indicator.