SV验证-2建立组件
本文采用system verilog语言对一个router.v模块进行验证。参考文档为Synopsys公司2012年出版的《SystemVerilog Testbench Lab Guide》
其顶层文件如下图,本节中出现的组件全在test.sv文件中调用。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicWZwpmL0UGOyEjN4ADMmJTNzYTN4U2MiRTY5gjYmFTNlhTOzgzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpeg)
1、Packet类
Packet类用来封装包信息。在test文件中,gen()到send()、send()到check()以及recv()到check()都需要发送包数据。在gen(0中使用randomization可以随机化Packet类中的源地址、目的地址和发包信息。
class Packet; # 申明一个包类
rand bit[3:0] sa, da; // random port selection
rand logic[7:0] payload[$]; // random payload array
string name; // unique identifier
constraint Limit { # 申明一个随机化控制块
sa inside {[0:15]};
da inside {[0:15]};
payload.size() inside {[2:4]};
}
extern function new(string name = "Packet");
extern virtual function bit compare(Packet pkt2cmp, ref string message);
extern virtual function void display(string prefix = "NOTE");
extern virtual function Packet copy();
endclass
为了进行包的检查及两个包的比较,需要申明一个比较数据包的任务。在任务中通过比较payload[$](参考包数据)和pkt2cmp_payload[$](采用包数据)来验证接收数据是否正确。其比较过程如下:
- 比较两者的大小是否相当,不相等则打印错误信息并返回0.
- 比较两者的数据是否相等,不相等则打印错误信息并返回0.
- 若数据相等则打印成功信息并返回1.
function bit Packet::compare(Packet pkt2cmp, ref string message);
if (payload.size() != pkt2cmp.payload.size()) begin
message = "Payload Size Mismatch:\n";
message = { message, $sformatf("payload.size() = %0d, pkt2cmp.payload.size() = %0d\n", payload.size(), pkt2cmp.payload.size()) };
return(0);
end
if (payload == pkt2cmp.payload) ;
else begin
message = "Payload Content Mismatch:\n";
message = { message, $sformatf("Packet Sent: %p\nPkt Received: %p", payload, pkt2cmp.payload) };
return(0);
end
message = "Successfully Compared";
return(1);
endfunction
其余函数中,new函数为构造函数,新建该类时调用。display函数用来实现打印信息,在调试过程中打印包的信息是非常有帮助的,copy函数用来复制包。
2、gen类
申明一个Generate类包用来生成发送的数据。
class Generator;
string name; // unique identifier
Packet pkt2send; // stimulus Packet object
pkt_mbox out_box; // mailbox to Drivers
int port_id = -1; // port_id of connected Driver
static int pkts_generated = 0; //packet count across all generators
extern function new(string name = "Generator", int port_id);
extern virtual task gen();
extern virtual task start();
endclass: Generator
该类中用到了一个新的类型叫mailbox,在SV中采用mailbox实现多个线程之间的数据通信,其用法与FIFO相似,存取采用put()和get()方法。本文通过定义pkt_mbox用来声明mailbox类型。
typedef class Packet;
typedef mailbox #(Packet) pkt_mbox;
在test文件中,复位任务执行后会立即执行gen()任务,在gen()任务执行过程如下:
- 如果是调试模式则打印类的名字;
- 为每个发送的包建立一个唯一的名称;
- 随机化发包的信息,若随机化失败则打印错误信息
task Generator::gen();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
this.pkt2send.name = $sformatf("Packet[%0d]", this.pkts_generated++);
//local refers to Generator class object in with clause
if (!this.pkt2send.randomize() with {if (local::port_id != -1) sa == local::port_id;})
begin
$display("\n%m\n[ERROR]%t Randomization Failed!\n", $realtime);
$finish;
end
endtask: gen
在start()任务中,循环次数由run_for_n_packets变量控制。执行this.gen()任务后,包信息中的数据被随机化,并通过out_box发送到Driver。
task Generator::start();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
fork
while (this.pkts_generated <run_for_n_packets || run_for_n_packets <= 0)
begin
this.gen();
begin
Packet pkt = this.pkt2send.copy();
this.out_box.put(pkt);
end
end
join_none
endtask: start
3、Driver基类和Driver类
Driver基类代码如下,其构造函数中包括名字和接口信号,send()任务为transactor,其余任务均为发送信号任务。
class DriverBase;
virtual router_io.TB rtr_io; // interface signal
string name; // unique identifier
bit[3:0] sa, da; // source and destination addresses
logic[7:0] payload[$]; // Packet payload
Packet pkt2send; // stimulus Packet object
extern function new(string name = "DriverBase", virtual router_io.TB rtr_io);
extern virtual task send();
extern virtual task send_addrs();
extern virtual task send_pad();
extern virtual task send_payload();
endclass
在生成包信息后,可以执行transactor任务将包发送到router上。发送一个包包含三部分:目的地址、打拍周期和数据信息。其传输过程如下图:
申明一个send()任务包含send_addrs()、send_pad()、send_payload()三个任务。
- send_addrs()任务,占4拍时钟周期,先将frame_n信号拉低表示开始传输信号,然后采用循环逐字节串口发送地址信息,
- send_pad()任务:占4拍时钟周期,该任务直接将din、valid_n信号拉低,等待5个时钟上升沿即可。
- send_payload任务:占payload.size()大小的时钟周期,每次循环将发送8bit的payload[$]数据信息。保持valid_n信号为低,发送最后1bit信号时拉高frame_n信号。
task DriverBase::send();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
this.send_addrs();
this.send_pad();
this.send_payload();
endtask
task DriverBase::send_addrs();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
this.rtr_io.cb.frame_n[this.sa] <= 1'b0;
for(int i=0; i<4; i++) begin
this.rtr_io.cb.din[this.sa] <= this.da[i];
@(this.rtr_io.cb);
end
endtask
task DriverBase::send_pad();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
this.rtr_io.cb.din[this.sa] <= 1'b1;
this.rtr_io.cb.valid_n[this.sa] <= 1'b1;
repeat(5) @(this.rtr_io.cb);
endtask
task DriverBase::send_payload();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
foreach(this.payload[index]) begin
for(int i=0; i<8; i++) begin
this.rtr_io.cb.din[this.sa] <= this.payload[index][i];
this.rtr_io.cb.valid_n[this.sa] <= 1'b0;
this.rtr_io.cb.frame_n[this.sa] <= ((index == (this.payload.size() - 1)) && (i == 7));
@(this.rtr_io.cb);
end
end
this.rtr_io.cb.valid_n[this.sa] <= 1'b1;
endtask
Driver类来源于DriverBase类的扩展,新增了三个属性(mailbox、semaphore)和两种方法。in_box将包从Generator传向Diver,out_box将包从Dirver传向Scoreboard。sem[]数组用来做输入输出端口的仲裁判断,以确保只有一个组件来连接一个输入输出端口。
`include "DriverBase.sv"
class Driver extends DriverBase;
pkt_mbox in_box; // Generator mailbox
pkt_mbox out_box; // Scoreboard mailbox
semaphore sem[]; // output port arbitration
extern function new(string name = "Driver", int port_id, semaphore sem[], pkt_mbox in_box, out_box, virtual router_io.TB rtr_io);
extern virtual task start();
endclass
在start()函数中是一个无限循环,循环中接收来自in_box的包并赋值给pkt2send,解析出pkt2send的sa信号若不等于该类指定的sa,则跳过到下一循环,否则继续解析出pkt2send的da和payload信息,使用sem[]阵列来仲裁由da指定的输出端口,然后通过send()任务将该包发给DUT。随后通过out_box将该包发给Scoreboard,循环的最后一步取出旗语中的键。
task Driver::start();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
fork
forever begin
this.in_box.get(this.pkt2send);
if (this.pkt2send.sa != this.sa) continue;
this.da = this.pkt2send.da;
this.payload = this.pkt2send.payload;
this.sem[this.da].get(1);
this.send();
this.out_box.put(this.pkt2send);
this.sem[this.da].put(1);
end
join_none
endtask: start
4、recv基类和recv类
该类对从路由器输出的数据包信号进行采样,并发送到Scoreboard的check()r任务中使之与发包前的数据进行比较。其原理图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlqQUO2E-1668320996043)(photos/receiver类.jpg)]
class ReceiverBase;
virtual router_io.TB rtr_io; // interface signals
string name; // unique identifier
bit[3:0] da; // output port to monitor
logic[7:0] pkt2cmp_payload[$]; // actual payload array
Packet pkt2cmp; // actual Packet object
extern function new(string name = "ReceiverBase", virtual router_io.TB rtr_io);
extern virtual task recv();
extern virtual task get_payload();
endclass
在recv()任务中,先申明一个静态变量来表示收包的数量,再运行get_payload()任务进行收包,最后将包信息赋值给私有变量。接收信号图如下:
task ReceiverBase::recv();
static int pkt_cnt = 0;
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
this.get_payload();
this.pkt2cmp.da = da;
this.pkt2cmp.payload = this.pkt2cmp_payload;
this.pkt2cmp.name = $sformatf("rcvdPkt[%0d]", pkt_cnt++);
endtask
在get_payload()任务中,先将pkt2cmp_payload[ ] 中 的 数 据 删 除 。 该 任 务 中 存 在 一 个 f o r k j o i n 块 和 一 个 f o r e v e r 块 , f o r k j o i n 块 用 来 检 测 信 号 接 收 是 否 完 毕 和 是 否 出 现 超 时 错 误 , 而 f o r e v e r 块 用 来 接 收 r o u t e r 的 输 出 数 据 , 保 存 到 p k t 2 c m p p a y l o a d [ ]中的数据删除。该任务中存在一个fork join块和一个forever块,fork join块用来检测信号接收是否完毕和是否出现超时错误,而forever块用来接收router的输出数据,保存到pkt2cmp_payload[ ]中的数据删除。该任务中存在一个forkjoin块和一个forever块,forkjoin块用来检测信号接收是否完毕和是否出现超时错误,而forever块用来接收router的输出数据,保存到pkt2cmppayload[]对列中。
task ReceiverBase::get_payload();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
this.pkt2cmp_payload.delete();
fork
begin: wd_timer_fork
fork: frameo_wd_timer
begin //循环直到检测到最后1bit发送完毕
wait(rtr_io.cb.frameo_n[da] != 0);
@(rtr_io.cb iff(rtr_io.cb.frameo_n[da] == 0 ));
end
begin
repeat(1000) @(rtr_io.cb); //若在1000个周期内都没结束该进程块则结束仿真
$display("\n%m\n[ERROR]%t Frame signal timed out!\n", $realtime);
$finish;
end
join_any: frameo_wd_timer
disable fork;
end: wd_timer_fork
join
forever begin
logic[7:0] datum;
for (int i=0; i<8; i=i) begin //循环时,每次收集8bit数据
if (!this.rtr_io.cb.valido_n[da])
datum[i++] = this.rtr_io.cb.dout[da];
if (this.rtr_io.cb.frameo_n[da])
if (i == 8) begin
this.pkt2cmp_payload.push_back(datum);
return;
end
else begin //数据没对齐则打印错误信息并结束仿真
$display("\n%m\n[ERROR]%t Packet payload not byte aligned!\n", $realtime);
$finish;
end
@(this.rtr_io.cb);
end
this.pkt2cmp_payload.push_back(datum);
end
endtask
Receiver类包含一个out_box,和start()任务。start()任务会执行一个非阻塞无限循环,每次循环会等待recv任务收包,一旦接收到包,该包就会被复制并通过mailbox传递到Scoreboard。
`include "ReceiverBase.sv"
class Receiver extends ReceiverBase;
pkt_mbox out_box; // Scoreboard mailbox
extern function new(string name = "Receiver", int port_id, pkt_mbox out_box, virtual router_io.TB rtr_io);
extern virtual task start();
endclass: Receiver
task Receiver::start();
if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name);
fork
forever begin
this.recv();
begin
Packet pkt = this.pkt2cmp.copy();
this.out_box.put(pkt);
end
end
join_none
endtask: start