9. 时序逻辑的开始 — 寄存器

9.1. 章节导读

前面几章我们重点介绍了用Verilog语言实现的几种简单组合逻辑,目的是让大家熟悉语法,学会设计的思想、方法和步骤。从本章开始我们开始进入到时序逻辑的设计,要进行时序逻辑的设计,那寄存器就是必不可少的元素,让我们一起来认识一下什么是寄存器,它能做什么、有什么特性、如何用Verilog语言来描述。

9.2. 理论学习

我们学习过数电知识的也都了解,组合逻辑最大的缺点就是会存在竞争冒险(详细可参考前面推荐的数字电路相关的书,这里不再在进行详细解释)问题,这种竞争冒险问题是非常危险的,常常会引起电路的不稳定性和工作时的不确定性,而我们使用时序逻辑就可以极大的避免这种问题,从而使系统更加稳定。而时序逻辑最基本的单元就是 寄存器,寄存器具有存储功能,一般是由D触发器构成,由时钟脉冲控制,每个D触发器(D Flip Flop ,DFF)能够存储一位二进制码。

D触发器的功能为:在一个脉冲信号(一般为晶振产生的时钟脉冲)上升沿或下降沿的作用下,将信号从输入端D送到输出端Q,如果时钟脉冲的边沿信号未出现,即使输入信号改变,输出信号仍然保持原值,且寄存器拥有复位清零功能,其复位又分为同步复位和异步复位。

区分一个设计是组合逻辑电路还是时序逻辑电路主要是看数据工作是不是在时钟沿下进行的,在FPGA的设计中,复杂的电路设计都要用到时序逻辑电路,往往都是以时序逻辑电路为主,组合逻辑为辅的混合逻辑电路。

9.3. 实战演练

9.3.1. 实验目标

前面章节中,我们设计并编写了使用按键控制LED灯的工程,在本章节我们同样使用这个例子,虽然实验效果相同,但本章是使用D触发器来进行控制的,和使用组合逻辑的控制方法有所不同。

当按键未按下时led灯处于熄灭状态;当按键被按下时led灯被点亮。

9.3.2. 硬件资源

与“点亮LED灯”的实验工程相同,我们需要用到开发板上的按键和LED灯,我们要使用按键KEY1点亮LED灯D6,如图 15‑1所示。

reg002

图 15‑1 硬件资源

由原理图可知,征途Pro开发板的按键未按下时为高电平、按下后为低电平;LED灯则为低电平点亮。如图 15‑2图 15‑3所示。

reg003

图 15‑2 按键部分原理图

reg004

图 15‑3 LED灯原理图

9.3.3. 程序设计

9.3.3.1. 模块框图

我们先给模块取一个名字叫flip_flop,接下来是分析端口信号:D触发器能够正常工作一定有时钟,每当时钟的“沿(上升沿或下降沿)”来到时我们采集到稳定有效的数据;其次还需要的就是复位信号,用于让触发器的回到初始状态把数据清零;因为是用按键控制led灯的亮灭,所以输入端我们还需要一个按键控制信号;输 出就只有一个控制led灯的信号,这里我们的输入输出信号都是1bit的。根据上面的分析设计出的Visio框图如图 15‑4所示。

reg005

图 15‑4 模块框图

端口列表与功能总结如表格 15‑1所示。

表格 15‑1 输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

工作时钟,频率50MHz

sys_rst_n

1Bit

Input

复位信号,低电平有效

key_in

1Bit

Input

按键信号输入

led_out

1Bit

Output

输出控制LED灯

9.3.3.2. 波形图绘制

D触发器根据复位的不同分为两种,一种是同步复位的D触发器,另一种时异步复位的D触发器,下面就是详细介绍这两种D触发器的异同,以及波形的设计。从本章开始,我们后面设计的工程项目都主要以时序逻辑为主,其中波形的设计尤为关键,希望大家能够认真体会其中的精妙之处。

同步复位的D触发器

同步复位的D触发器中的“同步”是和工作时钟同步的意思,也就是说,当时钟的上升沿(也可以是下降沿,一般习惯上为上升沿触发)来到时检测到按键的复位操作才有效,否则无效。如图 15‑5所示最右边的三根红色的竖线表达的就是这种效果,sys_rst_n被拉低后led_out没有立刻变为0,而是当syc_clk的上升沿到来的时候led_out才复位成功,在复位释放的时候也是相同原因。

reg006

图 15‑5 同步复位的D触发器波形图

异步复位的D触发器

异步复位的D触发器中的“异步”是和工作时钟不同步的意思,也就是说,寄存器的复位不关心时钟的上升沿来不来,只要有检测到按键被按下,就立刻执行复位操作。如图 15‑6所示最右边的两根红色的竖线表达了这种效果,sys_rst_n被拉低后led_out立刻变为0,而不是等待syc_clk的上升沿到来的时候l ed_out才复位,而在复位释放的时候led_out不会立刻变为key_in的值,因为还要等待时钟上升沿到来到时才能检测到key_in的值,此时才将key_in的值赋值给led_out。

reg007

图 15‑6 异步复位的D触发器波形图

同步复位的D触发器和异步复位的D触发器的不同点已经在上面已经进行了详细的讲解,主要就是复位有效的条件是“立刻”执行还是等待“沿”再执行的区别。但是他们也有很多相同点,相比于组合逻辑电路来讲,对于电路中产生的毛刺有着极好的屏蔽作用,如上图中间位置的一组红色竖线所示,是我们模拟在干扰情况下产生的毛刺现象 ,因为时序电路只有在沿到来时才检测信号是否有效,所以在两个上升沿之间的毛刺都会被自然的过滤掉,可以大大减少毛刺现象产生的干扰,提高了电路中数据的可靠性。

时序电路还有一个特点,就是“延一拍”的效果。上面两个图最左边的一组红色竖线所表达的就是这个现象。key_in在复位后的第一个时钟的上升沿来到时拉高,我们可以发现此时led_out并没有在同一时刻也跟着拉高,而在之前的组合逻辑中输出是在输入变化的同一时刻立刻变化的,这是什么原因呢?

因为我们所画的波形图都是基于前仿真的,没有加入门延时的信息,所以很多时候数据的变化都是和时钟直接对齐的。当表达时序逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据为在该时钟上升沿前一时刻的值;当表达组合逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据为在该时钟上升沿同一时刻的值。 而仿真工具在进行RTL代码的仿真时也遵循这个规则,我们也可以理解为仿真寄存器是按照建立时间Tsu(指触发器的时钟信号上升沿到来以前,数据稳定不变的最小时间)最大(一个时钟周期),保持时间Th(指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间)最小(为0)的理想环境下进行的;而在仿真组合逻辑时 因为没有时钟也就没有建立时间和保持时间的概念,所以数据只要有变化就立刻有效。这里我们在画波形图的时候一定要记住这个“延一拍”的效果,否则我们绘制的波形图就会和最后的仿真结果不符,也可能会导致最后的逻辑混乱。

9.3.3.3. 代码编写

同步复位的D触发器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module flip_flop
(
input wire sys_clk , //系统时钟50Mh,后面我们都是设计的时序电路
//所以一定要有时钟,时序电路中几乎所有的信
//号都是伴随着时钟的沿(上升沿或下降沿,习
//惯上用上升沿)进行工作的

input wire sys_rst_n, //全局复位,复位信号的主要作用是在系统出现
//问题是能够回到初始状态,或一些信号的初始
 //化时需要进行复位

 input wire key_in , //输入按键

 output reg led_out //输出控制led灯
 );

 //led_out:led灯输出的结果为key_in按键的输入值
 always@(posedge sys_clk) //当always块中的敏感列表为检测到sys_clk上升沿时
 //执行下面的语句

 if(sys_rst_n == 1'b0) //sys_rst_n为低电平时复位,但是这个复位有个大前
 //提,那就是当sys_clk的上升沿到来时,如果检测到
 //sys_rst_n为低电平则复位有效

 led_out <= 1'b0; //复位的时候一定要给寄存器变量赋一个初值,一般情
 //况下赋值为0(特殊情况除外),在描述时序电路时
 //赋值符号一定要使用“<=”
 else
 led_out <= key_in;

 endmodule

根据上面RTL代码综合出的RTL视图如图 15‑7所示,由一个选择器和一个寄存器构成。

reg008

图 15‑7 同步复位的D触发器RTL视图

异步复位的D触发器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
module flip_flop
(
input wire sys_clk , //系统时钟50Mh
input wire sys_rst_n , //全局复位
input wire key_in , //输入按键

output reg led_out //输出控制led灯
);

 //led_out:led灯输出的结果为key_in按键的输入值
 //当always块中的敏感列表为检测到sys_clk上升沿或sys_rst_n下降沿时执行下面的语句
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)//sys_rst_n为低电平时复位,且是检测到sys_rst_n的下
 //降沿时立刻复位,不需等待sys_clk的上升沿来到后再复位
 led_out <= 1'b0;
 else
 led_out <= key_in;

 endmodule

根据上面RTL代码综合出的RTL视图如图 15‑8所示,只有一个寄存器构成。reg009

图 15‑8 异步复位的D触发器RTL视图(一)

如果复位时的值不是0而改为1,相当于复位时将D触发器置为1,则使用D 触发器上的置位端口,其综合的RTL视图如图 15‑9所示。

reg010

图 15‑9 异步复位的D触发器RTL视图(二)

通过上面同步复位和异步复位综合出的D触发器的RTL视图对比,我们可以发现,采用同步复位会多出来一个选择器的结构,这里我们可能不禁会有疑问,为什么多了一个选择器?我们设计的RTL逻辑中并没有想表达这个选择器的意思,这显然对我们最初想要表达的的设计是多余的,所以我们在使用Intel(Altera)芯片时 最好使用异步复位(如果是Xilinx的芯片则推荐使用同步复位,Xilinx的UltraFas方法学则推荐使用局部复位或最好不使用复位),这样子就可以节约更多的逻辑资源,更利于时序的收敛,之所以会有这样的差异是由于FPGA内部结构决定的,后面我们会从FPGA的内部结构中详细解释。

9.3.3.4. 仿真验证

仿真文件编写

仿真文件参考代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
\`timescale 1ns/1ns
module tb_flip_flop();

//reg define
reg sys_clk ;
reg sys_rst_n ;
reg key_in ;

//wire deifne
 wire led_out ;

 //初始化系统时钟、全局复位和输入信号
 initial begin
 sys_clk = 1'b1; //时钟信号的初始化为1,且使用“=”赋值,
 //其他信号的赋值都是用“<=”
 sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为0
 key_in <= 1'b0; //输入信号按键的初始化,为0和1均可
 #20
 sys_rst_n <= 1'b1; //初始化20ns后,复位释放,因为是低电平复位
 //所示释放时,把信号拉高后系统才开始工作
 #210
 sys_rst_n <= 1'b0; //为了观察同步复位和异步复位的区别,在复位释放后
 //电路工作210ns后再让复位有效。之所以选择延时210ns
 //而不是200ns或220ns,是因为能够使复位信号在时钟下
 //降沿时复位,能够清晰的看出同步复位和异步复位的差别
 #40
 sys_rst_n <= 1'b1; //复位40ns后再次让复位释放掉
 end

 //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
 always #10 sys_clk = ~sys_clk; //使用always产生时钟信号,让时钟每隔10ns反转一
 //次,即一个时钟周期为20ns,换算为频率为50MHz

 //key_in:产生输入随机数,模拟按键的输入情况
 always #20 key_in <= {$random} % 2; //取模求余数,产生非负随机数0、1,每隔20ns
 //产生一次随机数(之所以每20ns产生一次随机
 //数而不是之前的每10ns产生一次随机数,是为
 //了在时序逻辑中能够保证key_in信号的变化的
 //时间小于等于时钟的周期,这样就不会产生类
 //似毛刺的变化信号,虽然产生的毛刺在时序电
 //路中也能被滤除掉,但是不便于我们观察波形)

 //------------------------------------------------------------
 initial begin
 $timeformat(-9, 0, "ns", 6);
 $monitor("@time %t: key_in=%b led_out=%b", $time, key_in, led_out);
 end
 //------------------------------------------------------------

 //------------------flip_flop_inst-------------------
 flip_flop flip_flop_inst
 (
 .sys_clk (sys_clk ), //input sys_clk
 .sys_rst_n (sys_rst_n ), //input sys_rst_n
 .key_in (key_in ), //input key_in

 .led_out (led_out ) //output led_out
 );

 endmodule

begin…end是一个串行块在Testbench中被使用时其内部的语句是顺序执行的,在本例中的13-28行代码中,我们多次进行延时,其时间是在之前基础上叠加的,而不是从0时刻开始计算时间。

仿真波形分析

我们在观察时序逻辑时不能再像观察组合逻辑那样子,因为输入和输出会有延一拍的效果,如果输入数据在前一个时钟的上升沿变化,则输出数据不会立刻变化,而是在下一个时钟的上升沿才变化,这样的现象我们简单化运用——当时钟和信号在同一时刻变化时,我们以时钟的上升沿前一时刻采集的输入信号为依据来产生输出信号,这样我 们就可以很好的观察时序逻辑产生的波形了。

同步复位的D触发器

仿真出来的波形如图 15‑10所示,我们让仿真运行了500ns即可得到较好的观察效果。首先复位为高电平的那一刻是和时钟的上升沿对齐的,根据上面的原则,其实此处的上升沿采集到的复位信号为该上升沿前一时刻的值,也就是低电平,所以寄存器处于复位状态,使led_out依然保持为低电平,而在下一个时钟的上升沿 前一时刻时复位信号已经为高电平,复位被释放,且key_in为高电平,所以此时led_out也为高电平,这种分析最后的现象完全契合了延一拍的效果(直观上看到的波形是对齐的,其实我们要取的值是时钟上升沿前一时刻得值),中间我们又加入了一段时间的复位,可以看到同步复位的效果。和最初设计的波形图对比发现是完 全一致的。

reg011

图 15‑10 同步复位仿真波形图

我们观察“Transcript”界面(如图 15‑11所示)中打印的结果,也发现key_in和led_out的值是延一拍的对应关系,有些时候会因这种延时一拍的关系导致对打印数据的观测不是很直观,特别是我们中间还加了复位的控制,不知道的还以为是出现了错误,使之更难于观察,这也是我们为什么在本节中没有列 出真值表的原因。在时序逻辑电路中真值表并不能很清晰的表达时序的对应关系,反而是用波形图表达的更加清晰,而在以后的设计中,我们并不是不再用“Transcript”界面打印信息,而是不再这样完全把信号进行简单的列举显示,我们会打印一些关键时刻点的打印信息或者是设计一些特殊的打印信息以方便对特殊时刻点信号 的观察。

reg012

图 15‑11 Transcript界面

异步复位的D触发器

我们使用和同步复位的D触发器一样的时序电路分析波形的方法,如图 15‑12所示,经过仔细的验证发现符合异步复位D触发器的逻辑设计。而我们就不再打印异步复位的D触发器的信息了。

reg013

图 15‑12 异步复位仿真波形图

9.3.4. 上板验证

9.3.4.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 15‑2所示。引脚配置如图 15‑13所示。

表格 15‑2 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

时钟

sys_rst_n

Input

M15

复位按键

key_in

Input

M2

按键

led_out

Output

L7

LED灯

reg014

图 15‑13 引脚配置图

9.3.4.2. 结果验证

如图 15‑14所示,开发板连接12V直流电源和USB-Blaster下载器JTAG端口。线路正确连接后,打开开关为板卡上电。

reg015

图 15‑14 程序下载连线图

如图 15‑15所示,使用“Programmer”为开发板下载程序。

reg016

图 15‑15 程序下载窗口

下载完成后按下按键KEY1,会发现同时被绑定的led随着按键的按下会被点亮,而按键松开时又熄灭,实现了我们最初预想的设计,如图 15‑16所示。

reg017

图 15‑16 上板效果图

9.4. 章末总结

本章首次讲解了FPGA中的时序逻辑,这也是我们正式学习、认识FPGA的一个新开始,因为用FPGA设计的大型电路几乎都是以时序逻辑为主的,我们也会在今后更加看到FPGA大显身手的时刻。

本章中我们还对比了同步复位的D触发器和异步复位的D触发器在波形、代码编写、逻辑资源上的不同,并给出了推荐的用法,希望大家能够在以后的设计中加以规范,熟练使用。在代码的设计上要深刻体会组合逻辑和时序逻辑的差别,特别是在用always块描述时,要理解电平触发和沿触发的区别,注意敏感列表的不同。

always块实现时序逻辑时无论是单比特信号还是多比特信号都具有这种延一拍的效果。我们在后面设计时要养成一些“条件反射”,即做到根据波形写代码的时候看到波形中有延一拍的现象时就要想到用always块的时序逻辑来实现;看到always块表达时序逻辑时就要想要波形中会延一拍的效果,我们经常会听到有人说把 数据“打一拍”其实就是这个意思。

新语法总结

重点掌握

1、always语句块描述时序逻辑的用法

2、<=(赋值号的一种,阻塞赋值,在可综合的模块中表达时序逻辑的语句时使用)

知识点总结

1、了解什么是D触发器(寄存器);

2、同步复位的D触发器和异步复位的D触发器的区别;

3、理解组合逻辑电路和时序逻辑电路在波形设计和代码实现上的区别;

4、掌握如何画时序逻辑电路的波形;

5、理解时序电路中会延一拍的现象;

6、学会分析时序电路仿真出的波形;

7、理解begin…end在Testbench中的用法及意义。

9.5. 拓展训练

细心的朋友可能会发现发两个仿真的波形在最开始的一段时间内有所不同,同步复位的D触发器在开始一段时间内是红色的信号,而异步复位的D触发器就没有这种现象,产生这种现象的原因是什么,仿真波形中的红色信号又代表什么意思呢?