example: cpp: Enhanced documentation for rfnoc_* examples

- Added program documentation string.
- Marked required options as required().
- Added short option strings for required options.
- Made individual option descriptions more descriptive.
- Harmonized naming of options where appropriate.
  Introduced alias options for renamed options to keep
  backward compatibility.
This commit is contained in:
Andre Zoch 2025-10-27 16:31:54 +01:00 committed by Jörg Hofrichter
parent 981d2d41e1
commit fbc5ad9eb0
4 changed files with 473 additions and 142 deletions

View File

@ -202,42 +202,80 @@ void pretty_print_flow_graph(std::vector<std::string> blocks)
///////////////////// MAIN ////////////////////////////////////////////////////
int UHD_SAFE_MAIN(int argc, char* argv[])
{
const std::string program_doc =
"usage: rfnoc_nullsource_ce_rx [-h] [--args ARGS] [--file FILE] [--null]\n"
" [--time TIME] [--spb SPB] [--spp SPP]\n"
" [--throttle-cycles THROTTLE_CYCLES]\n"
" [--format {double,float,short}]\n"
" [--progress] [--stats] [--continue]\n"
" [--nullid NULLID]"
"\n\n"
"This example demonstrates how to use the RFNoC Null Source/Sink block to\n"
"generate and stream test data. The program requires an FPGA image that\n"
"includes the RFNoC Null Source/Sink block. It is useful for verifying\n"
"RFNoC data paths and testing system connectivity without external\n"
"signals. Generated data is also written to a file for validation\n"
"purposes.\n"
"\n"
"Usage example:\n"
" Receive 1 second of samples from the RFNoC Null Source/Sink block\n"
" and display statistics:\n"
" rfnoc_nullsource_ce_rx --args \"addr=192.168.10.2\"\n"
" --nullid \"0/NullSrcSink#0\"\n"
" --spb 10000 --spp 256 --throttle-cycles 0\n"
" --progress --stats --time 1\n";
// variables to be set by po
std::string args, file, format, nullid;
size_t spb, spp, throttle_cycles;
double rate, total_time;
double total_time;
// setup the program options
po::options_description desc("Allowed options");
// clang-format off
desc.add_options()
("help", "help message")
("args", po::value<std::string>(&args)->default_value("type=x300"), "multi uhd device address args")
("file", po::value<std::string>(&file)->default_value("graph_samples.dat"), "name of the file to write binary samples to, set to stdout to print")
("null", "run without writing to file")
("time", po::value<double>(&total_time)->default_value(0), "total number of seconds to receive")
("spb", po::value<size_t>(&spb)->default_value(10000), "samples per buffer")
("spp", po::value<size_t>(&spp)->default_value(64), "samples per packet (on FPGA and wire)")
("throttle-cycles", po::value<size_t>(&throttle_cycles)->default_value(0), "Number of cycles to force in between packets")
("rate", po::value<double>(&rate)->default_value(1e6), "rate at which samples are produced in the null source")
("format", po::value<std::string>(&format)->default_value("sc16"), "File sample type: sc16, fc32, or fc64")
("progress", "periodically display short-term bandwidth")
("stats", "show average bandwidth on exit")
("continue", "don't abort on a bad packet")
("nullid", po::value<std::string>(&nullid)->default_value("0/NullSrcSink#0"), "The block ID for the null source.")
("help,h", "Show this help message and exit.")
("args", po::value<std::string>(&args)->default_value("type=x300"), "Single USRP device selection and "
"configuration arguments."
"\nSpecify key-value pairs (e.g., addr, serial, type, master_clock_rate) separated by commas."
"\nSee the UHD manual for model-specific options."
"\nExamples:"
"\n --args \"addr=192.168.10.2\""
"\n --args \"addr=192.168.10.2,master_clock_rate=200e6\""
"\nIf not specified, UHD connects to the first available device.")
("file", po::value<std::string>(&file)->default_value("graph_samples.dat"), "Name of the raw binary "
"file to which received data will be written.")
("null", "Run without writing to file.")
("time", po::value<double>(&total_time)->default_value(0), "Total number of seconds to receive.")
("spb", po::value<size_t>(&spb)->default_value(10000), "Size of the host data buffer in samples.")
("spp", po::value<size_t>(&spp)->default_value(64), "Specifies the number of samples per packet sent "
"from the FPGA to the host. Adjusting this value can affect streaming efficiency and packetization behavior.")
("throttle-cycles", po::value<size_t>(&throttle_cycles)->default_value(0), "Number of cycles to force "
"in between packets.")
("format", po::value<std::string>(&format)->default_value("sc16"), "Specifies the data format of the "
"file. The data will be written as interleaved IQ samples in one of the following numeric formats: 'double' "
"(64-bit float, fc64), 'float' (32-bit float, fc32), or 'short' (16-bit integer, sc16, scaled to int16 range "
"-32768 to 32767)."
"\nChoosing 'short' as the file format matches the sc16 over-the-wire format and is usually sufficient. Using "
"'float' or 'double' does not improve precision, but may be more convenient for post-processing or for "
"compatibility with certain analysis tools.")
("progress", "Periodically display the estimated short-term USRP device to host streaming rate in "
"samples per second.")
("stats", "Show the total number of samples received and the elapsed time when the program exits.")
("continue", "Continue streaming even if a bad packet is received.")
("nullid", po::value<std::string>(&nullid)->default_value("0/NullSrcSink#0"), "The block ID for the "
"null source. Format is typically \"mboard_index/NullSrcSink#N\" (e.g. \"0/NullSrcSink#0\").")
;
// clang-format on
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
// print the help message
if (vm.count("help")) {
std::cout << "[RFNOC] Connect a null source to another (processing) block, "
"and stream the result to file."
<< desc << std::endl;
return EXIT_SUCCESS;
std::cout << program_doc << std::endl;
std::cout << desc << std::endl;
return ~0;
}
po::notify(vm); // only called if --help was not requested
bool bw_summary = vm.count("progress") > 0;
bool stats = vm.count("stats") > 0;
@ -285,7 +323,7 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
stream_args_args["block_id"] = nullid;
/////////////////////////////////////////////////////////////////////////
//////// 4. Configure blocks (packet size and rate) /////////////////////
//////// 4. Configure blocks (packet size) //////////////////////////////
/////////////////////////////////////////////////////////////////////////
std::cout << "Samples per packet coming from null source: " << spp << std::endl;
const size_t BYTES_PER_SAMPLE = 4;

View File

@ -41,6 +41,42 @@ void sig_int_handler(int)
***************************************************************************/
int UHD_SAFE_MAIN(int argc, char* argv[])
{
const std::string program_doc =
"usage: rfnoc_radio_loopback [-h] [--args ARGS] [--spp SPP]\n"
" [--rx-freq RX_FREQ] [--tx-freq TX_FREQ]\n"
" [--rx-gain RX_GAIN] [--tx-gain TX_GAIN]\n"
" [--rx-ant RX_ANT] [--tx-ant TX_ANT]\n"
" [--rx-blockid RX_BLOCKID]\n"
" [--tx-blockid TX_BLOCKID]\n"
" [--rx-chan RX_CHAN] [--tx-chan TX_CHAN]\n"
" [--rx-bw RX_BW] [--tx-bw TX_BW]\n"
" [--rx-timestamps RX_TIMESTAMPS]\n"
" [--setup SETUP] [--nsamps NSAMPS]\n"
" [-r RATE] [--duration DURATION] [--int-n]\n"
" [--ref {internal,external,mimo,gpsdo}]\n"
" [--pps {internal,external,mimo,gpsdo}]\n"
" [--dot]"
"\n\n"
"This example streams baseband data from a selected RFNoC RX radio block\n"
"and channel to a TX radio block and channel.\n"
"The connection is dynamically established between the RX radio and TX\n"
"radio; if DDC and DUC blocks are present, data is routed through them\n"
"for digital down/up conversion. Streaming can be performed for a\n"
"user-defined duration or continuously.\n"
"\n"
"Key features:\n"
"- Dynamically connects RX radio (or DDC) to TX radio (or DUC) using\n"
" RFNoC.\n"
"- Flexible selection of radio blocks and channels.\n"
"- Configurable sample rate, frequency, gain, bandwidth, and antenna for\n"
" both RX and TX.\n"
"- Shows how to enable timestamping of RX packets.\n"
"\n"
"Usage example:\n"
"Receive a signal at 3.5 GHz at re-transmit it at 2.4 GHz using the\n"
"default radio sampling rate and using two different RFNoC radio blocks:\n"
" rfnoc_radio_loopback --args \"addr=192.168.10.2\"\n"
" --rx-blockid \"0/Radio#0\" --tx-blockid \"0/Radio#1\"";
// variables to be set by po
std::string args, rx_ant, tx_ant, rx_blockid, tx_blockid, ref, pps;
size_t total_num_samps, spp, rx_chan, tx_chan;
@ -51,44 +87,73 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
po::options_description desc("Allowed options");
// clang-format off
desc.add_options()
("help", "help message")
("args", po::value<std::string>(&args)->default_value(""), "UHD device address args")
("spp", po::value<size_t>(&spp)->default_value(0), "Samples per packet (reduce for lower latency)")
("rx-freq", po::value<double>(&rx_freq)->default_value(0.0), "Rx RF center frequency in Hz")
("tx-freq", po::value<double>(&tx_freq)->default_value(0.0), "Tx RF center frequency in Hz")
("rx-gain", po::value<double>(&rx_gain)->default_value(0.0), "Rx RF center gain in Hz")
("tx-gain", po::value<double>(&tx_gain)->default_value(0.0), "Tx RF center gain in Hz")
("rx-ant", po::value<std::string>(&rx_ant), "Receive antenna selection")
("tx-ant", po::value<std::string>(&tx_ant), "Transmit antenna selection")
("rx-blockid", po::value<std::string>(&rx_blockid)->default_value("0/Radio#0"), "Receive radio block ID")
("tx-blockid", po::value<std::string>(&tx_blockid)->default_value("0/Radio#1"), "Transmit radio block ID")
("rx-chan", po::value<size_t>(&rx_chan)->default_value(0), "Channel index on receive radio")
("tx-chan", po::value<size_t>(&tx_chan)->default_value(0), "Channel index on transmit radio")
("rx-bw", po::value<double>(&rx_bw), "RX analog frontend filter bandwidth in Hz")
("tx-bw", po::value<double>(&tx_bw), "TX analog frontend filter bandwidth in Hz")
("rx-timestamps", po::value<bool>(&rx_timestamps)->default_value(false), "Set timestamps on RX")
("setup", po::value<double>(&setup_time)->default_value(0.1), "seconds of setup time")
("nsamps", po::value<size_t>(&total_num_samps)->default_value(0), "total number of samples to receive")
("rate", po::value<double>(&rate)->default_value(0.0), "Sampling rate")
("duration", po::value<double>(&total_time)->default_value(0), "total number of seconds to receive")
("int-n", "Tune USRP with integer-N tuning")
("ref", po::value<std::string>(&ref), "clock reference (internal, external, gpsdo, mimo)")
("pps", po::value<std::string>(&pps), "PPS source (internal, external, mimo, gpsdo)")
("help,h", "Show this help message and exit.")
("args", po::value<std::string>(&args)->default_value(""), "Single USRP device selection and "
"configuration arguments."
"\nSpecify key-value pairs (e.g., addr, serial, type, master_clock_rate) separated by commas."
"\nSee the UHD manual for model-specific options."
"\nExamples:"
"\n --args \"addr=192.168.10.2\""
"\n --args \"addr=192.168.10.2,master_clock_rate=200e6\""
"\nIf not specified, UHD connects to the first available device.")
("spp", po::value<size_t>(&spp)->default_value(0), "Specifies the number of samples per packet sent "
"from the RFNoC radio block. Use smaller values to reduce latency.")
("rx-freq", po::value<double>(&rx_freq)->default_value(0.0), "RX RF center frequency in Hz.")
("tx-freq", po::value<double>(&tx_freq)->default_value(0.0), "TX RF center frequency in Hz.")
("rx-gain", po::value<double>(&rx_gain)->default_value(0.0), "RX gain for the RF chain in dB.")
("tx-gain", po::value<double>(&tx_gain)->default_value(0.0), "TX gain for the RF chain in dB.")
("rx-ant", po::value<std::string>(&rx_ant), "RX antenna port selection string selecting a specific "
"antenna port for USRP daughterboards having multiple antenna connectors per RF channel."
"\nExample: --ant \"TX/RX\"")
("tx-ant", po::value<std::string>(&tx_ant), "TX antenna port selection string selecting a specific "
"antenna port for USRP daughterboards having multiple antenna connectors per RF channel."
"\nExample: --ant \"TX/RX\"")
("rx-blockid", po::value<std::string>(&rx_blockid)->default_value("0/Radio#0"), "Specifies the RFNoC "
"radio block ID to use for receiving. Format is typically \"mboard_index/Radio#N\" (e.g., 0/Radio#0).")
("tx-blockid", po::value<std::string>(&tx_blockid)->default_value("0/Radio#1"), "Specifies the RFNoC "
"radio block ID to use for transmitting. Format is typically \"mboard_index/Radio#N\" (e.g., 0/Radio#1).")
("rx-chan", po::value<size_t>(&rx_chan)->default_value(0), "Selects the channel index within the "
"chosen RX radio block for data reception (e.g., 0 for the first channel).")
("tx-chan", po::value<size_t>(&tx_chan)->default_value(0), "Selects the channel index within the "
"chosen TX radio block for data transmission (e.g., 0 for the first channel).")
("rx-bw", po::value<double>(&rx_bw), "Sets the analog frontend filter bandwidth for the RX path in "
"Hz. Not all USRP devices support programmable bandwidth; if an unsupported value is requested, the device "
"will use the nearest supported bandwidth instead.")
("tx-bw", po::value<double>(&tx_bw), "Sets the analog frontend filter bandwidth for the TX path in "
"Hz. Not all USRP devices support programmable bandwidth; if an unsupported value is requested, the device "
"will use the nearest supported bandwidth instead.")
("rx-timestamps", po::value<bool>(&rx_timestamps)->default_value(false), "Enables timestamping on "
"received packets, associating each sample with a precise time value.")
("setup", po::value<double>(&setup_time)->default_value(0.1), "Sets the amount of time (in seconds) "
"the program waits for hardware locks (e.g. LO) to ensure the device is properly synchronized before starting "
"reception.")
("nsamps", po::value<size_t>(&total_num_samps)->default_value(0), "Total number of samples to "
"receive. The program stops when this number is reached.")
("rate,r", po::value<double>(&rate)->default_value(0.0), "Sets the sample rate for both RX and TX "
"radio blocks. The specified rate must be supported by the selected radio; supported values differ between "
"USRP models. Refer to the UHD manual for valid sample rates for your hardware.")
("duration", po::value<double>(&total_time)->default_value(0), "Total number of seconds to receive. "
"The program stops when this number is reached.")
("int-n", "Use integer-N tuning for USRP RF synthesizers. With this mode, the LO can only be tuned in "
"discrete steps, which are integer multiples of the reference frequency. This mode can improve phase noise "
"and spurious performance at the cost of coarser frequency resolution.")
("ref", po::value<std::string>(&ref), "Sets the source for the frequency reference. Available values "
"depend on the USRP model. Typical values are 'internal', 'external', 'mimo', and 'gpsdo'.")
("pps", po::value<std::string>(&pps), "Specifies the PPS source for time synchronization. Available "
"values depend on the USRP model. Typical values are 'internal', 'external', 'mimo', and 'gpsdo'.")
("dot", "Outputs the RFNoC graph as a DOT-format representation, showing the connections between "
"blocks for visualization or analysis.")
;
// clang-format on
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
// print the help message
if (vm.count("help")) {
std::cout << boost::format("RFNoC: Radio loopback test %s") % desc << std::endl;
std::cout
<< std::endl
<< "This application streams data from one radio to another using RFNoC.\n"
<< std::endl;
std::cout << program_doc << std::endl;
std::cout << desc << std::endl;
return ~0;
}
po::notify(vm); // only called if --help was not requested
/************************************************************************
* Create device and block controls
@ -123,6 +188,14 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
uhd::rfnoc::connect_through_blocks(
graph, rx_radio_ctrl_id, rx_chan, tx_radio_ctrl_id, tx_chan, skip_pp);
graph->commit();
std::cout << "Active connections:" << std::endl;
if (vm.count("dot")) {
std::cout << graph->to_dot() << std::endl;
} else {
for (auto& edge : graph->enumerate_active_connections()) {
std::cout << "* " << edge.to_string() << std::endl;
}
}
rx_radio_ctrl->enable_rx_timestamps(rx_timestamps, rx_chan);
@ -154,6 +227,8 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
<< std::endl
<< std::endl;
}
std::cout << "Actual Sample Rate: " << (rate / 1e6) << " Msps..." << std::endl
<< std::endl;
// set the center frequency
if (vm.count("rx-freq")) {

View File

@ -46,6 +46,72 @@ void sig_int_handler(int)
int UHD_SAFE_MAIN(int argc, char* argv[])
{
// clang-format off
const std::string program_doc =
"usage: rfnoc_replay_samples_from_file [-h] [--args ARGS] -f FREQ [-r RATE]\n"
" [--tx-args TX_ARGS]\n"
" [--radio-id RADIO_ID]\n"
" [--radio-chan RADIO_CHAN]\n"
" [--replay-id REPLAY_ID]\n"
" [--replay-chan REPLAY_CHAN]\n"
" [--nsamps NSAMPS] [--file FILE]\n"
" [--lo-offset LO_OFFSET]\n"
" [--gain GAIN] [--ant ANT]\n"
" [--bw BW]\n"
" [--ref {internal,external,mimo,gpsdo}]\n"
" [--dot]"
"\n\n"
"This example program demonstrates how to use the RFNoC C++ API\n"
"to create an RFNoC graph involving an RFNoC Replay block for playback of\n"
"baseband samples through the radio.\n"
"The program loads sample data into the Replay block, configures the RF\n"
"chain, and plays back the data through the radio. Playback can be\n"
"continuous or for a specified number of samples.\n"
"\n"
"Notes:\n"
" - The Replay block is included in most default FPGA images for\n"
" RFNoC-capable USRP devices, so custom FPGA builds are usually not\n"
" required to use this example program.\n"
" - The USRP may round the requested transmit sample rate to the nearest\n"
" supported value. For accurate playback, please ensure that the sample\n"
" rate of the input data in the file matches the USRP sample rate.\n"
" If supported by your USRP device model, you could also change the\n"
" master clock rate to an integer multiple of the desired sample rate\n"
" to achieve precise sampling. For available master clock rates for a\n"
" specific USRP device model, see the UHD manual.\n"
"\n"
"Key features:\n"
"- Loads baseband samples from a binary file and uploads them to the\n"
" RFNoC Replay block for playback through the radio.\n"
"- Dynamically builds and configures an RFNoC graph connecting the Replay\n"
" block to the radio block, with automatic integration of a DUC block if\n"
" present.\n"
"- Supports flexible selection of Replay and radio blocks and channels\n"
" via program options.\n"
"- Allows configuration of key transmission parameters, including\n"
" frequency, sample rate, gain, bandwidth, LO offset, and antenna.\n"
"- Enables continuous or finite playback of samples, as specified by the\n"
" user.\n"
"\n"
"Usage examples:\n"
"1. Replay a signal from a file on default radio and using default replay\n"
" block.\n"
" rfnoc_replay_samples_from_file --args \"addr=192.168.10.2\"\n"
" --freq 2.4e09 --rate 10e06\n"
" --file usrp_samples.dat\n"
"2. Replay a signal from a file explicitly selecting and configuring the\n"
" radio and replay block:\n"
" rfnoc_replay_samples_from_file --args \"addr=192.168.10.2\"\n"
" --freq 2.4e09 --rate 10e06\n"
" --file usrp_samples.dat\n"
" --radio-id 1 --radio-chan 0\n"
" --replay-id 0 --replay-chan 0\n"
"3. Replay an LTE signal at a sampling rate of 30.72MSps with master\n"
" clock rate changed to an integer multiple of the sampling rate:\n"
" rfnoc_replay_samples_from_file --args \"addr=192.168.10.2,master_clock_rate=184.32e6\"\n"
" --freq 2.4e09 --rate 30.72e06\n"
" --file LTE_30d72MSps_sc16.dat\n";
// clang-format on
// We use sc16 in this example, but the replay block only uses 64-bit words
// and is not aware of the CPU or wire format.
std::string wire_format("sc16");
@ -59,41 +125,75 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
size_t radio_id, radio_chan, replay_id, replay_chan, nsamps;
po::options_description desc("Allowed Options");
po::options_description alias_options;
po::options_description all_options;
// clang-format off
desc.add_options()
("help", "help message")
("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args")
("tx_args", po::value<std::string>(&tx_args), "Block args for the transmit radio")
("radio_id", po::value<size_t>(&radio_id)->default_value(0), "radio block to use (e.g., 0 or 1).")
("radio_chan", po::value<size_t>(&radio_chan)->default_value(0), "radio channel to use")
("replay_id", po::value<size_t>(&replay_id)->default_value(0), "replay block to use (e.g., 0 or 1)")
("replay_chan", po::value<size_t>(&replay_chan)->default_value(0), "replay channel to use")
("nsamps", po::value<size_t>(&nsamps)->default_value(0), "number of samples to play (0 for infinite)")
("file", po::value<std::string>(&file)->default_value("usrp_samples.dat"), "name of the file to read binary samples from")
("freq", po::value<double>(&freq), "RF center frequency in Hz")
("lo-offset", po::value<double>(&lo_offset), "Offset for frontend LO in Hz (optional)")
("rate", po::value<double>(&rate), "rate of radio block")
("gain", po::value<double>(&gain), "gain for the RF chain")
("ant", po::value<std::string>(&ant), "antenna selection")
("bw", po::value<double>(&bw), "analog front-end filter bandwidth in Hz")
("ref", po::value<std::string>(&ref), "clock reference (internal, external, mimo, gpsdo)")
("dot", "instead of a textual representation, generate a dot graph of the RFNoC connections")
("help,h", "Show this help message and exit.")
("args", po::value<std::string>(&args)->default_value(""), "Single USRP device selection and "
"configuration arguments."
"\nSpecify key-value pairs (e.g., addr, serial, type, master_clock_rate) separated by commas."
"\nSee the UHD manual for model-specific options."
"\nExamples:"
"\n --args \"addr=192.168.10.2\""
"\n --args \"addr=192.168.10.2,master_clock_rate=200e6\""
"\nIf not specified, UHD connects to the first available device.")
("tx-args", po::value<std::string>(&tx_args), "Provides additional configuration options for the TX "
"radio block as key-value pairs (e.g., mode_n, lo_source). The available options depend on the USRP model. "
"Refer to the UHD manual for a complete list and details of supported arguments for your hardware.")
("radio-id", po::value<size_t>(&radio_id)->default_value(0), "Specifies the RFNoC radio block index "
"to use for transmission. On devices with multiple radio blocks, this argument selects which RF frontend "
"(e.g., 0 for the first radio, 1 for the second) is addressed for streaming.")
("radio-chan", po::value<size_t>(&radio_chan)->default_value(0), "Selects the channel index within "
"the chosen RFNoC radio block for data transmission. This determines which physical transmit channel (e.g., 0 "
"for the first channel, 1 for the second) is used for streaming.")
("replay-id", po::value<size_t>(&replay_id)->default_value(0), "Specifies the RFNoC Replay block "
"index to use for playback (e.g., 0 for the first Replay block).")
("replay-chan", po::value<size_t>(&replay_chan)->default_value(0), "Selects the channel index within "
"the chosen Replay block for data playback.")
("nsamps", po::value<size_t>(&nsamps)->default_value(0), "Sets the total number of samples to play "
"back from the file. If set to 0, playback is continuous; otherwise, playback stops after the specified "
"number of samples.")
("file", po::value<std::string>(&file)->default_value("usrp_samples.dat"), "Specifies the name of the "
"binary file containing baseband samples to be loaded into the Replay block for playback."
"\nThis example program requires the input file to use the sc16 format (interleaved 16-bit signed integers for "
"I/Q), although the Replay block itself can support other data formats.")
("freq,f", po::value<double>(&freq)->required(), "RF center frequency in Hz.")
("lo-offset", po::value<double>(&lo_offset), "LO offset for the frontend in Hz.")
("rate,r", po::value<double>(&rate), "TX sample rate in samples/second. Note that each "
"USRP device only supports a set of discrete sample rates, which depend on the hardware model and "
"configuration. If you request a rate that is not supported, the USRP device will automatically select and "
"use the closest available rate.")
("gain", po::value<double>(&gain), "TX gain for the RF chain in dB.")
("ant", po::value<std::string>(&ant), "Antenna port selection string selecting a specific antenna "
"port for USRP daughterboards having multiple antenna connectors per RF channel."
"\nExample: --ant \"TX/RX\"")
("bw", po::value<double>(&bw), "Sets the analog frontend filter bandwidth for the TX path in Hz. Not "
"all USRP devices support programmable bandwidth; if an unsupported value is requested, the device will use "
"the nearest supported bandwidth instead.")
("ref", po::value<std::string>(&ref), "Sets the source for the frequency reference. Available values "
"depend on the USRP model. Typical values are 'internal', 'external', 'mimo', and 'gpsdo'.")
("dot", "Outputs the RFNoC graph as a DOT-format representation, showing the connections between "
"blocks for visualization or analysis.")
;
alias_options.add_options()
("tx_args", po::value<std::string>(&tx_args), "") // alias for --tx-args for backward compatibility
("radio_id", po::value<size_t>(&radio_id), "") // alias for --radio-id
("radio_chan", po::value<size_t>(&radio_chan), "") // alias for --radio-chan
("replay_id", po::value<size_t>(&replay_id), "") // alias for --replay-id
("replay_chan", po::value<size_t>(&replay_chan), "") // alias for --replay-chan
;
all_options.add(desc).add(alias_options);
// clang-format on
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
// Print help message
po::store(po::parse_command_line(argc, argv, all_options), vm);
// print the help message
if (vm.count("help")) {
cout << "UHD/RFNoC Replay samples from file " << desc << endl;
cout << "This application uses the Replay block to playback data from a file to "
"a radio"
<< endl
<< endl;
return EXIT_FAILURE;
std::cout << program_doc << std::endl;
std::cout << desc << std::endl;
return ~0;
}
po::notify(vm); // only called if --help was not requested
/************************************************************************
* Create device and block controls

View File

@ -222,6 +222,83 @@ bool check_locked_sensor(std::vector<std::string> sensor_names,
int UHD_SAFE_MAIN(int argc, char* argv[])
{
const std::string program_doc =
"usage: rfnoc_rx_to_file [--help] [--args ARGS] [--file FILE]\n"
" [--format FORMAT] [--duration DURATION]\n"
" [--nsamps NSAMPS] [--spb SPB] [--spp SPP]\n"
" [--streamargs STREAMARGS] [--progress]\n"
" [--stats] [--sizemap] [--null] [--continue]\n"
" [--setup SETUP] [--radio-id RADIO_ID]\n"
" [--radio-chan RADIO_CHAN] [--rate RATE]\n"
" [--freq FREQ] [--lo-offset LO_OFFSET]\n"
" [--gain GAIN] [--ant ANT] [--bw BW]\n"
" [--ref {internal,external,mimo,gpsdo}]\n"
" [--skip-lo] [--int-n] [--block-id BLOCK_ID]\n"
" [--block-port BLOCK_PORT]\n"
" [--block-props BLOCK_PROPS]"
"\n\n"
"This example demonstrates how to use the RFNoC C++ API to\n"
"receive samples from a single channel of a single USRP device.\n"
"It builds and configures an RFNoC graph that connects the radio block\n"
"(RF frontend) to the DDC (digital downconverter, if present), and then\n"
"to the host RxStreamer.\n"
"If specified, an additional RFNoC processing block can be inserted into\n"
"the chain; the graph is built dynamically so that received samples are\n"
"processed by this block, enabling customized RX sample processing.\n"
"The received data is streamed to a raw binary file on the host.\n"
"\n"
"Note: The additional RFNoC block must be available in the FPGA, which\n"
"typically requires a custom FPGA build synthesizing extra RFNoC blocks\n"
"such as FFT or FIR into the design.\n"
"\n"
"Key features:\n"
"- Builds an RFNoC graph that connects the RxStreamer, Radio, and DDC (if\n"
" present) RFNoC blocks for sample acquisition.\n"
"- Supports flexible insertion of an optional RFNoC processing block into\n"
" the signal chain, as specified by the --block-id option, allowing\n"
" users to process samples with custom FPGA logic prior to file capture.\n"
"- Configures all involved RFNoC blocks, including the RxStreamer, Radio,\n"
" and DDC. If an additional RFNoC processing block is inserted via the\n"
" --block-id option, it is configured with the block properties\n"
" specified by the --block-props option.\n"
"- Automatically sets up connections between RFNoC blocks and configures\n"
" streaming parameters according to user-specified options, ensuring\n"
" that all selected blocks are properly integrated into the signal\n"
" chain.\n"
"\n"
"Usage examples:\n"
" 1. Receive 10 ms data at 2.4 GHz using a sample rate of 10 MSps and\n"
" save it to file:\n"
" rfnoc_rx_to_file --args \"addr=192.168.10.2\" --freq 2.4e09\n"
" --rate 10e06 --duration 0.01\n"
" --file \"usrp_samples.dat\"\n"
" The program will print the RFNoC connections, that were created\n"
" to propagate the data from the radio to the RX streamer. E.g.\n"
" this might look like:\n"
" Active connections:\n"
" * 0/Radio#0:0==>0/DDC#0:0\n"
" * 0/DDC#0:0-->RxStreamer#0:0\n"
" 2. Use Radio 1 to receive 10 ms data at 2.4 GHz using a sample rate of\n"
" 10 MSps and save it to file:\n"
" rfnoc_rx_to_file --args \"addr=192.168.10.2\" --radio-id 1\n"
" --radio-chan 0 --freq 2.4e09 --rate 10e06\n"
" --duration 0.01 --file usrp_samples.dat\"\n"
" The printed RFNoC connections now show that Radio 1 is used:\n"
" Active connections:\n"
" * 0/Radio#1:0==>0/DDC#1:0\n"
" * 0/DDC#1:0-->RxStreamer#0:0\n"
" 3. Insert an RFNoC FFT block into the RFNoC block chain:\n"
" rfnoc_rx_to_file --args \"addr=192.168.10.2\"\n"
" --block-id \"0/FFT#0\" --block-port 0\n"
" --block-props \"bypass_mode=1\"\n"
" --freq 2.4e09 --rate 10e06 --duration 0.01\n"
" The printed RFNoC connections now show that the FFT block got\n"
" inserted in between the DDC and the RxStreamer:\n"
" Active connections:\n"
" * 0/Radio#0:0==>0/DDC#0:0\n"
" * 0/DDC#0:0-->0/FFT#0:0\n"
" * 0/FFT#0:0-->RxStreamer#0:0"
"\n";
// variables to be set by po
std::string args, file, format, ant, subdev, ref, wirefmt, streamargs, block_id,
block_props;
@ -232,53 +309,94 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
po::options_description desc("Allowed options");
// clang-format off
desc.add_options()
("help", "help message")
("file", po::value<std::string>(&file)->default_value("usrp_samples.dat"), "name of the file to write binary samples to")
("format", po::value<std::string>(&format)->default_value("sc16"), "File sample format: sc16, fc32, or fc64")
("duration", po::value<double>(&total_time)->default_value(0), "total number of seconds to receive")
("nsamps", po::value<size_t>(&total_num_samps)->default_value(0), "total number of samples to receive")
("spb", po::value<size_t>(&spb)->default_value(10000), "samples per buffer")
("spp", po::value<size_t>(&spp), "samples per packet (on FPGA and wire)")
("streamargs", po::value<std::string>(&streamargs)->default_value(""), "stream args")
("progress", "periodically display short-term bandwidth")
("stats", "show average bandwidth on exit")
("sizemap", "track packet size and display breakdown on exit")
("null", "run without writing to file")
("continue", "don't abort on a bad packet")
("args", po::value<std::string>(&args)->default_value(""), "USRP device address args")
("setup", po::value<double>(&setup_time)->default_value(1.0), "seconds of setup time")
("radio-id", po::value<size_t>(&radio_id)->default_value(0), "Radio ID to use (0 or 1).")
("radio-chan", po::value<size_t>(&radio_chan)->default_value(0), "Radio channel")
("rate", po::value<double>(&rate)->default_value(1e6), "RX rate of the radio block")
("freq", po::value<double>(&freq)->default_value(0.0), "RF center frequency in Hz")
("lo-offset", po::value<double>(&lo_offset), "Offset for frontend LO in Hz (optional)")
("gain", po::value<double>(&gain), "gain for the RF chain")
("ant", po::value<std::string>(&ant), "antenna selection")
("bw", po::value<double>(&bw), "analog frontend filter bandwidth in Hz")
("ref", po::value<std::string>(&ref), "reference source (internal, external, mimo)")
("skip-lo", "skip checking LO lock status")
("int-n", "tune USRP with integer-N tuning")
("block-id", po::value<std::string>(&block_id), "If block ID is specified, this block is inserted between radio and host.")
("block-port", po::value<size_t>(&block_port)->default_value(0), "If block ID is specified, this block is inserted between radio and host.")
("block-props", po::value<std::string>(&block_props), "These are passed straight to the block as properties (see set_properties()).")
("help", "Show this help message and exit.")
("file", po::value<std::string>(&file)->default_value("usrp_samples.dat"), "Name of the raw binary "
"file to which received data will be written.")
("format", po::value<std::string>(&format)->default_value("sc16"), "Specifies the data format of the "
"file. The data will be written as interleaved IQ samples in one of the following numeric formats: 'double' "
"(64-bit float, fc64), 'float' (32-bit float, fc32), or 'short' (16-bit integer, sc16, scaled to int16 range "
"-32768 to 32767)."
"\nChoosing 'short' as the file format matches the sc16 over-the-wire format and is usually sufficient. Using "
"'float' or 'double' does not improve precision, but may be more convenient for post-processing or for "
"compatibility with certain analysis tools.")
("duration", po::value<double>(&total_time)->default_value(0), "Total number of seconds to receive. "
"The program stops when this number is reached.")
("nsamps", po::value<size_t>(&total_num_samps)->default_value(0), "Total number of samples to "
"receive. The program stops when this number is reached.")
("spb", po::value<size_t>(&spb)->default_value(10000), "Size of the host data buffer that is "
"allocated for each Rx channel."
"\nLarger values may help to improve throughput.")
("spp", po::value<size_t>(&spp), "Specifies the number of samples per packet sent from the FPGA to "
"the host. Adjusting this value can affect streaming efficiency and packetization behavior.")
("streamargs", po::value<std::string>(&streamargs)->default_value(""), "Specifies additional "
"key-value arguments for configuring the RX streamer. These options allow advanced control over streaming "
"behavior, such as buffer sizes or data formats.")
("progress", "Periodically display the estimated short-term USRP device to host streaming rate in "
"samples per second.")
("stats", "Show the total number of samples received and the elapsed time when the program exits.")
("sizemap", "Track and display a breakdown of received packet sizes on exit. This helps diagnose "
"streaming performance and packetization issues.")
("null", "Run without writing to file.")
("continue", "Continue streaming even if a bad packet is received.")
("args", po::value<std::string>(&args)->default_value(""), "Single USRP device selection and "
"configuration arguments."
"\nSpecify key-value pairs (e.g., addr, serial, type, master_clock_rate) separated by commas."
"\nSee the UHD manual for model-specific options."
"\nExamples:"
"\n --args \"addr=192.168.10.2\""
"\n --args \"addr=192.168.10.2,master_clock_rate=200e6\""
"\nIf not specified, UHD connects to the first available device.")
("setup", po::value<double>(&setup_time)->default_value(1.0), "Sets the amount of time (in seconds) "
"the program waits for hardware locks (e.g. LO) to ensure the device is properly synchronized before starting "
"reception.")
("radio-id", po::value<size_t>(&radio_id)->default_value(0), "Specifies the RFNoC radio block index "
"to use for reception. On devices with multiple radio blocks, this argument selects which RF frontend (e.g., "
"0 for the first radio, 1 for the second) is addressed for streaming.")
("radio-chan", po::value<size_t>(&radio_chan)->default_value(0), "Selects the channel index within "
"the chosen RFNoC radio block for data acquisition. This determines which physical receive channel (e.g., 0 "
"for the first channel, 1 for the second) is used for streaming.")
("rate", po::value<double>(&rate)->default_value(1e6), "RX sample rate in samples/second. Note that "
"each USRP device only supports a set of discrete sample rates, which depend on the hardware model and "
"configuration. If you request a rate that is not supported, the USRP device will automatically select and "
"use the closest available rate.")
("freq", po::value<double>(&freq)->default_value(0.0), "RF center frequency in Hz.")
("lo-offset", po::value<double>(&lo_offset), "LO offset for the frontend in Hz.")
("gain", po::value<double>(&gain), "RX gain for the RF chain in dB.")
("ant", po::value<std::string>(&ant), "Antenna port selection string selecting a specific antenna "
"port for USRP daughterboards having multiple antenna connectors per RF channel."
"\nExample: --ant \"TX/RX\"")
("bw", po::value<double>(&bw), "Sets the analog frontend filter bandwidth for the RX path in Hz. Not "
"all USRP devices support programmable bandwidth; if an unsupported value is requested, the device will use "
"the nearest supported bandwidth instead.")
("ref", po::value<std::string>(&ref), "Sets the source for the frequency reference. Available values "
"depend on the USRP model. Typical values are 'internal', 'external', 'mimo', and 'gpsdo'.")
("skip-lo", "Skip checking and waiting for hardware locks (LO, reference, MIMO synchronization) "
"before starting reception.")
("int-n", "Use integer-N tuning for USRP RF synthesizers. With this mode, the LO can only be tuned in "
"discrete steps, which are integer multiples of the reference frequency. This mode can improve phase noise "
"and spurious performance at the cost of coarser frequency resolution.")
("block-id", po::value<std::string>(&block_id), "Specifies an RFNoC block to insert between the radio "
"and host (e.g., if your FPGA image includes a routable FFT or FIR block, use the RFNoC block identifiers "
"\"0/FFT0\" or \"0/FIR0\" to address them)."
"\nThe specified block must be present in the FPGA image; a list of available RFNoC blocks can be obtained "
"using the uhd_usrp_probe command.")
("block-port", po::value<size_t>(&block_port)->default_value(0), "Selects which input port of the "
"RFNoC block specified by --block-id receives data from the radio block. Use the port index (e.g., 0 or 1) to "
"choose the connection point.")
("block-props", po::value<std::string>(&block_props), "Sets RFNoC-specific key-value properties, "
"which are passed directly to the block specified by --block-id. For RFNoC blocks shipped with UHD, "
"block-specific options are documented in the UHD manual.")
;
// clang-format on
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
// print the help message
if (vm.count("help")) {
std::cout << "UHD/RFNoC RX samples to file " << desc << std::endl;
std::cout << std::endl
<< "This application streams data from a single channel of a USRP "
"device to a file.\n"
<< std::endl;
std::cout << program_doc << std::endl;
std::cout << desc << std::endl;
return ~0;
}
po::notify(vm); // only called if --help was not requested
bool bw_summary = vm.count("progress") > 0;
bool stats = vm.count("stats") > 0;
@ -305,7 +423,7 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
std::cout << "Creating the RFNoC graph with args: " << args << std::endl;
auto graph = uhd::rfnoc::rfnoc_graph::make(args);
// Create handle for radio object
// Create handle for radio object (e.g. 0/Radio#0)
uhd::rfnoc::block_id_t radio_ctrl_id(0, "Radio", radio_id);
// This next line will fail if the radio is not actually available
auto radio_ctrl = graph->get_block<uhd::rfnoc::radio_control>(radio_ctrl_id);
@ -401,28 +519,6 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
radio_ctrl->set_rx_antenna(ant, radio_chan);
}
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(1000 * setup_time)));
// check Ref and LO Lock detect
if (not vm.count("skip-lo")) {
check_locked_sensor(
radio_ctrl->get_rx_sensor_names(radio_chan),
"lo_locked",
[&](const std::string& sensor_name) {
return radio_ctrl->get_rx_sensor(sensor_name, radio_chan);
},
setup_time);
if (ref == "external") {
check_locked_sensor(
graph->get_mb_controller(0)->get_sensor_names(),
"ref_locked",
[&](const std::string& sensor_name) {
return graph->get_mb_controller(0)->get_sensor(sensor_name);
},
setup_time);
}
}
if (vm.count("spp")) {
std::cout << "Requesting samples per packet of: " << spp << std::endl;
radio_ctrl->set_property<int>("spp", spp, radio_chan);
@ -496,6 +592,28 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
std::cout << "Actual RX Rate: " << (rate / 1e6) << " Msps..." << std::endl
<< std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(int64_t(1000 * setup_time)));
// check Ref and LO Lock detect
if (not vm.count("skip-lo")) {
check_locked_sensor(
radio_ctrl->get_rx_sensor_names(radio_chan),
"lo_locked",
[&](const std::string& sensor_name) {
return radio_ctrl->get_rx_sensor(sensor_name, radio_chan);
},
setup_time);
if (ref == "external") {
check_locked_sensor(
graph->get_mb_controller(0)->get_sensor_names(),
"ref_locked",
[&](const std::string& sensor_name) {
return graph->get_mb_controller(0)->get_sensor(sensor_name);
},
setup_time);
}
}
if (vm.count("block-props")) {
std::cout << "Setting block properties to: " << block_props << std::endl;
graph->get_block(uhd::rfnoc::block_id_t(block_id))