软件版本:无
操作系统:WIN10 64bit
硬件平台:适用所有系列FPGA
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述
本小节讲解Verilog语法的一般设计规范,需要掌握时序或组合电路设计中需要注意的几点,掌握设计避免出现锁存器。
2设计规范
上一节课我们讲解了竞争与冒险,其实我们在编写代码中,多注意设计规范,能够减少大多数的竞争与冒险的问题。
在verilog编程时需要注意以下几点:
(1)时序电路建模时,用非阻塞赋值(<=)。
(2)锁存器电路建模时,用非阻塞赋值(<=)。
(3)用always和组合逻辑建模时,用阻塞赋值(=)。
(4)在同一个always块中建立时序和组合逻辑模型时,用非阻塞赋值(<=)。
(5)在同一个always块中不要既用阻塞赋值又用非阻塞赋值(<=)。
(6)不要在多个always块中为同一个变量赋值。
阻塞赋值“=”对应组合逻辑电路赋值,并且会阻塞后面的赋值操作,有先后顺序。非阻塞赋值“<=”对应时序逻辑电路赋值,所有非阻塞赋值操作在同一时刻进行赋值。
2.1 时序逻辑电路中,对寄存器用非阻塞赋值。
时序电路中使用非阻塞赋值可以有效消除竞争与冒险。
比如下面代码描述,由于无法确定 a 与 b 阻塞赋值的操作顺序,也就是实际延迟时间未知,就有可能带来竞争冒险。
always @(posedge clk)begina = b ; //阻塞赋值,关键词“ = ”b = a ; //阻塞赋值,关键词“ = ”end
如果使用非阻塞赋值,两个赋值操作是同时进行的,因此就不会带来竞争冒险。例:
always @(posedge clk) begina <= b ; //非阻塞赋值,关键词“ <= ”b <= a ; //非阻塞赋值,关键词“ <= ”end
2.2 组合逻辑电路中,对寄存器用阻塞赋值。
设计实现 C = A&B, F=C&D 的组合逻辑功能,也就是F= A&B&D,用非阻塞赋值语句设计如下。
always @(*)beginC <= A & B ; //非阻塞赋值,关键词“ <= ”F <= C & D ; //非阻塞赋值,关键词“ <= ”end
两条赋值语句同时赋值,F <= C & D 中使用的是信号 C 的旧值,因此导致此时的逻辑是错误的,F 的逻辑值不等于 A&B&D。
而且,此时要求信号 C 具有存储功能,但不是时钟驱动,所以 C 可能会被综合成锁存器(latch),导致竞争冒险。
对代码进行如下修改,F = C & D 的操作一定是在 C = A & B 之后,此时 F 的逻辑值等于 A&B&D,符合设计。
always @(*)beginC = A & B ; //阻塞赋值,关键词“ = ”F = C & D ; //阻塞赋值,关键词“ = ”end
2.3 同一个 always 块中建立时序和组合逻辑模型时,用非阻塞赋值。
在时序电路中常常可能会涉及组合逻辑,但是如果赋值操作使用非阻塞赋值,会可能导致出现问题。
例:
always @(posedge clk or negedge rst_n)beginif (!rst_n) beginq <= 1'b0; //非阻塞赋值,关键词“ <= ”endelse beginq <= a || b; //非阻塞赋值,关键词“ <= ”endend
2.4 同一个 always 块中不要既使用阻塞赋值又使用非阻塞赋值。
always 语句涉及的组合逻辑中,既有阻塞赋值又有非阻塞赋值时,可能会导致意外的结果。例:
always @(*) beginC = A & B ; //阻塞赋值,关键词“ = ”F <= C & D ; //非阻塞赋值,关键词“ <= ” ,不能同时使用end
此时信号 C 阻塞赋值完毕以后,信号 F 才会被非阻塞赋值,结果可能正确。
如果 F 信号有其他的负载,F 的最新值并不能马上传递出去,数据有效时间还是在下一个触发时刻。此时要求 F 具有存储功能,可能会被综合成 latch,导致竞争与冒险。例:
always @(*)beginC <= A & B ; //非阻塞赋值,关键词“ <= ”F = C & D ; //阻塞赋值,关键词“ = ”end
信号 C 被非阻塞赋值,下一个触发时刻才会有效。而 F = C & D 虽然是阻塞赋值,但是信号 C 不是阻塞赋值,所以 F 逻辑中使用的还是 C 的旧值。
在时序电路里既有阻塞赋值,又有非阻塞赋值会怎样呢?例:
always @(posedge clk or negedge rst_n)beginif (!rst_n)begin q <= 1'b0; //非阻塞赋值,关键词“ <= ”endelse beginq = a & b; //阻塞赋值,关键词“ = ”endend
假如复位信号与时钟同步,那么由于复位导致的信号 q 为 0,是在下一个时钟周期才有效。如果是信号 a 或 b 导致的 q 为 0,则在当期时钟周期内有效。假如 q 还有其他负载,就会导致 q 的时序特别混乱,显然不符合设计需求。
2.5 不允许在多个 always 块中为同一个变量赋值
Verilog语法中不允许在多个 always 块中为同一个变量赋值。如果这样做了,该信号拥有多驱动源,这种行为是禁止的。当然,也不允许 assign 语句为同一个变量进行多次连线赋值。从信号角度来讲,多驱动时,同一个信号变量在很短的时间内进行多次不同的赋值结果,就有可能产生竞争冒险。从语法来讲,很多编译器检测到多驱动时,也会报错的。
2.6 设计避免产生latch
Latch是锁存器,是一种对脉冲电平敏感的存储单元电路,它们可以在特定输入脉冲电平作用下改变状态。锁存,就是把信号暂存以维持某种电平状态。锁存器的最主要作用是缓存,其次完成高速的控制器与慢速的外设的不同步问题,再其次是解决驱动的问题,最后是解决一个 I/O 口既能输出也能输入的问题。锁存器是利用电平控制数据的输入,它包括不带使能控制的锁存器和带使能控制的锁存器。
如图所示:
触发器(flip-flop),是边沿敏感的存储单元,数据存储的动作(状态转换)由某一信号的上升沿或者下降沿进行同步的。
如图所示:
寄存器(register),在FPGA中用来暂时存放中间运算的数据和结果的变量。一个变量声明为寄存器时,它既可以被综合成触发器,也可能被综合成 Latch。但是大多数情况下我们希望它被综合成触发器,但是有时候由于代码书写问题,它会被综合成不期望的 Latch。
锁存器与触发器最大区别在于,锁存器是电平触发,而触发器是边沿触发。锁存器在不锁存数据时,输出随输入变化,但一旦数据锁存时,输入对输出不产生任何影响。
Latch 的主要危害有:
(1)输入状态可能多次变化,容易产生毛刺,增加了下一级电路的不确定性。
(2)在大部分 FPGA 的资源中,可能需要比触发器更多的资源去实现 Latch 结构。
(3)锁存器的出现使得静态时序分析变得更加复杂。
Latch 多用于门控时钟(clock gating)的控制,一般设计时,应当避免 Latch 的产生。
Latch 的产生主要有下面几种情况:
1、在组合逻辑中,不完整的if-else结构,会产生latch。
避免此类latch的方法主要有两种,一种是补全if-else结构,或者对信号赋初值。
但是在时序逻辑中,不完整的if-else结构,不会产生latch,这是因为,寄存器具有存储功能,且其值在时钟的边沿下才会改变。
2、 在组合逻辑中,当 case 选项列表不全且没有加 default 关键字,或有多个赋值语句不完整时,也会产生 Latch。
消除此种 latch 的方法也是 2 种,将 case 选项列表补充完整,或对信号赋初值。
补充完整 case 选项列表时,可以罗列所有的选项结果,也可以用 default 关键字来代替其他选项结果。
总之,为避免 latch 的产生,在组合逻辑中,需要注意以下几点:
1)if-else 或 case 语句,结构一定要完整;
2)不要将赋值信号放在条件判断中;
3)敏感信号列表建议多用 always@(*)。