1//
2// Copyright 2019 Ettus Research, A National Instruments Company
3//
4// SPDX-License-Identifier: LGPL-3.0-or-later
5//
6// Module: PkgRfnocBlockCtrlBfm
7//
8// Description: This package includes a high-level bus functional model (BFM)
9// for a block controller. This models a software block controller and allows
10// communication with a single RFNoC block. It includes the following:
11//
12//   - A CtrlIfaceBfm for an AXIS-Ctrl port connection of a block
13//   - One or more ChdrIfaceBfm for the AXIS-CHDR port connections
14//   - A connection for the backend interface of a block
15//
16
17
18//-----------------------------------------------------------------------------
19// SV Interface for the RFNoC Backend Iface
20//-----------------------------------------------------------------------------
21
22typedef struct packed {
23  bit [476:0] reserved0;
24  bit         soft_chdr_rst;
25  bit         soft_ctrl_rst;
26  bit         flush_en;
27  bit [31:0]  flush_timeout;
28} backend_config_v1_t;
29
30typedef struct packed {
31  bit [439:0] reserved0;
32  bit [5:0]   mtu;
33  bit         flush_done;
34  bit         flush_active;
35  bit [31:0]  noc_id;
36  bit [7:0]   ctrl_max_async_msgs;
37  bit [5:0]   ctrl_fifosize;
38  bit [5:0]   num_data_o;
39  bit [5:0]   num_data_i;
40  bit [5:0]   proto_ver;
41} backend_status_v1_t;
42
43typedef union packed {
44  backend_config_v1_t v1;
45} backend_config_t;
46
47typedef union packed {
48  backend_status_v1_t v1;
49} backend_status_t;
50
51
52interface RfnocBackendIf(
53  input logic chdr_clk,
54  input logic ctrl_clk
55);
56  backend_config_t cfg;
57  backend_status_t sts;
58
59  modport master (
60    input  chdr_clk,
61    input  ctrl_clk,
62    output cfg,
63    input  sts
64  );
65  modport slave (
66    input  chdr_clk,
67    input  ctrl_clk,
68    input  cfg,
69    output sts
70  );
71endinterface : RfnocBackendIf
72
73
74//-----------------------------------------------------------------------------
75// RFNoC Block Controller Bus Functional Model
76//-----------------------------------------------------------------------------
77
78package PkgRfnocBlockCtrlBfm;
79
80  import PkgChdrUtils::*;
81  import PkgChdrBfm::*;
82  import PkgCtrlIfaceBfm::*;
83  import PkgChdrIfaceBfm::*;
84
85  export PkgChdrBfm::ChdrPacket;
86  export PkgCtrlIfaceBfm::CtrlIfaceBfm;
87  export PkgChdrIfaceBfm::ChdrIfaceBfm;
88  export PkgChdrIfaceBfm::packet_info_t;
89  export PkgChdrIfaceBfm::packet_info_equal;
90
91
92  //---------------------------------------------------------------------------
93  // Block Controller BFM
94  //---------------------------------------------------------------------------
95  //
96  // This class models a block controller in software
97  //
98  //---------------------------------------------------------------------------
99
100  class RfnocBlockCtrlBfm #(CHDR_W = 64, ITEM_W = 32);
101
102    local virtual RfnocBackendIf.master  backend;
103    local CtrlIfaceBfm                   ctrl;
104    local ChdrIfaceBfm #(CHDR_W, ITEM_W) m_data[$];
105    local ChdrIfaceBfm #(CHDR_W, ITEM_W) s_data[$];
106    local bit                            running;
107
108    localparam CMD_PROP_CYC = 5;
109
110    typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t chdr_word_t;
111    typedef ChdrData #(CHDR_W, ITEM_W)::item_t      item_t;
112
113    // Class constructor to create a new BFM instance.
114    //
115    //   backend:   Interface for the backend signals of a block
116    //   m_ctrl:    Interface for the CTRL master connection (EP's AXIS-Ctrl output)
117    //   s_ctrl:    Interface for the CTRL slave connection (EP's AXIS-Ctrl input)
118    //   dst_port:  Destination port to use in generated control packets
119    //   src_port:  Source port to use in generated control packets
120    //
121    function new(
122      virtual RfnocBackendIf.master    backend,
123      virtual AxiStreamIf #(32).master m_ctrl,
124      virtual AxiStreamIf #(32).slave  s_ctrl,
125      input   ctrl_port_t              dst_port = 10'd2,
126      input   ctrl_port_t              src_port = 10'd1
127    );
128      this.backend = backend;
129      this.ctrl = new(m_ctrl, s_ctrl, dst_port, src_port);
130      this.running = 0;
131    endfunction : new
132
133    // Add a master data port. This should connect to a DUT slave input.
134    //
135    //   m_chdr:             Virtual master interface to connect new port to.
136    //   max_payload_length: Maximum payload length (in bytes) to create when
137    //                       building packets from data.
138    //   ticks_per_word:     Number of timebase clock ticks to increment per
139    //                       CHDR word.
140    //
141    function int add_master_data_port(
142      virtual AxiStreamIf #(CHDR_W).master m_chdr,
143      int max_payload_length = 2**$bits(chdr_length_t),
144      int ticks_per_word     = CHDR_W/ITEM_W
145    );
146      ChdrIfaceBfm #(CHDR_W, ITEM_W) bfm =
147        new(m_chdr, null, max_payload_length, ticks_per_word);
148      m_data.push_back(bfm);
149      return m_data.size() - 1;
150    endfunction : add_master_data_port
151
152    // Add a slave data port. This should connect to a DUT master output.
153    //
154    //   s_chdr: Virtual slave interface to connect new port to
155    //
156    function int add_slave_data_port(
157      virtual AxiStreamIf #(CHDR_W).slave s_chdr
158    );
159      ChdrIfaceBfm #(CHDR_W, ITEM_W) bfm = new(null, s_chdr);
160      s_data.push_back(bfm);
161      return s_data.size() - 1;
162    endfunction : add_slave_data_port
163
164    // Add a master data port. This is equivalent to add_master_data_port()
165    // except it accepts a port number and it waits until the preceding ports
166    // are connected to ensure that ports are connected in the correct order.
167    //
168    //   port_num:           The port number to which m_chdr should be connected
169    //   m_chdr:             Master CHDR interface to connect to the port
170    //   max_payload_length: Maximum payload length (in bytes) to create when
171    //                       building packets from data.
172    //   ticks_per_word:     Number of timebase clock ticks to increment per
173    //                       CHDR word.
174    //
175    task connect_master_data_port(
176      int port_num,
177      virtual AxiStreamIf #(CHDR_W).master m_chdr,
178      int max_payload_length = 2**$bits(chdr_length_t),
179      int ticks_per_word     = CHDR_W/ITEM_W
180    );
181      ChdrIfaceBfm #(CHDR_W, ITEM_W) bfm =
182        new(m_chdr, null, max_payload_length, ticks_per_word);
183      wait (m_data.size() == port_num);
184      m_data.push_back(bfm);
185    endtask : connect_master_data_port
186
187    // Add a slave data port. This is equivalent to add_slave_data_port()
188    // except it accepts a port number and it waits until the preceding ports
189    // are connected to ensure that ports are connected in the correct order.
190    //
191    //   port_num:  The port number to which m_chdr should be connected
192    //   s_chdr:    Master CHDR interface to connect to the port
193    //
194    task connect_slave_data_port(
195      int port_num,
196      virtual AxiStreamIf #(CHDR_W).slave s_chdr
197    );
198      ChdrIfaceBfm #(CHDR_W, ITEM_W) bfm = new(null, s_chdr);
199      wait (s_data.size() == port_num);
200      s_data.push_back(bfm);
201    endtask : connect_slave_data_port
202
203    // Start the data and control BFM's processes running.
204    task run();
205      assert (backend.sts.v1.proto_ver == 1) else begin
206        $fatal(1, "The connected block has an incompatible backend interface");
207      end
208      if (!running) begin
209        ctrl.run();
210        foreach (m_data[i])
211          m_data[i].run();
212        foreach (s_data[i])
213          s_data[i].run();
214        running = 1;
215      end
216    endtask : run
217
218    // Return a handle to the control BFM
219    function CtrlIfaceBfm get_ctrl_bfm();
220      return ctrl;
221    endfunction : get_ctrl_bfm
222
223    // Return a handle to the indicated master port BFM
224    function ChdrIfaceBfm #(CHDR_W, ITEM_W) get_master_data_bfm(int port);
225      assert (port >= 0 && port < m_data.size()) else begin
226        $fatal(1, "Invalid master port number");
227      end
228      return m_data[port];
229    endfunction : get_master_data_bfm
230
231    // Return a handle to the indicated slave port BFM
232    function ChdrIfaceBfm #(CHDR_W, ITEM_W) get_slave_data_bfm(int port);
233      assert (port >= 0 && port < s_data.size()) else begin
234        $fatal(1, "Invalid slave port number");
235      end
236      return s_data[port];
237    endfunction : get_slave_data_bfm
238
239    // Set the maximum payload size for packets. This value is used to split
240    // large send requests across multiple packets.
241    //
242    //   port:        Master port whose maximum length you want to set
243    //   max_length:  Maximum payload length in bytes for each packet
244    //
245    function void set_max_payload_length(int port, int max_length);
246      assert (port >= 0 && port < m_data.size()) else begin
247        $fatal(1, "Invalid master port number");
248      end
249      m_data[port].set_max_payload_length(max_length);
250    endfunction
251
252    // Return the maximum payload size for packets. This value is used to split
253    // large send requests across multiple packets.
254    //
255    //   port: Master port whose maximum length you want to get
256    //
257    function int get_max_payload_length(int port);
258      assert (port >= 0 && port < m_data.size()) else begin
259        $fatal(1, "Invalid master port number");
260      end
261      return m_data[port].get_max_payload_length();
262    endfunction
263
264    // Set the timestamp ticks per CHDR_W sized word.
265    //
266    //   port:            Master port whose timestamp increment you want to set
267    //   ticks_per_word:  Amount to increment the timestamp per CHDR_W sized word
268    //
269    function void set_ticks_per_word(int port, int ticks_per_word);
270      assert (port >= 0 && port < m_data.size()) else begin
271        $fatal(1, "Invalid master port number");
272      end
273      m_data[port].set_ticks_per_word(ticks_per_word);
274    endfunction
275
276    // Return the timestamp ticks per CHDR_W sized word.
277    //
278    //   port:  Master port whose timestamp increment you want to get
279    //
280    function int get_ticks_per_word(int port);
281      assert (port >= 0 && port < m_data.size()) else begin
282        $fatal(1, "Invalid master port number");
283      end
284      return m_data[port].get_ticks_per_word();
285    endfunction
286
287    // Get static info about the block
288    function logic [7:0] get_proto_ver();
289      return backend.sts.v1.proto_ver;
290    endfunction : get_proto_ver
291
292    function logic [31:0] get_noc_id();
293      return backend.sts.v1.noc_id;
294    endfunction : get_noc_id
295
296    function logic [5:0] get_num_data_i();
297      return backend.sts.v1.num_data_i;
298    endfunction : get_num_data_i
299
300    function logic [5:0] get_num_data_o();
301      return backend.sts.v1.num_data_o;
302    endfunction : get_num_data_o
303
304    function logic [5:0] get_ctrl_fifosize();
305      return backend.sts.v1.ctrl_fifosize;
306    endfunction : get_ctrl_fifosize
307
308    function logic [5:0] get_mtu();
309      return backend.sts.v1.mtu;
310    endfunction : get_mtu
311
312    // Soft-Reset the CHDR path
313    //
314    //   rst_cyc: Number of cycles to wait for reset completion
315    //
316    task reset_chdr(input int rst_cyc = 100);
317      assert (running) else begin
318        $fatal(1, "Cannot call flush_and_reset until RfnocBlockCtrlBfm is running");
319      end
320
321      // Assert soft_chdr_rst then wait
322      // Note: soft_chdr_rst must be driven in the ctrl_clk domain
323      @(posedge backend.ctrl_clk);
324      backend.cfg.v1.soft_chdr_rst = 1;
325      repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
326      backend.cfg.v1.soft_chdr_rst = 0;
327      @(posedge backend.ctrl_clk);
328      repeat (rst_cyc) @(posedge backend.ctrl_clk);
329    endtask : reset_chdr
330
331    // Soft-Reset the Control path
332    //
333    //   rst_cyc: Number of cycles to wait for reset completion
334    //
335    task reset_ctrl(input int rst_cyc = 100);
336      assert (running) else begin
337        $fatal(1, "Cannot call flush_and_reset until RfnocBlockCtrlBfm is running");
338      end
339
340      // Assert soft_ctrl_rst then wait
341      @(posedge backend.ctrl_clk);
342      backend.cfg.v1.soft_ctrl_rst = 1;
343      repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
344      backend.cfg.v1.soft_ctrl_rst = 0;
345      repeat (rst_cyc) @(posedge backend.ctrl_clk);
346    endtask : reset_ctrl
347
348    // Flush the data ports of the block
349    //
350    //   idle_cyc: Number of idle cycles before done is asserted
351    //
352    task flush(input logic [31:0] idle_cyc = 100);
353      assert (running) else begin
354        $fatal(1, "Cannot call flush until RfnocBlockCtrlBfm is running");
355      end
356
357      // Set flush timeout then wait
358      backend.cfg.v1.flush_timeout = idle_cyc;
359      repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
360      repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
361      // Start flush then wait for done
362      @(posedge backend.ctrl_clk);
363      backend.cfg.v1.flush_en = 1;
364      @(posedge backend.ctrl_clk);
365      while (~backend.sts.v1.flush_done) @(posedge backend.ctrl_clk);
366      // Deassert flush then wait
367      backend.cfg.v1.flush_en = 0;
368      while (backend.sts.v1.flush_active) @(posedge backend.ctrl_clk);
369      repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
370      repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
371    endtask : flush
372
373    // Flush the data ports of the block then reset the CHDR
374    // path, wait then reset the ctrl path
375    //
376    //   idle_cyc: Number of idle cycles before done is asserted
377    //   chdr_rst_cyc: Number of cycles to wait for chdr_rst completion
378    //   ctrl_rst_cyc: Number of cycles to wait for ctrl_rst completion
379    //
380    task flush_and_reset(
381      input logic [31:0] idle_cyc     = 100,
382      input int          chdr_rst_cyc = 100,
383      input int          ctrl_rst_cyc = 100
384    );
385      assert (running) else begin
386        $fatal(1, "Cannot call flush_and_reset until RfnocBlockCtrlBfm is running");
387      end
388
389      // Set flush timeout then wait
390      backend.cfg.v1.flush_timeout = idle_cyc;
391      repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
392      repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
393      // Start flush then wait for done
394      @(posedge backend.ctrl_clk);
395      backend.cfg.v1.flush_en = 1;
396      @(posedge backend.ctrl_clk);
397      while (~backend.sts.v1.flush_done) @(posedge backend.ctrl_clk);
398      // Assert chdr_rst then wait
399      reset_chdr(chdr_rst_cyc);
400      // Assert ctrl_rst then wait
401      reset_ctrl(ctrl_rst_cyc);
402      // Deassert flush then wait
403      backend.cfg.v1.flush_en = 0;
404      while (backend.sts.v1.flush_active) @(posedge backend.ctrl_clk);
405      repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
406      repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
407    endtask : flush_and_reset
408
409
410    // Send a CHDR data packet out the CHDR data interface.
411    //
412    //   port:       Port to send the CHDR packet on.
413    //   data:       Data words to insert into the CHDR packet.
414    //   data_bytes: Size of data in bytes. If omitted or -1, data_bytes will
415    //               be calculated based on the number of words in data.
416    //   metadata:   Metadata words to insert into the CHDR packet. Omit this
417    //               argument (or set to an empty array) to not include
418    //               metadata.
419    //   pkt_info:   Data structure containing packet header information.
420    //
421    task send(
422      input int           port,
423      input chdr_word_t   data[$],
424      input int           data_bytes  = -1,
425      input chdr_word_t   metadata[$] = {},
426      input packet_info_t pkt_info    = 0
427    );
428      assert (running) else begin
429        $fatal(1, "Cannot call send until RfnocBlockCtrlBfm is running");
430      end
431      assert (port >= 0 && port < m_data.size()) else begin
432        $fatal(1, "Invalid master port number");
433      end
434
435      m_data[port].send(data, data_bytes, metadata, pkt_info);
436    endtask : send
437
438
439    // Send a CHDR data packet, filling the payload with items.
440    //
441    //   port:       Port to send the CHDR packet on.
442    //   items:      Data items to insert into the CHDR packet's payload.
443    //   metadata:   Metadata words to insert into the CHDR packet. Omit this
444    //               argument (or set to an empty array) to not include
445    //               metadata.
446    //   pkt_info:   Data structure containing packet header information.
447    //
448    task send_items(
449      input int           port,
450      input item_t        items[$],
451      input chdr_word_t   metadata[$] = {},
452      input packet_info_t pkt_info    = 0
453    );
454      assert (running) else begin
455        $fatal(1, "Cannot call send_items until RfnocBlockCtrlBfm is running");
456      end
457      assert (port >= 0 && port < m_data.size()) else begin
458        $fatal(1, "Invalid master port number");
459      end
460
461      m_data[port].send_items(items, metadata, pkt_info);
462    endtask : send_items
463
464
465    // Send data as one or more CHDR data packets. The input data and metadata
466    // is automatically broken into max_payload_length'd packets. The
467    // timestamp, if present, is set for the first packet and updated for
468    // subsequent packets. The EOB and EOV are only applied to the last packet.
469    //
470    //   port:       Port to send the CHDR packet(s) on.
471    //   data:       Data words to insert into the CHDR packets.
472    //   data_bytes: Size of data in bytes. If omitted or -1, data_bytes will
473    //               be calculated based on the number of words in data.
474    //   metadata:   Metadata words to insert into the CHDR packet(s). Omit
475    //               this argument (or set to an empty array) to not include
476    //               metadata.
477    //   pkt_info:   Data structure containing packet header information.
478    //
479    task send_packets(
480      input int           port,
481      input chdr_word_t   data[$],
482      input int           data_bytes  = -1,
483      input chdr_word_t   metadata[$] = {},
484      input packet_info_t pkt_info    = 0
485    );
486      assert (running) else begin
487        $fatal(1, "Cannot call send_packets until RfnocBlockCtrlBfm is running");
488      end
489      assert (port >= 0 && port < m_data.size()) else begin
490        $fatal(1, "Invalid master port number");
491      end
492
493      m_data[port].send_packets(data, data_bytes, metadata, pkt_info);
494    endtask : send_packets
495
496
497    // Send one or more CHDR data packets, filling the payload with items. The
498    // input data and metadata is automatically broken into
499    // max_payload_length'd packets. The timestamp, if present, is set for the
500    // first packet and updated for subsequent packets. The EOB and EOV are
501    // only applied to the last packet.
502    //
503    //   port:       Port to send the CHDR packet(s) on.
504    //   items:      Data items to insert into the payload of the CHDR packets.
505    //   metadata:   Metadata words to insert into the CHDR packet(s). Omit
506    //               this argument (or set to an empty array) to not include
507    //               metadata.
508    //   pkt_info:   Data structure containing packet header information.
509    //
510    task send_packets_items(
511      input int           port,
512      input item_t        items[$],
513      input chdr_word_t   metadata[$] = {},
514      input packet_info_t pkt_info    = 0
515    );
516      assert (running) else begin
517        $fatal(1, "Cannot call send_packets_items until RfnocBlockCtrlBfm is running");
518      end
519      assert (port >= 0 && port < m_data.size()) else begin
520        $fatal(1, "Invalid master port number");
521      end
522
523      m_data[port].send_packets_items(items, metadata, pkt_info);
524    endtask : send_packets_items
525
526
527    // Receive a CHDR data packet on the CHDR data interface and extract its
528    // contents.
529    //
530    //   port:        Port to receive the CHDR packet from.
531    //   data:        Data words from the received CHDR packet.
532    //   data_bytes:  The number of data bytes in the CHDR packet. This
533    //                is useful if the data is not a multiple of the
534    //                chdr_word_t size.
535    //   metadata:    Metadata words from the received CHDR packet. This
536    //                will be an empty array if there was no metadata.
537    //   pkt_info:    Data structure to receive packet header information.
538    //
539    task recv_adv(
540      input  int           port,
541      output chdr_word_t   data[$],
542      output int           data_bytes,
543      output chdr_word_t   metadata[$],
544      output packet_info_t pkt_info
545    );
546      assert (running) else begin
547        $fatal(1, "Cannot call recv_adv until RfnocBlockCtrlBfm is running");
548      end
549      assert (port >= 0 && port < s_data.size()) else begin
550        $fatal(1, "Invalid slave port number");
551      end
552
553      s_data[port].recv_adv(data, data_bytes, metadata, pkt_info);
554    endtask : recv_adv
555
556
557    // Receive a CHDR data packet on the CHDR data interface and extract its
558    // contents, putting the payload into a queue of items.
559    //
560    //   port:        Port to receive the CHDR packet from.
561    //   items:       Items extracted from the payload of the received packet.
562    //   metadata:    Metadata words from the received CHDR packet. This
563    //                will be an empty array if there was no metadata.
564    //   pkt_info:    Data structure to receive packet header information.
565    //
566    task recv_items_adv(
567      input  int           port,
568      output item_t        items[$],
569      output chdr_word_t   metadata[$],
570      output packet_info_t pkt_info
571    );
572      assert (running) else begin
573        $fatal(1, "Cannot call recv_items_adv until RfnocBlockCtrlBfm is running");
574      end
575      assert (port >= 0 && port < s_data.size()) else begin
576        $fatal(1, "Invalid slave port number");
577      end
578
579      s_data[port].recv_items_adv(items, metadata, pkt_info);
580    endtask : recv_items_adv
581
582
583    // Receive a CHDR data packet on the CHDR data interface and extract the
584    // data. Any metadata or timestamp, if present, are discarded.
585    //
586    //   port:        Port number for the block to receive from
587    //   data:        Data words from the received CHDR packet
588    //   data_bytes:  The number of data bytes in the CHDR packet. This
589    //                is useful if the data is not a multiple of the
590    //                chdr_word_t size.
591    //
592    task recv(
593      input  int         port,
594      output chdr_word_t data[$],
595      output int         data_bytes
596    );
597      assert (running) else begin
598        $fatal(1, "Cannot call recv until RfnocBlockCtrlBfm is running");
599      end
600      assert (port >= 0 && port < s_data.size()) else begin
601        $fatal(1, "Invalid slave port number");
602      end
603
604      s_data[port].recv(data, data_bytes);
605    endtask : recv
606
607
608    // Receive a CHDR data packet on the CHDR data interface and extract its
609    // payload into a queue of items. Any metadata or timestamp, if present,
610    // are discarded.
611    //
612    //   port:        Port number for the block to receive from.
613    //   items:       Data items extracted from payload of the received packet.
614    //   data_bytes:  The number of data bytes in the CHDR packet. This
615    //                is useful if the data is not a multiple of the
616    //                chdr_word_t size.
617    //
618    task recv_items(
619      input  int    port,
620      output item_t items[$]
621    );
622      assert (running) else begin
623        $fatal(1, "Cannot call recv_items until RfnocBlockCtrlBfm is running");
624      end
625      assert (port >= 0 && port < s_data.size()) else begin
626        $fatal(1, "Invalid slave port number");
627      end
628
629      s_data[port].recv_items(items);
630    endtask : recv_items
631
632
633    // Receive one ore more CHDR data packets and extract their contents,
634    // putting the payload into a queue of items. Any metadata or timestamp, if
635    // present, are discarded.
636    //
637    //   port:      Port number for the block to receive from.
638    //   items:     Items extracted from the payload of the received packets.
639    //   num_items: (Optional) Minimum number of items to receive. This must be
640    //              provided, unless eob or eov are set. Defaults to -1, which
641    //              means that the number of items is not limited.
642    //   eob:       (Optional) Receive up until the next End of Burst (EOB).
643    //              Default value is 1, so an entire burst is received.
644    //   eov:       (Optional) Receive up until the next End of Vector (EOV).
645    //
646    task recv_packets_items(
647      input  int           port,
648      output item_t        items[$],
649      input int            num_items = -1,  // Receive a full burst by default
650      input bit            eob       = 1,
651      input bit            eov       = 0
652    );
653      assert (running) else begin
654        $fatal(1, "Cannot call recv_items_adv until RfnocBlockCtrlBfm is running");
655      end
656      assert (port >= 0 && port < s_data.size()) else begin
657        $fatal(1, "Invalid slave port number");
658      end
659
660      s_data[port].recv_packets_items(
661        items, num_items, eob, eov);
662    endtask : recv_packets_items
663
664
665    // Receive one or more CHDR data packets and extract their contents,
666    // putting the payload into a queue of items and the metadata into a queue
667    // of CHDR words.
668    //
669    //   port:      Port number for the block to receive from.
670    //   items:     Items extracted from the payload of the received packets.
671    //   metadata:  Metadata words from the received CHDR packets. This will be
672    //              an empty array if there was no metadata.
673    //   pkt_info:  Data structure to receive packet information. The
674    //              timestamp, if present, will correspond to the time of the
675    //              first sample, whereas vc/eob/eov will correspond to the
676    //              state of the last packet.
677    //   num_items: (Optional) Minimum number of items to receive. This must be
678    //              provided, unless eob or eov are set. Defaults to -1, which
679    //              means that the number of items is not limited.
680    //   eob:       (Optional) Receive up until the next End of Burst (EOB).
681    //              Default value is 1, so an entire burst is received.
682    //   eov:       (Optional) Receive up until the next End of Vector (EOV).
683    //
684    task recv_packets_items_adv(
685      input  int           port,
686      output item_t        items[$],
687      output chdr_word_t   metadata[$],
688      output packet_info_t pkt_info,
689      input int            num_items = -1,  // Receive a full burst by default
690      input bit            eob       = 1,
691      input bit            eov       = 0
692    );
693      assert (running) else begin
694        $fatal(1, "Cannot call recv_items_adv until RfnocBlockCtrlBfm is running");
695      end
696      assert (port >= 0 && port < s_data.size()) else begin
697        $fatal(1, "Invalid slave port number");
698      end
699
700      s_data[port].recv_packets_items_adv(
701        items, metadata, pkt_info, num_items, eob, eov);
702    endtask : recv_packets_items_adv
703
704
705    // Transmit a raw CHDR packet.
706    //
707    //   port:    Port number on which to transmit the packet
708    //   packet:  Packet to transmit
709    //
710    task put_chdr(
711      input int                  port,
712      input ChdrPacket #(CHDR_W) packet
713    );
714      assert (running) else begin
715        $fatal(1, "Cannot call put_chdr until RfnocBlockCtrlBfm is running");
716      end
717      assert (port >= 0 && port < m_data.size()) else begin
718        $fatal(1, "Invalid master port number");
719      end
720
721      m_data[port].put_chdr(packet);
722    endtask : put_chdr
723
724
725    // Receive a raw CHDR packet.
726    //
727    //   port:    Port number on which to receive the packet
728    //   packet:  Data structure to store received packet
729    //
730    task get_chdr(
731      input  int                  port,
732      output ChdrPacket #(CHDR_W) packet
733    );
734      assert (running) else begin
735        $fatal(1, "Cannot call get_chdr until RfnocBlockCtrlBfm is running");
736      end
737      assert (port >= 0 && port < s_data.size()) else begin
738        $fatal(1, "Invalid slave port number");
739      end
740
741      s_data[port].get_chdr(packet);
742    endtask : get_chdr
743
744
745    // Receive a raw CHDR packet, but don't remove it from the receive queue.
746    //
747    //   port:    Port number on which to peek
748    //   packet:  Data structure to store received packet
749    //
750    task peek_chdr(
751      input  int                  port,
752      output ChdrPacket #(CHDR_W) packet
753    );
754      assert (running) else begin
755        $fatal(1, "Cannot call peek_chdr until RfnocBlockCtrlBfm is running");
756      end
757      assert (port >= 0 && port < s_data.size()) else begin
758        $fatal(1, "Invalid slave port number");
759      end
760
761      s_data[port].peek_chdr(packet);
762    endtask : peek_chdr
763
764
765    // Return the number of packets available in the receive queue for the
766    // given port.
767    //
768    //   port:  Port for which to get the number of received packets
769    //
770    function int num_received(int port);
771      assert (port >= 0 && port < s_data.size()) else begin
772        $fatal(1, "Invalid slave port number");
773      end
774
775      return s_data[port].num_received();
776    endfunction
777
778
779    // Wait until packets have completed transmission.
780    //
781    //   port:  Port for which to wait
782    //   num:   Number of packets to wait for. Set to -1 or omit the argument
783    //          to wait for all currently queued packets to complete
784    //          transmission.
785    //
786    task wait_complete(int port, int num = -1);
787      assert (running) else begin
788        $fatal(1, "Cannot call wait_complete until RfnocBlockCtrlBfm is running");
789      end
790      assert (port >= 0 && port < m_data.size()) else begin
791        $fatal(1, "Invalid master port number");
792      end
793
794      m_data[port].wait_complete(num);
795    endtask
796
797
798    // Set the stall probability for the indicated slave port.
799    //
800    //   port:        Port for which to set the probability
801    //   stall_prob:  Probability as a percentage (0-100)
802    //
803    function void set_slave_stall_prob(int port, int stall_prob);
804      assert (port >= 0 && port < s_data.size()) else begin
805        $fatal(1, "Invalid slave port number");
806      end
807
808      s_data[port].set_slave_stall_prob(stall_prob);
809    endfunction
810
811
812    // Set the stall probability for the indicated master port.
813    //
814    //   port:        Port for which to set the probability
815    //   stall_prob:  Probability as a percentage (0-100)
816    //
817    function void set_master_stall_prob(int port, int stall_prob);
818      assert (port >= 0 && port < m_data.size()) else begin
819        $fatal(1, "Invalid master port number");
820      end
821
822      m_data[port].set_master_stall_prob(stall_prob);
823    endfunction
824
825
826    // Send a read request packet on the AXIS-Ctrl interface and get the
827    // response.
828    //
829    //   addr:   Address for the read request
830    //   word:   Data word that was returned in response to the read
831    //
832    task reg_read(
833      input  ctrl_address_t addr,
834      output ctrl_word_t    word
835    );
836      assert (running) else begin
837        $fatal(1, "Cannot call reg_read until RfnocBlockCtrlBfm is running");
838      end
839
840      ctrl.reg_read(addr, word);
841    endtask : reg_read
842
843
844    // Send a a write request packet on the AXIS-Ctrl interface and get the
845    // response.
846    //
847    //   addr:   Address for the write request
848    //   word:   Data word to write
849    //
850    task reg_write(
851      ctrl_address_t addr,
852      ctrl_word_t    word
853    );
854      assert (running) else begin
855        $fatal(1, "Cannot call reg_write until RfnocBlockCtrlBfm is running");
856      end
857
858      ctrl.reg_write(addr, word);
859    endtask : reg_write
860
861    // Compare data vectors
862    static function bit compare_data(
863      input chdr_word_t lhs[$],
864      input chdr_word_t rhs[$],
865      input int         bytes = -1
866    );
867      int bytes_left;
868      if (lhs.size() != rhs.size()) return 0;
869      bytes_left = (bytes > 0) ? bytes : ((lhs.size()*$size(chdr_word_t))/8);
870      for (int i = 0; i < lhs.size(); i++) begin
871        chdr_word_t mask = {$size(chdr_word_t){1'b1}};
872        if (bytes_left < $size(chdr_word_t)/8) begin
873          mask = (1 << (bytes_left * 8)) - 1;
874        end else if (bytes_left < 0) begin
875          return 1;
876        end
877        if ((lhs[i] & mask) != (rhs[i] & mask)) return 0;
878      end
879      return 1;
880    endfunction : compare_data
881
882  endclass : RfnocBlockCtrlBfm
883
884
885endpackage : PkgRfnocBlockCtrlBfm
886