SV系统集成篇之二:验证环境的组装

本文转自:http://www.eetop.cn/blog/html/28/1561828-2316834.html

Verifier董在将建筑材料打包(package)好运进施工场地以后,就准备着手开始搭建了。搭建之前,他又给我抛出了几个问题:

  • 路桑,我应该使用硬件的方式(module)来封装环境,还是软件的方式(class)来实现呢?
  • 从复用的角度来看,我应该使用模块验证的哪一级环境更易于集成同时又保持子模块package的封装性(不做源代码修改)呢?
  • 到了checker部分,我是否还需要复用模块级的checker?如果复用,那么我还需要再另外创建一个MCDF checker检查整体的功能吗?

 

Good questions!

 

我们先来回答第一个问题——

用硬件的方式还是软件的方式来封装验证环境?

 

在之前的《SV组件实现篇》中的《激励器的封装》一节中,我们已经对比了module和class对于数据和方法封装的异同,同时也建议对于验证组件的实现,我们倾向于用类来实现,这样可以更多地利用面向对象编程带来的便利。那么,在顶层testbench中如何去装载这些组件呢?一种合适的方式是,用容器来将验证组件都组织到一起,这样通过interface作为清晰的界限来隔离软件世界(验证环境)和硬件世界(DUT)。

 

这个用来组织软件组件的容器可以选择module或者class,那么选择哪一种更为合适呢?也许读者从下面的模块验证环境中暂时无法对比出一个清晰的答案,然而如果verifier董要将下面若干个子模块环境集成在一起,那么试想一下module还是class合适呢?

 

如果环境容器选择module,那么意味着嵌套子环境的容器也必须是module,因为只有“硬件才可以嵌套硬件”。那么这种选择使得在验证环境组件建立阶段失去了更多的自由度,读者在之前的学习当中已经懂得,module的例化无法做任何的延时和仿真时的动态选择,也因此失去了通过顶层的配置来选择合适环境结构的可能性。

 

如果选择了class,那么可以看到,真个软件世界的部分全部为类的世界,所以从之前给读者介绍的验证组件从”建立“到”连接 “再到”运行“的阶段就可以很好的咬合在一起了。

再来看看第二个问题吧——

子模块验证环境的层次应该到哪一级适合顶层的集成复用呢?

首先我们考虑一下,对于verifier董而言,他需要的子环境组件分别包括有:

  • stimulator。并不是每个子环境的stimulator他都需要。
  • monitor。并不是每个子环境的monitor他都需要。
  • checker。verifier准备自己动手写一个MCDF checker,而抛开之前模块验证时各个模块的checker。

 

从上面的列表可以发现,子模块环境中的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:

  • Registers的ini_agent
  • Slave的ini_agent
  • Formatter的rsp_agent

其余的agent则并不会存在于验证环境当中。读者可以从这一项处理中,发现我们首先做的是将可复用的部分“单元化”,这里的单元即agent,它就包含一个stimulator和一个monitor。

 

最后来看看verifier董的第三个问题——

是否还需要复用模块级的checker?如果复用了,那么还需要另外创建子系统的checker吗?

首先,我们来看看在验证工作刚展开时,verifier董对checker复用的设想。从上面的图上可以看到,MCDF checker是复用了模块级的checker,当然也因此需要模块级别的ini_mon和rsp_mon来做出数据比对,同时也另外实现了MCDF的reference model(参考模型),最终进行了整体MCDF的数据比对。从上面的最初设想来看,verifier董计划做的是:

  1. 首先在验证环境集成前期,主要依靠复用的模块级checker来保证各个模块的功能正常工作。
  2. 待设计集成稳定,以及各个功能模块checker都稳定工作,没有错误报出的时候,考虑实现MCDF的reference model和data checker部分。

 

从这个计划来看,前期的模块checker复用有益于快速检查设计集成和模块之间的接口时序,这使得集成工作可以主要着眼于验证的结构组织和快速的测试场景生成。待到了后期,伴随更复杂的测试场景,verifier董就有较宽裕的时间来实现MCDF级别的reference model和data checker。

 

读到这里,有些读者可能在考虑,我们是否还有必要再额外创建一个MCDF级别的reference model呢?这其中的理论依据在于,模块级的checker(c1,c2,c3 ... cN)即使对于每个模块自身功能检查都正确,但它们的集合{c1,c2,c3 ... cN}也依然无法保证集成后的子系统的功能是正常运转的。因为最大的问题可能性就出现在了模块之间的连线和时序上面。这也可以从实际验证工作中得到佐证,为什么有的时候我们在两个子系统的验证充分之后,到了芯片级验证刚开始的时候仍然不断出现这样那样的问题?因为我们在扫雷,扫的是集成验证的常规雷啊。

 

这时在verifier梅、尤、娄和董对四个功能模块的验证非常充分的情况下,verifier董提出了新的验证环境构想:

从上面这个新的验证环境构想来看,与之前的结构相比:

  • 新的环境中,按照模块验证包中的agent为复用单元,进行选择和例化。
  • 不再复用模块验证环境中的checker,而是直接实现MCDF checker中的reference model和data checker。这就让环境顿时变得清爽起来,因为,不再需要为了复用模块级的checker而不得不引入更多的agent和monitor来做MCDF内部各个模块接口的数据收集。新的MCDF checker可以直接利用regs_ini_mon、slv_ini_mon和fmt_rsp_mon来做数据收集。

 

环境看起来清爽了不少,而verifier董也需要为干净的环境做更多的功课:

  • 在前期集成验证的时候,除了需要考虑验证结构、测试场景,还需要实现MCDF checker,这使得短时间的验证压力增大。
  • verifier董也需要考虑MCDF checker的实现的精细程度。由于MCDF级别的测试,集中在系统集成测试方面,对于模块内部的功能检查不会涉及太多,所以MCDF reference model在实现的时候,也会从黑盒或者灰盒的角度来模拟MCDF的功能。
  • 如果发生了数据比对错误,那么调试部分会相比之前复用模块级checker的方案耗时长。因为验证师首先要确定哪个子模块发生了功能错误,再进一步去检查设计内部。而如果复用模块级checker,其内部详尽的检查会给验证师节省不少时间。

 

 

在回答了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

 

上面的代码部分是摘于各个模块验证环境的代码,为了给代码减重,便于读者理解验证环境的结构,我们省略了部分代码。读者可以从上面的代码部分发现:

  • 每个package都拥有单元化的agent,每个agent都拥有一个stimulator和一个monitor
  • 每个agent同时也需要传递虚接口,到内部的stimualtor和monitor。
  • 每个monitor都预留一个mailbox句柄,最终会与目标checker相连接。

 

 

再来看看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做了大量修建,只保留了必要的框架。从上面的框架中读者可以发现以下几点:

  • Verifier董实现了参考模型mcdf_refmod和数据比较器mcdf_checker,通过嵌套mcdf_refmod到mcdf_checker,确立了checker比较数据的方式,即通过参考模型进行数据的预测,最终将数据送出至mcdf_checker,与另外一侧formatter responder monitor监测到的数据进行比对。因此mcdf_refmode和mcdf_checker之间存在着数据缓存通道mailbox的创建和连接关系。这一点可以从mcdf_checker::connect()中观察到。
  • MCDF顶层软件环境mcdf_env容纳了一个regs_ini_agent、三个slv_ini_agent、一个fmt_rsp_agent和一个mcdf_checker。在正常例化之外,mcdf_env还进行了各个monitor同checker的数据缓存通道连接。这一点可以从mcdf_env::connect()中观察到。

 

最后,我们来看看顶层的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董是如何保证软件环境正常运行的这一问题的:

  • 首先,同其它子模块环境一样,实现三个独立的有先后顺序的创建、连接和运行过程语句块。这些阶段的顺序也通过了event得以在进程间进行触发通信。
  • 在创建阶段,组件的层次化可以通过一层层的new()构建函数来保证。所以,层次化的确定是自顶向下的。
  • 在连接阶段,需要考虑的是组件之间的通信(这个例子中使mailbox的连接)和虚接口的连接。因此,要确保的是,层层传递过程中,需要从源端(source point)传递句柄到目标端(target point)。这里要注意的,连接connect()方法从调用顺序来看是自动向下的,但是句柄的传递方向则不一定是自顶向下的。用户可以看到句柄的传递方向,或者说是组件之间的连接方向,实际上是同组件之间数据传输的方向一致的。
  • 在运行阶段,组件的运行顺序也是自顶向下的。例如上面的mcdf_tb::run的过程块中,通过env.run()的调用,进一步调用了其内部各个子组件的run()方法。因此通过层层调用,达到让真个环境中组件全部运转的目标。

 

这么看起来,verifier董这个顶层建筑师通过上面三个步骤就完成了验证环境的组装:

  1. 整理归纳子环境的package。
  2. 组织集合顶层环境environment和checker,构成顶层环境的package。
  3. 实现顶层的testbench,例化DUT和interface,并且完成软件验证环境的三个阶段:build、connect和run。

 

 

哇哦,房子已经搭建好了,然而……没有水、没有电、没有天然气,还是没法使用啊。要实现水电天然气的三通,下一节课《测试场景的生成》我们来看看测试场景的生成和激励数据的流动如何实现。只有让房子“动”起来,它才能为验证提供真正的帮助啊!

你可能感兴趣的:(SV系统集成篇之二:验证环境的组装)