路科验证的个人空间 https://blog.eetop.cn/1561828 [收藏] [复制] [分享] [RSS]

空间首页 动态 记录 日志 相册 主题 分享 留言板 个人资料

日志

SV组件实现篇之六:比较器和参考模型

已有 2932 次阅读| 2017-5-22 23:44 |个人分类:验证系统思想|系统分类:芯片设计

在同之前的verifier梅、尤、董完成了slave、arbiter和registers的模块验证之后,我们需要看看最后一位verifier娄是如何完成arbiter验证的。Verifier娄也依照之前的验证步骤,给出了arbiter的验证框图:
在实现了stimulator和monitor之后,就进入了数据比对和功能检查的环节了。在开始考虑实现checker之前,我们先来看看,如果要完成formatter的功能验证,需要考虑的功能点检查,即验证计划是什么?
  1. 复位检查。检查在复位以后,formatter各个输出信号的值。考虑到verifier娄是一位新手的情况下,他尽量将formatter作为一个黑盒去检查,而很少关注其内部的信号状态。
  2. 数据完整性。即数据的输入到输出,没有数据发生丢失,而且数据按照协议要求进行传输。
  3. 功能配置情况。即在寄存器功能配置下,formatter可以将来自不同slave的数据打包为不同长度的数据包。
  4. 协议检查。从arbiter到formatter,以及formatter到外部下行数据段的两个协议部分是否符合时序要求。
 
上面的验证功能点检查,从列出的功能点,到考虑给出的激励场景,都是互相对应的。而面对更复杂的设计,要列出的功能点检查项要更多呢!那么,我们就上面要检查的功能点,来看看verifier娄准备如何实现它的checker。实际中,我们可以就要检查的场景,分为异常检查、常规检查和协议检查:
  • 异常检查:由于某一异常事件触发,进而使得设计做出的响应。
  • 常规检查:指的是设计在正常工作状态下的表现,一般可以伴随长时间的稳定工作。
  • 时序检查:检查DUT的内部或者外部信号之间的时序是否符合设计要求。
 
接下来,我们将上面的功能点检查,与上面三种检查种类进行划分的话,它们的类属关系是,复位检查由于是复位信号的事件使得设计发生相应的变化,我们可以视为异常检查;数据完整性和功能配置情况都是在formatter稳定工作时的检查,可以归类为常规检查协议检查则因为特别注重时序性的要求,而应当归类为时序检查。而就这种检查的场景而言,实现功能检查的方式也不相同:异常检查主要就事件触发来检查设计的响应;而常规检查则通过查看设计的配置,来判断设计的工作状态是否符合预期;对于时序检查,则会通过捕捉信号间的时序关系来进行检查。对于这三种检查方式,本文也将依次详细论述。


异常检查
在设计异常的时候,通过设计外部或者内部的异常信号,我们可以检查设计的响应(也分内部和外部)是否正确。如果检查得更细致,我们还应该考虑:
  • 异常事件的触发是否合理,符合设计要求。
  • 异常事件的处理是否正确
  • 异常事件恢复的条件在满足之后,设计是否能够再次回到稳定的工作状态

下面的示例代码就是用来检查“设计复位”的。通过这个例子,读者可以知道,如何定义一些事件、已经如何针对事件作出响应和如何对事件检查的功能做出控制。


class fmt_ini_mon;
event reset_e;

task run();
fork
mon_reset();
join_none
endtask

task mon_reset();
forever begin
@(negedge vif.rstn);
-> reset_e;
end
endtask
endclass

首先来看看fmt_ini_mon,在其内部定义了成员事件reset_e,通过方法mon_reset()来捕捉复位事件,进而触发reset_e。那么这个reset_e用来通知谁呢?我们在本文最后的代码全览中可以看到,这个事件是用来通知比较器fmt_checker。在fmt_checker中,它对reset_e这个事件时刻保持关注,一旦等到了事件被触发,它便会执行功能检查chk_rst()。在下面的代码中,fmt_checker声明的事件reset_e是等同于fmt_ini_mon中的同名事件,而这两个事件的等价传递发生在了顶层的testbench环境中。而类似的,关于虚拟接口在chk_rst()中的引用也需要首先保证它们在顶层环境被连接到真正的物理接口上。在事件chk_rst()的检查中,会检查formatter的所有输出接口,且要求在复位触发时,输出信号被置为期望的值,否则通过系统函数$error()报错。

class fmt_checker;
bit en_chk_rst=1;
event reset_e;

virtual interface fmt_ini_if ini_vif;
virtual interface fmt_rsp_if rsp_vif;

task run();
fork
chk_rst();
join_none
endtask

task chk_rst();
forever begin
@reset_e;
if(en_chk_rst == 1) begin
if(ini_vif.f2a_ack !== 0
|| rsp_vif.fmt_chid !== 0
|| rsp_vif.fmt_length !== 0
|| rsp_vif.fmt_req !== 0
|| rsp_vif.fmt_data !== 0
|| rsp_vif.fmt_start !== 0
|| rsp_vif.fmt_end !== 0)
$error("fmt_checker:: reset value is not correct!");
end
end
endtask
endclass


常规检查
对于常规检查而言,它是一个长期监测并且时刻检查的行为,这就要求观察三个方面:
  • DUT的配置情况
  • DUT的输入数据
  • DUT的输出数据

对于这三方面的数据监视和最终的比较,这部分提出了两种可行的解决的方式:
  • 拆分检查:是将DUT要检查的功能点有机的剥离开,并且就每个功能点做独立的检查。
  • 整体检查:是将DUT的所有输入包括配置情况作为“参考模型”的输入,而参考模型会按照设计要求,将最终期望的数据输出。

下面是对两种检查方式的具体实现代码。

拆分检查
class fmt_ini_trans;
bit [ 1:0] id;
bit [31:0] data;
int length;

static function int dec_length(int l);
int len;
case(l)
0: len = 4;
1: len = 8;
2: len = 16;
3: len = 32;
default len = 32;
endcase
return len;
endfunction
endclass

class fmt_ini_mon;
fmt_ini_trans trans;
mailbox #(fmt_ini_trans) ini_mb;
virtual interface fmt_ini_if vif;

task run();
fork
forever begin
mon_trans();
put_trans();
end
join_none
endtask

task mon_trans();
forever begin
@(posedge vif.clk iff vif.rstn);
if(vif.mon.a2f_val === 1 && vif.mon.f2a_ack === 1) begin
trans = new();
case(vif.mon.a2f_id)
0: trans.length = trans.dec_length(vif.mon.slv0_len);
1: trans.length = trans.dec_length(vif.mon.slv1_len);
2: trans.length = trans.dec_length(vif.mon.slv2_len);
3: $error("fmt_ini_mon:: a2f_id value is not as expected");
endcase
trans.id = vif.mon.a2f_id;
trans.data = vif.mon.a2f_dat;
break;
end
end
endtask

task put_trans();
ini_mb.put(trans);
endtask
endclass

上面的fmt_ini_mon会在每次有效的数据输入时采集好数据,装入新创建的数据对象trans。这里我们会要求每次创建新的对象引来装入新的数据。fmt_ini_mon通过mon_trans()和put_trans()两个成员方法可以时刻监视和传输捕捉到的有效数据包。由于输入数据没有包的首和尾,因此定义的数据传输类fmt_ini_trans的数据成员也只装载每一个时钟周期的采样。另外需要注意的是,由于从寄存器register到formatter传递的包长配置信号slvX_len需要解码,因此我们在fmt_ini_trans中定义了解码的方法。而该方法dec_length()被声明为了静态方法是由于其它外部的函数也需要单独调用该方法(并不需要例化对象)。

class fmt_rsp_trans;
bit [ 1:0] id;
bit [31:0] data_q[$];
int length;

function bit compare(fmt_rsp_trans t);
if(id != t.id
|| length != t.length
|| data_q.size() != t.data_q.size())
return 0;
foreach(data_q[i]) begin
if(data_q[i] != t.data_q[i])
return 0;
end
return 1;
endfunction
endclass

class fmt_rsp_mon;
fmt_rsp_trans trans;
event req_trans_e;
mailbox #(fmt_rsp_trans) rsp_mb;
virtual interface fmt_rsp_if vif;

task run();
fork
forever begin
mon_trans();
put_trans();
end
mon_req_trans();
join_none
endtask

task mon_trans();
forever begin
@(posedge vif.clk iff vif.rstn);
if(vif.mon.fmt_start === 1) begin
trans = new();
trans.length = vif.mon.fmt_length;
trans.id = vif.mon.fmt_chid;
repeat(trans.length) begin
trans.data_q.push_back(vif.mon.fmt_data);
@(posedge vif.clk);
end
break;
end
end
endtask

task put_trans();
rsp_mb.put(trans);
endtask

task mon_req_trans();
forever begin
@(posedge vif.mon.fmt_req iff vif.rstn);
-> req_trans_e;
end
endtask
endclass

从fmt_rsp_mon的定义来看,主要做了两件事情。

首先,它同fmt_ini_mon类似的是,也捕捉了输出的数据,通过观察vif.mon.fmt_start信号来捕捉包首,进而逐次采样数据。在这里,因为formatter输出协议有明显的包的含义,所以,我们将输出数据包fmt_rsp_trans定义为内部包含多个数据的类,这方便与fmt_rsp_mon可以将一个完整的数据包存入其中。

其次,fmt_rsp_mon也单独捕捉了一个事件,即数据包要发送时的请求事件。当vif.mon_fmt_req从0跳转到1时,我们将这个数据包请求发送的事件捕捉下来,触发事件req_trans_e。而该事件又等价于在fmt_checker中同名的事件req_trans_e。fmt_checker也将利用这个事件,检查功能配置是否生效。

有一些读者在参考上面fmt_rsp_mon::mon_trans时对于数据采样为什么直接从vif.mon.fmt_start上升沿开始而不是从vif.mon.fmt_req开始可能有疑虑,因为按照协议的要求fmt_req需要先等待fmt_grant,再而才能出发fmt_start。那么这个时序部分的检查,在fmt_rsp_mon::mon_trans()采集数据包时可以省略吗?为什么?

答案是,我们当然很关注边界信号协议时序的检查,而这部分的检查我们则会交给第三部分的检查,即“时序检查”,在接下来的示例代码中读者可以看到。所以,我们在fmt_rsp_mon::mon_trans()中省略了检查,而使得代码变得更为清爽,不是吗?

此外,对于formatter输出端的数据类型fmt_rsp_trans定义中,也添加了其用来做数据比较的方法fmt_rsp_trans(fmt_rsp_trans t),这个方法会在后面的fmt_checker中用来比较两部分的数据,即实际采样到的输出数据和期望的输出数据进行比较,如果两个数据对象相同则返回1,否则返回0。

在定义了上面两个monitor即fmt_ini_mon和fmt_rsp_mon之后,将由fmt_checker做最终的功能检查:

class fmt_checker;
bit en_chk_data=1;
bit en_chk_len=1;

virtual interface fmt_ini_if ini_vif;
virtual interface fmt_rsp_if rsp_vif;

event req_trans_e;

mailbox #(fmt_ini_trans) ini_mb;
mailbox #(fmt_rsp_trans) rsp_mb;
mailbox #(fmt_rsp_trans) exp_mb;

function new();
ini_mb = new();
rsp_mb = new();
exp_mb = new();
endfunction

task run();
fork
ini2rsp_fmt();
chk_data();
chk_len();
join_none
endtask

task chk_data();
fmt_rsp_trans exp, rsp;
forever begin
wait(en_chk_data == 1);
fork
rsp_mb.get(rsp);
exp_mb.get(exp);
join
if(rsp.compare(exp) == 1)
$display("fmt_checker:: data compared succeeded!");
else
$error("fmt_checker:: data compared failed!");
end
endtask

task ini2rsp_fmt();
fmt_ini_trans s;
fmt_rsp_trans t;
forever begin
ini_mb.get(s);
t = new();
t.id = s.id;
t.length = s.length;
t.data_q.push_back(s.data);
repeat(t.length - 1) begin
ini_mb.get(s);
if(t.id != s.id)
$error("fmt_checker:: data input id is changed!");
t.data_q.push_back(s.data);
end
exp_mb.put(t);
end
endtask

task chk_len();
int length;
forever begin
@req_trans_e;
if(en_chk_len == 1) begin
case(rsp_vif.mon.fmt_chid)
0: length = fmt_ini_trans::dec_length(ini_vif.mon.slv0_len);
1: length = fmt_ini_trans::dec_length(ini_vif.mon.slv1_len);
2: length = fmt_ini_trans::dec_length(ini_vif.mon.slv2_len);
default: $error("fmt_checker:: id value is unexpected");
endcase
if(length != rsp_vif.mon.fmt_length)
$error("fmt_checker:: output length is not as the value configured");
end
end
endtask
endclass

在fmt_checker中声明了用来使能检查功能的控制信号en_chk_data和en_chk_len,这便于后期在顶层环境的集中控制。除了声明虚接口ini_vif和rsp_vif以外,fmt_checker还声明并且例化了三个用来装载数据的mailbox:ini_mb、rsp_mb和exp_mb。

由于从ini_mb收集到的数据类型是从formatter输入端采集到了fmt_ini_trans类型,而rsp_mb是从formatter输出端采集到的数据类型fmt_rsp_trans,这两种数据类型有不小的差别,前者存储单周期的数据,后者存储多个周期的数据包。如果要对这两种数据包做比较,我们需要将它们统一到同样地格式,这里fmt_checker选择了利用fmt_checker::ini2rsp_fmt()方法,将从ini_mb得到的数据利用其所含的信息包装成为fmt_rsp_trans类型,进而再存储到第三个mailbox exp_mb。

因为,我们认为exp_mb存储的数据是期望得到的数据,因为其内部的数据是我们通过formatter数据打包的逻辑组装而成的,并非是直接采样得到的数据。而rsp_mb中存储的采样到的输出数据,同exp_mb的数据成员均为fmt_rsp_trans,这就使得比较这两个FIFO之中的数据变得容易多了。fmt_checker::chk_data()做的就是先从这两个mailbox中取到数据对象,进而利用fmt_rsp_trans::comapre()做数据比较,将数据比对结果报告出来。

所以,对于功能检查点“数据完整性”的检查就可以通过上面两个方法chk_data()和ini2rsp_fmt()完成。那么,再来看看,对于“功能配置情况”应该如何检查呢?

fmt_checker::chk_len()通过等待事件req_trans_e来观察formatter在何时发起一个新的数据包。而检查registers到formatter配置是否成功就在于formatter输入端slvX_len的配置,是否可以控制输出包的长度。因此,通过比对slvX_len与fmt_length是否匹配,就可以完成“功能配置的检查“

因此,上面的检查方式,是将两个不同的功能点检查独立到不同的检查方法中完成,互相不影响,并且有独立的使能信号en_chk_data和en_chk_len可以控制这两种检查。

整体检查(参考模型)
除了独立拆分的检查方法之外,我们也可以按照硬件设计描述文档(端口、协议、状态机)来建立一个参考模型(reference model)。而模型的细致程度也依赖于checker的要求,检查得越细致,对参考模型的准确程度也要求越高。在日常中,除了verifier会自己写这样一个参考模型之外,有时候从系统工程师那里也可能得到了虚拟原型(virtual prototype)或者算法模型(algorithm model),该原型也是对于设计更具象的要求。对于虚拟原型和算法模型在验证环境的嵌入,我们也会在本书后面的章节中介绍。在这里,我们先讨论如何实现一个轻松一点,维护较少易复用的参考模型吧。

class fmt_refmod;
mailbox #(fmt_ini_trans) ini_mb;
mailbox #(fmt_rsp_trans) exp_mb;

event req_trans_e;

virtual interface fmt_ini_if ini_vif;
virtual interface fmt_rsp_if rsp_vif;

function new();
ini_mb = new();
exp_mb = new();
endfunction

task run();
fork
ini2rsp_fmt();
join_none
endtask

task ini2rsp_fmt();
fmt_ini_trans s;
fmt_rsp_trans t;
int len;
forever begin
@(req_trans_e);
ini_mb.get(s);
t = new();
t.id = s.id;
case(rsp_vif.mon.fmt_chid)
0: len = fmt_ini_trans::dec_length(ini_vif.mon.slv0_len);
1: len = fmt_ini_trans::dec_length(ini_vif.mon.slv1_len);
2: len = fmt_ini_trans::dec_length(ini_vif.mon.slv2_len);
default: $error("fmt_checker:: id value is unexpected");
endcase
t.length = len;
repeat(len - 1) begin
ini_mb.get(s);
if(t.id != s.id)
$error("fmt_checker:: data input id is changed!");
t.data_q.push_back(s.data);
end
exp_mb.put(t);
end
endtask
endclass


class fmt_checker;
bit en_chk_rst=1;
bit en_chk_data=1;

fmt_refmod refmod;

virtual interface fmt_ini_if ini_vif;
virtual interface fmt_rsp_if rsp_vif;

event reset_e;
event req_trans_e;

mailbox #(fmt_ini_trans) ini_mb;
mailbox #(fmt_rsp_trans) rsp_mb;
mailbox #(fmt_rsp_trans) exp_mb;

function new();
rsp_mb = new();
refmod = new();
endfunction

task run();
fork
chk_data();
refmod.run();
join_none
endtask

task chk_data();
fmt_rsp_trans exp, rsp;
forever begin
wait(en_chk_data == 1);
fork
rsp_mb.get(rsp);
refmod.exp_mb.get(exp);
join
if(rsp.compare(exp) == 1)
$display("fmt_checker:: data compared succeeded!");
else
$error("fmt_checker:: data compared failed!");
end
endtask

task connect();
refmod.ini_vif = ini_vif;
refmod.rsp_vif = rsp_vif;
refmod.req_trans_e = req_trans_e;
ini_mb = refmod.ini_mb;
exp_mb = refmod.exp_mb;
endtask
endclass

在整体检查的这段示例代码中,首先将参考模型定义为一个类fmt_refmod,它承担的任务就是将从fmt_ini_mon监测到的fmt_ini_trans数据通过自定义的逻辑转换为fmt_rsp_trans,因此它内部分别需要两个mailbox ini_mb和exp_mb。对于它做数据打包的成员方法fmt_refmod::ini2rsp_fmt(),有兴趣的读者可以发现,这个方法实际上是之前fmt_checker::ini2rsp_fmt()和fmt_checker::chk_len()的集成版本。因为它用来做数据打包长度的依据变为根据即将要发包时的寄存器配置数值,而不是之前从fmt_ini_trans::length得来。这就将检查数据包长度也同数据打包功能合并在了一起,而到了后期进行数据比对时,如果发生错误,那么可以想到的是,有可能是数据包长度的功能错误,也可能是本身formatter输入端数据采集的功能错误。

那么,这样一个完整的参考模型fmt_refmod一旦完成之后,就可以方便地集成到fmt_checker中。在上面的fmt_checker中除了需要例化fmt_checker::refmod之外,也需要注意新添加了方法fmt_checker::connect()。这一方法是用来对fmt_checker内部的组件同自身相连接的,而该方法的调用需要在稍后展示的外部formatter_tb中完成。通过这一方法使得fmt_checker::refmod中的虚接口和事件得到赋值,同时也将fmt_checker::refmod的mailbox赋值给fmt_checker中声明的mailbox指针。


点赞

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 注册

  • 关注TA
  • 加好友
  • 联系TA
  • 0

    周排名
  • 0

    月排名
  • 0

    总排名
  • 0

    关注
  • 254

    粉丝
  • 25

    好友
  • 33

    获赞
  • 45

    评论
  • 访问数
关闭

站长推荐 上一条 /1 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-5-13 18:20 , Processed in 0.022070 second(s), 12 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
返回顶部