mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
examples: Fix replay capture at coerced sample rates
rfnoc_rx_replay_samples_to_file.py could hang and then abort with "Timeout while loading replay buffer!" whenever the requested sample rate was not an integer divisor of the master clock rate. For example, requesting 200 Msps at an MCR of 368.64 MHz would leave the record buffer stuck at exactly 50% fullness. Two problems combined to cause this: First, the rate was read back from the DDC before the graph was committed. At that point the DDC's input sample rate property is not yet valid, so set_output_rate() falls back to setting the output edge property and returns the uncoerced, requested rate. The example then reported a rate that the DDC never actually applied. Second, run_capture() derived the number of samples to request from the radio with int(radio_rate / rate), truncating instead of rounding. With a radio rate of 368.64 Msps and a nominal rate of 200 Msps this yielded a ratio of 1 rather than the decimation the DDC had actually settled on, so the radio was told to produce only half the samples needed to fill the record buffer. The rate is now read back with get_output_rate() after graph.commit(), when property propagation has completed, and the user is warned when the requested rate had to be coerced. The rate ratio is computed with rounding, so the radio is asked for the full number of samples the record buffer needs. While here, fix two related issues in the readback path. The DDC port is now taken from the destination port of the discovered edge rather than assuming it matches the radio channel index, and the rate is read per channel (from the DDC where one is present, otherwise from the radio) so that chains without a DDC are not ignored. Rates are compared against a tolerance rather than for exact float equality. Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
This commit is contained in:
parent
323d066e64
commit
94fd6241b8
@ -58,6 +58,8 @@ except ImportError:
|
||||
|
||||
# sc16 (32-bit) samples on the USRP
|
||||
BYTES_PER_SAMP = 4
|
||||
# Sample rates are floats, so compare them with a tolerance (in Hz).
|
||||
RATE_TOLERANCE_HZ = 1.0
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
@ -198,7 +200,7 @@ def connect_radios(graph, replay, radio_chan_pairs, freqs, gains, antennas, rate
|
||||
if rate is None:
|
||||
rate = radio_chan_pairs[0][0].get_rate()
|
||||
print(f"Requested rate: {rate/1e6:.2f} Msps")
|
||||
actual_rate = None
|
||||
ddc_chan_pairs = []
|
||||
for replay_port_idx, rcp in enumerate(radio_chan_pairs):
|
||||
radio, chan = rcp
|
||||
print(
|
||||
@ -227,16 +229,35 @@ def connect_radios(graph, replay, radio_chan_pairs, freqs, gains, antennas, rate
|
||||
)
|
||||
if ddc_block is not None:
|
||||
print(f"Found DDC block on channel {chan}.")
|
||||
this_rate = uhd.rfnoc.DdcBlockControl(graph.get_block(ddc_block[0])).set_output_rate(
|
||||
rate, rcp[1]
|
||||
)
|
||||
ddc = uhd.rfnoc.DdcBlockControl(graph.get_block(ddc_block[0]))
|
||||
# The DDC port is the destination port of the edge, which is not
|
||||
# necessarily the same as the radio channel index.
|
||||
ddc_chan_pairs.append((ddc, ddc_block[1]))
|
||||
ddc.set_output_rate(rate, ddc_block[1])
|
||||
else:
|
||||
this_rate = rcp[0].set_rate(rate)
|
||||
if actual_rate is None:
|
||||
actual_rate = this_rate
|
||||
continue
|
||||
if actual_rate != this_rate:
|
||||
raise RuntimeError("Unexpected rate mismatch.")
|
||||
ddc_chan_pairs.append((None, None))
|
||||
radio.set_rate(rate)
|
||||
return rate, ddc_chan_pairs
|
||||
|
||||
|
||||
def get_actual_rate(radio_chan_pairs, ddc_chan_pairs, requested_rate):
|
||||
"""Read back the rate that was actually applied, after the graph was committed.
|
||||
|
||||
The DDC only supports integer decimations, so the requested rate may have
|
||||
been coerced. Reading it back before graph commit can return a stale value.
|
||||
"""
|
||||
rates = [
|
||||
ddc.get_output_rate(ddc_chan) if ddc is not None else radio.get_rate()
|
||||
for (radio, _), (ddc, ddc_chan) in zip(radio_chan_pairs, ddc_chan_pairs)
|
||||
]
|
||||
if max(rates) - min(rates) > RATE_TOLERANCE_HZ:
|
||||
raise RuntimeError(f"Unexpected rate mismatch between channels: {rates}")
|
||||
actual_rate = rates[0]
|
||||
if abs(actual_rate - requested_rate) > RATE_TOLERANCE_HZ:
|
||||
print(
|
||||
f"WARNING: Requested rate {requested_rate/1e6:.3f} Msps is not achievable "
|
||||
f"at the current master clock rate; coerced to {actual_rate/1e6:.3f} Msps."
|
||||
)
|
||||
return actual_rate
|
||||
|
||||
|
||||
@ -278,7 +299,9 @@ def run_capture(graph, replay, radio_chan_pairs, mem_stride, num_samps, rate, ca
|
||||
## Send stream command to all radios
|
||||
# This 'rate ratio' would be better handled by RFNoC. If the replay block
|
||||
# were to submit the stream command to the radio, this would not be necessary.
|
||||
rate_ratio = int(radio_chan_pairs[0][0].get_rate() / rate)
|
||||
# 'rate' is the coerced rate read back after commit, so the ratio is an
|
||||
# integer (the DDC decimation) up to floating point error.
|
||||
rate_ratio = round(radio_chan_pairs[0][0].get_rate() / rate)
|
||||
stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
|
||||
stream_cmd.num_samps = num_samps * rate_ratio
|
||||
stream_cmd.stream_now = False
|
||||
@ -369,10 +392,9 @@ def main():
|
||||
graph = uhd.rfnoc.RfnocGraph(args.args)
|
||||
replay = uhd.rfnoc.ReplayBlockControl(graph.get_block(args.block))
|
||||
radio_chan_pairs = enumerate_radios(graph, args.radio_channels)
|
||||
rate = connect_radios(
|
||||
rate, ddc_chan_pairs = connect_radios(
|
||||
graph, replay, radio_chan_pairs, args.freq, args.gain, args.antenna, args.rate
|
||||
)
|
||||
print(f"Using rate: {rate/1e6:.3f} Msps")
|
||||
# Set up streamer
|
||||
stream_args = uhd.usrp.StreamArgs(args.cpu_format, "sc16")
|
||||
stream_args.args["throttle"] = str(args.throttle)
|
||||
@ -384,6 +406,10 @@ def main():
|
||||
graph.connect(replay.get_unique_id(), chan, rx_streamer, chan)
|
||||
graph.commit()
|
||||
|
||||
# Only after commit are the propagated sample rates guaranteed to be final.
|
||||
rate = get_actual_rate(radio_chan_pairs, ddc_chan_pairs, rate)
|
||||
print(f"Using rate: {rate/1e6:.3f} Msps")
|
||||
|
||||
num_bytes = args.duration * rate * BYTES_PER_SAMP if args.duration is not None else None
|
||||
mem_stride, num_bytes = _sanitize_args(
|
||||
replay,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user