在之前的Lab3中,通过一个初具规模的MCDT的验证环境,学习到:
- 验证环境按照隔离的观念,应分为硬件DUT,软件验证环境,和处于信号媒介的接口interface。
- 对于软件验证环境,需要经历建立阶段(build),连接阶段(connect),产生激励阶段(generate)和发送激励阶段(transfer),只有当所有的激励发送完毕并且比较完全之后,才可以结束该测试。
从Lab4开始,要验证更大的子系统,即MCDF。与MCDT相比,MCDF主要添加了寄存器控制和状态显示功能,同时也添加了一个重要的数据整形功能。不同的package之间的功能是独立的,同一个package中的各个验证组件的功能也是独立的。
一、本次实验的测试功能点
测试功能点 | 测试内容 | 测试通过标准 | 测试类名 |
---|---|---|---|
寄存器读写测试 | 所有控制寄存器的读写测试;所有状态寄存器的读写测试 | 读写值是否正确 | mcdf_reg_write_read_test |
寄存器稳定性测试 | 非法地址读写;对控制寄存器的保留域进行读写;对状态寄存器进行写操作 | 通过写入和读出,确定寄存器的值时预期值,而不是紊乱值,同时非法寄存器操作也不能影响MCDF的整体功能 | mcdf_reg_illegal_access_test |
数据通道开关测试 | 对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过 | 在数据通过关闭情况下,数据无法写入,同时ready信号应该保持为低,表示不接收数据,但又能使得数据不被丢失,因此数据只会停留在数据通道端口 | mcdf_channel_disable_test |
优先级测试 | 将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试 | 如果优先级相同,那么arbiter应该采取轮询机制从各个通道接受数据,如果优先级不同,那么arbiter应该先接收高优先级通道的数据,同时最终所有的数据都应该从MCDF发送出来 | mcdf_arbiter_priority_test |
发包长度测试 | 将不同数据通道随机配置为各自的长度,在数据通道使能的情况下进行测试 | 从formatter发送出来的数据包长度应该同对于通道寄存器的配置值保持一一对应,同时数据也应该保持完整 | mcdf_formatter_length_test |
下行从端低带宽测试 | 将MCDF下行数据接收端设置为小存储量,低带宽的类型,由此使得在由formatter发送出数据之后,下行从端有更多的机会延迟grant信号的置位,用来模拟真实场景 | 在req拉高之后,grant应该在至少两个时钟周期以后拉高,以此来模拟下行从端数据余量不足的情况,当这种激励时序发生10次之后,可以停止测试 | mcdf_formatter_grant_test |
二、验证环境框图
三、代码实现
mcdf_pkg.sv
mcdf_pkg
包实现了mcdf_checker
功能,reg_agent
、chnl_agent
、fmt_agent
三个组件中的monitor
都会把监测到的数据交给mcdf_checker
。chnl_agent
监测到的数据是单个的数据,即32bit的数据,而fmt_agent
监测到的数据是一个数据包,意味着chnl_mb
和fmt_mb
里面的数据格式不一样,所以在比较之前,要将reg和channel监测到的数据通过mcdf_refmod
这个参考模型来做整形转化
mcdf_reg_t
结构体,用来描述寄存器数据的构成,包括长度、优先级、使能以及存储余量。
typedef struct packed {
bit[2:0] len;
bit[1:0] prio;
bit en;
bit[7:0] avail;
} mcdf_reg_t;
mcdf_refmod
类
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[7: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;
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
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
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
mcdf_checker
类
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;
foreach(this.chnl_mbs[i]) this.chnl_mbs[i] = new();
this.fmt_mb = new();
this.reg_mb = new();
this.refmod = new();
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);
this.exp_mbs[mont.ch_id].get(expt);
cmp = mont.compare(expt);
this.total_count++;
this.chnl_count[mont.ch_id]++;
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
mcdf_env
类
class mcdf_env;
chnl_agent chnl_agts[3];
reg_agent reg_agt;
fmt_agent fmt_agt;
mcdf_checker chker;
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;
$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();
join
endtask
virtual function void do_config();
endfunction
virtual function void do_report();
this.chker.do_report();
endfunction
endclass
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");
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.rsp_mb = this.chnl_gens[i].rsp_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;
rpt_pkg::logname = {this.name, "_check.log"};
rpt_pkg::clean_log();
$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 %0t 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 %0t 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_intf ch1_vif
,virtual chnl_intf 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);
endfunction
virtual function bit diff_value(int val1, int val2, string id = "value_compare");
if(val1 != val2) begin
rpt_pkg::rpt_msg("[CMPERR]",
$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("[CMPSUC]",
$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
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, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;
this.write_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, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;
this.write_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, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;
this.write_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
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==4; data_size==16;});
void'(chnl_gens[2].randomize() with {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; 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
rpt_pkg.sv
package rpt_pkg;
typedef enum {INFO, WARNING, ERROR, FATAL} report_t;
typedef enum {LOW, MEDIUM, HIGH, TOP} severity_t;
typedef enum {LOG, STOP, EXIT} action_t;
static severity_t svrt = LOW;
static string logname = "report.log";
static int info_count = 0;
static int warning_count = 0;
static int error_count = 0;
static int fatal_count = 0;
function void rpt_msg(string src, string i, report_t r=INFO, severity_t s=LOW, action_t a=LOG);
integer logf;
string msg;
case(r)
INFO: info_count++;
WARNING: warning_count++;
ERROR: error_count++;
FATAL: fatal_count++;
endcase
if(s >= svrt) begin
msg = $sformatf("@%0t [%s] %s : %s", $time, r, src, i);
logf = $fopen(logname, "a+");
$display(msg);
$fwrite(logf, $sformatf("%s\n", msg));
$fclose(logf);
if(a == STOP) begin
$stop();
end
else if(a == EXIT) begin
$finish();
end
end
endfunction
function void do_report();
string s;
s = "\n---------------------------------------------------------------\n";
s = {s, "REPORT SUMMARY\n"};
s = {s, $sformatf("info count: %0d \n", info_count)};
s = {s, $sformatf("warning count: %0d \n", warning_count)};
s = {s, $sformatf("error count: %0d \n", error_count)};
s = {s, $sformatf("fatal count: %0d \n", fatal_count)};
s = {s, "---------------------------------------------------------------\n"};
rpt_msg("[REPORT]", s, rpt_pkg::INFO, rpt_pkg::TOP);
endfunction
function void clean_log();
integer logf;
logf = $fopen(logname, "w");
$fclose(logf);
endfunction
endpackage
四、运行仿真
vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_data_consistence_basic_test -l mcdf_data_consistence_basic_test.log work.tb
log -r /*
run -all
仿真器会不停的发送激励、监测激励、数据比较
五、数据通道开关检查
在Lab4中,数据通道的开关,在数据通道关闭的情况下,mcdf_checker
应该不会收到输入端或者输出端的监测数据,因此不应该出现数据比较的信息。那么在这种情况下如何判断这个测试通过?而且还需要检查一个简单的时序,即当slave channel
被关闭时,当valid
拉高时,ready
信号不应该出现拉高的情况,否则设计是有问题的,因为数据可能没有被真正写入到FIFO,或者slave channel
此时并没有被真正关闭。
针对这个问题,在mcdf_checker
,将en
信号检测到,将其传入mcdf_checker
,同时也将3个chnl_intf
传入mcdf_checker
,这样mcdf_checker
可以观测到所需的valid
、ready
和en
信号,来完成这个检查。
do_channel_disable_check()
task do_channel_disable_check(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("[CHKERR]",
$sformatf("ERROR! %0t when channel disabled, ready signal raised when valid high",$time),
rpt_pkg::ERROR,
rpt_pkg::TOP);
end
endtask
通过forever
不断的监测,在每一个时钟上升沿的时候,并且复位信号已经复位的情况下,要监测chal_en[id]
这个信号,这个chal_en[id]
信号是从设计里面监测到的,即把这个信号放到mcdf_vif
中监测。mcdf_vif
会监测一些硬件信号,然后把监测到的硬件信号传给内部的验证组件。
mcdf_intf()
interface mcdf_intf(input clk, input rstn);
// To define those signals which do not exsit in
// reg_if, chnl_if, arb_if or fmt_if
logic chnl_en[3];
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input chnl_en;
endclocking
endinterface
然后例化 mcdf_intf()
并且从DUT中抓取信号
mcdf_intf mcdf_if(.*);
// mcdf interface monitoring MCDF ports and signals
assign mcdf_if.chnl_en[0] = tb.dut.ctrl_regs_inst.slv0_en_o;
assign mcdf_if.chnl_en[1] = tb.dut.ctrl_regs_inst.slv1_en_o;
assign mcdf_if.chnl_en[2] = tb.dut.ctrl_regs_inst.slv2_en_o;
slv0_en_o
、slv1_en_o
、slv2_en_o
在设计中,这三个bit分别送给设计中的Slave0
、Slave1
、Slave2
用来控制数据通道开关闭。mcdf_if
会抓取到这三个bit的信号,放到chnl_en[3]
中,mcdf_checker
会时刻监测这三个内部信号。检测到chnl_en === 0
时,那么当ch_valid === 1
的情况下,ch_ready
就不应该为1,即当数据通道关闭的情况下,valid和ready信号不应该同时拉高。否则将会报错误。
除了监测en
内部信号,还可以调用mcdf_refmod
里面的get_field_value()
得到RW_EN
,即代码改成
@(posedge this.mcdf_vif.clk iff (this.mcdf_vif.rstn && refmod.get_field_value(id, RW_EN));
六、优先级的检查
在Lab4中,对于优先级的检查,mcdf_checker依然无法从mcdf_refmod的预测数据中获取数据包前后的信息,也就是说mcdf_checker无法判断最终从mcdf发送出的数据是否符合了优先级的数据包发送顺序,那么将需要观测arbiter信号在mcdf_intf中声明并且观测。
do_arbiter_priority_check()
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();
if(id >= 0) begin
@(posedge this.arb_vif.clk);
if(this.arb_vif.mon_ck.a2s_acks[id] !== 1)
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
当时钟的上升沿到来,并且f2a_id_req===1
,表示formatter发送请求给arbiter准备取数据,然后调用get_slave_id_with_prio()
判断哪个id的优先级最高。
get_slave_id_with_prio()
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
得到最高优先级的那个slave之后,等待下一个时钟上升沿,判断arbiter给slave的ack信号是否为1,即a2s_acks[id] !== 1
。通过仲裁arbiter得到的slave的id和对应的ack[id]相比较,看是否是一致的,不一致则会报错。即需要判断当req发送请求之后,ack是否拉高同意请求。