这是在ouravr首发的。。(PS:EDN的图片上传实在是。。。所以先发别处。。)
阻塞赋值,非阻塞赋值,说实话,我刚开始也搞了好久,感觉不能深度把握,还有就是一直不明白为什么用always写组合电路时要用阻塞赋值,写时序电路的时候要用非阻塞赋值,今天就进行了一次探究。还有就是为什么总说“仿真和综合的结果不一样”
话不多说,上几段测试代码,这几段的核心都是一样的,很简单的与,或逻辑,就没写注释,相信大家很容易明白。Testbench都是一样的,没有修改,写的很粗糙,但是应该比较容易懂。
Code1
`timescale 1 ns/ 1 ns
module test(a,b,c,d,y);
input a,b,c,d;
output y;
reg y,tmp1,tmp2;
always @(a or b or c or d)
begin
tmp1 <= a&b;
tmp2 <= c&d;
y <= tmp1|tmp2;
end
endmodule
这个代码非常简单,就两个与逻辑,一个或逻辑,那么就让我们来看看,它是否按照我们所想的那样工作呢?
Testbench
`timescale 1 ns/ 1 ns
module test_vlg_tst();
// constants
// general purpose registers
reg eachvec;
// test vector input registers
reg a = 0;
reg b = 1;
reg c = 1;
reg d = 0;
integer i;
// wires
wire y;
// assign statements (if any)
test i1 (
// port map - connection between master ports and signals/registers
.a(a),
.b(b),
.c(c),
.d(d),
.y(y)
);
initial
begin
// code that executes only once
// insert code here --> begin
for(i=0;i<10;i = i+1)
begin
a = ~ a;
c = ~ c;
#20;
end
// --> end
$display("Running testbench");
end
endmodule
testbench写的也比较简单,就是一个初始化,每20ns进行一次运算,a和c翻转一次,b,d不变。那我们来看看波形吧。
Modelsim波形
code1 (原文件名:QQ截图未命名1.png) 可能波形不是太清楚,大家凑合着看吧,我们先看abcd和tmp1,tmp2的关系:符合代码所写的tmp1 为a&b,tmp2 为c&d。但是注意最下面的三个信号,也就是tmp1,tmp2,y,它们的关系出现了问题。
Y的值并不等于当前的tmp1,tmp2相或的值,而是等于上一次运算时tmp1,tmp2相或的值。就像延迟了一个周期(请允许我在这里用周期这个词,虽然不是时序电路。)。
没关系,我们接着来看第二个代码。
Code2
Code2基本与Code1一样,除了在always的敏感列表中加入了tmp1,tmp2。
`timescale 1 ns/ 1 ns
module test(a,b,c,d,y);
input a,b,c,d;
output y;
reg y,tmp1,tmp2;
always @(a or b or c or d or tmp1 or tmp2)
begin
tmp1 <= a&b;
tmp2 <= c&d;
y <= tmp1|tmp2;
end
endmodule
Modelsim波形
code2 (原文件名:未命名2.jpg) 但是我们可以看到,这时的仿真波形(注意只是仿真波形),与前面有了不同,它没有了Code1的一个周期的延时,而成了我们所想的纯粹的组合逻辑。那我们来深入探究一下,这里面究竟出了什么事情。
那我们先来猜测下,是不是因为tmp1,tmp2被添加到敏感列表后,整个代码运行的方式不一样了?
Code3
深入探究,这个为什么是这样的。
为了深入探究,我加入了一个新的变量,研究程序的进行究竟如何,它就是一个计数器,可以帮助我们判断always模块运行的次数。
`timescale 1 ns/ 1 ns
module test(a,b,c,d,y);
input a,b,c,d;
output y;
reg y,tmp1,tmp2;
reg [8:0]j=0;
always @(a or b or c or d or tmp1 or tmp2)
begin
j = j + 1;//这里用了阻塞赋值,因为j仅仅起一个指示作用,与逻辑没有关系,所以用阻塞不// 影响,而且方便分析
#5//这里加了一个延时,方便大家分析
tmp1 <= a&b;
tmp2 <= c&d;
y <= tmp1|tmp2;
end
endmodule
Modelsim波形
code3 (原文件名:QQ截图未命名3.png) 这个估计不容易看清楚,跟大家描述下,最下面那个是我新添加的起计数器作用的指示变量,每进入一次always模块,它的值就增1,可以看出,它的变化比较奇怪,大约就是一次延时15ns变化,一次延时5ns变化。
我们分析下,从计数器(就是变量j了,请允许我这样称呼)等于1开始分析:
j从0增至1,为阻塞赋值(也就是它要完全执行完),
然后延时5ns,
运行always后面的几个非阻塞赋值(这个过程在仿真中不消耗时间),
然后,tmp1,tmp2得到了新的值,而这个时候我们注意到y的值其实并没有随着tmp1,tmp2的更新而更新,也就是说没有表现出code2中的组合逻辑的特性,而是像code1中的那种延时的效果,在j变成2后再过5ns,y才得到了新的值。
而在我的这个代码中5ns只有一个地方,就是j增1后面有5ns的延时。
这说明在tmp1,tmp2更新后再次进入了always模块,然后j从1增至2,然后延时5ns,而此时abcd的值没有变,所以tmp1,tmp2不变,但是y却得到了上一次tmp1或tmp2的值,发生跳变。
所以像上面这种用非阻塞赋值写组合逻辑的方法,本质上来讲写出来的不是组合逻辑,而仅仅是因为时间太短,掩饰了其中的一个很短的延时(可能我这样说不太对,望高手指正。)。
而且这种写法,大家可以看出,为了算本来一次就可以ok的组合逻辑,仿真器不得不两次进入always模块,增加了仿真器的负担,可能这里的代码比较短,还看不出什么弊病,如果你有1000个always模块都这样写呢?我想,那样的话情况就应该大大不同了。
所以我们在写代码的时候还是要遵循那句老话:写组合逻辑的时候,always中要用阻塞赋值,写时序逻辑的时候,always模块中要用非阻塞赋值。(当然不排除为了特殊的目的不遵循这个建议)
Code4
阻塞赋值
`timescale 1 ns/ 1 ns
module test(a,b,c,d,y);
input a,b,c,d;
output y;
reg y,tmp1,tmp2;
always @(a or b or c or d)
begin
tmp1 = a&b;
tmp2 = c&d;
y = tmp1|tmp2;
end
endmodule
这次的波形就不贴了,波形同Code2
总结一下,(我觉得我说的还是老话),在赋值这个动作中有两个原子操作,1.计算右边式子的值,2.赋值。
阻塞赋值的动作是不可分割的,一次性解决。
而非阻塞赋值的动作时可以分割的,仿真器会先执行完所有的计算操作(因为它优先级高,可以去查查仿真动作的优先级),没有计算后,再赋值。
如果用电路来表示的话,阻塞赋值就是一条直线上的数据流,前面的没有执行完,后面的就无法动弹。
而非阻塞赋值就像是触发器,后面的电路无需等待前面执行完,被触发后直接可以拿输入的值运算,但是可能这个时候,真正的值前级还没有给出,所以在为组合逻辑建模时,采用非阻塞赋值容易出错,这是来源于它本身的模型特性。
还有就是初学者要弄明白,这里的波形都是仿真波形而已,这几段代码综合出的电路还是一样的,但是仿真波形不同,也就是造成了许多人所说的,仿真的波形与综合后的电路不符合。
罗嗦一下,上面四段代码的结果是不同,但是仅仅在仿真时不一样而已,他们在经过综合后的电路都是一样的,这点注意一下
综合后的电路。
综合出的电路 (原文件名:QQ截图未命名4.png)
文章评论(0条评论)
登录后参与讨论