开发平台:vivado 2020.1
仿真平台:modelsim 10.1d
前景提要
本人FPGA菜鸟一枚,本文旨在记录自己在工程中遇到的困惑。
我在Verilog代码中每次遇到 if语句就会想:if语句在T0时刻判断条件成功后,执行的语句是在T1时刻还是T0时刻立马执行?
通常在实际工程中无论是仿真还是逻辑分析仪抓信号结果都是:if语句在T0时刻判断条件成功后,执行的语句是在T1时刻。
modelsim时标取值是左侧取样还是右侧取样?
例1:
module test_ifelse(
input wire clk,
output reg [3:0] data_out
);
reg [3:0] data_in=0;
always@(posedge clk)begin
data_in <= data_in + 1;
end
always @(posedge clk)begin
if(data_in == 1)
data_out<= 1;
else if(data_in == 2)
data_out<= 2;
else if(data_in == 3)
data_out<= 3;
else if(data_in == 4)
data_out<= 4;
else if(data_in == 5)
data_out <=5;
else if(data_in == 6)
data_out <=6;
else
data_out<=0;
end
endmodule
modelsim仿真截图如下:
此时modelsim的时标取值为右侧取样
这也是平时在工程中所见的时序图。同时也是我产生了误解的原因之一,让我以为if语句的执行总是在判断条件成功后的下一拍。
其实不然,再看下一个例子。
例2:
module test_ifelse(
input wire clk,
input wire [3:0] data_in,
output reg [3:0] data_out
);
always @(posedge clk)begin
if(data_in == 1)
data_out<= 1;
else if(data_in == 2)
data_out<= 2;
else if(data_in == 3)
data_out<= 3;
else if(data_in == 4)
data_out<= 4;
else if(data_in == 5)
data_out <=5;
else if(data_in == 6)
data_out <=6;
else
data_out<=0;
end
endmodule
例2中将data_in改成外部输入的信号,data_in设置为0-6的伪随机数。testbench如下:
`timescale 1ns/1ns
module tb_test_ifelse();
reg clk;
reg [3:0] a;
wire [3:0] b;
initial begin
a =0;
clk =0;
end
always #20 a = {$random}%7;
always #10 clk = ~clk;
test_ifelse inst_test_ifelse(
.clk(clk),
.data_in(a),
.data_out(b)
);
endmodule
此时modelsim仿真截图如下:
从仿真图可以看到,在时钟上升沿采到数据后,data_out在同一时刻立马发生了变化。即结论:if语句是在当前时刻判断完成后,在当前时刻立马执行语句
为什么例1和例2的仿真结果看起来不一样呢?我们实际工程中又该如何区分呢?
问题分析
例1和例2的区别在于data_in时钟域不同。在例1模块中data_in和data_out是在同一个时钟域。在例2模块中,data_in为外部输入信号,不一定是和data_out在同一时钟域(先不考虑时序违规的情况,时序违规请参考这篇FPGA时序分析与约束文章)。
在FPGA中,寄存器是由触发器构成
在理想的寄存器模型中,q的输出是0延时的,即:时钟上升沿来临的时,0延时输出q的值。例如:
T0 | T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
clk | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 |
data_in(旧值) | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 |
q(旧值>>新值) | 0>>0 | 0>>0 | 0>>1 | 1>>1 | 1>>0 | 0>>1 | 1>>0 | 0>>0 | 0>>1 | 1>>1 | 1>>0 |
(PS:>>代表变化,例如0>>1表示 0变化到1)
由于这是理想情况下,在时钟上升沿来临的时刻,寄存器将旧值(data_in)赋值给新值(q)是瞬时完成的。
实际上,即使随着现代技术的更迭,寄存器的输出速度越来越快,但仍然需要时间,实际上的寄存器时序图应该是:
早期EDA厂商基于这种实际捕获信号的逻辑值来同时判断时序变化是否违规,以及新旧值是否变化正确。在现代随着大规模集成电路发展,工程信号多而逐渐淘汰。自上世纪七十年代后,Synopsys公司以及EDA理论先驱卡佛尔.米德均提出:将逻辑功能分析和时间延迟分析分开 即:功能分析只看逻辑功能对不对的问题,时序分析再看时序是否违规,时序快不快的问题。因此在功能仿真中,现代EDA工具大多倾向于以0填充tco延迟来右侧逼近时钟上升沿
(参考:《现代数字电路设计与实践》-陆广)
结果分析
现在回过头来看例1,画出例1的实际时序图:
T0 | T1 | T2 | T3 | T4 | T5 | T6 | T7 | |
---|---|---|---|---|---|---|---|---|
clk | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 | 上升沿 |
data_in(采样时刻值) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
data_in(采样时刻经过tco时刻后的变化) | 0>>1 | 1>>2 | 2>>3 | 3>>4 | 4>>5 | 5>>6 | 6>>7 | 7>>7 |
data_out(采样时刻值) | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
data_out(采样时刻经过tco时刻后的变化) | 0>>0 | 0>>1 | 1>>2 | 2>>3 | 3>>4 | 4>>5 | 5>>6 | 6>>7 |
总上所述,现在EDA工具以0代替tco延迟,右侧逼近时钟沿,画出例子1功能仿真时序图:
此时就和上面图1中moselsim显示的时序图一模一样了:
同理,画出例2实际时序图:
再以0填充tco延迟,画出例2功能仿真时序图:
此时就和上面图2中moselsim显示的时序图一模一样了:
以上都是modelsim中时标为右侧取样的例子,但实际工程中也有一些信号取样为左侧逼近取样。例如FIFO仿真测试程序如下:
module fifo_test(
input wire clk,
input wire rst,
output wire [7:0] fifo_data_out
);
reg [7:0] fifo_data_in=8'd23;
reg wr_en;
reg rd_en;
reg [5:0] wr_cnt;
reg [5:0] rd_cnt;
wire[5:0] wr_fifo_cnt;
wire[5:0] rd_fifo_cnt;
wire fifo_full;
wire fifo_empty;
always@(posedge clk)begin
if(rst == 1)begin
fifo_data_in <= 8'd23;
wr_en <= 1'b0;
wr_cnt <= 6'd0;
end
else if(wr_cnt == 16) begin
wr_en <= 0;
fifo_data_in <= fifo_data_in;
wr_cnt <= 6'd16;
end
else begin
wr_en <= 1'b1;
fifo_data_in <= fifo_data_in + 10;
wr_cnt <= wr_cnt + 1;
end
end
always @(posedge clk)begin
if(rst == 1)begin
rd_en <= 1'b0;
rd_cnt <= 6'd0;
end
else if(rd_cnt == 16)begin
rd_en <= 1'd0;
rd_cnt <= 6'd16;
end
else if(rd_fifo_cnt == 15) begin
rd_en <= 1'b1;
rd_cnt <= rd_cnt +1;
end
else begin
rd_en <= rd_en;
rd_cnt <= rd_cnt;
end
end
fifo1 inst_fifo1 (
.rst(1'b0),
.wr_clk(clk),
.rd_clk(clk),
.din(fifo_data_in),
.wr_en(wr_en),
.rd_en(rd_en),
.dout(fifo_data_out),
.full(fifo_full),
.empty(fifo_empty),
.rd_data_count(wr_fifo_cnt),
.wr_data_count(rd_fifo_cnt)
);
endmodule
测试程序如上,向FIFO写入16个数据后,再读16个数据出来。
testbench如下:
`timescale 1ns / 1ps
module tb_fifo_test();
reg clk,rst;
wire [7:0] data_out;
initial begin
clk = 0;
rst = 1;
#240;
rst = 0;
end
always #10 clk = ~clk;
fifo_test inst_fifo_test(
.clk(clk),
.rst(rst),
.fifo_data_out(data_out)
);
endmodule
打开modelsim仿真如下:
在250ns,时钟上升沿,fifo_data_in右侧取样为33,wr_en右侧取样为1’d1,wr_cnt右侧取样为6’d1
在270ns,时钟上升沿,fifo_data_in右侧取样为43,wr_en右侧取样为1’d1,wr_cnt右侧取样为6’d2
在290ns,时钟上升沿,fifo_data_in右侧取样为53,wr_en右侧取样为1’b1,wr_cnt右侧取样为6’d3,rd_fifo_cnt左侧取样
为6’d0
在590ns,时钟上升沿,rd_en右侧取样为1’b1,wr_fifo_cnt左侧取样
为6’d11,rd_cnt右侧取样为6’d1,rd_fifo_cnt左侧取样
为6’d15
由此可见,在实际工程中也会遇到modelsim等仿真软件左侧取样和右侧取样同时出现情况。此时我们随便取一个左侧取样的信号,不断的放大时标来观察情况如下:
在590ns,时钟上升沿,凡是左侧取样的信号,皆有0.1ns的延迟。此时modesim等仿真信号没有以0填充,必定有0.1ns的延迟,这可能就是寄存器翻转所带来的tco时间开销。
总结
verilog中if语句判断和执行是在同一时刻
凡是在modelsim功能仿真中,右侧取样的信号一定是在时钟沿时刻翻转,0延迟
凡是在modelsim功能仿真中,左侧取样的信号一定是在时钟沿右侧翻转,非0延迟