文章目录
reg_pkg
- 随机化产生一些命令,地址,命令字,但三者之间有一定的约束关系trans类。通过generator的信箱将事务类发送到driver,driver通过接口将trans类的信息发送到DUT。monitor在接口上采样,通过信箱发送到checker。
reg_pkg的事务类
class reg_trans;
rand bit[7:0] addr; //8位的命令地址'h00,'h04,'h08,'h10,'h14,'h18
rand bit[1:0] cmd; //命令:write,read,idle
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};
soft addr[7:5]==0;
/* addr第四位为1为只读寄存器,所以cmd约束为read */
addr[4]==1 -> soft cmd == `READ;
/* 命令地址高八位位0(控制寄存器地址) 并且命令是写,将无效位化为0*/
addr[7:4]==0 && cmd==`WRITE -> soft data[31:6]==0;
};
- 主要数据类型包括:
-
- addr :要写的数据地址,这地方由6个,控制3个,状态3个。都在参数包里定义了
-
- cmd :对该寄存器地址是读还是写
-
- data:写入到某个寄存器地址的数据是什么,不同位对应不同功能
//控制寄存器的数据格式
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。
- 数据约束
-
- 数据约束要在对寄存器命令非常熟悉
reg_driver类
- 从generator类通过信箱获得trans类并返回,通过接口发送到验证环境。所以主要成员变量两个信箱,一个接口
- 在run()里有两个重要函数,reset(),driver()。
- 在driver中的write函数通过接口对DUT进行激励。
reset()
task do_reset();
forever begin
@(negedge intf.rstn);
intf.cmd_addr <= 0;
intf.cmd <= `IDLE;
intf.cmd_data_m2s <= 0;
end
endtask
- 在各个模块的reset()函数都需要考虑哪些信息需要复位,对于reg_agent与DUT通过连接,复位时考虑这个接口里的信号即可
do_drive()
- 跟之前的一样,首先声明两个事务类,从信箱中获取这个事务类,然后将这个事务类通过write()写入到接口发送到DUT。
task do_drive();
reg_trans req, rsp;
@(posedge intf.rstn);
forever begin
this.req_mb.get(req); //从gen获取
this.reg_write(req); //发送到外部
rsp = req.clone();
rsp.rsp = 1;
this.rsp_mb.put(rsp);//返回给gen
end
reg_write(reg_trans t)
- 根据不同的t.cmd做相应的操作(这个操作:write,read,idle)和接口相连接的
- 注意在读操作下,将命令和地址发送到接口上之后,必须等待一个周期发送数据(这地方的操作是等待两个下降沿)
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
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
reg_generator
- 这个类中需要考虑待发送激励的随机化问题
- 在这个类中产生随机化的地址、命令、命令字通过信箱发送到driver
- 如果待发送的命令是读,则需要从rsp中获取数据
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
mon_trans
- 从接口中获取到信息,再通过信箱将信息发送到checker
- 根据接口中获得cmd去做不同的操作获取信息
- 将获取的信息通过专用信箱发送到checker
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 == `WRITE) begin
m.data = intf.mon_ck.cmd_data_m2s;
end
else 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 %s monitored addr %2x, cmd %2b, data %8x", $time, this.name, m.addr, m.cmd, m.data);
end
endtask
endclass
#reg_agent
- agent没什么好讲的
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
fmt_pkg
- 这个模块和其他的模块不一样,这是一个从机模块,需要模拟外界对fmt数据请求的速率有快有慢,相当于一个管道有粗有细。
fmt_trans
class fmt_trans;
rand fmt_fifo_t fifo; //fifo容量
rand fmt_bandwidth_t bandwidth;//fifo带宽
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;
};
- 本包中重要的是操作fifo容量和fifo的带宽。这两个可以模拟外部数据流速的快慢
fmt_driver
成员变量
local virtual fmt_intf intf;
mailbox #(fmt_trans) req_mb;
mailbox #(fmt_trans) rsp_mb;
local mailbox #(bit[31:0]) fifo;//模拟下行数据的buffer
local int fifo_bound; //fifo的长度
local int data_consum_peroid; //数据消耗周期,反应数据带宽
task run();
/* 并列的原因是模拟一个硬件的行为,随时处理 */
fork
this.do_receive();
this.do_consume();
this.do_config();
this.do_reset();
join
endtask
do_config()
- 从信箱中获取事务,根据事务里的fifo长度信息设置不同的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
/* 例化fifo信箱 */
this.fifo = new(this.fifo_bound);
case(req.bandwidth)//不同带宽下的fifo数据周期不同,带宽越宽,数据周期越短
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
do_reset()
- 考虑当前包与硬件间的接口哪些需要初始化。
task do_reset();
forever begin
@(negedge intf.rstn) //复位信号到来
intf.fmt_grant <= 0;
end
endtask
do_receive()
- 上行数据获取:将接口上的数据放入信箱
do_consume()
- 下行数据流出:从信箱中得到数据
fmt_generator
- 跟以前的很类似,例化两个信箱与driver通信
fmt_monitor
- 从接口中获得数据放入monitor信箱连接checker。
fmt_agent
- 例化driver,monitor并且设置接口。
mcdf_pkg
该模块需要模拟mcdf的硬件功能,将三个通道信箱内的数据打包与fmt输出的数据做比较。
mcdf_refmod
- 功能介绍:首先从reg_mbx中获取实时通道的配置信息,然后将三个输入信箱中获取事务,将输入事务转化为输出事务传递出去。
class mcdf_refmod;
local virtual mcdf_intf intf;
local string name;
mcdf_reg_t regs[3]; //记录三个通道的信息
mailbox #(reg_trans) reg_mb; //reg信箱
/* mon_data_t在chnl包中定义:主要包括data和id两个信息 */
mailbox #(mon_data_t) in_mbs[3];//输入通道信箱
mailbox #(fmt_trans) out_mbs[3];//输出通道信箱
function new(string name="mcdf_refmod");
this.name = name;
/* 这地方只例化了三个输出信箱,三个输入信箱在check层例化 */
/* 为什么要这样? */
foreach(this.out_mbs[i]) this.out_mbs[i] = new();
endfunction
task run();
fork
do_reset();
this.do_reg_update();
/* 模拟fmt对三个chnl数据做打包 */
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
/* 地址3:2 位对应的数据信息0,1,2 */
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; //ot可以同时保存3个通道的数据
mon_data_t it;
bit[2:0] len;
forever begin
/* peek()从信箱中赋值信息,如果信箱为空则会阻塞 */
this.in_mbs[id].peek(it);
ot = new();
len = this.get_field_value(id, RW_LEN);//获取包长度信息
//len = regs[id].len; //这与上一句是等效的
ot.length = len > 3 ? 32 : 4 << len; //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
- 注意再run中同时对3个通道进行打包,这地方如果有一个通道的输入信箱为空,那么此线程会阻塞。
mcdf_checker
- 例化各个对象,信箱。将各个信箱做连接
- 重要功能就是将输入到refmod的三个通道信箱打包之后与输入的fmt信箱作比较。
task do_compare();
fmt_trans expt, mont;
bit cmp;
forever begin
this.fmt_mb.get(mont); //从fmt信箱获取
this.exp_mbs[mont.ch_id].get(expt); //从输出信箱获取
cmp = mont.compare(expt); //mont与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
mcdf_env
- 这是一个次顶层
- 对chnl,reg,fmt,checker的agent对象进行例化,将各个monitor信箱连接到checker信箱
- 启动各个模块的run,这地方以默认约束做的run
后面的类就是顶层,可以针对不同的功能,制定不同的顶层