当前位置:首页 > EDA > 电子设计自动化
[导读]在Verilog/SystemVerilog仿真中,竞争条件(Race Condition)是导致“仿真结果与综合硬件不一致”的头号杀手。这种问题通常表现为:代码稍作修改(如增加打印语句)仿真就通过,或者同一份代码在两台机器上跑出不同结果。本文将结合ModelSim,解析竞争条件的成因与实战排查技巧。



在Verilog/SystemVerilog仿真中,竞争条件(Race Condition)是导致“仿真结果与综合硬件不一致”的头号杀手。这种问题通常表现为:代码稍作修改(如增加打印语句)仿真就通过,或者同一份代码在两台机器上跑出不同结果。本文将结合ModelSim,解析竞争条件的成因与实战排查技巧。


一、什么是Race Condition?


Race Condition发生在两个或多个进程(always块、initial块、assign语句)试图在同一个仿真时间步长(Time Step)内,读写同一个变量,且执行顺序不确定时。


核心特征:

• 仿真结果不稳定。


• 添加$display或改变仿真精度后,结果发生变化。


• 综合后的FPGA/ASIC行为与仿真不符。


二、最常见的三种竞争场景


1. 阻塞赋值(=)引起的竞争


在initial或always块中,使用阻塞赋值=读取另一个进程刚写入的信号。

// 进程A

initial begin

   #10 a = 1'b1; // 在时间10ns写入

end


// 进程B

initial begin

   #10 b = a; // 在时间10ns读取

end


问题:在10ns这个时间点上,a的写入和b的读取谁先执行?ModelSim的执行顺序是随机的(取决于调度算法)。这可能导致b有时为0,有时为1。


2. 非阻塞赋值(<=)与显示/监控任务的竞争


这是最容易踩的坑。$display、$monitor等系统任务在Active Region执行,而非阻塞赋值在NBA Region执行。

always @(posedge clk) begin

   q <= d; // 非阻塞赋值,进入NBA区

end


always @(posedge clk) begin

   $display("q=%b, d=%b", q, d); // 在Active区执行

end


现象:$display打印出的q值是旧值(时钟沿之前的值),而不是d的新值。这会让新手误以为逻辑错误,实则只是仿真调度顺序问题。


3. 组合逻辑中的多驱动


两个不同的always @(*)块试图驱动同一个reg变量。

always @(*) begin

   if (sel) y = a;

end


always @(*) begin

   if (!sel) y = b; // 错误:两个always块驱动y

end


后果:ModelSim可能通过编译,但仿真结果不确定,且综合时会报错或产生锁存器。


三、ModelSim中的定位与排查技巧


技巧1:使用+race编译选项(最强武器)


ModelSim提供了专门的编译开关来抓竞争条件。

# 在ModelSim控制台或.do文件中

vlib work

vmap work work

vlog +race=2 -sv my_design.v my_tb.v

vsim -novopt work.my_tb

run -all


参数解释:

• +race=1:报告潜在的竞争。


• +race=2:报告所有竞争,包括上面提到的NBA与$display的竞争。这是调试利器。


运行后,ModelSim会在Transcript窗口中直接指出竞争发生的行号和信号名。


技巧2:波形窗口的“Force on”与“Glitch”


如果在波形窗口看到信号出现极窄的毛刺(Glitch),且毛刺的宽度等于仿真分辨率(如1ps),这通常是多个进程在同一时刻驱动同一信号造成的。


操作:

1.  放大波形到皮秒级。

2.  观察信号是否在同一个时间点被拉高又被拉低。

3.  检查驱动该信号的所有always块。


技巧3:利用$display定位(慎用)


虽然$display可能引入竞争,但它可以用来“探测”执行顺序。

always @(posedge clk) begin

   $display("Time=%0t, Before q=%b", $time, q);

   q <= d;

   $display("Time=%0t, After q=%b", $time, q);

end


如果Before和After打印出的q值相同,说明$display在NBA赋值之前执行了。这能帮助理解调度顺序。


技巧4:检查“Zero-Time Loop”(零延时循环)


如果仿真卡死或CPU占用100%,可能是无意中创建了零延时循环。

// 危险代码

always @(*) begin

   a = b; // 组合逻辑

end


always @(*) begin

   b = a; // 组合逻辑,形成零延时反馈环路

end


排查:在ModelSim中暂停仿真,查看Call Stack或Process窗口,通常会看到一个进程反复被触发。


四、修正竞争条件的黄金法则


1. 时序逻辑:坚持使用非阻塞赋值(<=)


这是避免Race Condition的第一准则。非阻塞赋值保证了在同一个时钟沿,所有信号同时更新,且更新的是时钟沿前的值。

// 修正后

always @(posedge clk) begin

   q <= d; // 安全

end



2. 组合逻辑:使用阻塞赋值(=)且单一驱动


确保一个reg变量只被一个always @(*)块驱动。

// 修正后

always @(*) begin

   if (sel) y = a;

   else y = b; // 全覆盖,单一always块

end



3. 测试平台(Testbench):使用#1延迟规避竞争


在Testbench中,如果需要在同一时刻驱动信号和检查信号,使用微小的延迟来区分。

// 驱动

initial begin

   #10 clk = 0;

   #10 clk = 1;

end


// 检查

always @(posedge clk) begin

   #1; // 延迟1ns,确保在NBA赋值之后检查

   $display("q=%b", q); // 此时q是新值

end



五、结语


ModelSim中的Race Condition本质上是仿真调度机制与Verilog语义碰撞的结果。掌握+race=2编译选项,理解Active Region与NBA Region的区别,并在编码时严格遵守“时序逻辑用<=,组合逻辑用=且单驱动”的铁律,就能将这类诡异的Bug扼杀在摇篮中。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

在FPGA和ASIC设计流程中,仿真验证是一个至关重要的环节。ModelSim作为业界领先的仿真工具,以其强大的功能和高效的仿真速度赢得了广泛的应用。然而,随着设计复杂度的不断提升,仿真时间也随之延长,成为制约设计周期的...

关键字: ModelSim仿真 FPGA ASIC设计
关闭