目录
01 README
02 MCDF设计结构
2.1 功能描述
2.2 设计结构
2.3 接口与时序
2.3.1 系统信号接口
2.3.2 通道从端接口
2.3.3 整形器接口
2.3.4 控制寄存器接口
2.3.4.1 接口时序图
2.3.4.2 各数据位信息
03 验证框图
3.1 reg_pkg
3.1.1 reg_trans
3.1.2 reg_driver
3.1.3 reg_generator
3.1.4 reg_monitor
3.1.5 reg_agent
3.2 fmt_pkg
3.2.1 fmt_trans
3.2.2 fmt_driver
3.2.3 fmt_generator
3.2.4 fmt_monitor
3.2.5 fmt_agent
3.3 mcdf_pkg
3.3.1 mcdf_refmod
3.3.2 mcdf_checker
3.3.3 mcdf_env
3.3.4 mcdf_base_test
3.3.5 mcdf_reg_write_read_test
3.3.6 mcdf_reg_illegal_access_tets
3.3.7 mcdf_arbiter_priority_test
3.3.8 mcdf_channel_disbale_test
3.3.9 mcdf_coverage
04 覆盖率收集
01 README
最最开始系统地学习芯片领域相关只是大概追溯到23年12月初,此前只是有一些Verilog和数电基础,做过两个小的设计项目。整个12月和1月几乎都在学习FPGA的相关课程,仍记得在跨年夜在宿舍敲UART串口通信那篇文章@UART串口通信_uart串口通讯-CSDN博客,但同时也感受到在国外没有开发板的情况下确实很难把这些东西学好。因此年后转而开始学习更偏向于软件的数字IC验证。期间也是断断续续,国内学校的事情太多了,因此总是不能全身心投入,加上自己软件基础并不好,C++,python这些语言学的都不好。大概花了3个月的时间基本学完了SV的语法,验证的基本流程和熟悉验证软件的使用,但也不敢说能够熟练掌握。目前算是进入了下一个时间节点,开始学习UVM验证环境。因此基于MCDF项目对此前学习进行一个总结。大佬们轻喷。
02 MCDF设计结构
2.1 功能描述
设计模块的全称叫做multi-channel data formatter,他可以将上行多个数据通道的数据通过内部的SLAVE, FIFO给到仲裁器Arbiter, Arbiter选择从不同的FIFO中读取数据,给到下端的Formatter, 对数据进行整形,以数据包的形式送给下行的数据接收端。整个设计通过Register接受外部命令,对MCDF的功能进行修改。
2.2 设计结构
2.3 接口与时序
2.3.1 系统信号接口
clk(0) :时钟信号
rstn(0):复位信号
2.3.2 通道从端接口
CHx_DATA(31:0):通道数据输入。
CHx_VALID(0):通道数据有效标志信号,高位有效。
CHx_READY(0):通道数据接收信号,高位表示接收成功
当valid为高时,表示要写入数据。如果该时钟周期ready为高,则表示已经将数据写入;如果该时钟周期ready为低,则需要等到ready为高的时钟周期才可以将数据写入。
2.3.3 整形器接口
FMT_CHID(1:0):整形数据包的通道ID号。
FMT_LENGTH(4:0):整形数据包长度信号。
FMT_REQ(0):整形数据包发送请求。
FMT_GRANT(0):整形数据包被允许发送的接受标示。
FMT_DATA(31:0):数据输出端口。
FMT_START(0):数据包起始标示。
FMT_END(0):数据包结束标示。
- 整形器的作用是对数据进行打包,以数据包的形式向下行数据接收端传递数据,可以选择的数据包长度为4,8,16,32,这是由FMT_LENGTH信号来进行控制。
- 在发送数据包时,首先应该将FMT_REQ置为高,同时等待接收端的FMT_GRANT拉高,此时表示接收端可以进行数据接受。当FMT_GRANT变为高时,应该在下一个周期将FMT_REQ置为低。FMT_START也必须在接收到FMT_GRANT高有效的下一个时钟被置为高,且需要维持一个时钟周期。在FMT_START被置为高有效的同一个周期,数据也开始传送,数据之间不允许有空闲周期,即应该连续发送数据,直到发送完最后一个数据时,FMT_END也应当被置为高并保持一个时钟周期。在数据包传输过程中FMT_CHID和FMT_LENGTH应该保持不变,直到数据包发送完毕。
- 相邻的数据包之间应该至少有一个时钟周期的空闲,即FMT_END从高位被拉低以后,至少需要经过一个时钟周期,FMT_REQ才可以被再次置为高。
2.3.4 控制寄存器接口
CMD(1:0):寄存器读写命令。
CMD_ADDR(7:0):寄存器地址。
CMD_DATA_IN(31:0):寄存器写入数据。
CMD_DATA_OUT(31:0):寄存器读出数据。
2.3.4.1 接口时序图
当cmd为写指令时,需要把数据cmd_data_in写入到cmd_addr对应的寄存器中;当cmd为读指令时,即需要从cmd_addr对应的寄存器中读取数据,并在下一个周期,将数据驱动至cmd_data_out接口。
2.3.4.2 各数据位信息
- 地址0x00 通道控制寄存器 32bits 读写寄存器
bit(0):通道使能信号。1为打开,0位关闭。复位值为1。
bit(2:1):优先级。0为最高,3为最低。复位值为3。
bit(5:3):数据包长度,解码对应表为, 0对应长度4,1对应长度8,2对应长度16,3对应长度32,其它数值(4-7)均暂时对应长度32。复位值为0。
bit(31:6):保留位,无法写入。复位值为0。
- 地址0x10 通道状态寄存器 32bits 只读寄存器
bit(7:0):上行数据从端FIFO的可写余量,同FIFO的数据余量保持同步变化。复位值为FIFO的深度数。
bit(31:8):保留位,复位值为0。
03 验证框图
一共分为chnl_pkg, reg_pkg, fmt_pkg和mcdf_pkg, 各个包之间通过interface进行连接
3.1 reg_pkg
3.1.1 reg_trans
寄存器传输类,包含地址、命令、数据和响应字段
class reg_trans;
rand bit[7:0] addr;
rand bit[1:0] cmd ;
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};
addr[7:4] ==0 && cmd == 'WRITE -> soft data [31:6] == 0;//读写寄存器要求高26位为0
soft addr [7:5] == 0;
addr[4] == 1 -> soft cmd == 'READ;//地址为0x10,0x14,0x18的是通道1,2,3的只读存储器
};
function reg_trans clone() ;
reg_trans c = new() ;
c.addr = this.addr;
c.cmd = this.cmd ;
c.data = this.data;
c.rsp = this.rsp ;
return c ;
endfunction
function string sprint();
string s ;
s = {s, $sformatf("========================n")};
s = {s, $sformatf("reg_trans object content is as below:\n")};
s = {s, $sformatf("addr = %2x:\n", this.addr)};//两位16进制数
s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2进制数
s = {s, $sformatf("data = %8x:\n", this.data)};//8位16进制数
s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10进制数,无字符宽度
s = {s, $sformatf("=======================\n")};
return s;
endfunction
endclass
3.1.2 reg_driver
寄存器驱动器,用于生成寄存器传输事务并将其发送到接口。
该类包括了一个 do_drive 任务来驱动事务的发送,一个 do_reset 任务来处理复位信号,以及一个 reg_write 任务来处理寄存器的读写操作。
class reg_driver;
local string name;
local virtual reg_intf intf;
mailbox #(reg_trans) reg_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 is null");
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
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);//这是一个复合条件,iff 表示 "if and only if",即当且仅当。
//所以整个条件表示:只有当 intf.rstn 为逻辑真时,才触发在 intf.clk 的上升沿执行操作。
case(t.cmd)
'WRITE:begin
intf.drv_ck.cmd_addr <= t.addr;
intf.drv_ck.cmd <= t.cmd;
intf.drv.cmd_data_m2s <= t.data;
end
'READ :begin
intf.drv_ck.cmd_addr <= t.addr;
intf.drv_ck.cmd <= t.cmd;
repeat(2) @(negedge intf.clk);//为何要等两个下降沿?
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_addr <= 0 ;
intf.drv_ck.cmd <= 'IDLE;
endtask
endclass
3.1.3 reg_generator
生成寄存器传输事务,并提供一个 send_trans 任务来发送事务并处理响应。
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_tans 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("register packet randomization fail");
$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)//确保收到的响应中包含有效的响应标志。如果响应标志无效,则通过 $error 报错。
else $error("[RSPERR] 0%t error response received!", $time);
endtask
function string sprint();
string s ;
s = {s, $sformatf("========================n")};
s = {s, $sformatf("reg_trans object content is as below:\n")};
s = {s, $sformatf("addr = %2x:\n", this.addr)};//两位16进制数
s = {s, $sformatf("cmd = %2b:\n", this.cmd )};//2位2进制数
s = {s, $sformatf("data = %8x:\n", this.data)};//8位16进制数
s = {s, $sformatf("rsp = %0d:\n", this.rsp )};//10进制数,无字符宽度
s = {s, $sformatf("=======================\n")};
return s;
endfunction
//用于在随机化后显示对象的内容。
function void post_randomize();
string s;
s = {"AFTER RANDOMIZE \n",this.sprint()};
$display (s);
endfunction
endclass
3.1.4 reg_monitor
寄存器监视器,用于监视寄存器传输事务并将其存储到邮箱中。
class reg_monitor;
local string name;
local virtual reg_intf intf;
mailbox #(reg_trans) mon_mb;
function new(string name = "reg_monitor");
this.name = name;
endfunction
function void set_interface(virtual reg_intf intf);
if(intf == null)
$error("interface handle is null");
else
this.intf = intf;
endfunction
task run();
this.mon_trans();
endtask
//其功能是监视寄存器接口的传输事务,并将监视到的信息存储到邮箱 mon_mb 中
task mon_trans();
reg_trans m;
forever begin
@(posedge intf.clk iff(intf.rstn && intf.mon_ck.cmd != 'IDLE));
m = new();
m.addr = intf.mon_ck.cmd_addr;
m.cmd = intf.mon_ck.cmd;
if(intf.mon_ck.cmd == 'READ)begin
@(posedge intf.clk);
m.data = intf.mon_ck.cmd_data_s2m;
end
mon_mb.put(m);
$display("%0t reg driver [%s] sent addr %2x,cmd %2b,data %8x",$time,this.name,m.addr,m.cmd,m.data);
end
endtask
endclass
3.1.5 reg_agent
组合 reg_driver 和 reg_monitor,并提供了一个 run 任务来同时运行驱动器和监视器。
class reg_agent;
local string name;
reg_driver driver;
reg_monitor monitor;
local virtual reg_intf vif;
function new(string name = "reg_agent");
this.name = name;
this.driver = new({name,".driver"});
this.monitor = new({name,".monitor"});
endfunction
function void set_interface(virtual reg_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
task run();
fork
driver.run();
monitor.run();
join
endtask
endclass
3.2 fmt_pkg
import rpt_pkg::*;
typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIOF, ULTRA_FIFO} fmt_fifo_t;
typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t;
typedef enum bit[2:0]{
FMT_REQ = 3'b000
,FMT_WIDTH_GRANT = 3'b001
,FMT_START = 3'b011
,FMT_SEND = 3'b010
,FMT_END = 3'b110
,FMT_IDLE = 3'b111
} fmt_state_t;
3.2.1 fmt_trans
模拟数据的比较和打印
class fmt_trans;
rand fmt_fifo_t fifo;
rand fmt_bandwidth_t bandwidth;
bit [9:0] length;
bit [31:0]data[];
bit [1:0] ch_id;
bit rsp;
constraint cstr{
soft fifo == MED_FIFO ;
soft bandwidth == MED_WIDTH;
};
function fmt_trans clone();
fmt_trans c = new();
c.fifo = this.fifo;
c.bandwidth = this.bandwidth;
c.length = this.length;
c.data = this.data;
c.ch_id = this.ch_id;
c.rsp = this.rsp;
return c;
endfunction
function string sprint();
string s ;
s = {s, $sformatf("========================\n")};
s = {s, $sformatf("fmt_trans object content is as below:\n")};
s = {s, $sformatf("fifo = %s:\n", this.fifo)};
s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};
s = {s, $sformatf("length = %s:\n", this.length)};
s = {s, $sformatf("rsp = %0d:\n", this.rsp )};
foreach (data[i]) s = {s,$sformatf("data[%0d] = %8x \n", i, this.data[i])};
s = {s, $sformatf("ch_id = %0d:\n", this.ch_id)};
s = {s, $sformatf("rsp = %0d:\n", this.rsp )};
s = {s, $sformatf("=======================\n")};
return s;
endfunction
//??为啥要做这两个比较呢,fmt_trans例化后的内容不应该就和fmt_trans中的一样吗
//当你创建一个类(如 fmt_trans)的实例(对象)时,每个实例通常拥有自己的一套成员变量。
//这意味着,即使两个对象是相同类的不同实例,它们的成员变量也可能拥有不同的值。
//检查它们是否具有相同的长度和通道ID,并且它们的数据数组是否相同
function bit compare(fmt_trans t);
string s;
compare = 1;//假设对象最初是相等的。
s = "\n============================\n";
s = {s,$sformatf("COMPARING fmt_trans object at time %0d \n", $time)};
if(this.length != t.length)begin//this.lengh指的是18行的length
compare = 0;
s = {s, $sformatf("sobj length %0d != tobj length %0d \n", this.length, t.length)};
end
if(thi.ch_id != t.ch_id)begin
compare = 0;
s = {s, $sformatf("sobj ch_id %0d != tobj ch_id %0d \n", this.ch_id, t.ch_id)};
end
foreach(this.data[i])begin
if(this.data[i] != t.data[i])begin
compare = 0;
s = {s, $sformatf("sobj data[%0d] %8x != tobj data[%0d] %8x \n",i, this.data[i], i, t.data[i])};
end
end
if(compare == 1) s = {s, "COMPARED SUCCESS!\n"};
else s = {s, "COMPARED FAILURED! \n "};
rpt_pkg::rpt_msg("[CMPOBJ]", s, rpt_pkg::INFO, rpt_pkg::MEDIUM);
endfunction
endclass
3.2.2 fmt_driver
模拟数据发送和接受行为
class fmt_driver;
local string name;
local virtual fmt_intf intf;
mailbox #(fmt_trans) req_mb;
mailbox #(fmt_trans) rsp_mb;//与generator通信
local mailbox #(logical[31:0]) fifo;//模拟formatter的下行数据
local int fifo_bound; //模拟的下行通道最多能接受多少数据
local int data_consum_period;// 反应下行数据输出的快慢。反比
function new(string name = "fmt_driver");
this.name = name;
this.fifo = new();
this.fifo_bound = 4096;
this.data_consum_period = 1;
endfunction
function void set_interface(virtual fmt_intf intf);
if(intf == null)
$error("interface handle is null");
else
this.intf = intf;
endfunction
task run();
fork
this.do_receive();
this.do_consume();
this.do_config();
this.do_reset();
join
//配置driver
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_FIOF : this.fifo_bound = 512 ;
ULTRA_FIFO : this.fifo_bound = 2048;
endcase
this.fifo = new(this.fifo_bound);
case(req.bandwidth) //为啥位宽低data_consum_period大呢?
//可能是因为在低带宽条件下,数据传输速率较慢,
//因此需要更长的周期来消费数据,以匹配数据的传输速率。
LOW_WIDTH : this.data_consum_period = 8 ;
MED_WIDTH : this.data_consum_period = 4 ;
HIGH_WIDTH : this.data_consum_period = 2 ;
ULTRA_WIDTH : this.data_consum_period = 1 ;
endcase
rsp = req.clone();
rsp.rsp = 1;//这里的 rsp 成员变量是一个标志位,用于表示某种状态或响应的有效性。
this.rsp_mb.put(rsp);
end
endtask
task do_reset();
forever begin
@(negedge intf.rstn)
intf.fmt_grant <= 0;
end
endtask
//模拟具有随机延迟的数据消费场景。按照随机的时间间隔取出数据
task do_consume();
logic[31:0] data;
forever begin
void'(this.fifo.try_get(data));// 使用 try_get 方法尝试从FIFO中取出一个数据项并赋值给 data 变量
//void'() 是SystemVerilog中的一个特性,它用来忽略 try_get 方法返回的状态值
repeat($urandom_range(1, this.data_consum_period)) @(posedge intf.clk);//这里反应消耗数据的快慢
//等待intf.clk的上升沿,repeat重复一次
end
endtask
endclass
//当数据接受的请求信号fmt_req拉高时,且FIFO深度足够时,fmt_grant拉高,表示接收方准备好接受数据了
//
task do_receive();//模拟接受数据
forever begin
@(posedge intf.fmt_req);
forever begin
@(posedge intf.clk);
if((this.fifo_bound-this.fifo.num()) >= intf.fmt_length)
//fifo_bound是选择的fifo的总深度
//检查当前FIFO的剩余空间是否足够存放即将到来的数据。
//如果剩余空间大于或等于 intf.fmt_length 指定的长度,则跳出内部循环。
break;//只跳出内部的这个forever。当fifo余量足够时,拉高grant
end
intf.drv_ck.fmt_grant <= 1;//表示接收方准备好接受数据了
@(posedge intf.fmt_start);//等待 intf.fmt_start 信号的下一个上升沿,
//这表示发送方已经准备好发送数据。
fork
begin
@(posedge intf.clk);
intf.drv_ck.fmt_grant <= 0;//维持一拍后就拉低,不再授权数据传输
end
join_none
repeat(intf.fmt_length)begin
@(nedge intf.clk);
this.fifo.put(intf.fmt_data);//连续放入数据
end
end
endtask
3.2.3 fmt_generator
创建、随机化、发送和接收 fmt_trans 类型的数据事务
class fmt_generator;
rand fmt_fifo_t fifo = MED_FIFO;
rand fmt_bandwidth_t bandwidth = MED_WIDTH;
mailbox #(fmt_trans) req_mb;//在 fmt_generator 类中,req_mb 用于发送 fmt_trans 类型的请求事务,
//而 rsp_mb 用于接收响应事务。这种方式使得 fmt_generator 可以作为一个独立的组件,
//模拟发送请求并等待响应,而不依赖于特定的通信协议实现细节。
mailbox #(fmt_trans) rsp_mb;
constraint cstr{
soft fifo == MED_FIFO;
soft bandwidth == MED_WIDTH;
}
function new();
this.req.mb = new();
this.rsp.mb = new();
endfunction
task start();
send_trans();
endtask
//产生事务并放到本地信箱
task send_trans();
fmt_trans req, rsp;
req = new();//为何这里是MED_FIFO呢,不等于就随机化失败?什么道理
//因为192行就是这样给的
assert (req.randomize with {local::fifo != MED_FIFO -> fifo == local::fifo
local::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;
})
else $fatal("[RNDFALL] formatter packet randomize failure!");
$display(req.sprint());
this.req_mb.put(req);//放入请求
this.rsp_mb.get(rsp);//取出响应
$display(rsp.sprint());
assert(rsp.rsp)//检查响应是否有效
else $error("[RSPERR] %0t error response received!", $time);
endtask
function string sprint();
string s;
s = {s, $sformatf("========================\n")};
s = {s, $sformatf("fmt_generator object content is as below:\n")};
s = {s, $sformatf("fifo = %s:\n", this.fifo)};
s = {s, $sformatf("bandwidth = %s:\n", this.bandwidth )};
s = {s, $sformatf("=======================\n" )};
return s;
endfunction
function void post_randomize();
string s;
s = {"AFTER RANDOMIZE \n", this.sprint()};
$display(s);
endfunction
endclass
3.2.4 fmt_monitor
用于观察和记录通过接口 fmt_intf 传输的数据事务
class fmt_monitor;
local string name;
local virtual fmt_intf intf;
mailbox #(fmt_trans) mon_mb;//用于存储监视到的 fmt_trans 类型的事务。
function new(string name = "fmt_generator");
this.name = name;
endfunction
function void set_interface(virtual fmt_intf intf);
if(intf == null)
$error("interface handle is null")
else
this.intf = intf;
endfunction//设置监视器要监视的接口
task run();
this.mon_trans();
endtask
task mon_trans();
fmt_trans m;
string s;
forever begin
@(posedge intf.mon_ck.fmt_start);//等待 intf.mon_ck.fmt_start 信号的上升沿,表示一个新事务的开始。
m = new();
m.length = intf.mon_ck.fmt_length;
m.ch_id = intf.mon_ck.fmt_chid;
m.data = new[m.length];//接口中获取事务的长度、通道ID和数据,并将它们赋值给 m 对象。
foreach(m.data[i])begin
@(posedge intf.clk);//在每个时钟上升沿从接口中获取数据,并将其存储在 m.data 数组中。
m.data[i] = intf.mon_ck.fmt_data;
end
mon_mb.put(m);
s = {s, $sformatf("========================\n")};
s = {s, $sformatf("%0t %s monitored a packet:\n", $time, this.name)};
s = {s, $sformatf("length = %0d:\n", m.length)};
s = {s, $sformatf("chid = %0d:\n", m.ch_id )};
foreach (m.data[i] s = {s,$sformatf("data[%0d] = %8x \n", i, m.data[i])};
s = {s, $sformatf("=======================\n" )};
$display(s);
end
endtask
endclass
3.2.5 fmt_agent
组合驱动器(fmt_driver)和监视器(fmt_monitor)组件
class fmt_agent;
local string name;
fmt_driver driver;
fmt_monitor monitor;
local virtual fmt_intf vif;
function new(string name = "fmt_agent");
this.name = name;
this.driver = new({name, ".driver"});
this.monitor= new({name, ".monitor"});
endfunction
function void set_interface(virtual fmt_intf vif);
this.vif = vif;
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
task run();
fork
driver.run();
monitor.run();
join
endtask
endclass
3.3 mcdf_pkg
import chnl_pkg::*;
import reg_pkg ::*;
import arb_pkg ::*;
import fmt_pkg ::*;
import rpt_pkg ::*;
typedef struct packed{
bit[2:0] lenl ;//后面用来规定各寄存器通道的数据包长度,优先级,使能信号,可写余量
bit[1:0] prio ;
bit en ;
bit[7:0] avail;
}mcdf_reg_t
typedef enum{RW_LEN, RW_PRIO, RW_EN, RD_AVAIL}mcdf_field_t;//寄存器的不同比特位代表不同的功能,详见蓝皮书376页
3.3.1 mcdf_refmod
用于测试或模拟MCDF模块的寄存器行为,形成一个参考模型
class mcdf_refmod;
local virtual mcdf_intf intf;
local string name;
mcdf_reg_t regs[3];
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);
if(t.addr[31 :4] == 0 && t.cmd == 'WRITE)begin
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;
mon_data_t it;
bit[2:0] len;
forever begin
this.in_mbs[id].peek(it);
ot = new();
len = this.get_field_value(id, RW_LEN);
ot.length = len > 3 ? 32:4 << len;//根据len的值计算数据包的长度。
//为什么数据包长度这样定义 //如果len大于3,则长度为32,否则长度len左移4位。
//因为第50行规定第4到6位存放数据包长度
ot.data = new[ot.length];//根据计算出的长度,动态分配一个整型数组给ot.data,
//用于存储数据包的数据。
ot.ch_id = id;
foreach(ot.data[i])begin
this.in_mbs[id].get(it);//从信箱中取数据,给到it
ot.data[i] = it.data;
end
this.out_mbs[id].put(ot);//将格式化后的事务ot放入到输出mailbox(out_mbs)中
end
endtask
function int get_field_value(int id, mcdf_field_t f);//mcdf_field_t是18行的枚举类型
case(f)
RW_LEN : return regs[id].len ;//这四个case分别代表什么?
RW_PRIO : return regs[id].prio ;
RW_EN : return regs[id].en ;
RD_AVAIL: return regs[id].avail;
endcase
endfunction
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;//应该还是和task do_reg_update有关
end
end
endtask
function void set_interface(virtual mcdf_intf intf);
if(intf == null)
$error("interface handle is null");
else
this.intf = intf;
endfunction
endclass
3.3.2 mcdf_checker
将refmod的数据与fmt的数据进行对比
class mcdf_checker;
local string name;
local int err_count;
local int total_count;
local int chl_count[3];
local virtual mcdf_intf intf;
local refmod mcdf_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;
foreach(this.chnl_mbs[i]) this.chnl_mbs[i] = new();
this.fmt_mb = new();
this.reg_mb = new();
this.refmod = new();
foreach (this.refomd.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");
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);//formatter中的数据
this.exp_mbs[mont.ch_id].get(expt);//期望的数据
cmp = mont.compare(expt);//调用的是fmt_pkg里面的compare函数
this.total_count++;//是拿来计数什么的?
this.chnl_count[mont.ch_id]++;//是拿来计数什么的?
if(cmp == 0)begin//不相等
this.err_count++;
rpt_pkg::rpt_msg("[CMP FAIL]", $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("[CMP SUCD]", $sformatf("%0t %0dth times comparing and succeeded! MCDF monitored output packet is different 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]) 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 = "\n--------------------------------------------\n";
rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
endfunction
endclass
3.3.3 mcdf_env
用于构建和运行MCDF模块的测试环境
class mcdf_env;
chnl_agent chnl_agts[3];
reg_agent reg_agt;
fmt_agent fmt_agt;
mcdf_checker chker;
mcdf_coverage cvrg;
protected string name;
function new(string name = "mcdf_env");
this.name = name;
this.chker = new();
foreach(chnl_agts[i])begin
this.chnl_agts[i] = new($sformatf("chnl_agts[%0d]", i));
this.chnl_agts[i].monitor.mon_mb = this.chker.chnl_mbs[i];
end
this.reg_agt = new("reg_agt");
this.reg_agt.monitor.mon_mb = this.chker_reg_mb;
this.fmt_agt = new("fmt_agt");
this.fmt_agt.monitor.mon_mb = this.chker.fmt.mb;
this.cvrg = new();
$display("%s instantiated and connected objects", this.name);
endfunction
virtual task run();
$display($sformatf("*****************%s started***********", this.name));
this.do_config();
fork
this.chnl_agts[0].run)();
this.chnl_agts[1].run)();
this.chnl_agts[2].run)();
this.reg_agt.run)();
this.fmt_agt.run)();
this.chker.run();
this.cvrg.run();
join
endtask
virtual function void do_config();//之前式用do_config来配置generator。但是
endfunction //现在将其移植到mcdf_base_test中来进行配置了
virtual function void do_report();
this.chker.do_report();
this.cvrg.do_reporet();
endfunction
endclass
3.3.4 mcdf_base_test
测试模板
class mcdf_base_test;
chnl_generator chnl_gens[3];
reg_generator reg_gen;
fmt_generator fmt_gen;
mcdf_env env;
protected string name;
function new(string name = "mcdf_base_test");
this.name = name;
this.env = new(env);
//将每个gens给到env进行代理
foreach(this.chnl_gens[i])begin
this.chnl_gens[i] = new();
this.env.chnl_agts[i].driver.req_mb = this.chnl_gens[i].req_mb;
this.env.chnl_agts[i].driver.rsq_mb = this.chnl_gens[i].rsq_mb;
end
this.reg_gen = new();
this.env.reg_agt.driver.req_mb = this.reg_gen.req_mb;
this.env.reg_agt.driver.rsp_mb = this.reg_gen.rsp_mb;
this.fmt_gen = new();
this.env.fmt_agt.driver.req_mb = this.fmt_gen.req_mb;
this.env.fmt_agt.driver.rsp_mb = this.fmt_gen.rsp_mb;
$display("%s instantiated and connected objects", this.name);
endfunction
virtual task run();
fork
env.run();
join_none
rpt_pkg::rpt_msg("[TEST]",
$sformatf("===========%s AT TIME 0%t STARTED=======", this.name,$time),
rpt_pkg::INFO;
rpt_pkg::HIGH);
this.do_reg();
this.do_formatter();
this.do_data();
rpt_pkg::rpt_msg("[TEST]",
$sformatf("===========%s AT TIME 0%t FINISHED=======", this.name,$time),
rpt_pkg::INFO;
rpt_pkg::HIGH);
this.do_report();
$finish();
endtask
//do register configuration
virtual task do_reg();
endtask
//do external formatter down stream slave configuration
virtual task do_formatter();
endtask
//do data transition from 3 channel slaves
virtual task do_data();
endtask
//do simulation SUMMARY
virtual function void do_report();
this.env.do_report();
rpt_pkg::do_report();
endfunction
virtual function void set_interface( virtual chnl_intf ch0_vif
,virtual chnl_agent ch1_vif
,virtual chnl_agent ch2_vif
,virtual reg_intf reg_vif
,virtual fmt_intf fmt_vif
,virtual mcdf_intf mcdf_vif);
this.env.chnl_agts[0].set_interface(ch0_vif);
this.env.chnl_agts[1].set_interface(ch1_vif);
this.env.chnl_agts[2].set_interface(ch2_vif);
this.env.reg_agt.set_interface(reg_vif);
this.env.fmt_agt.set_interface(fmt_vif);
this.env.chker.set_interface(mcdf_vif);
this.env.cvrg.set_interface('{ch0_vif, ch1_vif, ch2_vif,}, reg_vif, arb_vif, fmt_vif, mcdf_vif);
endfunction
virtual function bit diff_value(int val1, int val2, string id ="value_compare");
if(val1 != val2)begin
rpt_pkg::rpt_msg("[CMP ERR]",
$sformatf("ERROR! %s val1 %8x != val2 %8x", id, val1, val2),
rpt_pkg::ERROR;
rpt_pkg::TOP);
return 0;
end
else begin
rpt_pkg::rpt_msg("[CMP SUC]",
$sformatf("SUCCESS! %s val1 %8x == val2 %8x", id, val1, val2),
rpt_pkg::INFO;
rpt_pkg::HIGH);
return 1;
end
endfunction
virtual task idle_reg();
void'(reg_gen.randomize() with {cmd == 'IDLE; addr == 0; data == 0;});
reg_gen.start();
endtask
virtual task write_reg(bit[7:0]addr, bit[31:0] data);
void'(reg_gen.randomize() with {cmd == 'WRITE; addr == local::addr; data == local::data;});
reg_gen.start();
endtask
virtual task read_reg(bit[7:0]addr, output bit[31:0] data);
void'(reg_gen.randomize() with {cmd == 'READ; addr == local::addr;});
reg_gen.start();
data = reg_gen.data;
endtask
endclass
3.3.5 mcdf_data_consistence_basic_test
读写数据一致性检测,检查寄存器读进去和写出来的数据是否一致
class mcdf_data_consistence_basic_test extends mcdf_base_test;
function new(string name = "mcdf_data_consistence_basic_test");
super.new(name);
endfunction
//检查读写是否一致
task do_reg();
bit[31:0] wr_val, rd_val;
//slv0 with len=8,prior=0,en=1
wr_val = (1<<3)+(0<<1)+1;//为什么要这样设置呢
this.wrtie_reg('SLV0_RW_ADDR, wr_val);//写进去
this.read_reg('SLV0_RW_ADDR, rd_val);//读回来
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));//把写进去的值和读回来的值做一个比较
//看看写进去的值是否合法
//slv1 with len=16,prior=1,en=1
wr_val = (2<<3)+(1<<1)+1;
this.wrtie_reg('SLV1_RW_ADDR, wr_val);
this.read_reg('SLV1_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
//slv2 with len=32,prior=2,en=1
wr_val = (3<<3)+(2<<1)+1;
this.wrtie_reg('SLV2_RW_ADDR, wr_val);
this.read_reg('SLV2_RW_ADDR, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
//send IDLE command
this.idle_reg();
endtask
//配置格式化器(fmt_gen)的随机值,包括 FIFO 大小和带宽。
task do_formatter();
void'(fmt_gen.randomize()with{fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
fmt_gen.start();
endtask
//生成数据
task do_data();
void'(chnl_gens[0].randomize() with {ntrans == 100; ch_id == 0; data_nidles == 0; pkt_nidles == 1; data_size == 8;});
void'(chnl_gens[1].randomize() with {ntrans == 100; ch_id == 1; data_nidles == 1; pkt_nidles == 1; data_size == 16;});
void'(chnl_gens[2].randomize() with {ntrans == 100; ch_id == 2; data_nidles == 2; pkt_nidles == 1; data_size == 32;});
fork
chnl_gens[0].start();
chnl_gens[1].start();
chnl_gens[2].start();
join
#10us;//wait until all data haven been transfered through MCDF
endtask
endclass
3.3.5 mcdf_reg_write_read_test
寄存器读写测试,包括对控制读写寄存器和状态只读寄存器的测试
class mcdf_reg_write_read_test extends mcdf_base_test;
function new (string name = "mcdf_reg_write_read_test");
super.new(name);
endfunction
virtual task do_reg();
bit [31:0]wr_val, rd_val;
bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};//读写寄存器
bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};//只读寄存器
int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;//PAC_LEN_WIDTH是定义在param_def中的常数
bit[31:0] check_pattern[] = '{32'h0000_FFC0, 32'hFFFF_0000};//类似于地址检查库
foreach (chnl_rw_addrs[i])begin
foreach(check_pattern[j])begin
wr_val = check_pattern[j];
this.write_reg(chnl_rw_addrs[i], wr_val);
this.read_reg(chnl_rw_addr[i], rd_val);
void'((this.diff_value(wr_val & ((1<<pwidth)-1), rd_val));//为什么,(1<<pwidth)-1=31
end//按位与操作。这种掩码结果的二进制形式会有pwidth个低位是1。即保留wr_val的低pwidth位,其余位清零
end//如果wr_val是32'h0000_FFC0,且pwidth是5,那么结果是32'h0000_0010。
//仅比较有效位(低pwidth位),忽略无关的高位。这在寄存器操作中非常重要,
//因为某些寄存器的高位可能是未定义或保留位,比较时应避免这些位的影响。
foreach(chnl_ro_addrs[i])begin
wr_val = 32'hFFFF_FF00;//由于是只读寄存器,因此写进入的值是确定的
this.write_reg(chnl_ro_addrs[i],wr_val);
this.read_reg(chnl_ro_addrs[1], rd_val);
void'(this.diff_value(0, rd_val & wr_val));//这用于将函数调用的返回值转换为 void,实际上是忽略函数的返回值。
end//对于只读寄存器,我们期望写入操作不会改变寄存器的值。
//因此,我们预期从只读寄存器读取到的值应该是0(这就是为什么第一个参数是0)。
this.idle_reg();
endtask
endclass
3.3.6 mcdf_reg_illegal_access_tets
寄存器稳定性测试。
class mcdf_reg_illegal_access_tets extends mcdf_base_test;
function new(string name = "mcdf_reg_illegal_access_tets");
super.new(name);
endfunction
task do_reg();
bit [31:0]wr_val, rd_val;
bit [7:0] chnl_rw_addrs[] = '{'SLV0_RW_ADDR, 'SLV1_RW_ADDR, 'SLV2_RW_ADDR};
bit [7:0] chnl_ro_addrs[] = '{'SLV0_R_ADDR, 'SLV1_R_ADDR, 'SLV2_R_ADDR};
int pwidth = 'PAC_LEN_WIDTH + 'PRIO_WIDTH + 1;
bit[31:0] check_pattern[] = '{((1<<pwidth)-1), 0, ((1<<pwidth)-1)};
foreach (chnl_rw_addrs[i])begin
foreach(check_pattern[j])begin
wr_val = check_pattern[j];
this.write_reg(chnl_rw_addrs[i],wr_val);
this.read_reg(chnl_rw_addrs[i],rd_val);
void'(this.diff_value(wr_val, rd_val));
end
end
foreach(chnl_ro_addrs[i])begin
this.read_reg(chnl_ro_addrs[i],rd_val);
end
this.idle_reg();
endtask
endclass
3.3.7 mcdf_arbiter_priority_test
arbiter优先级测试
如果各数据通道优先级相同,那么arbiter应该采取轮询机制从各个通道接受数据,如果优先级不同,先从高优先级的通道接收数据
class mcdf_arbiter_priority_test extends mcdf_base_test;
function new (string name = "mcdf_arbiter_priority_test");
super.new(name)
endfunction
task do_arbiter_priority_check();
int id;
forever begin
@(posedge this.arb_vif.clk iff (this.arb_vif.rstn && this.arb_vif.mon_ck.f2a_id_req===1));
id = this.get_slave_id_with_prio();//得到三个slave的最高优先级
if(id >= 0) begin
@(posedge this.arb_vif.clk);
if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)//acks【i】是设计的硬件信号的slvae通道选择
rpt_pkg::rpt_msg("[CHKERR]",
$sformatf("ERROR! %0t arbiter received f2a_id_req===1 and channel[%0d] raising request with high priority, but is not granted by arbiter", $time, id),
rpt_pkg::ERROR,
rpt_pkg::TOP);
end
end
endtask
//模拟arbiter仲裁逻辑
function int get_slave_id_with_prio();
int id=-1;
int prio=999;
//寻找最高优先级
foreach(this.arb_vif.mon_ck.slv_prios[i]) begin
if(this.arb_vif.mon_ck.slv_prios[i] < prio && this.arb_vif.mon_ck.slv_reqs[i]===1) begin
id = i;
prio = this.arb_vif.mon_ck.slv_prios[i];
end
end
return id;
endfunction
endclass
3.3.8 mcdf_channel_disbale_test
数据通道开关检测
在数据通道关闭的情况下检测数据写入是否通过
class mcdf_channel_disbale_test extends mcdf_base_tese;
function new (string name = "mcdf_channel_disbale_test");
super.new(name)
endfunction
task do_mcdf_channel_disable_test (int id)
forever begin
@(posedge this.mcdf_vif.clk iff(this.mcdf_vif.rstn && this.mcdf_vif.mon_ck.chnl_en[id] == 0));
if (this.chnl_vifs[id].mon_ck.ch_valid == 1 && this.chnl_vifs[id].mon_ck.ch_ready == 1)
rpt_pkg::rpt_msg("[CHECKER]",
$sformatf("ERROR! %0t whern channel disabled, ready signal raised while valid signal is high", $time),
rpt_pkg::ERROR,
rpt_pkg::TOP);
end
endtask
endclass
3.3.9 mcdf_coverage
覆盖率收集
class mcdf_coverage;
local virtual chnl_intf chnl_vifs[3];
local virtual arb_intf arb_vif;
local virtual mcdf_intf mcdf_vif;
local virtual reg_intf reg_vif;
local virtual fmt_intf reg_vif;
local string name;
local int delay_req_to_grant;
//对所有控制、状态寄存器的读写进行测试。
covergroup cg_mcdf_reg_write_read;
addr: coverpoint reg_vif.mon_ck.cmd_addr{
type_option.weight = 0;//权重设置为0,表示不关心此coverpoint的覆盖率,否则cross以后会出现很多的bin
bins slv0_rw_addr = {'SLV0_RW_ADDR};
bins slv1_rw_addr = {'SLV1_RW_ADDR};
bins slv2_rw_addr = {'SLV2_RW_ADDR};
bins slv0_r_addr = {'SLV0_R_ADDR };
bins slv1_r_addr = {'SLV1_R_ADDR };
bins slv2_r_addr = {'SLV2_R_ADDR };
}//8bit地址实际上会生成256个bin,256-6=250会被平均分配到这6个bin中,
//但默认max为64个bin,所以剩下的250要分配到64-6=58个bin里面
//建议如果要做cross,那需要被cross的这些功能点都weight=0,然后在cross中重新bin
cmd: coverpoint reg_vif.mon_ck.cmd{
type_option.weight = 0;
bins write = {'WRITE};
bins read = {'READ};
bins idle = {'IDLE};
}//cmd是2bit的,所以还有一个默认的bin
cmdXaddr: cross cmd, addr{
bins slv0_rw_addr = binsof (addr.slv0_rw_addr);
bins slv1_rw_addr = binsof (addr.slv1_rw_addr);
bins slv2_rw_addr = binsof (addr.slv2_rw_addr);
bins slv0_r_addr = binsof (addr.slv0_r_addr );
bins slv1_r_addr = binsof (addr.slv1_r_addr );
bins slv2_r_addr = binsof (addr.slv2_r_addr );
bins write = (cmd.write);
bins read = (cmd.read );
bins idle = (cmd.idle );
//因为之前设置权重为0,所以需要再一次的声明,这样可以只关心所需要的bin即可,不会生成没用的bin
bins write_slv0_rw_addr = binsof (cmd.write) && binsof (addr.slv0_rw_addr);
bins write_slv1_rw_addr = binsof (cmd.write) && binsof (addr.slv1_rw_addr);
bins write_slv2_rw_addr = binsof (cmd.write) && binsof (addr.slv2_rw_addr);
bins read_slv0_rw_addr = binsof (cmd.read ) && binsof (addr.slv0_rw_addr);
bins read_slv1_rw_addr = binsof (cmd.read ) && binsof (addr.slv1_rw_addr);
bins read_slv2_rw_addr = binsof (cmd.read ) && binsof (addr.slv2_rw_addr);
bins read_slv0_r_addr = binsof (cmd.read ) && binsof (addr.slv0_r_addr );
bins read_slv1_r_addr = binsof (cmd.read ) && binsof (addr.slv1_r_addr );
bins read_slv2_r_addr = binsof (cmd.read ) && binsof (addr.slv2_r_addr );
}
endgroup
//对非法地址进行读写测试,对控制寄存器的保留域进行读写测试,对状态寄存器进行写操作测试。
covergroup cg_mcdf_reg_illegal_access;
addr: coverpoint reg_vif.mon_ck.cmd_addr {
type_option.weight = 0;
bins legal_rw = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};//合法读写寄存器地址
bins legal_r = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; //合法读地址范围
/*这里做了一些简化,这里对任意一个通道的读写寄存器进行了操作就满足覆盖率要求*/
/**但是实际上如果要对每一个通道的寄存器都进行覆盖率检测,那应该写为数组形式*/
/*bins legal_rw[] = {`SLV0_RW_ADDR, `SLV1_RW_ADDR, `SLV2_RW_ADDR};
bins legal_r[] = {`SLV0_R_ADDR, `SLV1_R_ADDR, `SLV2_R_ADDR}; */
bins illegal = {[8'h20:$], 8'hC, 8'h1C}; //非法地址范围
}
cmd: coverpoint reg_vif.mon_ck.cmd {
type_option.weight = 0;
bins write = {`WRITE};
bins read = {`READ};
}
//写数据覆盖点
wdata: coverpoint reg_vif.mon_ck.cmd_data_m2s {
type_option.weight = 0;
//数据合法范围,0~111111,即bit(0)使能信号、bit(2:1)优先级、bit(5:3)数据包长度都可以写
bins legal = {[0:'h3F]};
//数据非法范围,bit(31:6)保留位,无法写入
bins illegal = {['h40:$]};
}
//读数据覆盖点
rdata: coverpoint reg_vif.mon_ck.cmd_data_s2m {
type_option.weight = 0;
//读数据合法范围,0~11111111,即bit(7:0)上行数据从端FIFO的可写余量
bins legal = {[0:'hFF]};
illegal_bins illegal = default;
}
//交叉覆盖
cmdXaddrXdata: cross cmd, addr, wdata, rdata {
bins addr_legal_rw = binsof(addr.legal_rw);
bins addr_legal_r = binsof(addr.legal_r);
bins addr_illegal = binsof(addr.illegal);
bins cmd_write = binsof(cmd.write);
bins cmd_read = binsof(cmd.read);
bins wdata_legal = binsof(wdata.legal);
bins wdata_illegal = binsof(wdata.illegal);
bins rdata_legal = binsof(rdata.legal);
bins write_illegal_addr = binsof(cmd.write) && binsof(addr.illegal);//对非法地址进行写操作
bins read_illegal_addr = binsof(cmd.read) && binsof(addr.illegal);//对非法地址进行写操作
bins write_illegal_rw_data = binsof(cmd.write) && binsof(addr.legal_rw) && binsof(wdata.illegal);//对读写寄存器合法地址写入非法数据
bins write_illegal_r_data = binsof(cmd.write) && binsof(addr.legal_r) && binsof(wdata.illegal);//对只读寄存器合法地址写入非法数据
}
endgroup
//对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过。
covergroup cg_channel_disable;
ch0_en: coverpoint mcdf_vif.mon_ck.chnl_en[0] {
type_option.weight = 0;
wildcard bins en = {1'b1};//这里为什么要用wildcard来修饰
wildcard bins dis = {1'b0};
//wildcard修饰符用于位宽不定的信号,它使得值的匹配忽略位数上的差异。
//具体来说,wildcard可以用来简化匹配条件,特别是在信号中有不关心的位时
//wildcard可以让表达式中的任何x、z或?被当作0或1的通配符
}
ch1_en: coverpoint mcdf_vif.mon_ck.chnl_en[1] {
type_option.weight = 0;
wildcard bins en = {1'b1};//对应使能
wildcard bins dis = {1'b0};//对于禁用
}
ch2_en: coverpoint mcdf_vif.mon_ck.chnl_en[2] {
type_option.weight = 0;
wildcard bins en = {1'b1};
wildcard bins dis = {1'b0};
}
ch0_vld: coverpoint chnl_vifs[0].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
ch1_vld: coverpoint chnl_vifs[1].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
ch2_vld: coverpoint chnl_vifs[2].mon_ck.ch_valid {
type_option.weight = 0;
bins hi = {1'b1};
bins lo = {1'b0};
}
//采集当valid拉高时,代表要进行写操作,en拉低代表数据通道关闭
chenXchvld: cross ch0_en, ch1_en, ch2_en, ch0_vld, ch1_vld, ch2_vld {
bins ch0_en = binsof(ch0_en.en );
bins ch0_dis = binsof(ch0_en.dis);
bins ch1_en = binsof(ch1_en.en );
bins ch1_dis = binsof(ch1_en.dis);
bins ch2_en = binsof(ch2_en.en );
bins ch2_dis = binsof(ch2_en.dis);
bins ch0_hi = binsof(ch0_vld.hi);
bins ch0_lo = binsof(ch0_vld.lo);
bins ch1_hi = binsof(ch1_vld.hi);
bins ch1_lo = binsof(ch1_vld.lo);
bins ch2_hi = binsof(ch2_vld.hi);
bins ch2_lo = binsof(ch2_vld.lo);
bins ch0_en_vld = binsof(ch0_en.en ) && binsof(ch0_vld.hi);
bins ch0_dis_vld = binsof(ch0_en.dis) && binsof(ch0_vld.hi);
bins ch1_en_vld = binsof(ch1_en.en ) && binsof(ch1_vld.hi);
bins ch1_dis_vld = binsof(ch1_en.dis) && binsof(ch1_vld.hi);
bins ch2_en_vld = binsof(ch2_en.en ) && binsof(ch2_vld.hi);
bins ch2_dis_vld = binsof(ch2_en.dis) && binsof(ch2_vld.hi);
}
endgroup
//将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试。
covergroup cg_arbiter_priority;
ch0_prio: coverpoint arb_vif.mon_ck.slv_prios[0] {
bins ch_prio0 = {0};
bins ch_prio1 = {1};
bins ch_prio2 = {2};
bins ch_prio3 = {3}; //为什么会有4个优先级呢
}
ch1_prio: coverpoint arb_vif.mon_ck.slv_prios[1] {
bins ch_prio0 = {0};
bins ch_prio1 = {1};
bins ch_prio2 = {2};
bins ch_prio3 = {3};
}
ch2_prio: coverpoint arb_vif.mon_ck.slv_prios[2] {
bins ch_prio0 = {0};
bins ch_prio1 = {1};
bins ch_prio2 = {2};
bins ch_prio3 = {3};
}
endgroup
//测试从formatter发送出来的数据包长度是否同对应通道寄存器的配置一致
covergroup cg_formatter_length;
id: coverpoint fmt_vif.mon_ck.fmt_chid {
bins ch0 = {0};
bins ch1 = {1};
bins ch2 = {2};
illegal_bins illegal = default;
}
length: coverpoint fmt_vif.mon_ck.fmt_length {
bins len4 = {4};
bins len8 = {8};
bins len16 = {16};
bins len32 = {32};
illegal_bins illegal = default;
}
endgroup
//在req拉高以后,grant至少应该在2个时钟周期后拉高,以此来模拟下行从端数据余量不足的情况
covergroup cg_formatter_grant();
delay_req_to_grant: coverpoint this.delay_req_to_grant {
bins delay1 = {1};// delay_req_to_grant是一个自定义的软件信号,在task do_formater_sample()中被定义
bins delay2 = {2};//数字代表req与grant信号之间延迟的拍数字
bins delay3_or_more = {[3:10]};
illegal_bins illegal = {0};//req与grant信号之间没有延迟则非法
}
endgroup
function new(string name="mcdf_coverage");
this.name = name;
this.cg_mcdf_reg_write_read = new();
this.cg_mcdf_reg_illegal_access = new();
this.cg_channel_disable = new();
this.cg_arbiter_priority = new();
this.cg_formatter_length = new();
this.cg_formatter_grant = new();
endfunction
task run();
fork
this.do_reg_sample();
this.do_channel_sample();
this.do_arbiter_sample();
this.do_formater_sample();
join
endtask
//寄存器采样
task do_reg_sample();
forever begin
@(posedge reg_vif.clk iff reg_vif.rstn);
this.cg_mcdf_reg_write_read.sample();
this.cg_mcdf_reg_illegal_access.sample();
end
endtask
//数据通道采样
task do_channel_sample();
forever begin对于每一个时钟上升沿,至少有一个channel的valid信号为1时,即有数据发送进来时,才对disable做采样
@(posedge mcdf_vif.clk iff mcdf_vif.rstn);
if(chnl_vifs[0].mon_ck.ch_valid===1
|| chnl_vifs[1].mon_ck.ch_valid===1
|| chnl_vifs[2].mon_ck.ch_valid===1)
this.cg_channel_disable.sample();
end
endtask
//对优先级做采样
task do_arbiter_sample();
forever begin对于每一个时钟的上升沿,至少有一个channel的req为1,即至少有一个channel要发送数据了,才对其优先级做采样
@(posedge arb_vif.clk iff arb_vif.rstn);
if(arb_vif.slv_reqs[0]!==0 || arb_vif.slv_reqs[1]!==0 || arb_vif.slv_reqs[2]!==0)
this.cg_arbiter_priority.sample();
end
endtask
//对grant信号做采样
task do_formater_sample();
fork
forever begin//对于每一个时钟上升沿,当req为1时,对数据的长度做采样
@(posedge fmt_vif.clk iff fmt_vif.rstn);
if(fmt_vif.mon_ck.fmt_req === 1)
this.cg_formatter_length.sample();
end
forever begin
//当req拉高时,要通过变量delay_req_to_grant来计算延迟,对grant做采样
@(posedge fmt_vif.mon_ck.fmt_req);
this.delay_req_to_grant = 0;/*req信号与grant信号之间的延迟*/
forever begin//只有grant拉高时,才对grant做采样,进而通过delay_req_to_grant的值得到延迟
if(fmt_vif.fmt_grant === 1) begin
this.cg_formatter_grant.sample();
break;/*当fmt_vif.mon_ck.fmt_req信号和fmt_vif.fmt_grant信号都被拉高时,delay_req_to_grant为0,此时为非法状态,被采样*/
end
else begin
@(posedge fmt_vif.clk);
this.delay_req_to_grant++;/*每经过一拍,delay_req_to_grant加1*/
end
end
end
join
endtask
function void do_report();
string s;
s = "\n---------------------------------------------------------------\n";
s = {s, "COVERAGE SUMMARY \n"};
s = {s, $sformatf(" total coverage: %.1f \n", $get_coverage())}; //$get_coverage可以得到总体的全局覆盖率
s = {s, $sformatf(" cg_mcdf_reg_write_read coverage: %.1f \n" , this.cg_mcdf_reg_write_read.get_coverage())}; //get_coverage获取单个covergroup实例的覆盖率
s = {s, $sformatf(" cg_mcdf_reg_illegal_access coverage: %.1f \n", this.cg_mcdf_reg_illegal_access.get_coverage())};
s = {s, $sformatf(" cg_channel_disable_test coverage: %.1f \n" , this.cg_channel_disable.get_coverage())};
s = {s, $sformatf(" cg_arbiter_priority_test coverage: %.1f \n" , this.cg_arbiter_priority.get_coverage())};
s = {s, $sformatf(" cg_formatter_length_test coverage: %.1f \n" , this.cg_formatter_length.get_coverage())};
s = {s, $sformatf(" cg_formatter_grant_test coverage: %.1f \n" , this.cg_formatter_grant.get_coverage())};
s = {s, "---------------------------------------------------------------\n"};
rpt_pkg::rpt_msg($sformatf("[%s]",this.name), s, rpt_pkg::INFO, rpt_pkg::TOP);
endfunction
virtual function void set_interface(virtual chnl_intf ch_vifs[3]
,virtual reg_intf reg_vif
,virtual arb_intf arb_vif
,virtual fmt_intf fmt_vif
,virtual mcdf_intf mcdf_vif
);
this.chnl_vifs = ch_vifs;
this.arb_vif = arb_vif;
this.reg_vif = reg_vif;
this.fmt_vif = fmt_vif;
this.mcdf_vif = mcdf_vif;
if(chnl_vifs[0] == null || chnl_vifs[1] == null || chnl_vifs[2] == null)
$error("chnl interface handle is NULL, please check if target interface has been intantiated");
if(arb_vif == null)
$error("arb interface handle is NULL, please check if target interface has been intantiated");
if(reg_vif == null)
$error("reg interface handle is NULL, please check if target interface has been intantiated");
if(fmt_vif == null)
$error("fmt interface handle is NULL, please check if target interface has been intantiated");
if(mcdf_vif == null)
$error("mcdf interface handle is NULL, please check if target interface has been intantiated");
endfunction
endclass
04 覆盖率收集
运行完这几个test之后,得到覆盖率收集表如下