目录
Verliog编程风格
设计者对于电路的理解不同,那么其使用的硬件描述语言会直接影响到EDA软件的综合结果,电路的质量取决于工程师使用的描述风格和使用综合工具的能力
1. 逻辑推理
1.1 if-else和case语句
这两种语句,在使用过程中都要尽最大可能避免锁存器Latch的产生
之所以在硬件设计中避免latch的出现,主要原因是latch会产生毛刺(glitch),这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不易查出。因此,在设计中,应尽量避免latch的使用。
Reference
Verilog_case和if-else的综合 - ycc_job - 博客园 (cnblogs.com)https://www.cnblogs.com/ycc1997/p/11815691.html简言之:if语句的纯组合逻辑的生成的充要条件是不会出现无else配对的if语句,因为缺失else配对的if语句会隐含保持值原来的不变,会引入latch。case语句在语义上有并行的含义,会生成mutiplexer电路,但是同时要注意,如果case没有完全覆盖所有情况,那么暗含着保持原来值的语义,会生成latch,可以加入default语句来避免这种情况。
同时if-else语句在综合时会生成树状结构的链路,这是推荐的。
1.2 陷阱
1.2.1 阻塞与非阻塞
在编程时多注意以下几点,也可以避免大多数的竞争与冒险问题。
- 时序电路建模时,用非阻塞赋值。
- 组合逻辑建模时,用阻塞赋值。
- 在同一个 always 块中建立时序和组合逻辑模型时,用非阻塞赋值。
- 在同一个 always 块中不要既使用阻塞赋值又使用非阻塞赋值。
- 不要在多个 always 块中为同一个变量赋值。
- 避免 latch 产生。
Reference
6.4 Verilog 竞争与冒险 | 菜鸟教程 (runoob.com)https://www.runoob.com/w3cnote/verilog-competition-hazard.html同时可以利用触发器在时钟同步电路下对异步信号进行打拍延时,来消除异步时钟带来的竞争冒险是 Verilog 设计中经常用到的方法。
Tips:非阻塞赋值在下一个边沿到来是才会生效,但阻塞赋值即刻生效,例:
always @(*) begin
C <= A & B ;
F = C & D ;
end
如上代码描述,仿真角度看,信号 C 被非阻塞赋值,下一个触发时刻才会有效。而 F = C & D 虽然是阻塞赋值,但是信号 C 不是阻塞赋值,所以 F 逻辑中使用的还是 C 的旧值。
1.2.2 for-loop环路
为了实现可综合的设计,for-loop环路不应该用于类似软件的迭代算法,而是通常使用较短的代码形式来减少并行代码重复长度。
不可取方式:
powerX = 2;
for (i = 1; i < N; i ++)
powerX = powerX * X //重复的迭代循环不能放在loop中
for(循环条件)
sig1 = sig2; //不变的表达式,不适合放在loop中
data_out(I) = data_in(I);
end
这总迭代计算X的n次幂的实现方式在综合时会形成一个巨大的逻辑块,迭代次数中所有的寄存器都会被清楚的定义,减小整体的逻辑运算速度。同时,环路中若包含在每个循环中保持不变的表达式会使得综合工具花费非常大的时间来优化冗余,这也是不可取的。
可取的方式:
sig1 = sig2; //不变的表达式,不适合放在loop中,拿出来即可
for(循环条件)
data_out(I) = data_in(I);
end
1.2.3 寄存器与锁存器
寄存器是边沿触发,输出通常不随输入端变化而变化(有时间上的滞后);锁存器是电平触发,只要使能信号有效,输出总随输入变化而变化。锁存器比寄存器快,但锁存器对输入信号的毛刺敏感,如上所述,尽量避免锁存器的产生。
引起硬件动作的信号都应该放在敏感列表中,包括:组合电路描述中所有被读取的信号;时序电路中的时钟信号和异步控制信号,例:
不完整的敏感信息列表
always@(d or clr)
if(clr)
q = 1'b0;
else if(e)
q = d;
end
完整的信息列表
always@(d or clr or e)
if(clr)
q = 1'b0;
else if(e)
q = d;
end
概括上面的分析,对于编程风格,列出以下注意要点或需要遵循的准则:
(1)对希望形成组合逻辑的 if-else 和 case 语句,要完整地描述其各个分支,避免形成锁存器,一个可行的办法是在语句前为所有被赋值信号赋一个初始值。
(2) 有大量关于阻塞和非阻塞赋值为综合编码时广泛接受的准则:1. 利用阻塞赋值设计组合逻辑模型;2. 利用非阻塞赋值设计时序逻辑或混合逻辑模型。
(3)从不把阻塞和非阻塞赋值混合在一个 always 模块中
(4) 尽量使用简单的逻辑、简单的数学运算符。
(5) 进程的敏感列表应该列举完全,否则可能产生综合前后的仿真结果不同和引入锁存器的现象,可用 *替代来自动识别全部敏感变量。
(6) 在循环中不要放置不随循环变化的表达式。
(7) 对于复杂的数学运算要充分进行资源共享,如采用 if 块等
(8) 对于长的组合链路应该在代码编写阶段就注意描述成树状结构。
(9) 时序逻辑尽可能采用同步设计。
(10) 对具有不同的时序或面积限制的设计,应尽可能采用不同的代码描述以达到不同的要求(一般而言,面积与延时是相互冲突的)。
(11) 对于复杂系统设计,应尽量采用已有的算法和模块实现。
1.3 设计组织
本节讨论一些影响FPGA开发时会影响代码可读性、可重用性和综合效率的结构设计要考虑的内容
1.3.1 分割
一个设计应按照功能分割成较小的功能单元,每个功能单元都有一个公共的时钟域,并能独立进行验证。
- 数据通道和控制结构应分割成不同的模块
- 每个模块中只利用一个时钟和一个类型的复位信号是好的设计实践
1.3.2 参数化
- 宏定义define
宏定义就是起到一个速记员的作用。它不会使代码优化,但会使得代码的规模变小。宏定义的格式是:
'define macro_name(formal_argu_list) macro_text
例:利用宏定义改位宽
'define BIT_WIDTH 8
……
reg['BIT_WIDTH-1:0] bit_variable;//reg [7:0] bit_variable
2. 参数parameter
与宏定义不同,参数一般位于专门的模块。Verilog中用parameter来定义常量,即用parameter来定义一个标识符来代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可以提高程序的可读性和可维护性。另一个很有用的用途就是可以利用defparam或者在模块实例化的时候进行参数传递(即重写)
parameter定义常量,可以定义在模块内部或外部;常用于定义位宽或时间延迟(易变),此处以加一个常数的电路进行示例,如下:
定义方式为: parameter 标识符 = (位宽)常数;// 位宽默认为32位,如果指定位宽则以指定值为准
- 模块内部定义常量
module param_idef(
input clk,
input [2:0]din,
output reg [3:0]sum
);
//内部定义---------------
parameter ADD = 2'd1;
always@(posedge clk)
begin
sum <= din+ADD;
end
-------------------------
endmodule
- 外部定义
module param_odef
#(parameter ADD2 = 2'd1)
(
input clk,
input [2:0]din,
output reg [3:0]sum
);
always@(posedge clk)
begin
sum <= din+ADD2;
end
endmodule
子模块利用parameter定义常量,被顶层调用时默认为子模块中参数
module Top(...);//这是一个顶层模块
Decode #(4,0) U_D1(); //使得Width = 4, Polarity = 0;
Decode #(5) U_D2(); //使得 Width = 5,Polarity 不变,即为1;
endmodule
module Decode #(parameter Width = 1, Polarity = 1); //这是一个子模块
(
input
...
output
)
...
endmodule
3. 局部参数localparam
在Verilog HDL中,使用localparam(局部参数)声明常量,可以使代码清晰并有助于以后的维护。verilog-2001新引进了一个本地参数localparam。它和parameter的定义方法是一样的,作用域也相同。
这两个参数的区别就在于:本地参数localparam不能通过参数传递方式进行模块化修改,或者说,本地参数就是“地方法规”,是模块内自主权的一部分,外面改变不了。
2. 综合优化
2.1 速度与面积
2.2 资源共享
在编码风格上,只有在同一个条件语句(if-else和case)的不同分支中的算数操作才能资源共享。
2.3 流水线、重新定时和寄存器平衡
在速度优化的设计中,流水线是一种方法,它在成组的逻辑之间添加寄存器级数,用于增加流量和触发器到触发器的时序。一个好的设计模块通常可以增加附加的寄存器级数流水,只影响总的时滞和小的面积损失。综合对相同结构的流水线、重新定时和寄存器平衡的操作进行选择,但不添加或移去寄存器本身。相反,围绕逻辑移动寄存器的优化可平衡任何两个寄存器级之间延时,从而使最坏条件下的延时最小化。流水线、重新定时和寄存器平衡在手段上是十分类似的,不同的厂商之间也只有细微的改变。
一般认为流水线起源于最广泛采用的负载平衡的方法,只要流水存储器或乘法器等规则结构可以被综合工具识别,就可以用重新分布逻辑来重新构造。在这个情况下,流水线要求规则的流水存在,并且该流水容易被工具重新组织。
复杂组合逻辑之间添加寄存器的方法,是最常用的速度优化技术之一。它能显著地提高设计电路的运行速度上限。
在图 5-19 所示的例子中,只有一个寄存器插进乘法器作为输出寄存器(由乘法器模块的数字 1表示),其余的流水寄存器以不平衡的逻辑保留在输出端。启动流水线,可以把输出寄存器放进乘法器,如图 5-20 所示。符号中的数字 3 表示寄存器有三层内部的流水线
寄存器平衡不应该用于非关键路径,且带有不同复位类型的相邻触发器可以阻止寄存器平衡生。
2.4 有线状态机编译
有限状态机(FSM)编译是指在 RTL级自动识别有限状态机,并需要为速度/面积约束重新编码。这意味着只要使用标准的状态机结构,RTL 级是否准确编码并不重要。通过使用标准方式编码的状态机的规则结构,综合工具可以方便地提取状态传输和输出关系,并变换状态机使给定的设计和一组约束更加优化。
用标准编码的方式设计状态机,可以被综合工具识别和重新优化。二进制和顺序编码将与状态表示中的所有触发器有关,因此状态解码是必需的。丰富的逻辑以及为译码逻辑设计多个输入门的 FPGA 技术将最佳地实现这些状态机。对每个状态设置一个唯一的位,可实现一个有效(one-hot)编码。通过这个编码,不用使用状态译码,状态机通常运行更快。缺点是一个有效(one-hot)编码一般要求更多的寄存器。
通常使用格雷码替代一个有效(one-hot)编码,因为其具有两个特点:1. 异步输出;2. 低功率器件。如果状态机的输出或者状态机操作的任何逻辑是异步的,通常最好使用格雷码。
由于异步电路不能防止竞争条件和毛刺,因此在状态寄存器中两位之间的路径不同可能引起不可预测的行为,并且该行为与布局配置和寄生参数有关。考虑图 5-25 所示的 Moore 机的编码输出。在这个例子中,单个位被清除和单个位被置位的地方将发生状态转移事件,因而产生潜在的竞争条件。说明该情形的波形表示如图 5-26 所示
该问题的一个解决办法是采用格雷码。格雷码的任何转换只经历一个单个位的改变在分析编码方案的结构之后,我们就能理解格雷码可以用来安全地驱动异步输出。更具体的解释参考:
格雷码在异步过渡中的作用 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/99731054
假设我们跨时钟域过渡一个2bit数据,没有采用格雷码,在源时钟域数据从2b01 -> 2'b10
那么在目的时钟域,对应某个时钟沿采样出的三种情况可能包括2'b01、2'b10、以及2'b00、或者2'b11;其中2'b01和2'b10是我们预期希望的;2'b00和2'b11是错误的,要消除和避免的。
首先看一下为什么会出现后面两种错误的数据,根因就是在信号进行异步传输时,因为多bit数据的bit间传输延时可能不同,本来在源时钟域可以认为是相位相同的信号,在到达目的时钟域之后,就会出现相位差,如果这个相位差导致的中间状态正好被目的时钟采样,就会采样出中间状态。
那么我们来看2'b01 格雷码编码为2'b01 ;2'b10格雷码编码为2'b11,只有1bit变化,就保证了无论出现怎样的相位偏差,目的时钟域要么采样到的是2'b01,要么是2'b11,不会出现中间状态。
返回到之前的问题上去,问题提到在快时钟域打出的格雷码,可能在慢时钟域采样之间变化多次,那么不就违反了格雷码1bit变化的原则了?这就是没有真正理解格雷码在异步过渡中的作用导致的疑问。在慢时钟域采样时,只要本次采样的数据是正确的,预期的(也就是没有采样到中间状态),格雷码在异步过渡中就发挥了正确的作用;而慢时钟域的上次采样和本次采样并不是相邻码字,不关心,也不在意,因为本身时钟就不同,快时钟域的所有数据变化肯定不可能完全反映在慢时钟域。
大多数状态机编译器会去除无用的状态,并且智能到足以检测和去除那些不可能达到的状态。对于大多数应用,这将会优化速度和减小面积。但是对于高可靠的设备,状态机需要保留所有无用或不可能达到的状态,即要将所有可能或突发状态全部考虑在内,避免因为各种干扰产生的不在设计之中的状态。
3. 数字系统的同步设计
3.1 同步设计基本原理
数字系统由分层嵌套的有限状态机构成,而有限状态机在时钟信号的控制下才能发生状态的变化,所以同步设计成为了数字系统设计遵循的主要设计原则。同时同步设计要遵循一个重要准则,即不能通过组合逻辑电路来产生时钟信号以及异步的置位和复位信号。
下图(a)和(b)分别表示上升沿和下降沿触发寄存器的时序关系,寄存器的数据输入是 D,数据输出是 Q,定义时钟 Clock到Q端的时间为 tco,它是寄存器固有的时钟输出延时。
在系统是实现之前,综合过程利用的任何延时数值只是一个估计,元件产生延时的四个来源如下:
- 由输入信号上升和下降时间确定的转换率
- 门电路固有的 RC负载确定的门延时
- 通过引线的传播延时和门电路驱动的 RC负载延时
- 扇出负载增加了驱动器必须放电和充电的电容。
这些部件产生的延时分量是系统进行时序分析的基础。
3.2 建立和保持时间
除去前面提到的触发器输出相对于时钟沿的延时时间 tco之外,时序分析的基本参数还有输入建立时间 tsu和输入保持时间 th
- 输人建立时间 tsu是时钟沿到来前数据信号在输入端达到稳定需要的最小时间
- 输人保持时间 th是时钟沿到达后数据信号在输入端保持稳定需要的最小时间
静态时序分析要计算每个静态时序通道在最坏条件下的建立时间和保持时间裕量tm,如果在考虑工艺、电压和温度的最坏条件下能够满足要求,则表示设计的时序收敛,达到性能要求。
高质量的时钟信号允许更快的系统运行速度,但是抖动、偏移和持续失真会降低时钟的质量。
3.2.1 静态时序通道的建立和保持时间校验
考虑主时钟信号从时钟输人引脚分别到起始和目的定时元件路径上不同的延时和偏移,所以要计算三条路径上的延时。其中,源时钟延时(Source Clock Delay)是从时钟输入端口到起始定时元件路径上的延时;数据通道延时(Data Path Delay)是起始和目的定时元件之间组合电路产生的延时;目的时钟延时(Destination Clock Delay)是从时钟输入端口到目的定时元件路径上的延时。
简言之,目的时钟时延=原时钟时延+数字通道时延
- 数据的到达时间(Arrival Time):源时钟延时和数据通道延时之和
- 建立时间校验时:取两条路径上受工艺、电压和温度等参数影响产生延时的最大值(slow_max),目的时钟延时加上建立时间起始沿和捕获沿间隔的一个时钟周期为数据的要求时间(RequiredTime),此路径上的延时要取最小值(slow_min),如果最小值的要求时间减去最大值的到达时间得到的建立时间裕量合格,则能够保证其他好于最坏情况的建立时间裕量也合格。
所以时序裕量(Slack)表示设计是否满足时序要求,正值表示满足时序要求,负值表示不满足时序要求。建立时间的时序裕量为
Setup Slack =Smallest Data Required Time - Largest Data Arrival Time
源时钟延时和数据通道延时得到的到达时间要取路径上延时的最小值,目的时钟延时要取路径上延时的最大值,保持时间的裕量是最小值的到达时间减去最大值的要求时间。
保持时间的时序裕量为
Hold Slack = Smallest Data Arrival Time - Largest Data Required Time
同步电路设计中,如果保持时间的裕量不合格,设计在硬件中不可能运行起来,但是建立时间裕量不合格,设计在硬件中有可能降频运行起来,但是达不到要求的运行速度。
setup time是指待传输数据不能来太晚;hold time是指新数据不能来太早,以确保待传输数据保持一段时间。总结为一句话:当前待传输的数据,相对于Capture edge来说,必须早来(setup time)晚走(hold time)。
3.2.2 输入和输出端口的建立和保持时间校验
输入端口的静态数据通道起始定时元件在 FPGA 外部,要计算外部定时元件的时间延迟Tco 和外部路径上的延时 Trce delay,后者是数据通道的一部分,如下图所示,利用TCL 指令set_input_delay 设置外部上游器件到输入端口的延时,对建立时间的校验,Tco和 trcedelay 都要取最大值,而对保持时间的校验,二者要取最小值。
与输出端口有关的静态数据通道目的定时元件在 FPGA 外部,要计算外部定时元件的建立时间 Tsu 和保持时间 Thd,以及数据通道一部分的外部路径上的延时 Trce_delay,如下图所示,利用TCL指令 set output delay 设置外部输出端口到下游器件的延时,对建立时间的校验,取下游器件的 Tsu 和 trce_delay 最大值,而对保持时间的校验,取下游器件的Thd 和外部 trce delay 最小值。
3.3 同步设计中的异步问题
为了消除同步设计中多时钟带来的异步通信问题,我们需要介绍一些消除异步问题的方法
3.3.1 相位控制法
用于解决多个时钟信号周期成倍数关系且相位不同步的问题。
如图所示的多时钟系统,其中一个信号在两个时钟区域间传递,两个时钟有一定的倍数关系。图 (a)表示快慢时钟有不匹配的相位关系,数据的建立时间被破坏,出现准稳态的现象。对于不同周期有任意相位关系的两个时钟区域,如果其中至少一个时钟是在FPGA内部通过 PLL(锁相环)成 DLL(延锁相环)可控制的,且与另一个时钟有倍数关系(此例为两倍)的周期,如图 (b)所示,那么使用相位匹配可以用来消除时序冲突。PLL 通过调整(捕捉)较快时钟区域的相位,并与较慢的时钟区域(传送)相匹配。
3.3.2 双触发器技术
两个时钟域完全异步(无法用相位控制法),可以通过双触发技术来减小准稳态的发生
如图a所示,当上升沿到达时,触发器数据输入端同时跳变,建立时间打破,此时输出端出现准稳态或者亚稳态的情况,虽然FPGA的不稳定状态会很快恢复,但是带来了最后输出结果的不确定性
为了解决亚稳态的问题,使外设器件的异步输入电路也能安全地工作,可以用图 (b)所示的方法进行同步,在非同步输入的触发器后增加一个触发器,将非同步输人变为同步输人,确保不产生准稳态,使系统正常工作。即双倍同步一个异步的信号到新的时钟域,在采样异步信号s_src之后,s_meta 很大概率为准稳态,因此再添加一个寄存器REGB1进行第二次采样使 输出的s-dst 以十分低的概率成为准稳态,Verilog 程序为:
always@(posedge clk_dst)
begin
if(rst_dst)begin
signal_meta<=1'b0;
signal_dst<=1'b0;
end
else begin
signal_meta<=s-src; #异步输入s-src给寄存器REGB0一次
signal_dst<=signal_meta; #通过寄存器输出给原始寄存器REGB1
3.3.3 异步数据传输-FIFO结构
异步时钟域传递信号更灵活的方式是使用先进先出FIFO技术。在异步时钟域之间传输多位信号时可以利用次结构,常应用于标准总线接口之间传递数据或者读写突发寄存器。
使用异步 FIFO,数据可以在任意时间间隔在发送端运送,接收端把数据推出队列,因为它有处理数据的带宽。由于使用 FIFO 实现有限尺寸的任意队列,需要一定的控制来适当地防止溢出。因此要预先设置 FIFO 的深度和 FIFO的握手控制,包括发送速率的先验知识(突发或不突发)、最小的接收速率和相应的最大队列尺寸。
3.3.4 应用时钟使能信号
在许多应用中只将异步信号同步化还是不够的,当系统中有两个或两个以上不同信源的时钟时,数据的建立和保持时间很难得到保证,时序分析也变得十分复杂。解决的办法是同步不同信源的时钟,需要利用带使能端的D触发器,以及引入一个高频率时钟来实现不同信源时钟的同步。如图所示,图中Input 信号为不同信源的时钟(此例为 3MH2和5MHz),CIk 为20MHz 或更高的系统时钟频率,同步后的 3MHz 和5MHz 使能信号输入到数据输入寄存器的使能端,输出数据CE就与系统时钟频率同步,其中CE也被视为时钟使能信号。
图中的D触发器实现了用时钟使能信号CE代替数据输入信号Input,原理如下:
由于Clk是20MHz,远大于input信号的频率,因此能够保证输入信号有一个时钟的宽度,在经过该D触发器之后,输出端CE的频率由Clk决定,即通过时钟使能信号改变了信号的输出频率,因此将两个不同输入频率的D触发器都接上相同频率的Clk,可以输出相同频率的时钟使能信号,且该信号等于输入信号,也就实现了异步数据的同步化。只要输入信号的持续时间大于一个时钟宽度,那么该输入信号的值一定会被D触发器寄存,进而传递给时钟使能信号CE。
3.3.5 消除组合电路毛刺
组合电路产生的毛刺也是数字系统不能正常工作的原因之一,因此将组合逻辑修改为时序逻辑则会大幅降低毛刺。
图(a)给出一个产生毛刺脉冲的例子,并说明了毛刺脉冲产生的原因,因为电路的最高位布线短,计数从0111到1000 时,最高位先于其他位发生翻转,从0111 经过 1111 再到达1000,使与门动作产生毛刺脉冲。采用图(b)的电路,可以避免产生毛刺脉冲,使系统稳定地同步系统的时钟。
The end