目录
一、框架图
二、代码分析
按模块分布的package:
-chnl_pkg: //同lab3
chnl_trans;
chnl_generator;
chnl_agent->(chnl_driver, chnl_monitor);
-reg_chnl:
reg_trans;
reg_generator;
reg_agent->(reg_driver, reg_monitor);
-arb_pkg:
arb_trans;
arb_generator;
arb_agent->(arb_driver, arb_monitor)
-fmt_chnl:
fmt_trans;
fmt_generator;
fmt_agent->(fmt_driver, fmt_monitor);
reg_chnl
reg_trans,传输的数据包
类名:reg_trans
功能:reg与dut传输的数据包
函数:clone复制一个相同的对象
sprint打印所有属性
class reg_trans;
rand bit[7:0] addr;//`WRITE, `READ, `IDLE
rand bit[1:0] cmd;//`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR, `SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR
rand bit[31:0] data;
bit rsp;
constraint cstr {
soft cmd inside {`WRITE, `READ, `IDLE};
soft addr inside {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR, `SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR};//0x00,04,08;0x10,14,18
addr[7:4]==0 && cmd==`WRITE -> soft data[31:6]==0;//读写寄存器,写时,高26位为0
soft addr[7:5]==0;
addr[4]==1 -> soft cmd == `READ;
};
function reg_trans clone();
...
endfunction
function string sprint();
...
endfunction
endclass
reg_generator
类名:reg_generator激励产生器
功能:产生激励,自己控制发送的指令、地址、数据,把数据发送给请求mailbox
函数: new();//构造函数,创建请求mailbox和响应mailbox实例
task run();//执行send_trans()
task send_trans();
//a. 利用randomize with产生transaction的随机变量
//b. 将该数据包发送到请求mailbox
//c. 从req_mailbox中获取响应的trans,如果该响应的命令为读,将该响应的数据保存
//d. 通过判断其rsp来确定是否发送成功
string sprint();//将generator的产生的随机数据打印
void post_randomize();//打印随机化后的值
class reg_generator;
rand bit[7:0] addr = -1;
rand bit[1:0] cmd = -1;
rand bit[31:0] data = -1;
mailbox #(reg_trans) req_mb;
mailbox #(reg_trans) rsp_mb;
reg_trans reg_req[$];
constraint cstr{
soft addr == -1;
soft cmd == -1;
soft data == -1;
}
function new();
this.req_mb = new();
this.rsp_mb = new();
endfunction
task start();
send_trans();
endtask
// generate transaction and put into local mailbox
task send_trans();
reg_trans req, rsp;
req = new();
assert(req.randomize with {local::addr >= 0 -> addr == local::addr;
local::cmd >= 0 -> cmd == local::cmd;
local::data >= 0 -> data == local::data;
})
else $fatal("[RNDFAIL] register packet randomization failure!");
$display(req.sprint());
this.req_mb.put(req);
this.rsp_mb.get(rsp);
$display(rsp.sprint());
if(req.cmd == `READ)
this.data = rsp.data;
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask
function string sprint();
...
endfunction
function void post_randomize();
...
endfunction
endclass
reg_driver
类名:reg_driver激励发送器
功能:将数据包发送给dut
函数: new(string name = "chnl_initiator");//构造函数,初始化该激励发送器的name
void set_interface(virtual chnl_intf intf);//设置接口,接口与dut之间传输数据
task run();//运行,通过调用drive函数
task do_drive();//从req_mailbox获取对应的数据包,将该数据包发送给dut,将该数据包的响应rsp设置为1,输入req_mailbox
task do_reset();//复位
task chnl_write(input chnl_trans t);//接口时钟上升沿将数据包和数据有效位发送给dut
task chnl_idle();//发送状态空
class reg_driver;
local string name;
local virtual reg_intf intf;
mailbox #(reg_trans) req_mb;
mailbox #(reg_trans) rsp_mb;
function new(string name = "reg_driver");
this.name = name;
endfunction
function void set_interface(virtual reg_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run();
fork
this.do_drive();
this.do_reset();
join
endtask
task do_reset();
forever begin
@(negedge intf.rstn);
intf.cmd_addr <= 0;
intf.cmd <= `IDLE;
intf.cmd_data_m2s <= 0;
end
endtask
task do_drive();
reg_trans req, rsp;
@(posedge intf.rstn);
forever begin
this.req_mb.get(req);
this.reg_write(req);
rsp = req.clone();
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask
task reg_write(reg_trans t);
@(posedge intf.clk iff intf.rstn);
case(t.cmd)
`WRITE: begin
intf.drv_ck.cmd_addr <= t.addr;
intf.drv_ck.cmd <= t.cmd;
intf.drv_ck.cmd_data_m2s <= t.data;
end
`READ: begin //read
intf.drv_ck.cmd_addr <= t.addr;
intf.drv_ck.cmd <= t.cmd;
repeat(2) @(negedge intf.clk);//下一个时钟的下降沿采样就一定是当前的data
t.data = intf.cmd_data_s2m;
end
`IDLE: begin
this.reg_idle();
end
default: $error("command %b is illegal", t.cmd);
endcase
$display("%0t reg driver [%s] sent addr %2x, cmd %2b, data %8x", $time, name, t.addr, t.cmd, t.data);
endtask
task reg_idle();
@(posedge intf.clk);//等一拍
intf.drv_ck.cmd_addr <= 0;
intf.drv_ck.cmd <= `IDLE;
intf.drv_ck.cmd_data_m2s <= 0;
endtask
endclass
reg_monitor
reg_agent
fmt_pkg
- 整形器按照数据包的形式发送数据,数据包的长度有 4、8、16和 32。
- 整形器必须完整发送某个通道的数据包后,才可以准备发送下一个数据包,在发送数据包期间,fmt_chid和 fmt_length应该保持不变,直到数据包发送完毕
- DUT的formatter发送请求,fmt_pkg要将formatter的数据接收并按一定的速度消化,所以需要在内部放一个fifo,模拟接收数据与消耗数据
fmt_trans
类名:fmt_trans
功能:fmt的transaction
属性:fifo深度和消耗速度,数据包的长度,数据,通道号与响应
函数:function fmt_trans clone();//克隆
function string sprint();//打印
function bit compare(fmt_trans t);//比较函数
fmt_driver
类名:fmt_driver
功能:配置fifo的深度和消耗速度,接收formatter数据,模拟消耗数据
属性:fifo接收数据的buffer
函数:run();
do_receive();//从formatter接受数据
do_consume();//消耗数据
do_config();//配置fifo
do_reset();//复位
class fmt_driver;
local string name;
local virtual fmt_intf intf;
mailbox #(fmt_trans) req_mb;
mailbox #(fmt_trans) rsp_mb;
local mailbox #(bit[31:0]) fifo;//模拟fifo buffer
local int fifo_bound;
local int data_consum_peroid;//数据消耗周期
function new(string name = "fmt_driver");//设置这个fifo
this.name = name;
this.fifo = new();//指向无限大的信箱
this.fifo_bound = 4096;
this.data_consum_peroid = 1;
endfunction
function void set_interface(virtual fmt_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run();
fork
this.do_receive();
this.do_consume();
this.do_config();
this.do_reset();
join
endtask
//得到gene随机后的fmt_fifo_t和fmt_bandwidth_t值,配置内部fifo
task do_config();
fmt_trans req, rsp;
forever begin
this.req_mb.get(req);
case(req.fifo)
SHORT_FIFO: this.fifo_bound = 64;
MED_FIFO: this.fifo_bound = 256;
LONG_FIFO: this.fifo_bound = 512;
ULTRA_FIFO: this.fifo_bound = 2048;
endcase
this.fifo = new(this.fifo_bound);//指向固定深度的mailbox
case(req.bandwidth)
LOW_WIDTH: this.data_consum_peroid = 8;//数据消耗速度,发一个数据,等8个周期
MED_WIDTH: this.data_consum_peroid = 4;
HIGH_WIDTH: this.data_consum_peroid = 2;
ULTRA_WIDTH: this.data_consum_peroid = 1;//一个周期发一个数据
endcase
rsp = req.clone();
rsp.rsp = 1;
this.rsp_mb.put(rsp);
end
endtask
//复位
task do_reset();
forever begin
@(negedge intf.rstn)
intf.fmt_grant <= 0;//复位后,响应信号为0
end
endtask
//模拟从formatter接受数据
task do_receive();
forever begin
@(posedge intf.fmt_req);//req拉高了
@(posedge intf.clk iff(this.fifo_bound-this.fifo.num()) >= intf.fmt_length);//fifo余量>=formatter长度
intf.drv_ck.fmt_grant <= 1;//将grant给dut,当前可以接收数据
@(posedge intf.fmt_start);
fork
begin
@(posedge intf.clk);//grant保持两拍,开始一拍后置0
intf.drv_ck.fmt_grant <= 0;
end
join_none
repeat(intf.fmt_length) begin
@(negedge intf.clk);
this.fifo.put(intf.fmt_data);
end
end
endtask
//消耗数据
task do_consume();
bit[31:0] data;
forever begin
void'(this.fifo.try_get(data));//无论有无数据,一直在里面拿一个数据
repeat($urandom_range(1, this.data_consum_peroid)) @(posedge intf.clk);//拿一次数据等一段随机时间
end
endtask
endclass
fmt_generator
类名:fmt_generator
功能:随机生成fifo的大小和消耗速度
属性:fifo,bandwidth为随机数,fifo的大小和位宽
请求mailbox和响应mailbox
函数:function new();//实例化两个mailbox
task start();//调用send_trans()函数
task send_trans();//随机化fmt_trans,将该trans发送给driver,并接收该trans
function string sprint();//打印
function void post_randomize();//打印随机化后的值
fmt_monitor
类名:fmt_monitor
功能:获取数据包的长度,数据,通道号,最终送给checker作最终的数据比较。
函数:task mon_trans();//从接口上获取数据包的长度,数据,通道号
fmt_agent
mcdf_pkg
//mcdt的transaction
typedef struct packed {
bit[2:0] len;//长度
bit[1:0] prio;//优先级
bit en;//使能
bit[7:0] avail;//mcdt的margin,只读
} mcdf_reg_t;
typedef enum {RW_LEN, RW_PRIO, RW_EN, RD_AVAIL} mcdf_field_t;
mcdf_refmod
类名:mcdf_refmod
功能:将每个通道输入的数据按通道按寄存器输入的格式打包输出
属性: reg_mb 捕捉reg_trans的参数
regs[i]存储捕捉到的i通道的寄存器的参数
in_mbs[i] 发送的数据
out_mbs[i] 打包的数据
函数: task run();
task do_reg_update();//捕捉寄存器读写,用来更新寄存器内部的信号值,通过寄存器的值进行打包
task do_packet();//利用通道内对应寄存器的值对通道打包,
class mcdf_refmod;
local virtual mcdf_intf intf;
local string name;
mcdf_reg_t regs[3];//chnl 0,1,2的寄存器,RW和R
mailbox #(reg_trans) reg_mb;
mailbox #(mon_data_t) in_mbs[3];
mailbox #(fmt_trans) out_mbs[3];
function new(string name="mcdf_refmod");
this.name = name;
foreach(this.out_mbs[i]) this.out_mbs[i] = new();//例化
endfunction
task run();
fork
do_reset();
this.do_reg_update();//模拟寄存器,对寄存器进行配置
do_packet(0);//打包
do_packet(1);
do_packet(2);
join
endtask
//捕捉寄存器读写,用来更新寄存器内部的信号值,通过寄存器的值进行打包
task do_reg_update();
reg_trans t;
forever begin
this.reg_mb.get(t);//捕捉reg_trans的数据
if(t.addr[31:4] == 0 && t.cmd == `WRITE) begin//先判断为读操作,再根据操作的通道号对regs赋值
this.regs[t.addr[3:2]].en = t.data[0];//使能
this.regs[t.addr[3:2]].prio = t.data[2:1];//优先级
this.regs[t.addr[3:2]].len = t.data[5:3];//数据包长度
end
else if(t.addr[7:4] == 1 && t.cmd == `READ) begin//只读
this.regs[t.addr[3:2]].avail = t.data[7:0];
end
end
endtask
//利用通道内对应寄存器的值对通道打包
task do_packet(int id);
fmt_trans ot;//package格式
mon_data_t it;//单一数据
forever begin
wait(this.in_mbs[id].num() > 0);//有数据可以打包
ot = new();
ot.length = 4 << (this.get_field_value(id, RW_LEN) & 'b11);//reg长度解码表0->4,1->8,2->16,3->32,其他->32
ot.data = new[ot.length];
ot.ch_id = id;
foreach(ot.data[m]) begin
this.in_mbs[id].get(it);
ot.data[m] = it.data;
end
this.out_mbs[id].put(ot);//拿完了放进去
end
endtask
function int get_field_value(int id, mcdf_field_t f);
case(f)
RW_LEN: return regs[id].len;
RW_PRIO: return regs[id].prio;
RW_EN: return regs[id].en;
RD_AVAIL: return regs[id].avail;
endcase
endfunction
//reset
task do_reset();
forever begin
@(negedge intf.rstn);
foreach(regs[i]) begin
regs[i].len = 'h0;
regs[i].prio = 'h3;
regs[i].en = 'h1;
regs[i].avail = 'h20;
end
//清空各个mailbox
foreach(out_mbs[i]) out_mbs[i].delete();
end
endtask
function void set_interface(virtual mcdf_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
endclass
checker
类名:mcdf_checker
功能:将每个通道输入的数据按通道按寄存器输入的格式打包输出
属性:err_count 误码数据个数计数
total_count 全部数据个数计数
chnl_count 通道数据个数计数
函数:new(string name="mcdf_checker");//实例化mailbox和referance model,连接mailbox
task run();
do_compare();//直接从输出拿数据,根据id和输入进行比较
do_report();//报告,输出所有数据
class mcdf_checker;
local string name;
local int err_count;
local int total_count;
local int chnl_count[3];
local virtual mcdf_intf intf;
local mcdf_refmod refmod;
mailbox #(mon_data_t) chnl_mbs[3];
mailbox #(fmt_trans) fmt_mb;
mailbox #(reg_trans) reg_mb;
mailbox #(fmt_trans) exp_mbs[3];
function new(string name="mcdf_checker");
this.name = name;
//实例化mailbox
foreach(this.chnl_mbs[i]) this.chnl_mbs[i] = new();//实例化mailbox
this.fmt_mb = new();
this.reg_mb = new();
this.refmod = new();
//连接mailbox
foreach(this.refmod.in_mbs[i]) begin
this.refmod.in_mbs[i] = this.chnl_mbs[i];
this.exp_mbs[i] = this.refmod.out_mbs[i];
end
this.refmod.reg_mb = this.reg_mb;
this.err_count = 0;
this.total_count = 0;
foreach(this.chnl_count[i]) this.chnl_count[i] = 0;
endfunction
function void set_interface(virtual mcdf_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
this.refmod.set_interface(intf);
endfunction
task run();
fork
this.do_compare();
this.refmod.run();
join
endtask
task do_compare();
fmt_trans expt, mont;
bit cmp;
forever begin
this.fmt_mb.get(mont);//从输出拿数据,根据id进行比较
this.exp_mbs[mont.ch_id].get(expt);
cmp = mont.compare(expt);
this.total_count++;//每比较一次+1
this.chnl_count[mont.ch_id]++;//每通道比较一次+1
if(cmp == 0) begin
this.err_count++;
rpt_pkg::rpt_msg("[CMPFAIL]",
$sformatf("%0t %0dth times comparing but failed! MCDF monitored output packet is different with reference model output", $time, this.total_count),
rpt_pkg::ERROR,
rpt_pkg::TOP,
rpt_pkg::LOG);
end
else begin
rpt_pkg::rpt_msg("[CMPSUCD]",
$sformatf("%0t %0dth times comparing and succeeded! MCDF monitored output packet is the same with reference model output", $time, this.total_count),
rpt_pkg::INFO,
rpt_pkg::HIGH);
end
end
endtask
function void do_report();//总结
string s;
s = "\n---------------------------------------------------------------\n";
s = {s, "CHECKER SUMMARY \n"};
s = {s, $sformatf("total comparison count: %0d \n", this.total_count)};
foreach(this.chnl_count[i]) s = {s, $sformatf(" channel[%0d] comparison count: %0d \n", i, this.chnl_count[i])};
s = {s, $sformatf("total error count: %0d \n", this.err_count)};
foreach(this.chnl_mbs[i]) begin
if(this.chnl_mbs[i].num() != 0)
s = {s, $sformatf("WARNING:: chnl_mbs[%0d] is not empty! size = %0d \n", i, this.chnl_mbs[i].num())};
end
if(this.fmt_mb.num() != 0)
s = {s, $sformatf("WARNING:: fmt_mb is not empty! size = %0d \n", this.fmt_mb.num())};
s = {s, "---------------------------------------------------------------\n"};
rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
endfunction
endclass
Makefile
仿真运行指令
vsim -l sim.log -c -voptargs=+acc -classdebug -solvefaildebug -sv_seed 0
+TESTNAME=mcdf_data_consistence_basic_test -
|mcdf_data_consistence_basic_tast.log work.tb
-classdebug,这是为了提供更多的 SV类调试功能
-solvefaildebug,这是为了在 SV随机化失败之后有更多的信息提供出来
-sv_seed 0,暂时给固定的随机种子 0
+TESTNAME=mcdf_data_consistence_basic_test,这是指定仿真选择的调试
-| mcdf_data_consistence_basic_test_sim.log,这是让仿真的记录保存在特定的测 试文件名称中
收集覆盖率指令
-vsim -i -classdebug -solvefaildebug -coverage -coverstore COVERAGE_STORAGE_PATH -sv_seed random +TESTNAME=mcdf_data_consistence_basic_test -
|mcdf_data_consistence_basic_tast.log work.tb
-coverage: 会在仿真时产生代码覆盖率数据, 功能覆盖率数据则默认会生成,与此选项无关。
-coverstore COVERAGE STORAGE_PATH: 在仿真结束时,生成覆盖率数据并存储到COVERAGE_STORAGE PATH
-testname TESTNAME: 本次仿真的 test名称,你可以使用同+TESTNAME选项-样的test名称。这样在仿真结束后,将在COVERAGE_ STORAGE_ PATH下产生一个覆盖率数据文件"{TESTNAME}_ {SV_ SEED}.data" 。由于仿真时我们传入的种子是随机值,因此我们每次提交测试,在测试结束后都将产生一个独一无二的覆盖率数据。例如mcdf_ full_ random_test_1988811153.data。
合并覆盖率
- 运行不同的仿真,或者运行同一个test,它们都会生成独一无二的数据库。接下来,你就可以将之前统一在COVERAGE STORAGE_PATH :下面生成的xxx.data覆盖率数据做合并了。你可以在Questasim的仿真窗口中敲入命令。
vcover merge -out merged coverage.ucdb C:/questasim64 10.6c/examples
这个命令即是将你之前产生的若干个xxxx.data的覆盖率合并在一起,生成一个合并在一起的覆盖率文件。所以,在测试前期,你提交的测试越多,那么理论上覆盖率的增长也就越明显。- 接下来,你可以点击File -> Open来打开这个合并后的UCDB覆盖率数据库(注意选择文件类型UCDB就可以看到这个文件了)。当你打开这个数据库之后,你可以发现合并后的数据库要比
之前单独提交的任何一个测试在仿真结束时的该次覆盖率都要高。例如你可以在covergroups窗
口栏中查看功能覆盖率,也可以在Analysis窗口中查看代码覆盖率。
分析覆盖率
你可以依旧使用Questasim来打开UCDB利用工具来查看覆盖率,或者更直观的方式是在打开
当前覆盖率数据库的同时,生成HTML报告。选择Tools -> Coverage Report-> HTML
Covergroups
Statements
Branches
Toggles
参考文献
https://blog.csdn.net/Turn_vs/article/details/125071985
https://blog.csdn.net/qq_41186941/article/details/122852006
https://blog.csdn.net/dinghj3/article/details/122312429