本文转自:http://www.eetop.cn/blog/html/28/1561828-2316834.html
Verifier董在将建筑材料打包(package)好运进施工场地以后,就准备着手开始搭建了。搭建之前,他又给我抛出了几个问题:
Good questions!
我们先来回答第一个问题——
用硬件的方式还是软件的方式来封装验证环境?
在之前的《SV组件实现篇》中的《激励器的封装》一节中,我们已经对比了module和class对于数据和方法封装的异同,同时也建议对于验证组件的实现,我们倾向于用类来实现,这样可以更多地利用面向对象编程带来的便利。那么,在顶层testbench中如何去装载这些组件呢?一种合适的方式是,用容器来将验证组件都组织到一起,这样通过interface作为清晰的界限来隔离软件世界(验证环境)和硬件世界(DUT)。
这个用来组织软件组件的容器可以选择module或者class,那么选择哪一种更为合适呢?也许读者从下面的模块验证环境中暂时无法对比出一个清晰的答案,然而如果verifier董要将下面若干个子模块环境集成在一起,那么试想一下module还是class合适呢?
如果环境容器选择module,那么意味着嵌套子环境的容器也必须是module,因为只有“硬件才可以嵌套硬件”。那么这种选择使得在验证环境组件建立阶段失去了更多的自由度,读者在之前的学习当中已经懂得,module的例化无法做任何的延时和仿真时的动态选择,也因此失去了通过顶层的配置来选择合适环境结构的可能性。
如果选择了class,那么可以看到,真个软件世界的部分全部为类的世界,所以从之前给读者介绍的验证组件从”建立“到”连接 “再到”运行“的阶段就可以很好的咬合在一起了。
再来看看第二个问题吧——
子模块验证环境的层次应该到哪一级适合顶层的集成复用呢?
首先我们考虑一下,对于verifier董而言,他需要的子环境组件分别包括有:
从上面的列表可以发现,子模块环境中的checker并不是子系统集成的必要组件,而stimulator和monitor需要优先考虑。那么按照这个要求,进一步对子模块验证环境进行组织的话,就会变为下面的结构。这个结构是根据上一篇《SV组件实现篇》中对各个组件进行的层次组织。可以发现的是,除了原先的顶层环境environment,以及在initiator一侧的ini_stim和ini_mon组件、responder一侧的rsp_stim和rsp_mon组件之外,还有子验证环境中需要的checker。
上面的组件中,新填入了一层,称之为agent(单元)。之所以添加这样一层,将ini_stim和ini_mo组织到ini_agent,以及将rsp_stim和rsp_mon组织到rsp_agent,就是为了更好地服务于日后的顶层集成。例如,对于MCDF的验证环境组织,我们将大致需要如下agent:
其余的agent则并不会存在于验证环境当中。读者可以从这一项处理中,发现我们首先做的是将可复用的部分“单元化”,这里的单元即agent,它就包含一个stimulator和一个monitor。
最后来看看verifier董的第三个问题——
是否还需要复用模块级的checker?如果复用了,那么还需要另外创建子系统的checker吗?
首先,我们来看看在验证工作刚展开时,verifier董对checker复用的设想。从上面的图上可以看到,MCDF checker是复用了模块级的checker,当然也因此需要模块级别的ini_mon和rsp_mon来做出数据比对,同时也另外实现了MCDF的reference model(参考模型),最终进行了整体MCDF的数据比对。从上面的最初设想来看,verifier董计划做的是:
从这个计划来看,前期的模块checker复用有益于快速检查设计集成和模块之间的接口时序,这使得集成工作可以主要着眼于验证的结构组织和快速的测试场景生成。待到了后期,伴随更复杂的测试场景,verifier董就有较宽裕的时间来实现MCDF级别的reference model和data checker。
读到这里,有些读者可能在考虑,我们是否还有必要再额外创建一个MCDF级别的reference model呢?这其中的理论依据在于,模块级的checker(c1,c2,c3 ... cN)即使对于每个模块自身功能检查都正确,但它们的集合{c1,c2,c3 ... cN}也依然无法保证集成后的子系统的功能是正常运转的。因为最大的问题可能性就出现在了模块之间的连线和时序上面。这也可以从实际验证工作中得到佐证,为什么有的时候我们在两个子系统的验证充分之后,到了芯片级验证刚开始的时候仍然不断出现这样那样的问题?因为我们在扫雷,扫的是集成验证的常规雷啊。
这时在verifier梅、尤、娄和董对四个功能模块的验证非常充分的情况下,verifier董提出了新的验证环境构想:
从上面这个新的验证环境构想来看,与之前的结构相比:
环境看起来清爽了不少,而verifier董也需要为干净的环境做更多的功课:
在回答了verifier董的这三个问题之后,我们最后来看看verifier董所做的——
顶层环境的实现
为了将整个MCDF的集成环境需要解决的三点,即模块例化、连接和运行更直观地呈现给大家,我们已经将verifier董的代码做了简化,让读者可以直观看到整个环境的“骨架”(backbone):
// 接口定义 (省略部分代码)
interface regs_ini_if; endinterface
interface slv_ini_if; endinterface
interface fmt_rsp_if; endinterface
interface mcdf_if; endinterface
// registers package定义 (省略部分代码)
package regs_pkg;
class regs_ini_trans; endclass
class regs_ini_stm;
virtual interface regs_ini_if vif;
task run(); endtask
endclass
class regs_ini_mon;
virtual interface regs_ini_if vif;
mailbox #(regs_ini_trans) mb;
task run(); endtask
endclass
class regs_ini_agent;
virtual interface regs_ini_if vif;
regs_ini_stm stm;
regs_ini_mon mon;
function new();
stm = new();
mon = new();
endfunction
task run(); endtask
function void assign_vi(virtual interface regs_ini_if intf);
vif = intf;
stm.vif = intf;
mon.vif = intf;
endfunction
endclass
endpackage
// slave package定义 (省略部分代码)
package slv_pkg;
class slv_ini_trans; endclass
class slv_ini_stm;
virtual interface slv_ini_if vif;
task run(); endtask
endclass
class slv_ini_mon;
virtual interface slv_ini_if vif;
mailbox #(slv_ini_trans) mb;
task run(); endtask
endclass
class slv_ini_agent;
virtual interface slv_ini_if vif;
slv_ini_stm stm;
slv_ini_mon mon;
function new();
stm = new();
mon = new();
endfunction
task run(); endtask
function void assign_vi(virtual interface slv_ini_if intf);
vif = intf;
stm.vif = intf;
mon.vif = intf;
endfunction
endclass
endpackage
// formatter package定义 (省略部分代码)
package fmt_pkg;
class fmt_rsp_trans; endclass
class fmt_rsp_stm;
virtual interface fmt_rsp_if vif;
task run(); endtask
endclass
class fmt_rsp_mon;
virtual interface fmt_rsp_if vif;
mailbox #(fmt_rsp_trans) mb;
task run(); endtask
endclass
class fmt_rsp_agent;
virtual interface fmt_rsp_if vif;
fmt_rsp_stm stm;
fmt_rsp_mon mon;
function new();
stm = new();
mon = new();
endfunction
task run(); endtask
function void assign_vi(virtual interface fmt_rsp_if intf);
vif = intf;
stm.vif = intf;
mon.vif = intf;
endfunction
endclass
endpackage
上面的代码部分是摘于各个模块验证环境的代码,为了给代码减重,便于读者理解验证环境的结构,我们省略了部分代码。读者可以从上面的代码部分发现:
再来看看verifier董构建的mcdf_pkg package:
package mcdf_pkg;
import regs_pkg::*;
import slv_pkg::*;
import fmt_pkg::*;
class mcdf_refmod;
mailbox #(regs_ini_trans) regs_ini_mb;
mailbox #(slv_ini_trans) slv_ini_mb[3];
mailbox #(fmt_rsp_trans) exp_mb;
function new();
regs_ini_mb = new();
foreach(slv_ini_mb[i]) slv_ini_mb[i] = new();
exp_mb = new();
endfunction
task run(); endtask
endclass
class mcdf_checker;
mcdf_refmod refmod;
virtual interface mcdf_if vif;
mailbox #(regs_ini_trans) regs_ini_mb;
mailbox #(slv_ini_trans) slv_ini_mb[3];
mailbox #(fmt_rsp_trans) exp_mb;
mailbox #(fmt_rsp_trans) rsp_mb;
function new();
refmod = new();
rsp_mb = new();
endfunction
function void connect();
regs_ini_mb = refmod.regs_ini_mb;
foreach(slv_ini_mb[i]) slv_ini_mb[i] = refmod.slv_ini_mb[i];
exp_mb = refmod.exp_mb;
endfunction
task run(); endtask
endclass
class mcdf_env;
regs_ini_agent regs_ini_agt;
slv_ini_agent slv_ini_agt[3];
fmt_rsp_agent fmt_rsp_agt;
mcdf_checker chk;
virtual interface mcdf_if vif;
function new();
regs_ini_agt = new();
foreach(slv_ini_agt[i]) slv_ini_agt[i] = new();
fmt_rsp_agt = new();
chk = new();
endfunction
function void connect(); // 内部组件的连接
chk.connect();
regs_ini_agt.mon.mb = chk.regs_ini_mb;
foreach(slv_ini_agt[i]) slv_ini_agt[i].mon.mb = chk.slv_ini_mb[i];
fmt_rsp_agt.mon.mb = chk.rsp_mb;
endfunction
task run(); // 内部组件的并行运行
fork
regs_ini_agt.run();
foreach(slv_ini_agt[i]) slv_ini_agt[i].run();
fmt_rsp_agt.run();
chk.run();
join_none
endtask
function void assign_vi(virtual interface mcdf_if intf);
vif = intf;
chk.vif = intf;
endfunction
endclass
endpackage
同样地为了呈现出简介的衣装,我们也对mcdf_pkg做了大量修建,只保留了必要的框架。从上面的框架中读者可以发现以下几点:
最后,我们来看看顶层的MCDF testbench准备怎么接待验证环境、接口和DUT:
module mcdf_tb;
import mcdf_pkg::*;
event build_end_e;
event connect_end_e;
// 信号声明
// 接口例化
regs_ini_if regs_ini_intf();
slv_ini_if slv_ini_intf0();
slv_ini_if slv_ini_intf1();
slv_ini_if slv_ini_intf2();
fmt_rsp_if fmt_rsp_intf();
mcdf_if mcdf_intf();
... // 省略了接口的连接
... // 省略了DUT的例化
// 验证环境(软件)的创建、连接和运行
mcdf_env env;
initial begin: build // 创建阶段
env = new();
-> build_end_e; // 触发连接阶段
end
initial begin: connect // 连接阶段
wait(build_end_e.triggered());
// 虚接口的连接
env.assign_vi(mcdf_intf);
env.regs_ini_agt.assign_vi(regs_ini_intf);
env.slv_ini_agt[0].assign_vi(slv_ini_intf0);
env.slv_ini_agt[1].assign_vi(slv_ini_intf1);
env.slv_ini_agt[2].assign_vi(slv_ini_intf2);
env.fmt_rsp_agt.assign_vi(fmt_rsp_intf);
// env内部组件的连接
env.connect();
->connect_end_e; // 触发运行阶段
end
initial begin: run // 运行阶段
wait(connect_end_e.triggered());
fork
env.run(); // 运行整个验证环境
join_none
end
endmodule
在这个顶层TB中,除了例化DUT(硬件)、interface(硬件软件的中介),就剩下mcdf_env(软件)的创建、连接和运行了。在之前《SV组件实现篇》中我们也简单介绍过,软件部分的创建、连接和运行应该分顺序进行,来确保句柄的传递从而避免句柄悬空的问题。我们看看,verifier董是如何保证软件环境正常运行的这一问题的:
这么看起来,verifier董这个顶层建筑师通过上面三个步骤就完成了验证环境的组装:
哇哦,房子已经搭建好了,然而……没有水、没有电、没有天然气,还是没法使用啊。要实现水电天然气的三通,下一节课《测试场景的生成》我们来看看测试场景的生成和激励数据的流动如何实现。只有让房子“动”起来,它才能为验证提供真正的帮助啊!