|
前言:
阻塞与非阻塞赋值是 Verilog 语言中最基本的部分,也是让大部分 Verilog 新手最困惑的地方。
关于阻塞与非阻塞的著作文章可谓汗牛充栋,这些文章对阻塞与非阻塞赋值的原理进行了非常详细的讲
解,但新手读了之后依然有种似懂非懂的感觉,编码过程中一如既往的犯错。所以,本文的目的立足于
提供一种实用化的解决方案,用最简单的语言和形象的类比让新手能够一目了然的明白正确的编码方式
以及相应的电路行为逻辑,关于仿真细节的讲解不是本文重点,需要了解更多细节的朋友可以参考文后
列举的参考文献。
本文共分为三部分:
第一部分是正确使用阻塞与非阻塞赋值的基本原则。
第二部分是阻塞与非阻塞赋值对应的电路行为逻辑。
第三部分是阻塞与非阻塞赋值的原理简介。
第一部分 正确使用阻塞与非阻塞赋值的基本原则(Golden Rule)
编码原则很多,就阻塞非阻塞赋值而言,新手最需要牢记的是其中三条:
1、时序逻辑一定用非阻塞赋值”<=”,一旦看到敏感列表有 posedge 就用”<=”。
2、组合逻辑一定用”=” ,一旦敏感列表没有 posedge 就用”=”,一旦看到 assign 就用”=”。
3、时序逻辑和组合逻辑分成不同的模块,即一个 always 模块里面只能出现非阻塞赋值”<=”或者”=”。如
果发现两种赋值并存,一个字”改”,心存侥幸可能会给后续工作带来更多麻烦。
以上三条,对新手而言不必追求为什么,需要的就是条件反射的照章办事。最后说一句,新手可能记不
住哪个符号是阻塞赋值,哪个是非阻塞赋值,大家可以数数, ”非阻塞赋值”一共 5 个字,“阻塞赋值“ 4
个字,所以非阻塞用的符号”<=”比阻塞赋值用的符号”=”长。
第二部分 阻塞与非阻塞赋值对应的电路行为逻辑
第一节给出了三条最基本的编码原则,有个朋友可能会想,按照这三条编码原则写出来的代码会按怎样
的逻辑工作呢?这一节就是回答这个问题。
首先解释一下阻塞赋值与非阻塞赋值的含义。
所谓的阻塞赋值”=”就是说, 在这个语句没有执行完之前,后面的语句是不执行的。
这里执行的含义是指完成变量值的更新。
非阻塞赋值”<=”是指, 所有的语句可以并发执行,而前面的值是否执行完毕不会影响后面的语句,
换句话说,语句的顺序是无关紧要的。
举个例子,假设一个模块,有 2 个寄存器, b 和 c,初值都是 1。 a 为输入信号线。在某个时刻,因为某
种原因,模块被触发执行。对于组合逻辑而言,一般是输入信号值变了,对于时序逻辑而言,一般是时
钟沿到了。
首先看组合逻辑:假设输入 a = 2;
always@(a ,b)
begin
b = a;
c = b;
end
由于是阻塞赋值,所以首先执行完第一句 b=a,执行完成之后 b=2。接着执行 c=b,执行完成后 c=2,一
次仿真结束后 b=c=2;
对于时序逻辑而言,依然假设 a =2;
always@(posedge clk)
begin
b <= a;
c <= b;
end
由于是非阻塞赋值,首先执行第一句 b<=a,这时候 a = 2,但是还没有执行完第一句的时候,第二句 c<=b
也执行了,由于第一句没有执行完, b 的值还是 1,这时候赋值给 c 的值也是 1。执行完毕的结果就是
c=1,b=2.等到模块再次被触发的时候 c 的值更新为 2。
第三部分 阻塞与非阻塞赋值的原理简介
有个朋友可能就会问了,凭啥第一句执行到一半就该第二句执行呢?到底是第一句先完成赋值呢还是第二句
先完成赋值?答案是,谁先完成赋值都没关系,结果是一样的。
为什么说结果一样呢? 因为两种赋值方式分别是按照下面的顺序执行的。
阻塞赋值,就跟 C 语言一样,严格按照代码书写的先后顺序执行,所有值都是立即更新,并且在下面的
语句中按照新值执行。
而时序电路就不一样了,大家可以这么理解时序逻辑的代码行为,一次执行分为两轮:
第一轮是所有的左值都先赋给临时变量,
第二轮用输入值以及和右值同名的临时变量值去更新左值。
比如上面的例子,第一轮,赋给临时变量: tempc=c;tempb=b。
第二轮,临时变量更新左值, b = a;c = tempb;output = tempc。
从上面的分析也可以看出,组合逻辑的结果与代码顺序直接相关,而时序逻辑与代码顺序没有关系。这就是所
谓的顺序执行(组合逻辑)与并发执行(时序逻辑)。
为了进一步理解这两种赋值方式的行为,下面用对应的电路进行说明,以前面的代码为例。大家首先闭
上眼睛想想,对应的电路是什么样子的呢?
其实答案很简单,对于阻塞赋值来说,如图一所示,综合的结果就是一根导线,当然,可能有反相器,
buf 什么的,反正还是可以看作一根线。
到这里,应该就很容易理解顺序执行的行为方式了。
有细心的朋友可能会问,如果换种写法呢?
always@(a,b)
begin
c = b;
b = a;
end
很显然,这种电路的行为跟之前是不一样的,从逻辑来看会产生类似于非阻塞赋值的结果,但很显然不
满足非阻塞赋值并发执行的特点。如果把输入电平 a 触发改成时钟边沿触发 posedge clk,出来的就是寄
存器,但这违反了时序逻辑不用阻塞赋值的原则,所以严重不推荐。至于这种组合逻辑描述方式出来的
电路是啥我也不知道,大家可以自己综合看看,或者哪位高人补上~~应该注意的是,如果想象不出这种
怪异的 coding 方式会产生何种电路,就不要这么写,因为实现这种逻辑最好的办法是采用非阻塞方式描
述。
而对于非阻塞赋值而言,如图二所示,综合出来的结果就是 2 个寄存器。对 b,c 赋值的过程就是寄存器
输入采样的过程,很显然两个采样是同时进行的,而且一次时钟沿只会采样一次,所以输入值 a 会首先
被采样到 b,再在下一个时钟被采样到 c.
总结一下:
关于两种赋值方式,首先讲述了代码执行的过程,然后用直连线和寄存器分别对应了两种描
述方式。应该指出的是,非阻塞赋值用寄存器的类比是完全准确的,而阻塞赋值用直连线的类比却未必
准确,只不过因为一般认为直连线是从输入到输出依次更新的,而且没有传输以外的延迟,所以这种类
比有助于新人理解,虽然不够严密。大家熟悉了之后就应该按照更严谨的方式去理解。
补充一下,如果写出下面这样的代码,在时序电路中使用阻塞赋值的话,综合出来就只有一个寄存器,不是两个,寄
存器的输入 D 端是 a,输出 Q 端是 b 和 c,两个信号在同一根线上
always@(posedge clk)
begin
b = a;
c = b;
end
所以这样的写法很容易造成设计和综合结果不匹配的情况,即使不是要设计两级寄存器,也最好不要用阻塞赋值。
另外一个就是,脑袋里面需要有时序逻辑和组合逻辑的框图。
将组合逻辑和时序逻辑分开写,组合逻辑用=,时序逻辑用<=,
时序逻辑内的判断越简单越好,尽量都放在组合逻辑里面做,时序逻辑只负责 flop 一下。
所以,最重要的是, 新手应该严格按照规则来,见到 posedge 就写 <= ,而根本不考虑 = ,免得综合出怪东西来。
这种代码应该严格杜绝,呵呵~~
————————————————
版权声明:本文为CSDN博主「Times_poem」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Times_poem/article/details/52032890 |
|