Debugging simulation mismatches
Mismatches指的是仿真结果和预期结果不一样,当出现该问题之后,首先从代码本身找问题;
例如:
if (data == 3)
写成了 if( data = 3 )
为了最大程度避免这一问题,可以写成 if ( 3 == data )
,这样写的话,如果少写了一个=
,编译的时候会报错。
(当然也不排除工具本身出现bug)
VCS中也有一些编译选项来帮助寻找 Mismatched问题。(用的不多)
+race
:以文件(race.out)的形式报告代码中存在的竞争冒险。例如,同时对一个变量进行读写,倒底是先执行读操作,还是先执行写操作。
$vcdplusdeltacycleon
:也可以用于定位竞争冒险对应的代码。(locate race condition code)
vcddiff & vcat
:比较两个VCD波形。(这里是以文本的方式比较,用的不多,一般是在波形窗口进行比较。)
SImulation Mismatches 产生的原因
从RTL描述到门级网表的综合过程,因为代码的看问题,逻辑综合工具可能做出了和预期不一样的判断,从而导致前仿成功,而后仿失败。
Race Conditions
① Write-Read
Race:using and setting a value at the same time
module race;
reg a;
initial begin
a = 0; #10 a = 1;
end
initial begin
#10 if(a) $display("May not print");
end
endmodule
这里两个 initial
语句块的执行顺序是不确定的,所以 $display()
可能永远不会执行,具体取决于仿真工具如何处理这两个语句块。
改进方法: 将两个语句是时间分开
module race;
reg a;
initial begin
a = 0; #10 a = 1;
end
initial begin
#11 if(a) $display("Will print");
end
endmodule
② 同时赋值(Write-Write)
③ Continuous assignment evaluation
改进:时序电路尽可能用非阻塞赋值语句进行赋值操作
不定态的说法其实是只存在于仿真过程中,无法确定一个端口的逻辑值是0还是1。在实际的电路中,要么是0,要么是1。
对于改进后的代码,根据仿真事件队列,非阻塞赋值的的赋值操作是要晚于连续赋值语句 assgin 的,所以当第一个时钟上升沿来的时候,p是不定态,if 语句认为不定态是 false,所以不会输出内容。第二个时钟上升沿的时候,p和q都稳定为1,正常输出内容。
④ Time Zero Races
在给 reset 赋值为 0 之前,reset 是一个不定态。那么:
① X → 0 的变化,到底算不算是一个下降沿?
② 如果认为是下降沿,那么这个下降沿变化和 always 语句到底谁在前?
改进:
(x→1的变化肯定不是下降沿。)
⑤ Filp-Flop Race:Read-Write
如下所示,这里在组合电路中使用了非阻塞赋值语句,第一个always语句对应dff1,第二个always语句对应dff2。
那么这个电路能否得到想要则结果:
① 仿真结果是否符合预期?
② 综合工具得到的电路是否是想要的?(就是下面的RTL代码,综合后能够得到上面的电路吗?)
对上述代码的唯一改进就是使用非阻塞赋值,如下所示。对于如下代码,仿真结果和综合结果是都会是符合预期的。
一些好的代码规则
① 组合电路使用阻塞赋值,时序电路使用非阻塞赋值
② 组合电路使用 always @(*)
③ 一个变量同一时间只在一个地方进行赋值。(多驱动一般用于SoC ,接口的双向端口,通过定义wire使用多驱动源)
Lab 两级触发器
这里就使用 Filp-Flop Race 部分给出的示例进行实验,并且回答提出的两个问题。
源代码
dff_exp.v
//-------------------------------
// File Name : dff_exp.v
// Data : 2023-07-24
// Designer : Jyw
//-------------------------------
module dff_exp(
// Inputs
input wire clk_i,
input wire rst_l_i,
input wire d,
// Outputs
output reg q
);
//-------------------------------
// ref define area
//-------------------------------
reg q1;
//-------------------------------
// Style 1 (YES) non-blocking
//-------------------------------
`ifdef DFF_STYLE1
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q1 <= 1'b0;
else
q1 <= d;
end
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q <= 1'b0;
else
q <= q1;
end
`endif
//-------------------------------
// Style 2 blocking
//-------------------------------
`ifdef DFF_STYLE2
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q1 = 1'b0;
else
q1 = d;
end
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q = 1'b0;
else
q = q1;
end
`endif
endmodule // dff_exp
dff_tb.v
//------------------------------
// File Name : dff_exp.v
// Data : 2023-07-24
// Designer : Jyw
//------------------------------
`timescale 1ns/1ns
module dff_tb;
//------------------------------
// reg & wire define
//------------------------------
reg clk_i;
reg rst_l_i;
reg d;
reg q;
//------------------------------
// clock & reset & d generate
//------------------------------
initial begin
clk_i <= 1'b0;
forever #5 clk_i <= ~clk_i;
end
initial begin
rst_l_i <= 1'b1;
repeat (2) @ (posedge clk_i);
rst_l_i <= 1'b0;
repeat (2) @ (posedge clk_i);
rst_l_i <= 1'b1;
repeat (20) @ (posedge clk_i);
$finish(2);
end
initial begin
forever begin
@ ( posedge clk_i );
d <= 1'b0;
@ ( posedge clk_i );
d <= 1'b1;
end
end
//--------------------------------
// Instance of dff_exp
//--------------------------------
dff_exp u_dff_exp(
//Inputs
.clk_i(clk_i),
.rst_l_i(rst_l_i),
.d(d),
//Outputs
.q(q)
);
//-------------------------------
// dump file
//-------------------------------
initial begin
$vcdpluson();
end
endmodule // dff_tb
Makefile
.PHONY: com cov sim clean
OUTPUT = simv_dff_exp
ALL_DEFINE = +define+DFF_STYLE2
# Code coverage command
# vdp file name
VPD_NAME = +vpdfile+$(OUTPUT).vpd
# Compile command
VCS = vcs -sverilog +v2k -timescale=1ns/1ns \
-debug_all \
+notimingcheck \
+nospecify \
+vcs+flush+all \
$(ALL_DEFINE) \
$(VPD_NAME) \
-o $(OUTPUT) \
-l compile.log
# Simulation command
SIM = ./$(OUTPUT) \
$(VPD_NAME) \
-l $(OUTPUT).log
# Start Compile
com:
$(VCS) -f file_list
# Start simulation
sim:
$(SIM)
debug:
dve -vpd $(OUTPUT).vpd &
# Start Clean
clean:
rm -rf ./csrc *.daidir *.log *.vpd *.vdb simv* *.key *tace.out*
非阻塞赋值实现
先使用宏定义 DFF_STYLE1
,对使用非阻塞赋值的实现的方式进行仿真。仿真得到的波形如下:
q1比d慢一拍,q比q1慢一拍,符合预期,电路正确。
综合的结果:
阻塞赋值实现
使用宏定义 DFF_STYLE2
,对使用阻塞赋值的实现的方式进行仿真。仿真得到的波形如下:
此时,q 和 q1 同时改变,这并不是我们想要的。(我们想要的是 q 的变化比 q1 慢半拍。)
虽然仿真结果是错误的,但是综合的结果却是正确的。
其他写法的仿真和综合
两级触发器的实现,除了上述两种写法之外,还有其他几种写法,如下:
//-------------------------------
// Style 3
//-------------------------------
`ifdef DFF_STYLE3
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q1 <= 1'b0;
q <= 1'b0;
else
q1 <= d;
q <= q1;
end
`endif
//-------------------------------
// Style 4
//-------------------------------
`ifdef DFF_STYLE4
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q1 = 1'b0;
q = 1'b0;
else
q1 = d;
q = q1;
end
`endif
style 3 的仿真结果和综合结果:
style 4 的仿真结果和综合结果:
//-------------------------------
// Style 5 交换顺序
//-------------------------------
`ifdef DFF_STYLE5
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q <= 1'b0;
q1 <= 1'b0;
else
q <= q1;
q1 <= d;
end
`endif
//-------------------------------
// Style 6
//-------------------------------
`ifdef DFF_STYLE6
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
q = 1'b0;
q1 = 1'b0;
else
q = q1;
q1 = d;
end
`endif
Style 5 和 Style 6 的仿真结果和综合结果都是满足预期的。
//-------------------------------
// Style 7
//-------------------------------
`ifdef DFF_STYLE7
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
{q,q1} <= 2'b0;
else
{q,q1} <= {q1,d};
end
`endif
//-------------------------------
// Style 8
//-------------------------------
`ifdef DFF_STYLE8
always @ ( posedge clk_i, negedge rst_l_i) begin
if (!rst_l_i)
{q,q1} = 2'b0;
else
{q,q1} = {q1,d};
end
`endif
Style 7 的仿真结果和综合结果:
Style 8 的仿真结果和综合结果: