之前学习的一些验证要素: 验证环境按隔离的概念,分为硬件DUT,软件testbench和接口interface; 验证阶段分为建立阶段(build),连接阶段(connect),产生激励阶段(generator)和发送激励阶段(transfer)。
MCDF子系统相比MCDT添加了数据整形、寄存器控制和状态显示功能。整个验证环境各组件之间相互独立,依靠event和mailbox同步和通信。
Testbench结构
- reg_pkg构造与chnl_pkg类似,initiator变为driver,generator数据将发给driver,monitor数据发给checker
- fmt_agent作为responsor,被动接收数据
package fmt_pkg;
import rpt_pkg::*;
typedef enum {SHORT_FIFO, MED_FIFO, LONG_FIFO, ULTRA_FIFO} fmt_fifo_t; // 定义FIFO容量
typedef enum {LOW_WIDTH, MED_WIDTH, HIGH_WIDTH, ULTRA_WIDTH} fmt_bandwidth_t; // 定义带宽
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();
...
function string sprint();
...
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
compare = 0;
s = {s, $sformatf("sobj length %0d != tobj length %0d \n", this.length, t.length)};
end
if(this.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 FAILURE!\n"};
s = {s, "=======================================\n"};
rpt_pkg::rpt_msg("[CMPOBJ]", s, rpt_pkg::INFO, rpt_pkg::MEDIUM);
endfunction
endclass
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;
local int fifo_bound; // FIFO最大长度
local int data_consum_peroid; // 数据消耗周期
function new(string name = "fmt_driver");
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
task do_config(); // 从fmt_driver获取配置
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);
case(req.bandwidth)
LOW_WIDTH: this.data_consum_peroid = 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();
...
task do_receive(); // 接收数据
forever begin
@(posedge intf.fmt_req);
forever begin
@(posedge intf.clk);
if((this.fifo_bound-this.fifo.num()) >= intf.fmt_length) //等待余量大于长度
break;
end
intf.drv_ck.fmt_grant <= 1;
@(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 // 重复length次
@(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
class fmt_generator;
rand fmt_fifo_t fifo = MED_FIFO;
rand fmt_bandwidth_t bandwidth = MED_WIDTH;
mailbox #(fmt_trans) req_mb;
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
// generate transaction and put into local mailbox
task send_trans();
fmt_trans req, rsp;
req = new();
assert(req.randomize with {local::fifo != MED_FIFO -> fifo == local::fifo;
local::bandwidth != MED_WIDTH -> bandwidth == local::bandwidth;
})
else $fatal("[RNDFAIL] formatter packet randomization 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();
...
function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
$display(s);
endfunction
endclass
class fmt_monitor;
...
class fmt_agent; // 盒子,例化组件,调用各组件run()
...
endpackage
- mcdf_pkg实现mcdf_checker的功能,需要通过reference model模拟硬件功能完成比对
`include "param_def.v"
package mcdf_pkg;
import chnl_pkg::*;
import reg_pkg::*;
import arb_pkg::*;
import fmt_pkg::*;
import rpt_pkg::*;
typedef struct packed {
bit[2:0] len;
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;
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
foreach(out_mbs[i]) out_mbs[i].delete(); // 清空mailbox,也可用new()
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
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]; // 在refmod内实例化
function new(string name="mcdf_checker");
...
function void set_interface(virtual mcdf_intf intf);
...
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); // 调用compare()
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
...
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); //调用rpt_pkg()替代$diaplay(),定义了一些消息等级
endfunction
endclass
class mcdf_env; // 例化所有组件
...
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 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
class mcdf_data_consistence_basic_test extends mcdf_base_test; //填充do_reg(),do_formatter()和do_data()
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
endpackage
划分测试功能点
测试功能点 | 测试内容 | 测试通过标准 | 测试类名 |
---|---|---|---|
寄存器读写测试 | 所有控制寄存器的读写测试;所有状态寄存器的读写测试 | 读写值是否正确 | 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 |
- 根据划分的测试点mdcf_checker需实现的一些检查功能
- 寄存器读写(参考compare())
- 数据通道开关检查:在mcdf_intf中监测en信号,传入mcdf_checker,将3个chnl_intf也传入,task:mcdf_checker::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
- 优先级检查:观测arbiter信号并定义task:mcdf_checker::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
function int get_slave_id_with_prio(); // 模拟arbiter仲裁逻辑
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
- 发包长度检查(已实现)
- 下行从端低带宽测试:对下行从端配置约束,使其可以实现在低带宽数据消耗下自身缓存量逐渐减少,而频繁在formatter request信号拉高时延迟grant信号拉高;考虑如何观测request与grant之间超过两个时钟周期的时序延迟
仿真命令
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
-classdebug:提供更多SV类调试功能
-solvefaildebug:在SV随机化失败后提示更多信息