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