实验四 锁存器、触发器、寄存器

4.1 实验目的

  1. 理解时序逻辑电路的概念和基本原理;

  2. 掌握锁存器、触发器和寄存器的原理和架构及代码实现;

  3. 熟悉数字电路的设计、仿真流程,最后在DE1-SOC开发板上验证设计 。

4.2 原理介绍

4.2.1 时序逻辑电路

前三个实验中,我们学习了基本的逻辑门、多路数据选择器、译码器,这些电路都是组合逻辑电路,也就是任一时刻产生的输出信号仅仅取决于该时刻的输入信号的状态,与以前的输入信号无关。接下来我们将介绍时序逻辑电路的基本概念和原理。

在时序逻辑电路中,任一时刻的输出信号不仅取决于该时刻的输入信号,还取决于电路原来的状态,或者说,还与以前的输入信号有关。这样表述可能不好理解,我们先举一个实际的例子来说明一下。

有一个自动饮料售卖机,饮料10元,可投入货币有1元、5元、10元三种。假设你有两个5元,那么你需要投币两次,机器才能弹出饮料。当完成第一次投币后,机器记录你的总投币金额为5元;当完成第二次投币后,机器记录你的总投币金额为10元,此时机器会弹出饮料,并将总投币金额重置为0元。如图4.1所示为自动饮料售卖机的电路结构,它包含了两部分,一部分是加法器,用来计算总投币金额,另一部分是存储电路,用来记录每次投币后的总金额。

image-20210630114008496

图4.1 自动售货机电路

 

图4.2 时序逻辑电路的结构框图

通过这个例子我们可以看出,时序逻辑电路由组合逻辑电路和存储电路构成,并且存储电路是必不可少的。组合逻辑电路完成输入到输出的逻辑处理,处理的结果同时会进入存储电路中存储下来,等到下一个时刻再和输入信号一起决定组合电路的输出。

时序电路的存储电路一般由锁存器、触发器和寄存器构成。接下来我们将详细介绍这三个器件。

4.2.2 锁存器

锁存器(latch)是数字电路中的一种具有记忆功能的逻辑元件,可以记录二进制数字信号“0”和“1”。锁存器(latch)是电平触发的存储单元,数据存储的动作取决于输入时钟信号(或者使能)的电平值,即当锁存器处于使能状态时,输出才会随着输入数据发生变化。

锁存器分为基本SR锁存器、门控SR锁存器和D锁存器,接下来我们分别详细介绍这三种锁存器,首先介绍基本SR锁存器,它是各种锁存器、触发器电路的基本构成部分。

基本SR锁存器

图4.3(a)是一个基本SR锁存器,我们来分析它为什么会有记忆功能。如果只有一个或非门G_1,那它的输出会随着输入的变化而变化,因此不具备记忆功能。当我们用另外一个或非门G_2将v{O1}反相(同时将G_2的另一个输入端接低电平),则G_2的输出v{O2}将与v{I1}同相。再将v{O2}接回到G_1的另外一个输入端,这时,即使原来加在v{I1}输入端上的信号消失了,v{O1}和$v_{O2}$的状态也能保持下去,因此便可以记录“0”或“1”的状态。

image-20210630121602334

图4.3 用或非门组成的锁存器 (a)、(b)电路结构(c)逻辑符号

我们习惯上会把基本SR锁存器画成图4.3(b)中所示的对称形式,图4.3(c)为其逻辑符号。Q和\bar{Q}称为输出端,并定义Q=1、\bar{Q}=0为基本SR锁存器的1状态,Q=0、\bar{Q}=1为基本SR锁存器的0状态,S端称为置位端或置1输入端,R端称为复位端或置0输入端。

当S=1、R=0时,Q=1、\bar{Q}=0,在S=1信号消失后,由于有Q端的高电平接回到G_2的另一个输入端,因而电路的1状态得以保持,这也被称为基本SR锁存器的置1操作。

当S=0、R=1时,Q=0、\bar{Q}=1,在R=1信号消失后,电路保持0状态不变,也被称为基本SR锁存器的置0操作。

当S=R=0时,电路维持原来的状态不变。

当S=R=1时,Q=\bar{Q}=0,这既不是定义的1状态,也不是定义的0状态。而且,在S和R同时回到0以后无法断定锁存器回到1状态还是0状态。因此,在正常工作时,输入信号应遵守SR=0的约束条件,亦即不允许输入S=R=1的信号。

将上述逻辑关系列成真值表,就得到基本SR锁存器的特性表。Q是基本SR锁存器的当前状态,也叫现态,Q^*是指基本SR锁存器新的状态,也叫次态。

表4.1 基本SR锁存器的特性表
S
R
Q
Q*
基本SR锁存器状态
0
0
0
0
保持
0
1
1
1
1
0
0
1
置1
1
0
1
1
0
1
0
0
置0
0
1
1
0
1
1
0
0注
不确定
1
1
1
0注

注:S、R的1状态同时消失后状态不定。

我们还可以进一步画出基本SR锁存器的波形,如图4.4所示。从图中我们可以看出,S、R是基本SR锁存器的输入信号,Q、\bar{Q}是基本SR锁存器的输出信号,当S为高电平、R为低电平时,基本SR锁存器执行置1操作,即Q被置为高电平;当S为低电平、R为高电平时,基本SR锁存器执行置0操作,即Q被置为低电平。

image-20210701092744956

图4.4 基本SR锁存器的典型工作波形
门控SR锁存器

基本SR锁存器的输出状态是由输入信号S或R直接控制的,而门控SR锁存器在基本SR锁存器前增加了一对与门G_3、G_4,并用CLK信号控制锁存器在某一指定时刻根据S、R输入信号确定输出状态,如图4.5(a)所示。

当CLK=0,G_3、G_4关闭,S、R端的信号无法通过G_3、G_4而影响输出状态,故输出保持原来的状态不变;当CLK=1,G_3、G_4打开,将S、R端的信号传送到基本SR锁存器的输入端,使Q、\Q根据S、R信号而改变状态;

图4.3(b)是门控SR锁存器的逻辑符号,C1表示$CLK$是编号为1的一个控制信号,1S和1R表示受C1控制的两个输入信号,只有在C1为有效电平时(C1=1),1S和1R才能其作用。框图外部的输入端处没有小圆圈表示CLK以高电平为有效信号(如果在CLK输入端有小圆圈,则表示CLK以低电平作为有效信号)。

image-20210630122521726

图4.5 门控SR锁存器

表4.2为门控SR锁存器的特性表,由表可见,只有当CLK=1时,门控SR锁存器的输出端的状态才受输入信号的控制,而且其特性表与基本SR锁存器的特性表是一样的。同样的,门控SR锁存器也应当遵守SR=0的约束条件。

表4.2 门控SR锁存器的特性表
CLK
S
R
Q
Q*
门控SR锁存器的状态
0
x
x
0
0
保持
0
x
x
1
1
1
0
0
0
0
保持
1
0
1
1
1
1
1
0
0
1
置1
1
1
0
1
1
1
0
1
0
0
置0
1
0
1
1
0
1
1
1
0
0注
不确定
1
1
1
1
0注

 

注:CLK回到低电平后状态不定。

我们也可以进一步画出门控SR锁存器的波形,如图4.6所示。从图中我们可以看出,CLK是门控SR锁存器控制信号,S、R是门控SR锁存器输入信号,Q、\Q是门控SR锁存器的输出信号。当CLK为高电平时,若S为高电平、R为低电平,门控SR锁存器执行置1操作,即Q被置为高电平;当S为低电平、R为高电平时,门控SR锁存器执行置0操作,即Q被置为低电平;也就是说,当CLK为高电平时,门控SR锁存器的特性与基本SR锁存器的特性是一样的。当CLK为低电平时,门控SR锁存器保持原来的状态不变;

image-20210701093130925

图4.6 门控SR锁存器的工作波形
门控D锁存器

门控SR锁存器必须严格遵守SR=0的约束条件,这就为电路设计带来了很多不便。为了解决门控SR锁存器带来的问题(S、R不能同时为1),在图4.5(a)所示的电路的R输入端连接一个非门G_5,从而保证了S和R不同时为1的条件,其电路结构如图4.7(a)所示,它只有两个输入端:数据输入D和控制输入CLK,有两个输出端Q和\bar{Q},图4.7(b)是门控D锁存器的逻辑符号。

注意:这里的基本SR​锁存器是用与非门构成的,即当S_g=R_g=1时,电路保持原来的状态不变;S_g=0,R_g=1时,电路置1;S_g=1,R_g=0时,电路置0;正常工作时,不应出现S_g=R_g=0。

image-20210630135111939

图4.7 逻辑门控D锁存器

我们来分析一下门控D锁存器的工作原理。当CLK=0时,G_3、G_4输出均为1,使G_1、G_2构成的基本SR​锁存器处于保持状态,无论D信号怎样变化,输出Q和\Q均保持不变。当需要更新状态时,将CLK置1,此时,根据送到D端新的二值信息将锁存器置为新的状态:如果D=0,无论基本SR锁存器原来状态如何,都将使Q=0、\Q=1;反之,D=1,Q=1、\Q=0。总结来说就是,如果D信号在CLK=1期间发生变化,Q端信号跟随D信号变化;当CLK由1跳变为0后,锁存器将锁存跳变前瞬间D端的逻辑值。它的特性表如表4.3所示。

表4.3 门控D锁存器特性表
CLK
D
Q
Q*
门控D锁存器的状态
0
x
0
0
保持
0
x
1
1
1
0
0
0
置0
1
0
1
0
1
1
0
1
置1
1
1
1
1

图4.8为其波形图。从波形图中我们可以看出,CLK是门控D锁存器的控制信号,D是门控D锁存器的输入信号,Q是门控D锁存器的输出信号。当CLK为高电平时,Q跟随D的变化而变化,所以Q的波形等于D的波形;当CLK为低电平时,锁存器进入保持状态,Q的波形不会跟随D的变化而变化。

image-20210630141737633

图4.8 门控D锁存器的工作波形

门控D锁存器的Verilog HDL描述

下面我们来从实际的逻辑设计中看如何用Verilog HDL来描述门控D锁存器。

使用Verilog HDL描述门控D锁存器有两种方法,一种是根据图4.7的结构使用基本的逻辑门元件,采用结构描述的方法来实现;另外一种方法是采用功能描述,即不涉及实现电路的具体描述,靠“算法”实现电路。对于不太喜欢低层次硬件逻辑图的人来说,功能描述风格的Verilog HDL是一种最佳选择。在实际的逻辑设计中,大多数情况下,我们也是选用功能描述的方法来实现门控D锁存器。

下面给出两种方法描述逻辑门控D锁存器的Verilog HDL代码。

// 逻辑门控D锁存器的结构描述
module Dlatch_Structural (
    input   Clk,
    input   D,
    output  Q
);
    
wire R_g, S_g, Qa, Qb /* synthesis keep */ ;
​
nand (S_g, D, Clk);
nand (R_g, ~D, Clk);
nand (Qa, S_g, Qb);
nand (Qb, R_g, Qa);
​
assign Q = Qa;
​
endmodule
代码4.1 逻辑门控D锁存器的结构描述
// 逻辑门控D锁存器的功能描述
module Dlatch_bh (
    input       Clk,
    input       D,
    output  reg Q
);
​
// gated D-latch
always @( * )
    if (Clk == 1'b1)
        Q = D;
​
endmodule
代码4.2 逻辑门控D锁存器的功能描述

在实际应用中,出现latch的两个原因: 在组合逻辑中,if或者case语句不完整的描述,比如if缺少else分支,case缺少default分支,都会导致代码在综合过程中出现latch。解决办法就是if必须带else分支,case必须带default分支。

需要注意的是,只有不带时钟的always语句的if或者case语句不完整才会产生latch, 带时钟的语句if或者case语句不完整描述不会产生latch,下面我们以if语句举例说明一下。

我们先来写一个不带else的always语句,代码如下:

module dlat(
input       en,
input       d,
output  reg q
);
​
always@(*)
begin
    if(en == 1)
        q = d;
endendmodule
代码4.3 不带else的if语句

我们使用Quartus的RTL Viewer来看下综合后的电路结构,如图4.9所示,图中标识出了“LATCH”字眼,可以看出这个电路就是latch。

image-20210622114631243

图4.9 不带else的if语句的RTL Viewer

下面我们把if语句缺少的else补充完整再来看下电路结构,代码如下

module dlat(
input       en,
input       d,
output  reg q
);
​
always@(*)
begin
    if(en == 1)
        q = d;
    else
        q = 1'b0;
endendmodule
代码4.4 带else的if语句

我们再来看一下Quartus综合出来的电路结构,如图4.10所示,电路综合后是一个mux选择电路,可以看出,加了else分支后电路就不会综合成latch了。

image-20210622115200430

图4.10 带else的if语句的RTL Viewer

4.2.3 触发器

触发器(Flip-flop)也是数字电路中一种具有记忆功能的逻辑元件,可以记录二进制数字信号“0”和“1”。触发器是边沿敏感的存储单元,数据存储的动作由时钟信号的上升沿或下降沿进行同步。

根据逻辑功能,触发器分为SR触发器、JK触发器、D触发器、T触发器,实际应用中,D触发器使用的最为频繁,下面我们也是着重介绍D触发器。

D触发器

D触发器可以由两个门控D​锁存器级联构成,如图4.11(a)所示。图中左边的锁存器称为主锁存器,右边的称为从锁存器。主锁存器的时钟信号正好与从锁存器反相,利用两个锁存器的交互锁存,则可实现存储数据和输入信号之间的隔离。

当CLK处于低电平时,CLK_1为高电平,因而FF_1的输出Q1跟随输入端D的状态变化,始终保持Q_1=D。与此同时,CLK2为低电平,FF_2的输出Q_2(也就是整个电路最后的输出Q)保持原来的状态不变。

当CLK由低电平跳变至高电平时,CLK_1随之变成了低电平,于是Q_1保持为CLK上升沿到达前瞬间输入端D的状态,此后不再跟随D的状态而改变。与此同时,CLK_2跳变为高电平,使Q_2与它的输入状态相同。由于FF_2的输入就是FF_1的输出Q_1,所以输出端Q便被置成了与CLK上升沿到达前瞬间D端相同状态,而与以后D端的状态无关。

image-20210630142953413

图4.11 主从D触发器

图4.11(b)是D触发器的逻辑符号,用CLK输入端处框内的“->”表示触发器为边沿触发方式。在特性表中,则用CLK一栏中的“ \uparrow”表示边沿触发方式,而且是上升沿触发,如表4.4所示,如果是下降沿触发,则应在CLK输入端加小圆圈,并在特性表中用“\downarrow”表示。

表4.4 D触发器特性表
Clk
D
Q
Q*
x
x
x
Q
0->1
0
0
0
0->1
0
1
0
0->1
1
0
1
0->1
1
1
1

 

通过D锁存器和D触发器的学习,可能有些同学已经发现了,D锁存器和D触发器的逻辑功能其实是相同的,只不过它们的触发方式有所不同。接下来我们通过将D触发器的波形图与D锁存器的波形图进行比较,来看一看,它们的触发方式不同在哪里。

image-20210630143744972

图4.12 D锁存器和D触发器的波形对比图

我们先看D触发器的波形图(左),D触发器在CLK为0时,主锁存器接收D的值,并将这个值锁存起来;当CLK变为1时,Q才会改变为CLK由0变为1时D端的值,所以,D触发器是在CLK由0变为1的这个边沿触发的。

而D锁存器就比较简单,当CLK为0时,锁存器没有被使能,处于保持的状态,Q的值不会发生变化;当CLK为1时,锁存器被使能,Q端的值随D的变化而变化,所以,D锁存器是CLK为高电平触发的。

D触发器的Verilog HDL描述

与D锁存器类似,D触发器的Verilog HDL描述也是有两种方法,一种是根据图4.11(a所示的结构使用连续复制语句来实现,另外一种是使用功能描述风格,使用always和if-else语句对输出变量赋值,其中posedge代表上升沿,negedge代表下降沿。

下面给出两种方法描述D触发器的Verilog HDL代码。

module DFF_Structural(
input       clk,
input       d,
output      q
);
​
// wires declaration
wire q1, q2;
​
// module Dlatch_Structural (input Clk, D, output Q);
Dlatch_Structural   U1 (
    .Clk (~clk),
    .D   (d),
    .Q   (q1)
);
Dlatch_Structural   U2 (
    .Clk (clk),
    .D   (q1),
    .Q   (q2)
);
assign q = q2;
​
endmodule
​
​
// 逻辑门控D锁存器的结构描述
module Dlatch_Structural (
    input   Clk,
    input   D,
    output  Q
);
    
wire R_g, S_g, Qa, Qb /* synthesis keep */ ;
​
nand (S_g, D, Clk);
nand (R_g, ~D, Clk);
nand (Qa, S_g, Qb);
nand (Qb, R_g, Qa);
​
assign Q = Qa;
​
endmodule
代码4.5 D触发器的结构描述
module DFF_Structural(
input       clk,
input       d,
output      q
);
​
// wires declaration
wire q1, q2;
​
// module Dlatch_Structural (input Clk, D, output Q);
Dlatch_Structural   U1 (
    .Clk (~clk),
    .D   (d),
    .Q   (q1)
);
Dlatch_Structural   U2 (
    .Clk (clk),
    .D   (q1),
    .Q   (q2)
);
assign q = q2;
​
endmodule
​
​
// 逻辑门控D锁存器的结构描述
module Dlatch_Structural (
    input   Clk,
    input   D,
    output  Q
);
    
wire R_g, S_g, Qa, Qb /* synthesis keep */ ;
​
nand (S_g, D, Clk);
nand (R_g, ~D, Clk);
nand (Qa, S_g, Qb);
nand (Qb, R_g, Qa);
​
assign Q = Qa;
​
endmodule
代码4.6 D触发器的功能描述

从上述两段代码也可以看出,相比于结构描述方法,使用功能描述的方法实现D触发器简洁、高效、易懂、不易出错,这也是我们在实际的逻辑设计中采用的方法。

接下来,我们来介绍两种在逻辑设计中常用的D触发器。

带复位信号的D触发器

在设计中,我们常常会使用到复位信号来设定电路的起始值或作清除的动作,强迫电路进入某种特定的状态。D触发器根据复位的不同分为两种,一种是同步复位D触发器,另一种是异步复位D触发器,下面我们来分析两种D触发器的异同。

异步复位D触发器中的“异步”是和工作时钟不同步的意思,也就是说,寄存器的复位不关心时钟的有效沿来不来,只要检测到复位信号有效,就立刻执行复位操作。图4.13所示为有异步复位端的D触发器的逻辑符号,Rst_n代表低电平有效的复位信号,我们来结合波形图分析一下它的工作过程。在Rst_n被拉低后Q立刻变为0,而不是等待CLK的上升沿到来的时候$Q$才复位;而在Rst_n复位释放的时候,Q不会立刻变为D的值,因为还要等待时钟上升沿到来时才能检测到D的值,此时才能将D的值赋值给Q。

image-20210630144319958

图4.13 异步复位D触发器的逻辑符号

image-20210630144629915

图4.14 异步复位D触发器的波形图

代码4.7为Verilog HDL描述的带有异步复位的D触发器,always语句有两个触发条件,一个是时钟信号clk上升沿,一个是复位信号rst_n的下降沿,而从if-else语句可得知复位信号rst_n的优先级比时钟信号clk高。图4.15为代码4.x综合后的RTL Viewer,从中可以看到,复位信号rst_n连接到了D触发器的异步复位CLRN端。

module DFF_Asynchronous_Reset(
input       clk,
input       rst_n,
input       d,
output  reg q
);
​
always@(posedge clk, negedge rst_n)
    if(~rst_n)
        q <= 1'b0;
    else
        q <= d;
​
endmodule
代码4.7 异步复位D触发器的Verilog HDL描述

image-20210624105558412

代码4.15 代码4.x的RTL Viewer

然后我们再来看同步复位D触发器,其中的“同步”是和工作时钟同步的意思,也就是说,复位信号只有在时钟的有效沿到来时,才能有效,否则,就无法完成复位工作。我们还是结合波形图来分析其工作过程。复位信号$rst_n$被拉低后$Q$没有立刻变为0,而是当时钟信号$clk$的上升沿到来的时候$Q$才复位成功。

image-20210701093419933

图4.16 同步复位D触发器的逻辑符号

image-20210630145004454

图4.17 同不复位D触发器的波形图

代码4.8为Verilog HDL描述的带有同步复位的D触发器,此时的always语句只有一个触发条件 -- 时钟信号clk的上升沿,也就是说,复位信号rst_n也要在时钟信号clk的上升沿的触发下才起作用。当rst_n=0,并且clk上升沿到来时,触发器的输出Q会被清零,当rst_n=1时,触发器可以正常工作。

module DFF_Synchronous_Reset(
input       clk,
input       rst_n,
input       d,
output  reg q
);
​
always@(posedge clk)
    if(~rst_n)
        q <= 1'b0;
    else
        q <= d;
​
endmodule
代码4.8 同步复位D触发器的Verilog HDL描述

image-20210624105405067

代码4.18 代码4.x的RTL Viewer

通过上面异步复位D触发器和同步复位D触发器的RTL Viewer对比,我们可以发现,采用同步复位会多出一个选择器的结构,所以我们在使用Intel芯片时最好使用异步复位,这样就可以节约更多的逻辑资源。

有使能端的D触发器

在实际工作中经常会遇到这样的问题,在时钟有效沿到来时,即使触发器的输入信号发生变化,但仍然希望其中一些触发器能够保持现在的状态不变。解决这一问题的方法之一是使用带使能端的触发器,这种触发器广泛的应用在FPGA或CPLD中,图4.19是其逻辑符号,当EN=0时,触发器的状态不会发生变化;当EN=1时,触发器相当于普通D触发器。

image-20210701093532616

图4.19 异步复位、同步使能D触发器的逻辑符号

我们结合波形图来分析它的工作原理,当EN=1时,Q的数据随D的变化而变化,相当于普通的D触发器;而当EN=1时,无论D怎样变化,都不会影响Q,触发器的状态不发生变化,而只有等EN再次为1时,Q才会继续随着D的变化而变化。

image-20210630145516922

图4.20 异步复位、同步使能D触发器的波形图

以下是异步复位、同步使能D触发器的Verilog HDL描述。

module DFF_Asynchronous_Enable(
input       clk,
input        rst_n,
input       en,
input       d,
output  reg q
);

always@(posedge clk, negedge rst_n)
    if(~rst_n)
        q <= 1'b0;
    else if(en)
        q <= d;

endmodule
代码4.9 异步复位、同步使能D触发器的Verilog HDL描述

图4.21为异步复位、同步使能的D触发器的RTL Viewer。从图中我们也可以看出,FPGA中的D触发器是带有同步使能端的。

image-20210622140303309

图4.21 带异步复位、同步使能的D触发器RTL Viewer

4.2.4 寄存器

寄存器(register)是时序逻辑电路中最重要的逻辑单元。在实际的数字系统中,通常把能够用来存储一组二进制代码的同步时序逻辑电路称为寄存器。

图4.22给出了带异步复位、同步使能端D触发器组成的4位寄存器,它由4个带有异步复位、同步使能的D触发器构成。其中,D_3 \sim D_0是4位数据输入,当EN=1时,在时钟上升沿到来时,Q_3=D_3、Q_2=D_2、Q_1=D_1、Q_0=D_0,即输入数据D_3 \sim D_0同时存入相应的触发器;当EN=0时,在时钟上升沿到来时,输出端的状态保持不变。

image-20210630150434590

图4.22 4个D触发器组成的4位寄存器

我们还是结合波形图来分析它的功能。当寄存器复位时,寄存器的输出Q[3:0]=4'b000;当寄存器退出复位后,若EN=1,D[3:0]的数据在时钟信号CLK的上升沿存入Q[3:0],比如D[3:0]=4'b0001,那么在下一个时钟上升沿,Q[3:0]=4'b0001;若EN=0,D[3:0]中的数据就不会存入Q[3:0],此时Q[3:0]保持原来的状态不变,如图中所示,Q[3:0]保持为4'b0110;只有当EN重新回到1后,新的数据才会存入寄存器。

image-20210630150622126

图4.23 4位寄存器的波形图

图4.22所示的寄存器也是本实验所要实现的目标之一,为了避免重复,它的框图和Verilog HDL代码我们将在4.3.2节给出。

4.3 实验目标

  • 设计一个4位的、带有异步复位、同步使能的寄存器;

  • 并将其加入到实验3实现的计算器中,要求将计算器运算的结果先进行寄存,再译码显示到七段数码管。

4.4 设计实现

4.4.1 设计思路

系统框图

首先,我们要对实验有一个整体的认识,我们先来看一下实验工程的系统框图,如图4.24所示。本实验是在实验三的基础上,在数据选择的和七段数码管译码器之间加入一个4位的寄存器,将计算器的运算结果先进行寄存再显示。

image-20210630153315129

图4.24 在计算器中插入寄存器

寄存器的数据输入为多路数据选择的输出F,KEY1作为寄存器的使能控制信号,KEY0作为寄存器的异步复位信号,时钟信号由DE-Cloud开发板上的名为FPGA_CLK1_50的50MHz时钟输入,一个时钟周期后,寄存器的输出数据流入七段数码管译码器,译码器译码输出并将结果显示七段数码管上,同时寄存器的输出也会显示到LED3 \sim LEDR0上。

我们将实验的端口列表和功能总结成表4.5。

表4.5 实验4输入输出信号描述
信号名称
位宽
方向
功能描述
a
4-bit
Input
计算器的第一个操作数
b
4-bit
Input
计算器的第二个操作数
sel
2-bit
Input
计算器的功能选择信号
en
1-bit
Input
寄存器的同步使能信号
clk
1-bit
Input
寄存器的时钟信号
rst_n
1-bit
Input
寄存器的异步复位信号
hex0_out
7-bit
Output
七段数码管译码器的输出信号
ledr_out
4-bit
Output
寄存器的数据输出
子模块框图

根据系统框图我们可以看到,实验一共分为8个模块,每个模块的功能如下:

表4.6 实验4模块功能简介
模块名称
功能描述
c1
与运算模块
c2
或运算模块
c3
异或运算模块
c4
非运算模块
mux4x1
四选一数据选择器
reg4bits
4位寄存器
decod7seg
七段数码管译码器
calculator
计算器系统

c1 ~ c4、mux4x1、decod7seg模块在实验三中已介绍过功能,不再做过多解释,这里主要介绍reg4bits模块,其模块框图如图4.25所示。

image-20210630153459118

图4.25 reg4bits模块框图
表4.7 reg4bits模块信号描述
信号名称
位宽
方向
功能描述
en
1 bit
Input
寄存器的同步使能信号
reset_n
1 bit
Input
寄存器的异步复位信号
clk
1 bit
Input
寄存器的时钟信号
D
4 bit
Input
寄存器的数据输入信号
Q
4 bit
Output
寄存器的数据输出信号

reg4bits模块的功能是当en=1时,d[3:0]中的数据存到该寄存器中,当en=0,寄存器保持原有的数据不变。该寄存器的结构和波形图在4.1.4已经介绍过,这里不再赘述。

4.4.2 代码实现

  • reg4bits模块:这个模块是本实验的重点,本实验的最终要实现的计算器是在实验三的基础上加入了这个模块。

    module reg4bits(
    input               clk,        // 时钟信号50MHz
    input               rst_n,      // 异步复位信号,低电平有效
    input               en,         // 同步使能信号,高电平有效
    input       [3:0]   d,          // 并行输入数据
    output  reg [3:0]   q           // 并行输出数据
    );
    
    // 当检测到clk上升沿或rst_n下降沿时执行下面的语句
    always@(posedge clk, negedge rst_n)
    begin
        if(~rst_n)              // rst_n为低电平复位,且不需要等待clk上升沿到来后再复位
            q <= 4'b0000;
        else if(en)
            q <= d;
        else
            q <= q;
    
    end
    
    endmodule
    代码4.10 reg4bits.v

    reg4bits子模块综合出来的RTL Viewer如图4.26所示。

    image-20210624122905599

图4.26 reg4bits模块RTL Viewer
  • calculator模块:完成reg4bits模块后,我们需要将其添加到实验三实现的计算器中,所以我们需要修改calculator.v文件,其中第72行 ~ 79行为主要的修改部分,主要是添加了reg4bits模块。

    module calculator(
        input           clk,        // 时钟信号,50MHz
        input           rst_n,      // 异步复位信号
        input           en,         // 寄存器使能信号
        input   [3:0]   a,          // 计算器的第一个操作数
        input   [3:0]   b,          // 计算器的第二个操作数
        input   [1:0]   sel,        // 计算器的功能选择信号
    
        output  [3:0]   ledr_out,   // 寄存器的数据输出
        output  [6:0]   hex0_out    // 七段数码管译码器的输出
    );
    
    
    // 变量声明
    wire [3:0]  operator_a;
    wire [3:0]  operator_b;
    wire [1:0]  operation_s;
    wire        reg4bits_rst_n;
    wire        reg4bits_en;
    
    // 输入信号赋值
    assign  operator_a      =   a;
    assign  operator_b      =   b;
    assign  operation_s     =   sel;
    assign  reg4bits_rst_n  =   rst_n;
    assign  reg4bits_en     =   ~en;
    
    
    wire [3:0]  f1;
    wire [3:0]  f2;
    wire [3:0]  f3;
    wire [3:0]  f4;
    wire [3:0]  f;
    wire [3:0]  m;
    wire [3:0]  g;
    wire [6:0]  hex0dec_output;
    
    // 与运算
    c1  c1_inst(
        .a  (operator_a),
        .b  (operator_b),
        .f  (f1)
    );
    // 或运算
    c2  c2_inst(
        .a  (operator_a),
        .b  (operator_b),
        .f  (f2)
    );
    // 异或运算
    c3  c3_inst(
        .a  (operator_a),
        .b  (operator_b),
        .f  (f3)
    );
    // 非运算
    c4  c4_inst(
        .a  (operator_a),
        .f  (f4)
    );
    // 在f1,f2,f3,f4四种运算结果中,选择一个运算结果f
    mux4x1 mux4x1_inst(
        .in1  (f1),
        .in2  (f2),
        .in3  (f3),
        .in4  (f4),
        .sel  (operation_s),
        .out  (f)
    );
    
    // 将选出的运算结果f存入寄存器中
    reg4bits reg4bits_inst(
        .clk    (clk),
        .rst_n  (reg4bits_rst_n),
        .en     (reg4bits_en),
        .d      (f),
        .q      (g)
    );
    
    // 寄存器的输出数据g输入至数码管译码器,经译码后显示至七段数码管上
    decod7seg decod7seg_inst(
        .hex        (g),
        .display    (hex0dec_output)
    );
    
    // 寄存器的输出数据g直接显示在LED上
    assign  ledr_out    = g;
    assign  hex0_out    = hex0dec_output;
    
    
    endmodule
代码4.11 calculator.v

calculator模块综合出来的RTL Viewer如图4.27所示,可以看出这和我们的系统框图是一致的。

1670381415577

图4.27 calculator模块RTL Viewer

4.5 实验步骤

4.5.1 创建工程和代码输入

1. 点击电脑右下角的开始菜单找到Quartus软件,双击Quartus (Quartus Prime 17.1)打开Quartus Prime软件。

2. 点击菜单File-->New Project Wizard弹出工程创建的对话框。在弹出的对话框中点击Next。

​3. 在您的DE1-SOC 工作文件夹下创建一个lab4的文件夹,并将工程路径指向该文件夹,且工程的名称也命名calculator。如图4.28所示。

图4.28 lab4的工程创建

 

图4.29 创建lab4工程

 

4. 完成创建工程后,打开后的工程Quartus Prime工程界面如图4.30所示。

图4.30 创建lab4工程

 

5. 在Quartus工具栏依次点击File-->New,在New窗口中选择Verilog HDL File后点击OK按钮新建1个空白Verilog HDL文件,命名为reg4bits.v,并新建名为v的文件夹,将该Verilog HDL文件保存在v文件夹中。将代码4.10 复制添加到reg4bits.v文件中。

图4.31 新建reg4bits.v文件

 

6. 将实验3中的c1.v、c2.v、c3.v、c4.v、decod7seg.v、mux4x1.v、calculator.v文件复制到本实验工程目录的v文件夹下,如图4.32所示。

图4.32 复制文件至本实验的工程路径

7. 点击Quartus软件工具栏的Project --> Add/Remove Files in Project ...,将前面拷贝的文件添加至本实验的Quartus工程,如图4.33-1,4.33-2和4.33-3所示。

 

 

 图4.33-1

  图4.33-2

图4.33-3 成功添加文件至Quartus工程

 

 

8. 修改calculator.v文件,参考代码4.11,相对于实验3,主要添加了时钟、复位、使能信号以及寄存器模块。

图4.34 calculator.v

 

 

9. 点击Quartus软件工具栏的Processing --> Start --> Start Analysis & Synthesis或点击image-20210603145513555按钮对Verilog HDL代码执行语法检查和综合。如果在该过程中提示有错误,请检查Verilog HDL代码语法,确保与上述代码块完全一致。

 

 

​ 图4.35

图4.36 执行语法检查和综合

4.5.2 仿真

1. 点击Quartus工具栏File -->Open打开v文件夹的calculator_tb.v文件,并将里面的代码替换成如下4.13的代码。

`timescale 1ns/1ns
module calculator_tb();


// 产生50MHz时钟信号
reg clk;
localparam  PERIOD = 20;                // 20ns = 50MHz
initial 
begin
    clk     =   1'b0;
    forever #(PERIOD/2) clk = ~clk;     // 每隔10ns,clk翻转一次
end

// 产生复位信号,用作4位寄存器的异步复位
reg rst_n;
initial 
begin
    rst_n = 1'b0;
    #(PERIOD)   rst_n = 1'b1;
end


// 产生计算器的两个操作数a, b、运算操作选择sel和4位寄存器的使能信号en
// 其中,为方便分析,运算操作选择sel固定为00,即只进行与运算
reg [3:0] a;
reg [3:0] b;
reg [1:0] sel;
reg       en;
initial 
begin
    #0
    a   = 4'd0;
    b   = 4'd0;
    sel = 2'b00;
    en  = 1'b1;
    #(PERIOD)
    a   = 4'd1;
    b   = 4'd1;
    sel = 2'b00;
    en  = 1'b1;
    #(PERIOD)
    a   = 4'd2;
    b   = 4'd2;
    sel = 2'b00;
    en  = 1'b1;
    #(PERIOD)
    a   = 4'd3;
    b   = 4'd3;
    sel = 2'b00;
    en  = 1'b0;
    #(PERIOD)
    a   = 4'd4;
    b   = 4'd4;
    sel = 2'b00;
    en  = 1'b0;
    #(PERIOD)
    a   = 4'd5;
    b   = 4'd5;
    sel = 2'b00;
    en  = 1'b1;
    #(PERIOD)
    a   = 4'd6;
    b   = 4'd6;
    sel = 2'b00;
    en  = 1'b1;
    #(PERIOD)
    a   = 4'd7;
    b   = 4'd7;
    sel = 2'b00;
    en  = 1'b1;
    $stop();
end


// 例化 calculator
wire    [3:0]   ledr_out;
wire    [6:0]   hex0_out;
calculator calculator_inst(
    .clk        (clk),
    .rst_n      (rst_n),
    .en         (en),
    .a          (a),
    .b          (b),
    .sel        (sel),

    .ledr_out   (ledr_out),
    .hex0_out   (hex0_out)
);


endmodule
代码4.13 calculator_tb.v

图4.37 修改仿真代码
  1. 点击Quartus工具栏Assignments --> Settings --> EDA Tool Settings --> Simulation,设置Quartus自动调用ModelSim仿真软件,并设置test bench为calculator_tb,设置完成后如图4.38所示。

    图4.38 设置test bech为calculator_tb

    其中,添加Compile test bench的具体步骤如下。

    图4.39 设置test bech的具体步骤
  2. 点击Quartus工具栏Tools --> Run Simulation Tool --> RTL Simulation启动ModelSim仿真,再点击Wave切换到仿真波形窗口,仿真波形如图4.40所示。

    图4.40 calculator仿真波形

仿真波形分析

为了方便分析,在编写仿真文件时,将sel固定为00,即计算器只进行与运算;

  • 0 ~ 20ns,a=b=(0000)~2~,经过与运算后,经数据选择器后输出m=(0000)~2~,此时,由于rst_n=0,所以寄存器处于复位状态,ledr_out=(0000)~2~;

  • 20ns时刻,a=b=(0001)~2~,则m=(0001)~2~,此时,rst_n上的低电平撤销,寄存器恢复正常状态,但此时clk上升沿还没有到来,所以ledr_out=(0000)~2~;

  • 30ns时刻,clk上升沿到来,并且en=1,所以寄存器的输出ledr_out=(0001)~2~,并且七段数码管译码器的输出hex0_out=(1111001)~2~,即数码管显示十进制数“1”;

  • 40ns时刻,a=b=(0010)~2~,m=(0010)~2~;

  • 50ns时刻,clk上升沿到来,en=1,所以ledr_out=(0010)~2~,hex0_out=(10100100)~2~,即数码管显示十进制数“2”;

  • 60ns时刻,a=b=(0011)~2~,m=(0011)~2~;

  • 70ns时刻,clk上升沿到来,而en=0,所以ledr_out和hex0_out保持不变,即数码管仍旧显示十进制数“2”;

  • 110ns时刻,clk上升沿到来,并且en恢复为高电平,所以ledr_out=(0101)~2~,hex0_out=(0010010)~2~,即数码管显示十进制数“5”;

综合上述的仿真数据,我们看到,只有到clk上升沿到来,并且en为高电平时,运算的结果才会显示到数码管上,这与我们预期的功能一致。

4.5.3 引脚分配、全编译与烧录

1. 点击Quartus菜单Assignments——Pin Planner。

图4.41

关于引脚分配信息可以查看DE1-SoC_v.5.1.3_HWrevF.revG_SystemCD\UserManual\DE1-SoC_User_manual.pdf第 22、25、26、28页或者E:\CD_Package\01-DE1-SoC\DE1-SoC_v.5.1.3_HWrevF.revG_SystemCD\Schematic\DE1-SoC.pdf的第3页。 

 图4.42

图4.43

图4.44

 图4.45

 图4.46

这里,a到b以及sel可以通过拨码开关SW0到SW9来控制,out信号分别输出到LEDR0到LEDR3,hex0_out输出到数码管0,en和rst_n连接到按键key1和key0。

图4.47

2. 点击Quartus软件工具栏的Processing --> Start Compilation或点击image-20210603145755433按钮编译lab4工程,并检查是否生成calculator.sof。

图4.48 编译工程
  1. 点击Quartus软件工具栏的Tools --> Programmerimage-20210603150014201按钮打开Programmer窗口,点击Hardware Setup选择USB-Blaster[USB-0],点击Auto Detect选择5CSEBA6器件,左键单击选中5CSEBA6器件再点击Change File,选择calculator.sof,勾选program/configure,点击Start下载calculator.sof。

     图4.49

      图4.50

      图4.51

    图4.52 烧录calculator.sof成功

4.5.4 实验现象观察

1. 在lab4中,使用的KEY、SW、LEDR和HEX如下图所示。

图4.53 lab4中KEY、SW、HEX、LEDR对应关系

2. 通过切换滑动开关SW9-0到 up 或 down 位置,点击KEY0,观察LEDR3-0的状态以及HEX0的显示来测试设计的功能。

a. 先按一次KEY0,寄存器异步复位,寄存器中的数据被清0,LEDR3 ~ LEDR0全部熄灭,HEX0显示十六进制数字“0”;

 

b. 拨动SW9 ~ SW8为“down、down”,SW3 ~ SW0为“down、down、down、up”,SW7 ~ SW4为“down、down、down、up”;按一次KEY1LEDR3 ~ LEDR0显示为“熄灭、熄灭、熄灭、点亮”,HEX0显示十六进制数字“1”;

即:a = (0001)2,b = (0001)2,a & b=(0001)2=(1){16};

 

 

c. 拨动SW9 ~ SW8为“down、down”,SW3 ~ SW0为“down、down、up、down”,SW7 ~ SW4为“down、down、up、down”;按一次KEY1LEDR3 ~ LEDR0显示为“熄灭、熄灭、点亮、熄灭”,HEX0显示十六进制数字“2”;

即:a = (0010)2,b = (0010)2,a & b=(0010)2=(2){16};

d. 此时,按一次KEY0,寄存器异步复位,寄存器中的数据被清0,LEDR3 ~ LEDR0全部熄灭,HEX0显示十六进制数字"0";

e. 拨动SW9 ~ SW8为“down、down”,SW3 ~ SW0为“down、down、up、up”,SW7 ~ SW4为“down、down、up、up”;按一次KEY1LEDR3 ~ LEDR0显示为“熄灭、熄灭、点亮、点亮”,HEX0显示十六进制数字“3”;

即:a = (0011)2,b = (0011)2,a & b=(0011)2=(3){16};

 

4.6 实验小结

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

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