很多人在学习verilog的时候,总是搞不懂阻塞赋值与非阻塞赋值。其实两者区分比较简单。
阻塞赋值就和高级语言(如C、java)中的赋值一样,写法也一样,都是直接用“=”。在语句块中,都是上一条语句执行完毕后,再执行下一条语句。也就是说,如果语句A执行依赖语句B执行的结果,在语句B执行完之前,语句A一定处于阻塞等待状态,待B的结果到达后再执行。
非阻塞赋值就需要有时序的概念,也就是流水作业,写法为“<=”。看过农民工搬砖的同学都知道,几个人站成一行传递砖块,有个喊号子的人,号子一响,所有人把自己一只手上的砖传给后一个人的同时,另一只手也接到前面一个人的砖,只要砖没搬完,所有人手上总是有块砖。号子就像时钟,每个农名工就是一个触发器(带存储功能),时钟边沿脉冲一到,就把当前的值传出去,而自己也同时更新。也就是说,在语句块中,如果语句A执行依赖语句B执行的结果,则在时钟沿到达时,语句B把自己此时拥有值(也就是上一周期运算并暂存在此的结果)传递给A的同时,B又要完成本时钟周期的运算;反过来说,语句A执行时,不需要阻塞着等待B完成本时钟周期的运算,而是直接把B中现在存有的值(即上一周期执行结果)取过来。
写verilog程序,脑子里一定要有“硬件电路”,对于赋值来说,你要清楚写出来的东西到底是“一根线”还是一个“触发器”,当然还有“锁存器”。对于阻塞赋值来说,“=”左右的逻辑是用线连接;而非阻塞赋值,“<=”左右使用触发器连接,触发器通常由时钟边沿触发控制。在逻辑综合时,阻塞赋值直接被综合成一堆组合逻辑,大家连成一片,属于一个周期内的运算;非阻塞逻辑被综合成时序逻辑,语句之间会添加触发器(带寄存器功能),大家干活由时钟控制,不在一个周期内工作。
我个人比较倾向于用assign语句进行阻塞赋值,一片组合逻辑写在一起;如果一定要使用always语句块写组合逻辑,则敏感表一定要写全乎了,少一个都会出意想不到的问题;组合逻辑之间用always语句块连接进行时序控制,always的敏感表里只有clk和reset信号,内部语句块都是非阻塞赋值。这样逻辑比较清晰。
再谈谈上面提到的“锁存器”。这个也很好理解。在高级语言(如C、java)中,写了if,可以不用写else;case语句,如果没有考虑到所有的情况,写不写default,对高级语言执行也没太大影响(所以说,高级程序员幸福啊!)。但是,verilog不一样,一定要记住,你写的语句都要变成电路,if-else可以理解为一个2选1的选择器,写了if不写else,那电路在else的时候就不知道该怎么办,不知道该怎么办那就只能保持不变;case也一样,可以理解为一个多路选择器,指明运算电路知道怎么处理,未指明的电路就只能保持原值,这一保持就生成了“锁存器”。我个人很不喜欢“锁存器”这个东西,一是觉得没else、没default逻辑不完整,硬件工程师有强迫症受不了;二是这种隐藏赋值使得程序不好理解,都是明白人,有事摆在面上说合适,性格直率没办法;三是锁存器会隐形增加硬件面积功耗,FPGA资源太金贵,节约惯了没办法。在绝大多数设计中避免产生锁存器,还有一个很重要原因,它会让你设计的时序完蛋,并且它的隐蔽性很强,非老手不能查出。锁存器最大的危害在于不能过滤毛刺。这对于下一级电路是极其危险的。所以,只要能用触发器的地方,就不用锁存器。