近期需要用到一段移位寄存器的程序,却在行为仿真时出现问题,代码如下(一个4位移位寄存器):
module sr4(
input clk ,
input en ,
input d ,
output reg q
);
reg [3:0] q_temp ;
always @(posedge clk)begin
if(en)begin
q_temp[3:0] <= {q_temp[2:0],d} ;
q <= 0 ;
end
else begin
q_temp <= q_temp ;
q <= q_temp[3] ;
end
end
endmodule
用两种不同的风格写测试文件时,仿真结果却不相同。
testbench(1)的仿真代码:
module sr4_tb;
reg clk;
reg en;
reg d;
wire q;
sr4 uut (
.clk(clk),
.en(en),
.d(d),
.q(q)
);
initial begin
clk = 0 ;
forever #5 clk = ~clk ;
end
//在四个时钟周期内,en=1,d一次输入1000
initial begin
en = 0 ;
@(posedge clk)
#10 en = 1 ;
d = 1 ;
#10 d = 0 ;
#30 en = 0 ;
end
endmodule
仿真波形如图:
testbench(1)的仿真波形
仔细观察发现,使能 en 和输入数据 d 在 15ns 处有效,而移位寄存器第一位立即装载进数据。这不符合逻辑啊,应该延迟一个时钟周期才对啊!
于是换了一种代码风格,testbench(2)代码风格如下:
module sr4_tb;
reg clk;
reg en;
reg d;
wire q;
sr4 uut (
.clk(clk),
.en(en),
.d(d),
.q(q)
);
initial begin
clk = 0 ;
forever #5 clk = ~clk ;
end
reg [5:0] en_tb ;
reg [5:0] d_tb ;
initial begin
en_tb = 6'b011110 ;
d_tb = 6'b010000 ;
end
//同样四个周期内产生en=1,d一次输入1000
always @(posedge clk)begin
en <= en_tb[5] ;
d <= d_tb[5] ;
en_tb[5:0] <= {en_tb[4:0] , 1'b0} ;
d_tb[5:0] <= {d_tb[4:0] , 1'b0} ;
end
endmodule
仿真波形如图:
testbench(2)波形
按照逻辑来说,移位寄存器的第一次移位进入会在串行输入信号 d 的下一个时钟周期开始。因此testbench(2)的波形才是正确的,那么testbench(1)的错误在哪儿?
出现了竞争!
仔细观察,testbench(1)中在 15ns 时,将 clk 和 en , d 的信号值同时改变,而接下来的逻辑中需要在 clk 的上升沿检测 en 和 d 的值,因此出现竞争。
而在testbench(2)中,en 和 d 的信号变化是由时钟沿来确定的。因此不会有竞争出现。
那么出现竞争的时候,仿真器的处理逻辑是什么样的呢??
实际测试结果,ISE 和 Modelsim 的处理逻辑是默认 en 和 d 改变在先,时钟跳变在后,因此 clk 的上升沿采集的是变化之后的 en 和 d ,因此没有延迟一个时钟周期。
而实际中是什么样子呢??
module test4(
input clk ,
input a ,
output reg b
);
always@(posedge clk)begin
b <= a ;
end
endmodule
实际的信号变化应该为这个样子:
当输入信号 a 的改变是在 clk 的上升沿触发时,表现为在当 clk 的上升沿检测时,检测到的是 a 改变前的值(因为触发器都有延时,a 在 clk 的上升沿一段延时之后才会改变), b 输出的是改变前 a 的值,在下一个上升沿输出改变后的值,这个时候才会表现为延时一个周期的现象。
仿真如下:
因此在设计和测试时,应该避免人为造成的竞争出现。
测试可用一个简单的状态机实现(或者将输入信号的改变放在always@(posedge clk)块中),本质就是将信号的改变归一到时钟沿之下,这样的仿真才符合实际。
网友回复:
这个和仿真器有关的。。。
verilog的仿真是基于事件的。。使用# ,马上改变数据。。但是没有激发posedge clk这个事件,所以当posedge clk触发后,采样的是变化后的值。。。
但是如果有@ 就不一样了。。。这个就马上激发posedge clk 了,就采样之前的值。。。
以上是我的理解。。。