后仿真,是对经过综合/布局布线后的门级网表进行仿真,与前仿真对rtl代码进行仿真,找出功能上的缺陷相比,后仿真主要检查时序上的问题。
相比较理想状况下的rtl仿真(功能仿真、前仿真),后仿消耗时间更多,在可读性差的cell里穿梭行走,debug的难度大大增强了,而一般我们在做后仿的时候,往往也意味着离tapeout的时间很急迫了。
那么,时间紧,任务重的情况下,后仿经验的积累就尤为重要了,除了打怪升级多练手外,我们还可以从彼此的踩过的坑中快速攥经验值。
没有复位的寄存器
一般情况下,设计里面的寄存器都要有复位端。但是,别忘了,后仿不仅仅包括设计的网表,还有各种model,比如analog model,这些model可能因为种种原因,存在没有初值没有复位的寄存器信号。这些寄存器在后仿时候往往给我们造成很大的麻烦。
1 | reg rg_a, rg_b; |
解决方案:
我们在testbench中通过force-release的方式对寄存器赋初值,因为变量是reg类型的,所以只要赋初值后,在没有其他条件触发时,会一直保持force进去的值。
1 | initial begin |
信号取反
当我们用网表进行后仿时,在testbench中驱动或者监测的DUT中的信号很可能是一个取反的信号。特别是端口上的信号,名字都没变,明明叫rstb的那货,行为上却像个rst。并不是设计人员粗心大意,而是DC和ICC这样的工具捣的鬼。
特别可恨地是,一个多比特数据信号,某个比特被悄悄取了反,稍不留神想和前仿对比调试时,连数据位置都定位不准~往往也是让验证工程师哑巴吃黄连了。
i_dut_if.mon_data = Top.hwdata;
这里的Top层的hwdata信号并不是你以为的hwdata,事实上,hwdata的第0位被悄悄插了个反相器。所以,正确的做法是:
i_dut_if.mon_data = {Top.hwdata[31:1], ~Top.hwdata[0]};
没有驱动的Pad口的高阻信号传递到设计内部
一般设计的顶层会有不少三态门的io口,当我们在bench中例化设计时,一定要注意这些三态门,除了只能使用wire类型的信号进行连接外,还要考虑没有驱动情况下的上拉/下拉。
如果忽略了上拉/下拉,在功能仿真时一般不会遇到问题,但是后仿时’z的状态传入设计,便悄悄埋下个雷,某个门的输出就会变成x,并最终导致仿真停下来。
解决方法呢,很简单,在bench中连接设计时,将三态门连接的信号加个上拉/下拉就好了。
1 | wire scl; |
如果其他地方已经占用了pullup怎么办呢?没关系,pullup还有不同强度,选个强度弱的,这样即便其他地方也会有上拉/下拉,也不影响了。
上面的上拉语句加上强度等级,即:pullup(weak1) p0 (scl)。
后仿的坑,不提两级同步器良心不会痛么?
不知道有多少次debug一根根变红的信号线,一遍遍艰难地追根溯源,最后,这个罪魁祸首就是看着无辜的两级同步器的第一级输出。
两级同步器的引入不就是为了解决跨时钟域信号的亚稳态问题嘛?那么同步器的第一级输出必然是充斥着时序违例的原罪,但是对于时序检查的约束而言,它并不知道这是跨时钟的两级同步器,更不知道这一级输出的时序违例并不要紧,因为这个输出会再经过一个寄存器才会被使用。
芯片后仿一般都是系统级验证,一个SOC系统上跨时钟域的时钟还真是不少,用到的同步器没个千啊,也有百啊。逢山开路,遇水搭桥,除了这样硬着头皮追源头,还有更好的办法么?
要是说有什么好办法,那必然是,在仿真之前,我们能把这些符合条件的Q0提前挑出来。如何能从网表茫茫寄存器中把这些同步器第一级输出给甄别出来呢?这还真不是验证工程师自己能决定的。一方面取决于代码风格,试想,如果同步器都调用共同的lib cell,问题是不是会变得简单起来呢?
如图所示,当所有同步模块都使用定义好的同步单元syncff。
1 | module syncff( |
1 | syncff i_syncff( |
这样我们就很容易找到第一级同步器的输出d_meta。再统一加入到waive list中。这样就可以提前避免后仿中不要的x传递。
instance{top.xxx.i_syncff.d_meta}{noTiming};
转载自芯司机微信公众号