来源| 杰瑞IC验证(ID:Jerry_IC) |原创作者| Jerry
书接上回,闲话不表。
上回说到,我们接到了一个需要100个相同agent的验证平台的活,我们已经写好了agent、宏定义、顶层的连接。
但是还有很多问题需要处理,比如:
100个agent,env中怎么声明例化?
seq怎么设计?
功能覆盖率怎么收集?
进行验证的时候用怎样的策略等等,今天我们就一起解决了它!
100个agent,我们怎么在env中例化呢?
没错,在上篇中顶层例化不能用的那个方法这里可以用了!
---我们把agent声明和例化成size为100的数组嘛~
有人问了,agent可以声明成数组?这个不是一个class吗?它又不是像bit、int这种数据类型?
这种声明还可以理解,比如:int aa[100]; 你这个class难道可以这么声明成数组?jerry_agent aa[100]; 这么写对吗?
哈哈,不要怀疑,class就是最强大的数据类型!
结构体都可以这么声明,class为啥不行?
刚才你写的这两句里,jerry_agent aa[100]里的jerry_agent的角色就可以看作int aa[100]里面的int,此时他们的职能可以理解为是一样的。
同理,我们在env中声明和例化也可如此,直接上代码!
class jerry_env extends uvm_env;
……
jerry_agent u_jerry_agt[`AGENT_NUM];
function void build_phase(uvm_phase phase);
……
for(int i=0;i<`AGENT_NUM;i++) begin
u_jerry_agt[i]=jerry_agent::type_id::create($sformatf(“u_jerry_agt[%0d]”,i),this);
end
……
endfunction
endclass
如上,我们又用起了我们心爱的小宏宏`AGENT_NUM,把jerry_agent声明成了size为`AGENT_NUM的数组,并在build_phase中借用for循环例化了起来。
顺便提一下,比如我们想在env中把刚才例化的agent和别的组件连接起来,也可以同样的直接当数组,用循环来搞定!
可以在connect_phase中,把它和APPLE这个组件的port相连:
function void connect_phase(uvm_phase phase);
for(int i=0;i<`AGENT_NUM;i++) begin
u_jerry_agt[i].ap.connect(APPLE.port);
end
endfunction
100个人心中有100个哈姆雷特,100个agent就有100个sequence。怎么组织和启动这些sequence呢?
这么多同类型的sequence,其实还是使用虚拟sequence机制进行组织比较友好。
有了Jerry前面的思路,这里怎么写其实也不是那么难了,例如在虚拟sequencer中,我们声明100个实体sequencer,还是声明成数组:
class jerry_vser extends uvm_sequencer;
……
jerry_sequencer sqr[`AGENT_NUM];
……
endclass
在虚拟sequence中,怎么启动这100个sequence呢?
既然定义成数组了,那当然还是用循环再加seq的启动代码来启动了。
对,现在大家已经很上道了!
这里需要注意的一点是,通常这100个seq肯定不是串行顺序的执行,而是并行启动和执行,这个并行执行就有坑了!
我们提到用循环和并行启动执行,当然很容易想到了for + fork-join_none结构。
这个在Jerry之前的一篇文章刚好提过,想不到那篇文章提到的坑,我们现在实实在在的就碰到了啊!这段在虚拟sequence中的核心代码,我们应该这么写:
`uvm_declare_p_sequencer(jerry_vser)
……
for(int i=0; i<`AGENT_NUM ;i++) fork
automatic int j=i;
`uvm_do_on_with(seq.p_sequencer.sqr[j],{……})
join_none
说到这个实际场景,多说一句,这种启动seq的结构,你需要结合实际应用和seq的具体行为决定是否需要在join_none后面多加一句“wait fork;”,以防止虚拟seq的结束把启动起来的seq kill掉了。
这个不是我们今天的重点,不在此展开了,大家在实际玩的过程中慢慢体会啦~
功能覆盖率收集接口信号或者变量的值,我们得到的是值但是关心的是值的信息。功能覆盖率代码编写要点3个字概括:“定、例、采”。
即定义covergroup,例化covergroup,以及在需要收集的时间点进行采集。
对于没有玩过覆盖率的初学者这里也可以先只是简单了解下,关注后续Jerry的文章会给大家从根上细聊的哈,这里不多说了。
我们要收集这100个agent的接口信息,这个covergroup也总不能写100次吧?怎么收集?
这里Jerry给大家提供的一种方法,还是顺着刚才的思路,大家可以先想想。
没错!正如各位天才想的那样,用class把covergroup包裹起来,然后把这个class例化成size为100的数组,套路和前面一样。
比如我们把covergroup包成这样一个class:
class panda;
covergroup AAA with function sample(int signal);
option.per_instance=1;
detect:coverpoint detect_signal {
bins one ={1};
bins zero ={0};
}
endgroup
endclass
然后我们把这个叫panda的class,在一个合适的地方例化100个,再让其与100个agent各自的接口产生联系进行收集就可以了,这个合适的地方你可以自己依照自己的平台结构决定,当然也可以像Jerry示例讲解的这样,使用一个专门的component去收集覆盖率,我们挑几个关键步骤聊聊:
首先,在这个class里,先把刚才的covergroup声名出来、以及把我们的100个接口声名出来,如下:
panda u_panda_cov[`AGENT_NUM];
virtual jerry_interface jerry_vif[`AGENT_NUM];
然后,我们把covergroup例化了,把virtual jerry_interface通过config_db机制拿到(对应回顾上篇在顶层把这100个接口通过config_db传出去了)。
for(int i=0;i<`AGENT_NUM;i++) begin
if(!uvm_config_db#(virtual jerry_interface)::get(null,$sformatf(“*u_jerry_agt[%0d]*”,i), “jerry_vif”, jerry_vif[i])
`uvm_fatal(……)
end
for(int i=0;i<`AGENT_NUM;i++) begin
u_panda_cov[i]=new();
end
最后,把接口和covergroup连起来进行采集,如下,在flag上升沿采集一下即可:
@(posedge xxx.flag)//这句只是示例,大家可以各种姿势采集
for(int i=0; i<`AGENT_NUM;i++) fork
automatic int j=i;
u_panda_cov[j].AAA.sample(jerry_vif[j].aa);
join_none
好了,我们接的这趟活,到现在为止在代码层面上需要考虑的特殊点应该已经搞定了!
但是,在开展验证的时候100个agent跑的会不会很慢呢?
哈哈,当然会很慢!
不过说实话,现实中也不可能有需要100个agent这么多的RTL。
不过不管多少个,对于这样很多相同类型的、agent之间独立的接口,我们的验证case制造策略,还是要本着简单原则,比如大部分的case我们只随机使能其中两个agent,别的都不使能甚至可以改写成根本不例化,等大部分的功能验证完成之后,再制造几个特殊的所有agent都用上的、火力全开的定向case进行覆盖即可。
说到底还是验证重点、优先级的把握问题,如果每个case都是所有的agent全开进行验证,那样验证效率低下不说,更重要的可能导致心态爆裂而死~
哈哈,Jerry今天的文章又即将进入尾声了,也许有一些内容在很多初学者看来信息量有点大或者有一些迷茫,没关系,不要着急慢慢来,可以先收藏起来,以后总会有用的哈。
坚持看杰瑞IC验证公众号,进步从一点一滴开始,从当下开始,加油!!
祝各位越来越牛逼!我们下次再聊~
——The End——