那些年,我们一起踩过的后仿真的坑
本文分享了后仿真(后仿)中的常见问题及解决方法:
后仿与前仿的区别:
后仿主要检查设计中的时序问题,在功能上接近真实设计运行状态。相比前仿(基于RTL代码的仿真),后仿更注重时序验证和 debug难度较高。
寄存器初始化问题:
设计中寄存器通常有复位端,但在实际应用中可能存在没有复位或初值的寄存器信号(如analog model)。解决方案是在testbench中通过force-release方式赋初值以确保寄存器正常工作。
信号取反问题:
在DUT中驱动或监测的信号可能与设计命名不一致(如rstb被当作rst),或者多比特数据信号的部分比特被取反。解决方法是在mon_data配置中正确处理信号翻转情况。
三态门上拉/下拉配置:
设计顶层的三态门IO口在 bench 中连接时需注意上拉/下拉配置。如果没有正确配置上拉/下拉,在功能仿真正常但后仿时可能导致仿真异常(如输出为x状态)。
两级同步器的问题:
两级同步器用于解决跨时钟域信号传输的亚稳态问题,但其第一级输出可能引发时序违例。解决方案包括在仿真前筛选符合条件的第一级同步器输出并加入waive list以避免x状态传递。
通过以上方法可以有效提升后仿效率并减少调试难度。
转载自芯司机微信公众号
一、简介
后仿真,是对经过综合/布局布线后的门级网表进行仿真,与前仿真对rtl代码进行仿真,找出功能上的缺陷相比,后仿真主要检查时序上的问题。
相比较理想状况下的rtl仿真(功能仿真、前仿真),后仿消耗时间更多,在可读性差的cell里穿梭行走,debug的难度大大增强了,而一般我们在做后仿的时候,往往也意味着离tapeout的时间很急迫了。
那么,时间紧,任务重的情况下,后仿经验的积累就尤为重要了,除了打怪升级多练手外,我们还可以从彼此的踩过的坑中快速攥经验值。
二、没有复位的寄存器
一般情况下,设计里面的寄存器都要有复位端。但是,别忘了,后仿不仅仅包括设计的网表,还有各种model,比如analog model,这些model可能因为种种原因,存在没有初值没有复位的寄存器信号。这些寄存器在后仿时候往往给我们造成很大的麻烦。
reg rg_a, rg_b;
always @(posedge clk) begin
rg_b <= rg_a;
end
在测试 benches中采用 force release 方式初始化寄存器。由于所使用的变量类型为 reg 类型,在赋初值后且无其他条件触发的情况下,则会持续保留该 force 输入的值。
initial begin
force TOP.rg_b = 0;
#0; // any delay time
release TOP.rg_b;
end
三、信号取反
当我们用网表进行后仿时,在testbench中驱动或者监测的DUT中的信号很可能是一个取反的信号。特别是端口上的信号,名字都没变,明明叫rstb的那货,行为上却像个rst。并不是设计人员粗心大意,而是DC和ICC这样的工具捣的鬼。
特别可恨地是,一个多比特数据信号,某个比特被悄悄取了反,稍不留神想和前仿对比调试时,连数据位置都定位不准~往往也是让验证工程师哑巴吃黄连了。
i_dut_if.mon_data = Top.hwdata;
在Top层次上观察到的hwdata信号与你认为的一样吗?实际上,在某些情况下(比如特定配置或操作中),在某些情境下(比如某个模块运行时),hwdata的第0位会被意外地插入了一个反相器。因此,请采取以下措施进行处理:
i_dut_if.mon_data = {Top.hwdata[31:1], ~Top.hwdata[0]};
未被驱动的Pad端口经由高阻抗信号传递至系统内部。在 typical design architecture 中, 顶层通常配置了大量三态门类型的io接口, 特别是在构建基准测试平台(bench)并导入待测设计时。需要注意的是, 除了采用wire类型的直接传输通道外, 还应确保每个三态门端口具备适当的上拉和下拉功能。即便暂时忽略掉所有端口上的上/下拉电平设置, 在功能仿真阶段通常仍能顺利运行, 但实际应用中, 当输入状态为‘z’(高阻抗状态)进入系统时, 可能会引发潜在的问题——若此时某逻辑单元输出变为无效(x状态)则可能导致整个仿真的终止。解决方案在于在 bench 构建阶段就对这些端口施加相应的电平控制逻辑
wire scl;
dut i_dut(.scl(scl));
pullup(scl);
如果其他地方已经占用pullup的话也没关系 拉起来还有不同强度可以选择 只要选择强度较弱的那种 这样即使其他地方也有上拉或下拉也不会影响整体效果
在上拉语句中增加强度等级 即pullup(weak1) p0 (scl)
经常需要 debug一根根变红的信号线 一层层追根溯源 最终发现这个错误的原因其实是无辜的两级同步器的第一级输出
引入两级同步器的目的就是为了解决跨时钟域信号传输中的亚稳态问题 因此第一级输出必然携带时序违例 因为它并不知道这是一个跨时钟域的两级同步器 并且它所携带的时间违例其实并不直接影响使用它的寄存器 因为这个信号会经过寄存器后再被使用
在系统级验证中 芯片后仿通常会涉及大量跨时钟域的时钟网络 每个 SOC 系统上可能就需要数百甚至上千个同步器 这确实是一个挑战 需要硬着头皮一个一个地排查源问题
如果有更好的方法那就太好了 但在仿真阶段 我们可以通过筛选的方式提前识别出符合条件的所有Q0 输出端口 这需要从网表中找出所有可能与同步模块相关的寄存器端口 并根据特定条件进行筛选
例如 如果所有同步模块都统一使用相同的lib cell进行配置 那么问题就会变得简单许多
module syncff(
input clk,
input rstb,
input d,
output reg q
);
reg d_meta;
always @(posedge clk or negedge rstb)begin
if(!rstb) d_meta <= 1'b0;
else d_meta <=d;
end
always @(posedge clk or negedge rstb)begin
if(!rstb) q <= 1'b0;
else q <=d_meta;
end
endmodule
syncff i_syncff(
.clk(clk),
.rstb(rstb),
.d(d),
.q(q)
);
通过这一级同步器的输出d_meta相对容易获得,并且可以通过这一级同步器的输出d_meta相对容易获得。然后将这一级同步器的输出d_meta系统性地整合进wave list中。这样就能够有效抑制后续仿真的不必要的变量传播。
instance{top.xxx.i_syncff.d_meta}{noTiming};
