建立时间(setup time)是指在触发器的时钟信号上升沿到来以前,数据稳定不变的时间,如果建立时间不够,数据将不能在这个时钟上升沿被打入触发器;保持时间(hold time)是指在触发器的时钟信号上升沿到来以后,数据稳定不变的时间,如果保持时间不够,数据同样不能被打入触发器。数据稳定传输必须满足建立时间和保持时间的要求,当然在一些情况下,建立时间和保持时间的值可以为零。
1.PLD内部产生毛刺的原因
使用分立元件设计数字系统时,由于PCB走线时存在分布电感和电容,所以几纳秒的毛刺将自然滤除,而在PLD内部并无分布电感和电容,所以在PLD/FPGA设计中,竞争和冒险问题将变得较为突出。
2.PLD内部毛刺的消除
一种常见的方法是利用D触发器的D输入端对毛刺信号不敏感的特点,在输出信号的保持时间内,用触发器读取组合逻辑的输出信号,这种方法类似于将异步电路转化为同步电路。
在仿真时,也可能会发现在FPGA器件对外输出引脚上有输出毛刺,但由于毛刺很短,加上PCB本身的寄生参数,大多数情况下,毛刺通过PCB走线基本可以被自然滤除,不用再外加阻容滤波。
优秀的设计方案,如采用格雷码计数器、同步电路,可以大大减少毛刺,但并不能完全消除毛刺。毛刺并不是对所有输入都有危害,例如D触发器的D输入端,只要毛刺不能出现在时钟的上升沿并且满足数据的建立和保持时间,就不会对系统造成危害。因此可以说D触发器的D输入端对毛刺不敏感。但D触发器的时钟端、置位端、清零端,则都是对毛刺敏感的输入端,任何一点毛刺就会使系统出错,但只要认真处理,可以把危害降到最低直至消除。
阻塞赋值与非阻塞赋值的区别
在always块中,阻塞赋值可以理解为赋值语句是顺序执行的,而非阻塞赋值可以理解为赋值语句是并发执行的。实际的时序逻辑设计中,一般情况下非阻塞赋值语句被更多地使用,有时为了在同一周期实现相互关联的操作,也使用阻塞赋值语句。注意:在实现组合逻辑的assign结构中,无一例外地都必须采用阻塞赋值语句。因此,要避免verilog HDL仿真时出现竞争与冒险现象,应遵循以下两个要点:
(1)在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构;
(2)在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构。
所谓阻塞是指在同一个always块中,后面的赋值语句从概念上是在前一赋值语句结束后再开始赋值的。
如果在一个过程块中阻塞赋值的右边变量正好是另一个过程块中的左边变量,这两个过程块又用同一个时钟沿触发,这时阻塞赋值操作会出现问题,即如果阻塞赋值的顺序安排不好,就会出现竞争。若值两个阻塞赋值操作用同一个时钟沿触发,则执行的顺序是无法确定的。例如下面的程序
1 module fboscl(clk, rst, y1, y2) ; 2 input clk, rst ; 3 output y1, y2 ; 4 reg y1, y2 ; 5 always @(posedge clk or posedge rst) 6 begin 7 if(rst) 8 y1 = 0 ; 9 else 10 y1 = y2 ; 11 end 12 13 always @(posedge clk or posedge rst) 14 begin 15 if(rst) 16 y2 = 1 ; 17 else 18 y2 = y1 ; 19 end 20 endmodule
程序中的两个always块是并行执行的,与先后顺序无关。如果前一个always块的复位信号先到0时刻,则y1和y2都会取1;而如果后一个always块的复位信号先到0时刻,则y1和y2都会取0。由此可知,该模块是不稳定的,必定会产生竞争和冒险的情况。
非阻塞赋值是指操作时刻开始时计算非阻塞赋值符号右边的表达式,赋值操作时刻结束时更新左边的值。在计算非阻塞赋值符号右边的表达式和更新左边的值期间,其他的verilog HDL语句,包括其他的Verilog非阻塞赋值语句都能同时计算非阻塞赋值符右边的表达式和更新左边的值;非阻塞赋值允许其他的Verilog语句同时进行操作。
非阻塞赋值操作只能用于对寄存器变量进行赋值,不允许用于连续赋值。如下程序:
module fboscl(clk, rst, y1, y2) ; input clk, rst ; output y1, y2 ; reg y1, y2 ; always @(posedge clk or posedge rst) begin if(rst) y1 <= 0 ; else y1 <= y2 ; end always @(posedge clk or posedge rst) begin if(rst) y2 <= 1 ; else y2 <= y1 ; end endmodule
在这个程序中的两个always块是并行执行的,与先后顺序无关。无论哪一个always块复位信号先到,两个always块中的非阻塞赋值都在赋值开始时刻计算非阻塞赋值符号右边的表达式,而在赋值操作时刻结束时更新左边的值。所以,这两个always块在复位信号到来后,在always块结束时,y1为0,y2为1是确定的。
可综合风格的Verilog模块的编程要点有8个,在编写程序时要牢记这8个要点。在绝大多数情况下,可以避免在综合后仿真出现的冒险问题,初学者按照这几点来编写Verilog模块程序,可以省去很多麻烦。这8个编程要点是:
(1)时序电路建模时,用非阻塞赋值;
(2)锁存器电路建模时,用非阻塞赋值;
(3)用always和组合逻辑模型时,用阻塞赋值;
(4)在同一个always块中建立时序和组合逻辑模型时,用非阻塞赋值;
(5)在同一个always块中不要既用非阻塞赋值又用阻塞赋值;
(6)不要在一个以上的always块中为同一变量赋值;
(7)用$strobe系统任务来显示使用非阻塞赋值的变量值;
(8)赋值时不要使用#0延时。
代码对综合的影响
使用Verilog对逻辑硬件进行建模和模拟的同时,必须理解代码与硬件实现的联系。
避免在综合时引入锁存器的方法有:
(1)组合函数的输出必须在每个可能的控制路径中被赋值。
(2)每次执行always块时,在生成组合逻辑的always块中赋值的所有信号都必须有明确的值。
(3)组合电路的每一个if语句都对应一个else语句。
(4)每一个case语句都对应一个default语句(在没有优先级的情况下优先使用。设计路径延时要小于if...else语句)。在使用条件语句时,要注意列出所有条件分支,否则,编译器认为条件不满足时,会引进一个触发器保持原值。在组合电路设计中,应避免这种隐含触发器的存在。但一般的设计不可能列出所有分支,为包含所有分支,可在if语句最后加上else语句,在case语句的最后加上default语句
用always块实现较复杂的组合逻辑电路
使用assign结构实现组合逻辑电路,在设计中会发现很多地方显得冗长且效率低下。如果适当地采用always块来设计组合逻辑,往往会更具实效。
设计一个简单的指令译码电路。
要求:电路通过对指令进行判断,对输入数据执行相应的操作,包括加、减、与、或和求反,并且无论是指令作用的数据还是指令本身发生变化,结果都要做出及时的反映。
解:显然,这是一个较为复杂的组合逻辑电路,如果采用assign语句,表达起来非常复杂。如果使用电平敏感的always块,并且运用case结构来进行分支判断,不但设计思想得到直观的体现,而且代码看起来非常整齐,便于理解。程序如下:
`define plus 3'd0 `define minus 3'd1 `define band 3'd2 `define bor 3'd3 `define unegate 3'd4 module alu_circuit(opcode, a, b, out) ; input [2:0]opcode ; input [7:0]a, b ; output [7:0]out ; reg [7:0]out ; always @(opcode or a or b) begin case(opcode) `plus: out = a + b ; `minus: out = a - b ; `band: out = a & b ; `bor: out = a | b ; `unegate: out = ~a ; default: out = 8'hx ; endcase end endmodule
同一组合逻辑电路用always块和连续赋值语句assign描述时,它们的代码形式完全不同。在always块中,虽然被赋值的变量一定要定义为reg型,但是适当运用default(在case结构中)和else(在if...else结构中)语句,通常可以综合为纯组合逻辑。值得注意的是如果不使用default或case语句对缺省项进行说明,则易生成意想不到的锁存器。