1 //
2 // Copyright 2020 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6 
7 #include <uhd/convert.hpp>
8 #include <uhd/exception.hpp>
9 #include <uhd/rfnoc/defaults.hpp>
10 #include <uhd/rfnoc/multichan_register_iface.hpp>
11 #include <uhd/rfnoc/property.hpp>
12 #include <uhd/rfnoc/registry.hpp>
13 #include <uhd/rfnoc/replay_block_control.hpp>
14 #include <uhd/types/stream_cmd.hpp>
15 #include <uhd/utils/math.hpp>
16 #include <uhdlib/utils/compat_check.hpp>
17 #include <uhdlib/utils/narrow.hpp>
18 #include <string>
19 
20 using namespace uhd::rfnoc;
21 
22 // Block compatability version
23 const uint16_t replay_block_control::MINOR_COMPAT = 0;
24 const uint16_t replay_block_control::MAJOR_COMPAT = 1;
25 
26 // NoC block address space
27 const uint32_t replay_block_control::REPLAY_ADDR_W = 8;
28 const uint32_t replay_block_control::REPLAY_BLOCK_OFFSET =
29     1 << replay_block_control::REPLAY_ADDR_W; // 256 bytes
30 
31 // Register offsets
32 const uint32_t replay_block_control::REG_COMPAT_ADDR                = 0x00;
33 const uint32_t replay_block_control::REG_MEM_SIZE_ADDR              = 0x04;
34 const uint32_t replay_block_control::REG_REC_RESTART_ADDR           = 0x08;
35 const uint32_t replay_block_control::REG_REC_BASE_ADDR_LO_ADDR      = 0x10;
36 const uint32_t replay_block_control::REG_REC_BASE_ADDR_HI_ADDR      = 0x14;
37 const uint32_t replay_block_control::REG_REC_BUFFER_SIZE_LO_ADDR    = 0x18;
38 const uint32_t replay_block_control::REG_REC_BUFFER_SIZE_HI_ADDR    = 0x1C;
39 const uint32_t replay_block_control::REG_REC_FULLNESS_LO_ADDR       = 0x20;
40 const uint32_t replay_block_control::REG_REC_FULLNESS_HI_ADDR       = 0x24;
41 const uint32_t replay_block_control::REG_PLAY_BASE_ADDR_LO_ADDR     = 0x28;
42 const uint32_t replay_block_control::REG_PLAY_BASE_ADDR_HI_ADDR     = 0x2C;
43 const uint32_t replay_block_control::REG_PLAY_BUFFER_SIZE_LO_ADDR   = 0x30;
44 const uint32_t replay_block_control::REG_PLAY_BUFFER_SIZE_HI_ADDR   = 0x34;
45 const uint32_t replay_block_control::REG_PLAY_CMD_NUM_WORDS_LO_ADDR = 0x38;
46 const uint32_t replay_block_control::REG_PLAY_CMD_NUM_WORDS_HI_ADDR = 0x3C;
47 const uint32_t replay_block_control::REG_PLAY_CMD_TIME_LO_ADDR      = 0x40;
48 const uint32_t replay_block_control::REG_PLAY_CMD_TIME_HI_ADDR      = 0x44;
49 const uint32_t replay_block_control::REG_PLAY_CMD_ADDR              = 0x48;
50 const uint32_t replay_block_control::REG_PLAY_WORDS_PER_PKT_ADDR    = 0x4C;
51 const uint32_t replay_block_control::REG_PLAY_ITEM_SIZE_ADDR        = 0x50;
52 
53 // Stream commands
54 const uint32_t replay_block_control::PLAY_CMD_STOP       = 0;
55 const uint32_t replay_block_control::PLAY_CMD_FINITE     = 1;
56 const uint32_t replay_block_control::PLAY_CMD_CONTINUOUS = 2;
57 
58 // Mask bits
59 constexpr uint32_t PLAY_COMMAND_TIMED_BIT  = 31;
60 constexpr uint32_t PLAY_COMMAND_TIMED_MASK = uint32_t(1) << PLAY_COMMAND_TIMED_BIT;
61 constexpr uint32_t PLAY_COMMAND_MASK       = 3;
62 
63 // User property names
64 const char* const PROP_KEY_RECORD_OFFSET = "record_offset";
65 const char* const PROP_KEY_RECORD_SIZE   = "record_size";
66 const char* const PROP_KEY_PLAY_OFFSET   = "play_offset";
67 const char* const PROP_KEY_PLAY_SIZE     = "play_size";
68 const char* const PROP_KEY_PKT_SIZE      = "packet_size";
69 
70 class replay_block_control_impl : public replay_block_control
71 {
72 public:
RFNOC_BLOCK_CONSTRUCTOR(replay_block_control)73     RFNOC_BLOCK_CONSTRUCTOR(replay_block_control),
74         _replay_reg_iface(*this, 0, REPLAY_BLOCK_OFFSET),
75         _num_input_ports(get_num_input_ports()),
76         _num_output_ports(get_num_output_ports()),
77         _fpga_compat(_replay_reg_iface.peek32(REG_COMPAT_ADDR)),
78         _word_size(
79             uint16_t((_replay_reg_iface.peek32(REG_MEM_SIZE_ADDR) >> 16) & 0xFFFF) / 8),
80         _mem_size(uint64_t(1ULL << (_replay_reg_iface.peek32(REG_MEM_SIZE_ADDR) & 0xFFFF)))
81     {
82         UHD_ASSERT_THROW(get_num_input_ports() == get_num_output_ports());
83         uhd::assert_fpga_compat(MAJOR_COMPAT,
84             MINOR_COMPAT,
85             _fpga_compat,
86             get_unique_id(),
87             get_unique_id(),
88             false /* Let it slide if minors mismatch */
89         );
90 
91         // Initialize record properties
92         _record_type.reserve(_num_input_ports);
93         _record_offset.reserve(_num_input_ports);
94         _record_size.reserve(_num_input_ports);
95         for (size_t port = 0; port < _num_input_ports; port++) {
96             _register_input_props(port);
97             _replay_reg_iface.poke64(
98                 REG_REC_BASE_ADDR_LO_ADDR, _record_offset.at(port).get(), port);
99             _replay_reg_iface.poke64(
100                 REG_REC_BUFFER_SIZE_LO_ADDR, _record_size.at(port).get(), port);
101         }
102 
103         // Initialize playback properties
104         _play_type.reserve(_num_output_ports);
105         _play_offset.reserve(_num_output_ports);
106         _play_size.reserve(_num_output_ports);
107         _packet_size.reserve(_num_output_ports);
108         for (size_t port = 0; port < _num_output_ports; port++) {
109             _register_output_props(port);
110             _replay_reg_iface.poke32(REG_PLAY_ITEM_SIZE_ADDR,
111                 uhd::convert::get_bytes_per_item(_play_type.at(port).get()),
112                 port);
113             _replay_reg_iface.poke64(
114                 REG_PLAY_BASE_ADDR_LO_ADDR, _play_offset.at(port).get(), port);
115             _replay_reg_iface.poke64(
116                 REG_PLAY_BUFFER_SIZE_LO_ADDR, _play_size.at(port).get(), port);
117             _replay_reg_iface.poke32(REG_PLAY_WORDS_PER_PKT_ADDR,
118                 (_packet_size.at(port).get() - CHDR_MAX_LEN_HDR) / _word_size,
119                 port);
120         }
121     }
122 
123     /**************************************************************************
124      * Replay Control API
125      **************************************************************************/
record(const uint64_t offset,const uint64_t size,const size_t port)126     void record(const uint64_t offset, const uint64_t size, const size_t port)
127     {
128         set_property<uint64_t>(
129             PROP_KEY_RECORD_OFFSET, offset, {res_source_info::USER, port});
130         set_property<uint64_t>(PROP_KEY_RECORD_SIZE, size, {res_source_info::USER, port});
131 
132         // The pointers to the new record buffer space must be set
133         record_restart(port);
134     }
135 
record_restart(const size_t port)136     void record_restart(const size_t port)
137     {
138         // Ensure that the buffer is properly configured before recording
139         _validate_record_buffer(port);
140         // Any value written to this register causes a record restart
141         _replay_reg_iface.poke32(REG_REC_RESTART_ADDR, 0, port);
142     }
143 
play(const uint64_t offset,const uint64_t size,const size_t port,const uhd::time_spec_t time_spec,const bool repeat)144     void play(const uint64_t offset,
145         const uint64_t size,
146         const size_t port,
147         const uhd::time_spec_t time_spec,
148         const bool repeat)
149     {
150         config_play(offset, size, port);
151         uhd::stream_cmd_t play_cmd =
152             repeat ? uhd::stream_cmd_t(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS)
153                    : uhd::stream_cmd_t(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
154         play_cmd.num_samps  = size / get_play_item_size(port);
155         play_cmd.time_spec  = time_spec;
156         play_cmd.stream_now = (time_spec == 0.0);
157         issue_stream_cmd(play_cmd, port);
158     }
159 
stop(const size_t port)160     void stop(const size_t port)
161     {
162         uhd::stream_cmd_t stop_cmd =
163             uhd::stream_cmd_t(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
164         issue_stream_cmd(stop_cmd, port);
165     }
166 
get_mem_size() const167     uint64_t get_mem_size() const
168     {
169         return _mem_size;
170     }
171 
get_word_size() const172     uint64_t get_word_size() const
173     {
174         return _word_size;
175     }
176 
177     /**************************************************************************
178      * Record Buffer State API
179      **************************************************************************/
get_record_offset(const size_t port) const180     uint64_t get_record_offset(const size_t port) const
181     {
182         return _record_offset.at(port).get();
183     }
184 
get_record_size(const size_t port) const185     uint64_t get_record_size(const size_t port) const
186     {
187         return _record_size.at(port).get();
188     }
189 
get_record_fullness(const size_t port)190     uint64_t get_record_fullness(const size_t port)
191     {
192         return _replay_reg_iface.peek64(REG_REC_FULLNESS_LO_ADDR, port);
193     }
194 
get_record_type(const size_t port) const195     io_type_t get_record_type(const size_t port) const
196     {
197         return _record_type.at(port).get();
198     }
199 
get_record_item_size(const size_t port) const200     virtual size_t get_record_item_size(const size_t port) const
201     {
202         return uhd::convert::get_bytes_per_item(get_record_type(port));
203     }
204 
205     /**************************************************************************
206      * Playback State API
207      **************************************************************************/
get_play_offset(const size_t port) const208     uint64_t get_play_offset(const size_t port) const
209     {
210         return _play_offset.at(port).get();
211     }
212 
get_play_size(const size_t port) const213     uint64_t get_play_size(const size_t port) const
214     {
215         return _play_size.at(port).get();
216     }
217 
get_max_items_per_packet(const size_t port) const218     uint32_t get_max_items_per_packet(const size_t port) const
219     {
220         return (_packet_size.at(port).get() - CHDR_MAX_LEN_HDR)
221                / get_play_item_size(port);
222     }
223 
get_max_packet_size(const size_t port) const224     uint32_t get_max_packet_size(const size_t port) const
225     {
226         return _packet_size.at(port).get();
227     }
228 
get_play_type(const size_t port) const229     io_type_t get_play_type(const size_t port) const
230     {
231         return _play_type.at(port).get();
232     }
233 
get_play_item_size(const size_t port) const234     size_t get_play_item_size(const size_t port) const
235     {
236         return uhd::convert::get_bytes_per_item(get_play_type(port));
237     }
238 
239     /**************************************************************************
240      * Advanced Record Control API calls
241      *************************************************************************/
set_record_type(const io_type_t type,const size_t port)242     void set_record_type(const io_type_t type, const size_t port)
243     {
244         set_property<std::string>(
245             PROP_KEY_TYPE, type, {res_source_info::INPUT_EDGE, port});
246     }
247 
248     /**************************************************************************
249      * Advanced Playback Control API
250      **************************************************************************/
config_play(const uint64_t offset,const uint64_t size,const size_t port)251     void config_play(const uint64_t offset, const uint64_t size, const size_t port)
252     {
253         set_property<uint64_t>(
254             PROP_KEY_PLAY_OFFSET, offset, {res_source_info::USER, port});
255         set_property<uint64_t>(PROP_KEY_PLAY_SIZE, size, {res_source_info::USER, port});
256         _validate_play_buffer(port);
257     }
258 
set_play_type(const io_type_t type,const size_t port)259     void set_play_type(const io_type_t type, const size_t port)
260     {
261         set_property<std::string>(
262             PROP_KEY_TYPE, type, {res_source_info::OUTPUT_EDGE, port});
263     }
264 
set_max_items_per_packet(const uint32_t ipp,const size_t port)265     void set_max_items_per_packet(const uint32_t ipp, const size_t port)
266     {
267         set_max_packet_size(CHDR_MAX_LEN_HDR + ipp * get_play_item_size(port), port);
268     }
269 
set_max_packet_size(const uint32_t size,const size_t port)270     void set_max_packet_size(const uint32_t size, const size_t port)
271     {
272         set_property<uint32_t>(PROP_KEY_PKT_SIZE, size, {res_source_info::USER, port});
273     }
274 
issue_stream_cmd(const uhd::stream_cmd_t & stream_cmd,const size_t port)275     void issue_stream_cmd(const uhd::stream_cmd_t& stream_cmd, const size_t port)
276     {
277         // Ensure that the buffer is properly configured before issuing a stream command
278         _validate_play_buffer(port);
279         RFNOC_LOG_TRACE("replay_block_control_impl::issue_stream_cmd(port="
280                         << port << ", mode=" << char(stream_cmd.stream_mode) << ")");
281 
282         // Setup the mode to instruction flags
283         const uint8_t play_cmd = [stream_cmd]() -> uint8_t {
284             switch (stream_cmd.stream_mode) {
285                 case uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS:
286                     return PLAY_CMD_STOP; // Stop playing back data
287                 case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE:
288                 case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE:
289                     return PLAY_CMD_FINITE; // Play NUM_SAMPS then stop
290                 case uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS:
291                     return PLAY_CMD_CONTINUOUS; // Playback continuously over the play
292                                                 // buffer until stopped
293                 default:
294                     throw uhd::value_error("Requested invalid stream command.");
295             }
296         }();
297 
298         // Calculate the number of words to transfer in NUM_SAMPS mode
299         if (play_cmd == PLAY_CMD_FINITE) {
300             uint64_t num_words =
301                 stream_cmd.num_samps * get_play_item_size(port) / get_word_size();
302             _replay_reg_iface.poke64(REG_PLAY_CMD_NUM_WORDS_LO_ADDR, num_words, port);
303         }
304 
305         // Set the time for the command
306         const uint32_t timed_flag = (stream_cmd.stream_now) ? 0 : PLAY_COMMAND_TIMED_MASK;
307         if (!stream_cmd.stream_now) {
308             const double tick_rate = get_tick_rate();
309             UHD_LOG_DEBUG("REPLAY",
310                 "Using tick rate " << (tick_rate / 1e6) << " MHz to set stream command.");
311             const uint64_t ticks = stream_cmd.time_spec.to_ticks(tick_rate);
312             _replay_reg_iface.poke64(REG_PLAY_CMD_TIME_LO_ADDR, ticks, port);
313         }
314 
315         // Issue the stream command
316         uint32_t command_word = (play_cmd & PLAY_COMMAND_MASK) | timed_flag;
317         _replay_reg_iface.poke32(REG_PLAY_CMD_ADDR, command_word, port);
318     }
319 
320 protected:
321     // Block-specific register interface
322     multichan_register_iface _replay_reg_iface;
323 
324 private:
_register_input_props(const size_t port)325     void _register_input_props(const size_t port)
326     {
327         // Get default property values
328         const io_type_t default_type = IO_TYPE_SC16;
329         const uint64_t record_offset = 0;
330         const uint64_t record_size   = _mem_size;
331 
332         // Initialize properties
333         _record_type.emplace_back(property_t<std::string>(
334             PROP_KEY_TYPE, default_type, {res_source_info::INPUT_EDGE, port}));
335         _record_offset.push_back(property_t<uint64_t>(
336             PROP_KEY_RECORD_OFFSET, record_offset, {res_source_info::USER, port}));
337         _record_size.push_back(property_t<uint64_t>(
338             PROP_KEY_RECORD_SIZE, record_size, {res_source_info::USER, port}));
339         UHD_ASSERT_THROW(_record_type.size() == port + 1);
340         UHD_ASSERT_THROW(_record_offset.size() == port + 1);
341         UHD_ASSERT_THROW(_record_size.size() == port + 1);
342 
343         // Register user properties
344         register_property(&_record_type.at(port));
345         register_property(&_record_offset.at(port));
346         register_property(&_record_size.at(port));
347 
348         // Add property resolvers
349         add_property_resolver({&_record_offset.at(port)}, {}, [this, port]() {
350             _set_record_offset(_record_offset.at(port).get(), port);
351         });
352         add_property_resolver({&_record_size.at(port)},
353             {&_record_size.at(port)},
354             [this, port]() { _set_record_size(_record_size.at(port).get(), port); });
355     }
356 
_register_output_props(const size_t port)357     void _register_output_props(const size_t port)
358     {
359         // Get default property values
360         const io_type_t default_type = IO_TYPE_SC16;
361         const uint64_t play_offset   = 0;
362         const uint64_t play_size     = _mem_size;
363         const uint32_t packet_size   = get_mtu({res_source_info::OUTPUT_EDGE, port});
364 
365         // Initialize properties
366         _play_type.emplace_back(property_t<std::string>(
367             PROP_KEY_TYPE, default_type, {res_source_info::OUTPUT_EDGE, port}));
368         _play_offset.push_back(property_t<uint64_t>(
369             PROP_KEY_PLAY_OFFSET, play_offset, {res_source_info::USER, port}));
370         _play_size.push_back(property_t<uint64_t>(
371             PROP_KEY_PLAY_SIZE, play_size, {res_source_info::USER, port}));
372         _packet_size.push_back(property_t<uint32_t>(
373             PROP_KEY_PKT_SIZE, packet_size, {res_source_info::USER, port}));
374         UHD_ASSERT_THROW(_play_type.size() == port + 1);
375         UHD_ASSERT_THROW(_play_offset.size() == port + 1);
376         UHD_ASSERT_THROW(_play_size.size() == port + 1);
377         UHD_ASSERT_THROW(_packet_size.size() == port + 1);
378 
379         // Register user properties
380         register_property(&_play_type.at(port));
381         register_property(&_play_offset.at(port));
382         register_property(&_play_size.at(port));
383         register_property(&_packet_size.at(port));
384 
385         // Add property resolvers
386         add_property_resolver({&_play_type.at(port)}, {}, [this, port]() {
387             _set_play_type(_play_type.at(port).get(), port);
388         });
389         add_property_resolver({&_play_offset.at(port)}, {}, [this, port]() {
390             _set_play_offset(_play_offset.at(port).get(), port);
391         });
392         add_property_resolver({&_play_size.at(port)},
393             {&_play_size.at(port)},
394             [this, port]() { _set_play_size(_play_size.at(port).get(), port); });
395         add_property_resolver({&_packet_size.at(port),
396                                   get_mtu_prop_ref({res_source_info::OUTPUT_EDGE, port})},
397             {},
398             [this, port]() { _set_packet_size(_packet_size.at(port).get(), port); });
399     }
400 
_set_play_type(const io_type_t type,const size_t port)401     void _set_play_type(const io_type_t type, const size_t port)
402     {
403         uint32_t play_item_size = uhd::convert::get_bytes_per_item(type);
404         _replay_reg_iface.poke32(REG_PLAY_ITEM_SIZE_ADDR, play_item_size, port);
405     }
406 
_set_record_offset(const uint64_t record_offset,const size_t port)407     void _set_record_offset(const uint64_t record_offset, const size_t port)
408     {
409         if ((record_offset % _word_size) != 0) {
410             throw uhd::value_error("Record offset must be a multiple of word size.");
411         }
412         if (record_offset > _mem_size) {
413             throw uhd::value_error("Record offset is out of bounds.");
414         }
415         _replay_reg_iface.poke64(REG_REC_BASE_ADDR_LO_ADDR, record_offset, port);
416     }
417 
_set_record_size(const uint64_t record_size,const size_t port)418     void _set_record_size(const uint64_t record_size, const size_t port)
419     {
420         if ((record_size % _word_size) != 0) {
421             _record_size.at(port) = record_size - (record_size % _word_size);
422             throw uhd::value_error("Record buffer size must be a multiple of word size.");
423         }
424         _replay_reg_iface.poke64(REG_REC_BUFFER_SIZE_LO_ADDR, record_size, port);
425     }
426 
_set_play_offset(const uint64_t play_offset,const size_t port)427     void _set_play_offset(const uint64_t play_offset, const size_t port)
428     {
429         if ((play_offset % _word_size) != 0) {
430             throw uhd::value_error("Play offset must be a multiple of word size.");
431         }
432         if (play_offset > _mem_size) {
433             throw uhd::value_error("Play offset is out of bounds.");
434         }
435         _replay_reg_iface.poke64(REG_PLAY_BASE_ADDR_LO_ADDR, play_offset, port);
436     }
437 
_set_play_size(const uint64_t play_size,const size_t port)438     void _set_play_size(const uint64_t play_size, const size_t port)
439     {
440         if ((play_size % _word_size) != 0) {
441             _play_size.at(port) = play_size - (play_size % _word_size);
442             throw uhd::value_error("Play buffer size must be a multiple of word size.");
443         }
444         if ((play_size % get_play_item_size(port)) != 0) {
445             _play_size.at(port) = play_size - (play_size % get_play_item_size(port));
446             throw uhd::value_error("Play buffer size must be a multiple of item size.");
447         }
448         _replay_reg_iface.poke64(REG_PLAY_BUFFER_SIZE_LO_ADDR, play_size, port);
449     }
450 
_set_packet_size(const uint32_t packet_size,const size_t port)451     void _set_packet_size(const uint32_t packet_size, const size_t port)
452     {
453         // MTU is max payload size, header with timestamp is already accounted for
454         const size_t mtu               = get_mtu({res_source_info::OUTPUT_EDGE, port});
455         const uint32_t item_size       = get_play_item_size(port);
456         const uint32_t mtu_payload     = mtu - CHDR_MAX_LEN_HDR;
457         const uint32_t mtu_items       = mtu_payload / item_size;
458         const uint32_t ipc             = _word_size / item_size; // items per cycle
459         const uint32_t max_ipp_per_mtu = mtu_items - (mtu_items % ipc);
460         const uint32_t payload_size    = packet_size - CHDR_MAX_LEN_HDR;
461         uint32_t ipp                   = payload_size / item_size;
462         if (ipp > max_ipp_per_mtu) {
463             ipp = max_ipp_per_mtu;
464         }
465         if ((ipp % ipc) != 0) {
466             ipp = ipp - (ipp % ipc);
467             RFNOC_LOG_WARNING(
468                 "ipp must be a multiple of the block bus width! Coercing to " << ipp);
469         }
470         if (ipp <= 0) {
471             ipp = DEFAULT_SPP;
472             RFNOC_LOG_WARNING("ipp must be greater than zero! Coercing to " << ipp);
473         }
474         // Packet size must be a multiple of word size
475         if ((packet_size % _word_size) != 0) {
476             throw uhd::value_error("Packet size must be a multiple of word size.");
477         }
478         const uint16_t words_per_packet =
479             uhd::narrow_cast<uint16_t>(ipp * item_size / _word_size);
480         _replay_reg_iface.poke32(
481             REG_PLAY_WORDS_PER_PKT_ADDR, uint32_t(words_per_packet), port);
482     }
483 
_validate_record_buffer(const size_t port)484     void _validate_record_buffer(const size_t port)
485     {
486         // The entire record buffer must be within the bounds of memory
487         if ((get_record_offset(port) + get_record_size(port)) > get_mem_size()) {
488             throw uhd::value_error("Record buffer goes out of bounds.");
489         }
490     }
491 
_validate_play_buffer(const size_t port)492     void _validate_play_buffer(const size_t port)
493     {
494         // Streaming requires that the buffer size is a multiple of item size
495         if ((get_play_size(port) % get_play_item_size(port)) != 0) {
496             throw uhd::value_error("Play size must be must be a multiple of item size.");
497         }
498         // The entire play buffer must be within the bounds of memory
499         if ((get_play_offset(port) + get_play_size(port)) > get_mem_size()) {
500             throw uhd::value_error("Play buffer goes out of bounds.");
501         }
502     }
503 
504     /**************************************************************************
505      * Attributes
506      *************************************************************************/
507     const size_t _num_input_ports;
508     const size_t _num_output_ports;
509 
510     // Block compat number
511     const uint32_t _fpga_compat;
512 
513     // These size params are configurable in the FPGA
514     const uint16_t _word_size;
515     const uint64_t _mem_size;
516 
517     std::vector<property_t<std::string>> _record_type;
518     std::vector<property_t<uint64_t>> _record_offset;
519     std::vector<property_t<uint64_t>> _record_size;
520     std::vector<property_t<std::string>> _play_type;
521     std::vector<property_t<uint64_t>> _play_offset;
522     std::vector<property_t<uint64_t>> _play_size;
523     std::vector<property_t<uint32_t>> _packet_size;
524 };
525 
526 UHD_RFNOC_BLOCK_REGISTER_DIRECT(
527     replay_block_control, REPLAY_BLOCK, "Replay", CLOCK_KEY_GRAPH, "bus_clk")
528