[FPGA 学习记录] 时序逻辑的开始---寄存器

时序逻辑的开始---寄存器


在前面几小节当中,我们重点使用 Verilog 语言,实现了几种简单的组合逻辑。目的是让大家熟悉一下语法,学会设计的思想方法和步骤。那么从本小节开始,我们就进入到时序逻辑的设计。

那么时序逻辑的设计,必不可少的就是我们的寄存器。那么寄存器的讲解可以分为两个部分:同样是理论部分和实战部分。

在理论部分我们会对寄存器的相关知识做一个讲解,在实战演练部分,我们会通过实验向大家展示时序逻辑的设计。下面就是第一部分:理论部分的学习

1 理论学习

学过数电的我们都知道,组合逻辑有一个最大的缺点就是存在竞争冒险,那么竞争冒险的相关知识,大家可以参考前面推荐的数字电路相关的书籍,这里不再进行过多的解释。

竞争冒险这个问题,在电路之中是非常危险的,它会使我们的电路处于一个不稳定的状态。然而如果我们使用时序逻辑就可以极大的避免这种问题,提高系统的稳定性。

1.2 寄存器

那么时序逻辑最基本的单元就是寄存器,寄存器它具有存储功能,一般是由 D 触发器构成,由时钟脉冲控制。每个 D 触发器,能够存储一位二进制码。那么 D 触发器,它的简化图就像这样

image-20231029174506048

它有一个时钟输入端口 CLK(clock的缩写),然后会有一个输入信号 D,还有一个输出信号是 Q,同时它还有一个复位信号 CLR(clear的缩写)。

D 触发器的工作原理就是,在脉冲信号 CLK(这个脉冲信号,在 FPGA 当中一般是晶振产生的时钟脉冲),在脉冲信号的上升沿或者下降沿的作用下(一般我们使用的是上升沿),在脉冲信号的上升沿将信号从输入端送到输出端。也就是说,输入端 D 输入的信号,在时钟 CLK 的上升沿就赋值给输出端 Q 了,然后 Q 一直会保持这个值,保持到什么时候呢?保持到检测到下一个上升沿,就会将新的输入端的信号,再次赋值给 Q。如果说时钟脉冲 CLK 的边沿信号一直没有出现(也就是说 CLK 没有时钟沿变化),即使输入信号改变,输入信号 D 怎么样改变,我们的输出信号依然保持原来的值。而且它具有复位清零功能,就是 CLR 信号,它的复位又可以分为同步复位和异步复位,那么同步复位和异步复位的知识,我们在后面会提到。

以上就是寄存器理论部分的讲解,那么接下来就开始实战演练。

2 实战演练

2.1 设计规划

2.1.1 实验目标

在点亮 LED 灯那个小节中,我们设计并编写了,使用按键控制 LED 灯的工程。那么在本小节当中我们同样使用这个例子,虽然最后的实验效果是相同的,但是在本次实验当中,我们使用 D 触发器来进行控制。这种方式和之前使用的组合逻辑控制方法是有所区别的,那么实验功能怎么实现呢?

2.1.2 硬件资源

同样使用按键,使用按键 KEY1 控制 LED 灯 D6。按键未按下,灯处于熄灭状态,按键按下灯被点亮,和之前的实验效果是相同的

Pinout

在开始程序设计之前,同样是文件体系的新建,这一部分大家应该很熟悉了

20231027161528_j9UXledLZX

2.2 波形绘制

那么文件体系建立完成之后,打开 doc 文件夹,同样的是新建一个 Visio 文件,用来绘制我们的模块框图和我们的波形图

2fJYW1VesE

那么波形工具箱加载完成,开始模块框图的绘制。找到我们的圆角矩形 拖拽出来,先给模块取一个名字

20231027162134_Bcwtx00VzR

接下来就是分析我们的端口信号,那么 D 触发器能够正常工作,一定要有时钟,每当时钟的沿(上升沿或者下降沿)来到时,我们采集到稳定有效的数据。

那么第一个信号肯定是时钟信号,时钟信号命名为 sys_clk

20231027162349_Wousyx3fdP

为什么这样命名呢?sys 代表系统,clk 代表时钟,sys_clk 这表示系统时钟。

除了时钟信号之外,还需要一个复位信号。复位信号的作用是:让触发器回到初始状态将数据清零。那么复位信号它的命名是 sys_rst_n

20231027162627_Zk9yTmCBA7

sys 同样代表是系统,rst 代表复位信号,后面的 _n 表示这个信号是低电平有效。如果说这个信号是高电平有效的,是不加后缀的,那么如果是某个信号是低电平有效的,我们习惯加一个下划线加 n,表示这个信号是低电平有效。

因为我们使用的是按键控制 LED 灯,所以说输入端还需要一个按键控制信号,按键控制信号命名为 key_in

20231027162834_WZjIiD2Zw5

最后,就是有一路输出信号到我们的 LED 灯,输出信号命名为 led_out

20231027163006_KlzSreIctp

那么这样模块框图就已经绘制完成了,下面可以开始波形图的绘制。

因为我们前面已经提到了,我们的 D 触发器有两种:一种是同步复位的 D 触发器,另一种是异步复位的 D 触发器。在这里我们会结合波形图,介绍一下这两种 D 触发器的异同。

那么首先,先来讲解一下同步复位的 D 触发器,我们结合一下我们的波形图。那么时钟信号的波形,我们直接使用 理想clk,选中 理想clk 将它拖拽出来,而且它自带参考线,方便我们图形的绘制

20231027163416_qcyGTSUsvk

然后是复位信号

20231027164003_cqiIIIMKcE

那么这样,我们完成了时钟信号和复位信号的波形绘制。

那么 D 触发器的同步复位,它说的同步是工作时钟同步的意思。也就是说当时钟的上升沿或者下降沿到来时,检测到按键的复位操作才是有效的,否则无效。那么这句话什么意思呢?我们使用波形图来表示一下

image-20231027170851013

比如说我们的输出信号 led_out 假如说它的变化不受按键控制,只受我们的复位信号控制。如果说它的输出信号一直为高电平,复位信号会让它变成低电平,那么在 ❶ 这个位置复位信号 sys_rst_n 是拉低了。因为复位信号会使我们的输出信号变为低电平,但在 ❶ 这个时刻复位信号拉低了,我们的输出信号并不会立刻拉低.因为只有在 ❷ 这个上升沿的位置,检测到复位信号拉低之后,才会拉低我们的输出信号.也就是说,输出信号不会在 ❶ 这个位置立即变化,输出信号不会因为复位信号已经拉低了就会立刻拉低,只有在时钟信号的上升沿,检测到复位信号拉低了这个复位信号才表示有效,才会进一步拉低我们的输出信号.也就是说,输出信号是在 ❷ 这个上升沿才进行变化的.因为只有在这个上升沿,才检测到复位信号是有效的.到了 ❸ 这个时钟的上升沿,复位信号仍然是有效的,所以说输出信号仍然保持低电平.而在下一个时钟的上升沿 ❹,检测到复位信号是没有效的了,我们的输出信号就可以拉高了.这里我们假设的是输出信号一直为高电平,只有复位信号才能将它变化.后面的时钟上升沿检测的复位信号是没有效的,所以说输出信号一直为高电平.

那么这样就表示 D 触发器的同步复位,只有时钟信号的上升沿检测到复位信号有效,它才是有效的.

那么与它对应的异步复位是什么情况呢?我们同样使用波形图来表示一下

image-20231027230914978

同样我们假设我们的输出信号一直为高电平,它的电平变化只受复位信号影响,不受其他信号影响,只有复位信号有效时输出低电平,其他时刻我们的输出信号都是高电平,我们这儿是做的一个假设.

D 触发器中的异步复位,它的异步是指:工作时钟不同步的意思.也就是说:寄存器的复位不关心时钟的上升沿来不来,只要检测到复位信号有效,立刻执行复位操作,在波形图上的表示就可以理解为 ❶ 这样.❶这儿的复位信号有效了,那么立刻拉低输出信号,不考虑时钟沿.那么到了 ❷ 这里呢?复位信号虽然已经被释放了,但是我们的输出信号并不会立刻的拉高,而是要等到下一个时钟上升沿采集到复位信号无效时 才会拉高输出信号,就像 ❸ 这样。那么这个波形就是异步复位的波形。

同步复位和异步复位的不同点,我们已经通过波形图进行了详细的讲解。它们两者的区别就是:复位有效的条件是立刻执行,还是等待上升沿或下降沿再执行。

同步复位和异步复位都存在于时序逻辑之中,那么时序逻辑相比于组合逻辑来讲,对电路中的产生的毛刺有极好的屏蔽作用。我们来举个例子:

我们使用按键控制 LED 灯,那么按键输入什么信号,直接赋值给 LED 灯,它俩的波形变化应该是一致的。比如说我们的按键信号输入来之后,在某个时钟周期内它产生了一段毛刺,那么如果假如是组合逻辑当中,那么输入信号是什么,输出信号就应该是什么。这个毛刺也会影响到我们的输出信号

image-20231029140611102

而在我们时序逻辑当中,对毛刺有着极好的屏蔽作用。波形是怎么变化的呢?在 ❶ 这个上升沿会对输入信号进行一个波形的采样,发现它是一个高电平,在下一个采样到来之前,❶ 到 ❷ 这一个时钟周期内输出信号都会保持一个高电平;那么下一次采样,❷ 采样到输入信号又是高电平,那么在 ❷ 到 ❸ 这个周期内又会保持高电平;直到 ❹ 这儿,再进行一次采样还是高电平,那么 ❹ 到 ❺ 这个周期内还会保持一定的高电平;❻ 这儿呢?仍然采样的是高电平,还会保持高电平,那么一直保持到最后。

image-20231029141406777

在时序逻辑当中,一个时钟周期内的这个毛刺现象,对我们的输出信号没有产生干扰。时序电路只有在沿到来时,才检测信号是否有效,在两个上升沿之间的毛刺现象都会被自然的过滤掉。这样就大大减少了毛刺现象产生的干扰,提高了电路的可靠性。那么这里展示的是毛刺对组合逻辑和时序逻辑的影响。

时序逻辑还有一个特点就是:它可以产生延迟一拍的一个效果。我们依然使用波形图来进行表示。

比如说我们的输入信号,假如说,我们输入信号的波形是下图这个样子的

image-20231029141853685

如果是在组合逻辑当中,输入信号是什么样子,输出信号就应该是什么样子,因为我们是直接将输入信号的值赋值给了输出信号

image-20231029142107567

但是如果是在时序逻辑当中,输出信号会延迟输入信号一拍,就是一个时钟周期,它的波形就会是这个样子

image-20231029142249720

这是什么原因呢?
当我们在表达组合逻辑时,如果时钟和数据是对齐的,这默认当前时钟沿采集到的数据是在该时钟上升沿同一时刻的值,❶ 这个上升沿采集到的输入信号就是 ❶ 这个上升沿同一时刻的值,是一个高电平,那么输出信号就是高电平。

如果是表达时序逻辑时,如果时钟和数据是对齐的,默认当前时钟沿采集到的数据是在该时钟上升沿前一时刻的值,❶ 这个时钟的上升沿采集的是输入信号的前一时刻的值,就是一个低电平,所以说,❶ 到 ❷ 这个时钟周期保持的是低电平;在下一个上升沿再次进行数据的采集,❷ 上升沿采集到的是 ❷ 这个位置的值,❷ 这个位置的值是高电平,那么后面就保持了一个高电平

image-20231029143740529

今后我们使用的大部分都是时序逻辑。在进行波形图的绘制过程中,一定要记住这个延迟一拍的一个效果,否则我们绘制的波形图就会和最后的仿真结果不符,导致逻辑混乱。

了解了同步复位、异步复位以及延迟一拍这些知识,我们完善我们的波形图

image-20231029144708185

那么波形图就绘制完成,接下来就参照波形图进行代码的编写

2.3 代码编写

首先是模块开始,然后是模块名称,然后是我们的端口列表,然后是模块结束

0AqdZMX476

那么端口列表当中首先是输入信号。第一个输入信号是我们的时钟信号(后面我们接触的全是时序逻辑,所以说一定会有时钟信号),我们这儿的时钟信号是由板载的晶振传入,它的频率是 50MHz;下面是复位信号,我们的复位信号是低电平有效,由板卡的复位按键输入;然后是输入的按键信号 key_in;最后是我们的输出信号 led_out,因为输出信号使用 always 进行赋值,它的变量类型是 reg 型

b6izdXjyas

这样端口列表编写完成,开始输出信号的赋值,我们使用 always 语句。在时序逻辑之中,always 语句块的敏感列表,一定是时钟的上升沿或者下降沿,我们这儿选择时钟的上升沿;然后是 if-else 语句。我们的复位信号是低电平有效,它有效一定存在一个大前提就是:在时钟的上升沿采集到有效信号(我们敏感列表当中已经写了),当我们的复位信号有效时我们给输出信号赋一个初值,那么初值赋为 0(一般初值都赋为 0)。这儿有一点要记住的是:时序逻辑之中赋值语句一定要使用非阻塞赋值,就是 <=。然后是 else 语句,那么当我们的复位信号无效时,我们将输入信号直接赋值给输出信号

euV5GZp5zw

flip_flop.v

module flip_flop
(
    input   wire    sys_clk     ,//50MHz
    input   wire    sys_rst_n   ,
    input   wire    key_in      ,
    
    output  reg     led_out
);

always@(posedge sys_clk)
    if (sys_rst_n == 1'b0)
        led_out <= 1'b0;
    else
        led_out <= key_in;

endmodule

2.4 代码编译

那么这样,同步复位下按键控制 LED 灯它的代码编写完成,我们保存,回到桌面

1EBIWD3Ns2

然后添加 .v 文件,进行一次全编译,检查代码错误,那么编译完成,点击 OK

20231029151851_AdhvOKpVrt

我们看一下 RTL 视图。那么这个就是我们编写的代码,综合出来的 RTL 视图

image-20231029152039243

它由一个选择器和一个寄存器—— D 触发器构成。

下面我们对代码进行修改,我们使用异步复位来实现功能。

那么其他位置不需要进行更改,我们只需要更改 always@(posedge sys_clk) 这个位置,修改一下敏感列表,再添加一个条件,或者在复位信号的下降沿

ClmBpgSh1U

那么这句话什么意思呢?当敏感列表检测到时钟信号的上升沿或者说复位信号的下降沿,执行下面这些语句。我们的复位信号是低有效,当检测到复位信号下降沿时立刻进行复位,不需要等待我们的时钟上升沿再进行复位。那么这样就实现了一个异步复位。

flip_flop.v

module flip_flop
(
    input   wire    sys_clk     ,//50MHz
    input   wire    sys_rst_n   ,
    input   wire    key_in      ,
    
    output  reg     led_out
);

always@(posedge sys_clk or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        led_out <= 1'b0;
    else
        led_out <= key_in;

endmodule

代码修改完成之后保存,回到我们的实验工程,重新进行全编译。编译完成,点击 OK

20231029152552_usvfnzp5k0

那么看一下 RTL 视图

image-20231029152721754

那么使用异步复位综合出来的 RTL 视图只有一个寄存器,也就是一个 D 触发器。

通过对比同步复位和异步复位综合出的 D 触发器的 RTL 视图,我们可以发现:如果使用同步复位会多出一个选择器的结构,这样会消耗更多的逻辑资源,我们的 Altera 芯片推荐我们使用异步复位。

回到我们代码文件,我们将代码修改为使用同步复位。代码修改完成之后,进行保存

7JqeniMPFI

flip_flop.v

module flip_flop
(
    input   wire    sys_clk     ,//50MHz
    input   wire    sys_rst_n   ,
    input   wire    key_in      ,
    
    output  reg     led_out
);

always@(posedge sys_clk)// or negedge sys_rst_n)
    if (sys_rst_n == 1'b0)
        led_out <= 1'b0;
    else
        led_out <= key_in;

endmodule

2.5 逻辑仿真

然后编写仿真文件,对同步复位的代码进行一个仿真验证,查看一下它的波形。

那么首先是时间参数,然后模块开始、模块名,然后是端口列表、模块结束

XFV6JhIzWX

首先定义三路输入信号。

因为使用 initial 语句,所以说信号类型是 reg 型;然后会引出输出信号,使用 wire 型

20231029153319_f39o6rCqm5

然后是信号赋值。

首先是时钟信号,时钟信号的初值我们使用阻塞赋值,初值赋为高电平;然后是复位信号,复位信号使用非阻塞赋值,初值赋为低电平;然后是模拟按键,非阻塞赋值,这个初值高电平低电平都行,我们这儿赋值为低电平;这儿加一个 #,延迟 20 个时间单位,就是 20ns,然后将我们的复位信号拉高。这儿为什么要进行拉高操作呢?是因为我们的复位信号是低电平有效,先拉低了然后复位完成之后再拉高,系统才能正常工作;然后再延迟一段时间,比如说 210 个时间单位,就是 210ns,再将我们的复位信号拉低(这儿之所以选择 210ns,是因为能够使复位信号在时钟的下降沿进行复位,可以清晰的看出同步复位和异步复位的区别);然后再进行延时,再把复位信号拉高。这儿的操作是为了观察同步复位和异步复位的区别。复位释放之后,电路工作 210ns,然后再让复位有效,然后复位完成之后延时 40ns,再次释放复位

20231029154430_3QmBM4u6UB

生成了复位信号之后,下面模拟我们的系统时钟,我们这儿使用 always 语句产生时钟,让时钟每隔 10ns 反转一次,即一个时钟周期是 20ns,可以换算为 50MHz( f = 1 T , 1 20 ns = 50 MHz f=\frac{1}{T}\text{,} \frac{1}{20\text{ns}}=50\text{MHz} f=T1,20ns1=50MHz);模拟时钟生成完成之后,开始输入信号 key_in,我们使用随机数模拟按键的输入,always #20 key_in <= {$random} % 2; 这条语句采用的是求模取余法产生非负随机数 0 和 1,每隔 20ns 产生一次随机数。之所以选择 20ns 是因为在时序逻辑中,保证我们的按键信号变化的时间小于或等于时钟的周期,这样不会产生毛刺,利于波形的观察

20231029160130_27pCkLkBrS

下面就开始实例化

20231029160718_IOkzBzSBlU

为了便于观察,我们使用两个系统函数

20231029161113_73Jz8hWYYR

tb_flip_flop.v

`timescale 1ns/1ns

module tb_flip_flop();

reg     sys_clk;
reg     sys_rst_n;
reg     key_in;

wire    led_out;

initial
    begin
        sys_clk = 1'b1;
        sys_rst_n   <= 1'b0;
        key_in <= 1'b0;
        #20
        sys_rst_n   <= 1'b1;
        #210
        sys_rst_n   <= 1'b0;
        #40
        sys_rst_n   <= 1'b1;
    end

always #10 sys_clk = ~sys_clk;
always #20 key_in <= {$random} % 2;

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

flip_flop flip_flop_inst
(
    .sys_clk  (sys_clk  ),//50MHz
    .sys_rst_n(sys_rst_n),
    .key_in   (key_in   ),
    
    .led_out  (led_out  )
);

endmodule

这样仿真文件就编写完成,我们保存,回到我们的实验工程,加载我们的仿真文件;然后进行仿真设置

20231029161521_tXtwiZl4eL

设置完成之后开始仿真,点击 RTL Simulation 这个位置

20231029161647_HMhnms6ql9

2.6 波形对比

编译完成之后查看波形。点击 Restart,我们清除已存在的波形;参数设置为 500ns,运行一次,然后全局视图

20231029164905_wy7nUQf6OJ

我们来查看一下各信号波形变化。那么添加一个参考线,添加完参考线之后选中任一信号,比如说我们选择时钟信号,使用这个位置可以找到信号的下一个上升沿或者下降沿。首先来看一下时钟信号的频率,锁定我们第一条参考线,然后添加第二条参考线,选中时钟信号找到时钟信号的下一个上升沿。我们可以通过这里观察两个参考线之间是一个时钟周期,那么时钟周期是 20ns,可以换算为 50MHz,那么时钟信号的生成是正确的

20231029165525_yTv1WVjN8a

那么接下来看一下我们的复位信号。那么复位信号首先是低电平,在 20ns 之后进行了拉高,看这个位置是 20ns,没有问题。然后锁定这条参考线,使用第一条参考线,查找一下复位信号的下降沿,选中复位信号然后点击这个位置,两条参考线之间的时间间隔是 210ns,和我们编写的一样,查找下一个上升沿,两线之间的间隔是 250ns,那么减去刚才的 210ns,这一段是 40ns,复位信号的生成也是正确的

20231029165920_0GVWdTMaxc

接下来看一下输入信号和输出信号。输入信号是随机数产生。我们找一下复位信号无效时,也就是说复位信号为高电平时。选择一段,我们选择这一段,这一段复位信号是高电平,复位信号无效,输出信号由输入信号直接赋值,因为是时序逻辑,所以说输出信号延迟输入信号一拍,那么这儿是正确的;复位信号在这个位置拉低了,但是我们的输出信号并没有立刻受到影响,而是在下一个时钟上升沿受到了影响,到这个位置复位信号拉高,我们的输出信号也没有立刻受到影响,而在下一个时钟上升沿才受到影响,与我们绘制的波形图是一致的

20231029170227_e0nkWiLApT

我们回到 .v 文件,将同步复位改为异步复位,保存。回到我们的 ModelSim 仿真软件,找到 Library 这个选项,然后打开工作列表,选中我们修改的 .v 文件,对它进行重新的编译,然后打开第一个选项卡,这儿就提示我们编译已经完成了

20231029170450_P3j08sWd1M

回到我们的波形窗口,选择 Restart 按钮清除已存在波形,然后运行一次。在这个波形当中我们直接查看输入、输出信号。当复位信号无效时,我们的输出信号是滞后我们的输入信号一个时钟周期的,那么这儿是正确的;因为是异步复位,当我们的复位信号有效时我们的输出信号会立刻受到影响,当我们的复位信号无效时,我们的输出信号并不会立刻受到影响,而是要等到下一个时钟的上升沿,才会输出我们输入信号的值,这与我们绘制的波形图是一致的

20231029170745_0FvNt2HxQD

实战演练到这儿就结束了。我们使用的实验工程它的效果,与之前点亮 LED 灯它的效果是相同的,我们不再进行上板的验证。

在本小节当中,我们正式开始了时序逻辑的学习,而且对同步复位、异步复位在波形、代码编写、逻辑资源以及仿真波形上做了一个对比。


参考资料:

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

第十一讲-时序逻辑的开始—寄存器(一)

第十一讲-时序逻辑的开始—寄存器(二)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值