aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2021-11-29 14:50:18 +0100
committerAaron Rossetto <aaron.rossetto@ni.com>2021-12-03 11:34:35 -0800
commitb49e4f1eb62081a035c8d2e1d7b50e65e027ad72 (patch)
tree7be7376fcde20adc9e8a16111b5aabba50b53cf8
parentf249b73036ce55b3beecad94cd0f362b6a2b8b5d (diff)
downloaduhd-b49e4f1eb62081a035c8d2e1d7b50e65e027ad72.tar.gz
uhd-b49e4f1eb62081a035c8d2e1d7b50e65e027ad72.tar.bz2
uhd-b49e4f1eb62081a035c8d2e1d7b50e65e027ad72.zip
docs: Improve documentation for properties and -propagation
-rw-r--r--host/docs/coding.dox4
-rw-r--r--host/docs/group_defs.dox2
-rw-r--r--host/docs/properties.dox453
-rw-r--r--host/docs/rfnoc_docs.dox8
-rw-r--r--host/include/uhd/rfnoc/noc_block_base.hpp3
-rw-r--r--host/include/uhd/rfnoc/node.hpp8
-rw-r--r--host/include/uhd/rfnoc/replay_block_control.hpp19
-rw-r--r--host/include/uhd/rfnoc_graph.hpp3
8 files changed, 498 insertions, 2 deletions
diff --git a/host/docs/coding.dox b/host/docs/coding.dox
index aab495c25..e5016ab04 100644
--- a/host/docs/coding.dox
+++ b/host/docs/coding.dox
@@ -10,6 +10,10 @@ The Multi-USRP class provides a high-level interface to a single USRP device
with one or more channels, or multiple USRP devices in a homogeneous
setup. See the documentation for uhd::usrp::multi_usrp.
+\subsection coding_api_hilevel_rfnoc High-Level: The RFNoC API
+
+\li \subpage page_properties
+
\subsection coding_api_hilevelclock High-Level: The Multi-USRP-Clock
The Multi-USRP-Clock class provides a high-level interface to a single clock
diff --git a/host/docs/group_defs.dox b/host/docs/group_defs.dox
index 312def6d9..769e6ec71 100644
--- a/host/docs/group_defs.dox
+++ b/host/docs/group_defs.dox
@@ -1,5 +1,5 @@
/*!
- * \defgroup rfnoc RFNoC
+ * \defgroup rfnoc_docs RFNoC
* \brief Additional documentation regarding RFNoC
*
* @{
diff --git a/host/docs/properties.dox b/host/docs/properties.dox
new file mode 100644
index 000000000..8de4a50ab
--- /dev/null
+++ b/host/docs/properties.dox
@@ -0,0 +1,453 @@
+/*! \page page_properties RFNoC Block Properties
+
+\tableofcontents
+
+\section props_intro Introduction
+
+One of the mechanisms that RFNoC blocks can use to control their configuration
+are *properties*. There are two types of properties within a block: User
+properties, and edge properties.
+
+To illustrate these types of properties, we start with an example: Take DDC
+block (uhd::rfnoc::ddc_block_control), which can take in a signal of a certain
+sampling rate, shift it in frequency, and resample to a lower sampling rate.
+
+First, we take a look at the *user properties*. User properties are properties
+which are attributes of a block, they simply define certain characteristics of
+the block. Here, we focus on two user properties: The frequency and the decimation.
+The former is a floating point value which stores, in Hz, the amount by which
+the incoming signal is shifted in frequency. The decimation is the integer
+value by which the incoming sampling rate is divided. Put differently, the user
+properties control the behaviour of the DDC block.
+
+On top of that, the DDC block also has an edge property for the sampling rate
+on both the outgoing edge and the incoming edge. This property describes
+something about the data on this graph edge.
+
+```
+ ┌──────────────┐
+ │ DDC Block │
+ samp_rate │ │ samp_rate
+───────────> - freq ├─────────>
+ │ - decim │
+ │ │
+ └──────────────┘
+```
+
+The key thing, which separates properties from other attributes associated with
+the block, is that we can describe relationships between the properties. In this
+example, the following relationships exist:
+
+- The `samp_rate` property on the outgoing edge equals the `samp_rate` property
+ on the incoming edge divided by the `decim` value.
+- The normalized frequency (required to program the digital hardware) is
+ calculated by dividing the `freq` user property by the `samp_rate` edge
+ property on the incoming edge.
+
+User properties also act as an API for RFNoC blocks. It is possible to access
+user properties using the uhd::rfnoc::node_t::get_property() and
+uhd::rfnoc::node_t::set_property() and uhd::rfnoc::node_t::set_properties() APIs.
+A list of user properties can be read by calling uhd::rfnoc::node_t::get_property_ids().
+
+Often, block controllers will provide explicit C++ APIs on top of the user
+properties. For example, the DDC block control has an API call
+uhd::rfnoc::ddc_block_control::set_freq(), which provides additional features on top of the
+user property (e.g., it can also set a command time). Direct access of user
+properties is thus often not necessary, and can be considered a lower-level API
+than directly calling block APIs.
+
+\section props_propprop Property Propagation
+
+By itself, these relationships between properties are already useful to describe
+something about the block, but the real power comes when connecting blocks. This
+allows blocks to communicate their settings, even without knowing which block
+they're connected to. Consider the following example, using a radio block
+(uhd::rfnoc::radio_control), the DDC block, and a hypothetical Modem Block, which
+expects incoming samples at a fixed data rate of 20 Msps. Assume we are running
+this flow graph in a USRP X310, with a master clock rate of 200 Msps:
+
+```
+ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
+ │ Radio Block │ │ DDC Block │ │ Modem Block │
+ │ │samp_rate │ │ samp_rate│ │
+ │ ├──────────> - freq ├──────────> │
+ │ │ │ - decim │ │ │
+ │ │ │ │ │ │
+ └─────────────┘ └──────────────┘ └──────────────┘
+```
+
+Upon initialization, the radio block will configure its hardware clocks, and
+set a sampling rate of 200 Msps on its outgoing edge property (`samp_rate`).
+This property is also the incoming edge property of the DDC block, which means
+it is now initialized its sampling rate to 200 Msps. Setting the `freq` property
+will now work accurately, because it can calculate normalized frequencies.
+
+The Modem Block will set its incoming edge property `samp_rate` to 20 Msps, which
+is also the outgoing edge property of the DDC block. The DDC block must now find
+a way to reconcile these edge properties, which it can achieve by setting the
+`decim` user property to 20. Now, the DDC has automatically set a decimation
+without anyone calling into its rate-changing APIs, and without the Modem Block
+or the Radio Block knowing anything about how the DDC block works.
+
+Similarly, if the Modem Block would have requested a sampling rate of 30.72 Msps,
+the DDC would not have been able to resolve the incoming and outgoing sampling
+rates with an integer decimation factor. It would have requested the radio to
+produce an integer multiple of 30.72 Msps, which the radio can't provide. Using
+properties, UHD would be able to throw an exception, telling the user that the
+requested combination is not achievable.
+
+\section props_multichan Properties on multi-channel blocks and resource types
+
+The DDC block is configurable to have a variable number of channels, and the
+same is true for the properties. To accommodate for this, both edge and user
+properties have an index value. A multi-channel DDC block is more accurately
+depicted as such with regard to its properties:
+
+```
+ ┌──────────────┐
+ │ DDC Block │
+samp_rate[i] │ │ samp_rate[i]
+──────────── > - freq[i] ├──────────────>
+ │ - decim[i] │
+ │ │
+ └──────────────┘
+```
+
+\b Note: The index of edge properties is tightly coupled to the actual edge
+being used (i.e., `samp_rate` on input edge 0 also has a property index of 0).
+However, user properties do not enforce this relationship. In the DDC block,
+it would be possible for the user property `decim[0]` to control channel 1 (which
+it doesn't). This is useful for properties that don't map directly to channels
+(e.g., a simpler version of the DDC block might only have a single `decim` user
+property for all channels).
+
+In summary, to accurately identify a property, not only its name is required, but
+also its type (input edge, user property, or output edge) and its index. For the
+sake of convenience, the type and index are sometimes combined into a
+uhd::rfnoc::res_source_info object.
+
+\section props_define Defining Properties
+
+To define properties in an RFNoC block, the block controller needs to declare
+class attributes of type uhd::rfnoc::property_t. This is a template type which can
+take in different types of C++ types (e.g., the `decim` user property is an
+integer type, and the `freq` user property is of type `double`). Upon creation
+of the class attribute, properties are configured with their property name (e.g.
+`freq`, `decim`), optionally a default value, their type (user or edge property),
+and their index. The uhd::rfnoc::ddc_block_control defines its frequency property
+attribute variable as such:
+
+~~~{.cpp}
+class ddc_block_control_impl : public ddc_block_control
+{
+ // ...all the other declarations ...
+private:
+ // Declare the property_t attributes for the frequency
+ std::vector<property_t<double>> _freq;
+};
+~~~
+
+Next, the attribute needs to be initialized, like any other class attribute.
+If we were to hard-code a 2-channel DDC with a default frequency shift of 0 Hz,
+this would be a valid initialization:
+~~~{.cpp}
+// ... other initializations ...
+_freq.reserve(2);
+_freq.push_back(property_t<double>("freq", 0.0, {res_source_info::USER, 0}));
+_freq.push_back(property_t<double>("freq", 0.0, {res_source_info::USER, 1}));
+~~~
+
+\b Note: It is important to choose a container for properties which does not
+modify their memory locations! If a `std::vector<>` is chosen as a container, it
+is required to `reserve()` memory before filling the container.
+
+Up until now, the property is just a class attribute. For UHD to make it
+available for property propagation, it needs to be registered by calling
+uhd::rfnoc::node_t::register_property():
+
+~~~{.cpp}
+// ... this is still happening within the context of the block controller:
+register_property(&_freq[0]);
+register_property(&_freq[1]);
+~~~
+
+The final step of creating properties is to define the relationship between
+them, which is covered in the following section.
+
+\section props_resolvers Property Resolvers and Property Resolution
+
+A property resolver is a function object that is associated with an input list
+of properties and an output list of properties. The function object will be
+executed when any property on the input list changes. It may modify any property
+in the output list. For more details, see uhd::rfnoc::node_t::add_property_resolver().
+
+As an example, here is a shortened version of one of the resolvers in
+uhd::rfnoc::ddc_block_control:
+
+~~~{.cpp}
+add_property_resolver(
+ {&decim}, // Input list: We trigger this when the user changes `decim`
+ {&decim, &samp_rate_out, &samp_rate_in}, // Output list: These will be changed
+ // The resolver function is passed in as a lambda:
+ [this, chan, ...]() { // The capture list was cropped for better readability
+ RFNOC_LOG_TRACE("Calling resolver for `decim'@" << chan);
+ decim = coerce_decim(double(decim.get())); // Only some decimations are valid
+ if (decim.is_dirty()) { // If nothing changes, don't poke any registers
+ set_decim(decim.get(), chan); // This will control hardware registers
+ }
+ if (samp_rate_in.is_valid()) {
+ samp_rate_out = samp_rate_in.get() / decim.get();
+ } else if (samp_rate_out.is_valid()) {
+ samp_rate_in = samp_rate_out.get() * decim.get();
+ }
+ });
+~~~
+
+A few noteworthy comments:
+- Even though this function object is called when modifying `decim`, it is still
+ possible to coerce `decim` to a different, valid value.
+- Properties may be in an invalid state (e.g., if they were never initialized)
+ which needs to be checked before accessing such properties. See also
+ uhd::rfnoc::property_base_t::is_valid().
+- Properties can be modified by assigning values to them which match their
+ template type.
+- We can tell if a property was modified by checking its clean/dirty state
+ (see also uhd::rfnoc::property_base_t::is_dirty()).
+- When the resolver function completes, we have ensured that `samp_rate_in`,
+ `samp_rate_out`, and `decim` are in a consistent state.
+
+
+\subsection props_resolvers_cldrty Clean/Dirty Attributes of Properties
+
+To keep track of which properties have been modified, every property has a "dirty"
+flag that is set when a property's value is changed. This dirty flagged is also
+used to determine which property resolver functions need to be called. A property
+resolver may modify (and thus dirty) other properties, but resolvers may never
+set properties to conflicting values. For example, the DDC block controller has
+a different resolver that is called when its input sampling rate is modified,
+but it uses the same equations to relate decimation, input rate, and output rate
+so the resulting values are consistent.
+
+Imagine a block that has both an `fft_size` and `bin_width` user property. They
+are related by `bin_width = sampling_rate_in / fft_size`, where `sampling_rate_in`
+is an input edge property. Adding two separate resolvers like this is valid:
+
+~~~{.cpp}
+add_property_resolver(
+ {&fft_size},
+ {&bin_width},
+ [this, &fft_size, &bin_width, &samp_rate_in]() {
+ // This will trigger the other resolver
+ bin_width = samp_rate_in.get() / fft_size.get();
+ });
+add_property_resolver(
+ {&bin_width},
+ {&fft_size},
+ [this, &fft_size, &bin_width, &samp_rate_in]() {
+ // This will trigger the other resolver
+ fft_size = samp_rate_in.get() / bin_width.get();
+ });
+~~~
+
+Both resolvers will modify the input for the other resolver. However, because
+they use the same equations, this does not incur a circular resolution.
+
+\b Note: When using floating-point type properties, it is possible to incur
+a circular resolution when floating point rounding errors occur, so they need
+to be accounted for. See, for example, the implementation of
+uhd::rfnoc::ddc_block_control.
+
+When all resolvers with dirty inputs have been run, and no conflicts have
+occurred, properties are flagged clean. When this happens, a clean callback is
+executed which can trigger further actions (it may not, however, modify
+properties. See uhd::rfnoc::node_t::register_property()).
+
+As an example, assume a block configures an FFT size. It first checks the FFT
+size is as power of two, then writes the log2 of the FFT size to a hardware
+register.
+
+The following two implementations have the same effect. First, we use the resolver
+to write to the hardware:
+~~~{.cpp}
+register_property(&fft_size);
+add_property_resolver(
+ {&fft_size},
+ {&fft_size},
+ [this, &fft_size]() {
+ fft_size = coerce_to_power_of_2(fft_size.get());
+ if (fft_size.is_dirty()) {
+ this->regs().poke32(FFT_SIZE_REG, log2(fft_size.get()));
+ }
+ });
+~~~
+
+Alternatively, we poke the register as part of cleaning the property:
+~~~{.cpp}
+register_property(
+ &fft_size,
+ [this, &fft_size](){ this->regs().poke32(FFT_SIZE_REG, log2(FFT_SIZE.get()); }
+);
+add_property_resolver(
+ {&fft_size},
+ {&fft_size},
+ [this, &fft_size]() {
+ fft_size = coerce_to_power_of_2(fft_size.get());
+ });
+~~~
+
+The differences are subtle:
+- The second approach separates the hardware interaction from the
+ coercion/resolution logic. Here, the resolver is *only* responsible for
+ maintaining the property in a valid state.
+- In the first approach, the register is poked as soon as the new value is
+ known. In the second approach, the poke happens only after the resolution is
+ complete.
+
+In this particular instance, the second approach is the more readable and is
+recommended. However, not always do properties map to poking registers (or other
+actions) in a clean, one-to-one manner, which is when the first approach is
+better suited.
+
+
+\section props_graph_resolution Graph Property Resolution
+
+In an RFNoC graph, after calling `uhd::rfnoc::rfnoc_graph::commit()`, edge
+properties are used to resolve properties of a whole graph. When any property is
+changed, the corresponding block's properties are resolved as explained before.
+However, by modifying the edge properties of a block, other blocks' properties
+may be dirtied as well. In this case, the resolver algorithm will keep resolving
+blocks until either all properties are clean, or a conflict is detected.
+
+\subsection props_graph_resolution_back_edges Back edges
+
+The graph object uses a topological sort to identify the order in which blocks
+are resolved. However, in RFNoC, it is OK to have loops, or *back edges*.
+
+Consider the following graph:
+
+```
+
+ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
+ │ │ │ │ │ │
+ │ Radio Block ├──────────> DDC Block ├──────────> Custom DSP │
+ │ │ │ │ │ │
+ └─────^───────┘ └──────────────┘ └──────┬───────┘
+ │ │
+ │ back edge │
+ └───────────────────────────────────────────────────┘
+```
+
+In this application, we receive a signal, use the DDC block to correct a
+frequency offset digitally, and then apply some custom DSP before transmitting
+the signal again, using the same radio block. In order to create a valid graph
+that UHD can handle, one of the edges needs to be declared a back edge.
+
+Edge properties are not propagated across back edges, however, they must still
+match up after property resolution is done. For example, if the DDC were to be
+configured to decimate the sampling rate, and the custom DSP block would not
+correct for that, the output edge property `samp_rate` of the custom DSP block
+would be mismatched compared to the input edge property `samp_rate` of the radio.
+
+
+\section props_unknown Handling unknown properties
+
+Since every block can define its own edge- and user properties, it is likely that
+a block may not have defined an edge property that an up- or downstream block
+has.
+
+Consider the example of the previous section. The "Custom DSP" block may not
+have defined edge properties for the sampling rate. The Radio and DDC blocks
+however, do have a `samp_rate` edge property defined.
+
+The way blocks handle such properties is by setting a *forwarding policy* (see
+uhd::rfnoc::node_t::set_prop_forwarding_policy() and
+uhd::rfnoc::node_t::set_prop_forwarding_map()).
+
+There are several forwarding policies (see uhd::rfnoc::node_t::forwarding_policy_t).
+The most common forwarding policy is uhd::rfnoc::node_t::forwarding_policy_t::ONE_TO_ONE.
+Here, properties that are applied to an input edge are forwarded to the
+corresponding output edge. For more special use cases,
+uhd::rfnoc::node_t::forwarding_policy_t::USE_MAP can be used to define
+forwarding rules for non-static cases (e.g., see uhd::rfnoc::switchboard_block_control).
+
+\section props_common_props Common Properties
+
+There are some properties that are commonly used, and blocks should use these
+property names if appropriate:
+
+Edge Properties:
+- `type`: It is recommended to always use this property on an edge. It should
+ describe the data type on this edge so the graph can see if types match. The
+ type descriptions are the same as with CPU and OTW types (e.g., sc16, fc32,
+ sc8, u8, ...).
+- `samp_rate`: Whenever a "sampling rate" is applicable, edges should set this
+ property. DDC, DUC, and Radio blocks all use this for configuring rates and
+ verifying sample rates match.
+- `scaling`: This property describes how a block has distorted a signal with
+ respect to amplitude. A scaling value of 0.99 means that a signal that used
+ to be fullscale (max. amplitude of 1.0) will only have a maximum amplitude of
+ 0.99. For example, the DDC and DUC blocks use this property to signal how
+ much they have affected the amplitude. The TX and RX streamer objects can then
+ apply the inverse of this scaling factor to correct for the scaling before
+ returning the signal to the user.
+
+User properties:
+- `spp`: Blocks that produce data in chunks of samples can use this to describe
+ their "sample per packet" values. Example: uhd::rfnoc::radio_control
+- `interp`, `decim`: Interpolation and decimation factors when doing rational
+ resampling. Examples: uhd::rfnoc::ddc_block_control, uhd::rfnoc::duc_block_control
+
+See also the following sections for properties that receive special treatment by
+the framework.
+
+\section props_special_props Special Properties
+
+There is a small number of edge properties that treated differently by the RFNoC
+framework.
+
+\section props_special_props_tickrate Tick Rate
+
+The "tick rate" is the rate that is used for converting floating point timestamps
+into integer ticks. The tick rate has the additional constraint that within a
+graph, the tick rate must be the same for all blocks. Therefore, the tick rate
+is defined by the framework for all blocks that derive from uhd::rfnoc::noc_block_base,
+and block authors cannot register properties with that name.
+
+The tick rate property is propagated to all edges to enforce all blocks having
+the same tick rate. Some blocks (like the radio blocks) set the tick rate via
+their direct access to the hardware, but most blocks use the property propagation
+mechanism to distribute the tick rate.
+
+To read back the tick rate, use uhd::rfnoc::noc_block_base::get_tick_rate().
+Within a block implementation, the uhd::rfnoc::noc_block_base::set_tick_rate()
+API call can be used to update the tick rate (most blocks do not have to do
+this).
+
+\section props_special_props_mtu MTU
+
+Because the MTU is part of the RFNoC framework (it is constrained by the buffer
+size chosen between blocks, and is thus read back from a register in the FPGA),
+its property is also created by the RFNoC framework for all blocks, and block
+authors cannot use the name `mtu` for their properties.
+
+However, MTUs can differ within a graph, and blocks might have additional
+constraints on MTU. For this reason, accessing the MTU properties directly is
+not possible from within a block controller, but rfnoc::noc_block_base provides
+several APIs to interact with MTUs:
+
+- noc_block_base::get_mtu(): Reading back the MTU determined by property
+ propagation on a particular edge.
+- noc_block_base::set_mtu(): Reduce the MTU on a particular edge. *Increasing*
+ the MTU is not possible.
+- noc_block_base::set_mtu_forwarding_policy(): Set the MTU forwarding policy.
+- noc_block_base::get_mtu_prop_ref(): Request access to a MTU property reference,
+ which can be used to trigger property propagation off of an MTU change.
+
+Many blocks (e.g., uhd::rfnoc::ddc_block_control, uhd::rfnoc::duc_block_control)
+do not resize blocks as they pass through them. That means the input MTU of
+these blocks equals the output MTU. Thus, they set the MTU forwarding policy to
+uhd::rfnoc::node_t::forwarding_policy_t::ONE_TO_ONE.
+
+
+*/
+// vim:ft=doxygen:
diff --git a/host/docs/rfnoc_docs.dox b/host/docs/rfnoc_docs.dox
new file mode 100644
index 000000000..0f209bb0c
--- /dev/null
+++ b/host/docs/rfnoc_docs.dox
@@ -0,0 +1,8 @@
+/*! \page page_rfnoc_docs RFNoC Block Properties
+
+\ingroup rfnoc_docs
+
+\li \ref page_properties
+
+*/
+// vim:ft=doxygen:
diff --git a/host/include/uhd/rfnoc/noc_block_base.hpp b/host/include/uhd/rfnoc/noc_block_base.hpp
index 6b1ac5bcd..21135227b 100644
--- a/host/include/uhd/rfnoc/noc_block_base.hpp
+++ b/host/include/uhd/rfnoc/noc_block_base.hpp
@@ -272,6 +272,9 @@ protected:
* This can be used to make the MTU an input to a property resolver. For
* example, blocks that have an spp property, such as the radio, can now
* trigger a property resolver based on the MTU.
+ *
+ * The reference is guaranteed to remain valid for the lifetime of this
+ * block.
*/
property_base_t* get_mtu_prop_ref(const res_source_info& edge);
diff --git a/host/include/uhd/rfnoc/node.hpp b/host/include/uhd/rfnoc/node.hpp
index 0a49d1609..5478eed07 100644
--- a/host/include/uhd/rfnoc/node.hpp
+++ b/host/include/uhd/rfnoc/node.hpp
@@ -229,6 +229,8 @@ protected:
* either be reset after the constructor is finished, or the next time
* properties are resolved.
*
+ * For more information, see \ref props_define.
+ *
* \param prop A reference to the property
* \param clean_callback A callback that gets executed whenever this property
* is dirty and gets marked clean
@@ -265,6 +267,8 @@ protected:
* (which is considered advanced usage), then the block needs to serialize
* access to this function itself.
*
+ * For more information, see \ref props_resolvers.
+ *
* \param inputs The properties that will cause this resolver to run
* \param outputs The properties that this resolver will write to
* \param resolver_fn The resolver function
@@ -290,6 +294,8 @@ protected:
* Typically, this function should only ever be called from within the
* constructor.
*
+ * See also \ref props_unknown.
+ *
* \param policy The policy that is applied (see also forwarding_policy_t).
* \param prop_id The property ID that this forwarding policy is applied to.
* If \p prop_id is not given, it will apply to all properties,
@@ -315,6 +321,8 @@ protected:
* propagation time:
* - Any value in the destination vector represents a non-existent port
*
+ * See also \ref props_unknown.
+ *
* \param map The map describing how properties should be propagated
*/
void set_prop_forwarding_map(const forwarding_map_t& map);
diff --git a/host/include/uhd/rfnoc/replay_block_control.hpp b/host/include/uhd/rfnoc/replay_block_control.hpp
index c7624d044..1afd8b4de 100644
--- a/host/include/uhd/rfnoc/replay_block_control.hpp
+++ b/host/include/uhd/rfnoc/replay_block_control.hpp
@@ -40,6 +40,25 @@ namespace uhd { namespace rfnoc {
* achieve the best throughput to the downstream block without exceeding the supported
* packet size. The maximum packet size can be explicitly set in terms of bytes or
* number of items to allow users to tune the performance to suit their application.
+ *
+ * \section rfnoc_block_replay_properties Block Properties
+ *
+ * User Properties:
+ *
+ * | Key | Description
+ * |---------------:|---------------------------------------------------------
+ * | record_offset | The base address for recordings (data will be written here). Will be set by record().
+ * | record_size | Size of the record buffer. Will be set by record().
+ * | play_offset | Base address for playback (streaming will start from here). Set by play() or config_play().
+ * | play_size | Size of the playback buffer. Set by play() or config_play().
+ * | packet_size | Size of outgoing packets (in bytes). Gets set by set_max_items_per_packet().
+ *
+ * Edge properties:
+ *
+ * | Key | Description
+ * |------:|-------------------------------------------------------------------
+ * | type | Data type. On the input, it set by the upstream block (see get_record_type()). For output, it should be provided by set_play_type().
+ *
*/
class UHD_API replay_block_control : public noc_block_base
{
diff --git a/host/include/uhd/rfnoc_graph.hpp b/host/include/uhd/rfnoc_graph.hpp
index b25d7c207..dc35892b1 100644
--- a/host/include/uhd/rfnoc_graph.hpp
+++ b/host/include/uhd/rfnoc_graph.hpp
@@ -179,7 +179,8 @@ public:
* \param src_port The port of the source block to connect.
* \param dst_blk The block ID of the destination block to connect to.
* \param dst_port The port of the destination block to connect to.
- * \param skip_property_propagation Skip property propagation for this edge
+ * \param skip_property_propagation Skip property propagation for this edge.
+ * See also \ref props_graph_resolution_back_edges.
*
* \throws uhd::routing_error if the source or destination ports are
* statically connected to a *different* block