第三章 硬件描述语言verilog(三)功能描述-时序逻辑

第六节 功能描述-时序逻辑

6.1 always语句

时序逻辑的代码一般有两种:同步复位的时序逻辑和异步复位的时序逻辑。在同步复位的时序逻辑中复位不是立即有效,而在时钟上升沿时复位才有效。其代码结构如下:

always@(posedge clk) begin
if(rst_n==1’b0)
代码语句;
else begin
代码语句;
end 
end

在异步复位的时序逻辑中复位立即有效,与时钟无关。其代码结构如下:

always@(posedge clk or negedge rst_n) begin
if(rst_n==1’b0)
代码语句;
else begin
代码语句;
end
end

针对时序逻辑的 verilog 设计提出以下建议:
为了教学的方便代码统一采用异步时钟逻辑,建议同学们都采用此结构,这样设计时只需考虑是用时序逻辑还是组合逻辑结构来进行代码编写即可。在实际工作中请遵从公司的相应规范进行代码设计。
使用 GVim 软件打开代码后,输入“Zuhe”命令后回车可得到组合逻辑的代码结构,输入“Shixu”命令后回车可得到时序逻辑的代码结构。
没有复位信号的时序逻辑代码设计是不规范的,建议不要这样使用。

6.2 D触发器

数字电路中介绍了多种触发器,如 JK 触发器、D 触发器、RS 触发器、T 触发器等。在 FPGA中使用的是最简单的触发器——D 触发器

6.2.1 D触发器结构

在这里插入图片描述图 1.3- 37 是 D 触发器的结构图,可以将其视为一个芯片,该芯片拥有 4 个管脚,其中 3 个是输入管脚:时钟 clk、复位 rst_n、信号 d;1 个是输出管脚:q。
该芯片的功能如下:当给管脚 rst_n 给低电平(复位有效),即赋值为 0 时,输出管脚 q 处于低电平状态。如果管脚 rst_n 为高电平,则观察管脚 clk 的状态,当 clk 信号由 0 变 1 即处于上升沿的时候,将此时 d 的值赋给 q。若 d 是低电平,则 q 也是低电平;若 d 是高电平,则 q 也是高电平。

6.2.2 D触发器波形

在这里插入图片描述
图 1.3- 38 为 D 触发器的功能波形图,该波形图反映了 D 触发器各个信号的变化情况,从左到右表示时间的走势。从图中可以看到时钟信号有规律地进行高低变化。
按照从左向右的顺序观察波形图可以发现:开始状态下,rst_n 等于 1,d 等于 0,q 等于 1

 随后 rst_n 由 1 变 0,此时输出信号 q 立即变成 0。对应的功能是:当给管脚 rst_n 低电平,也就是赋值为 0 时,输出管脚 q 处于低电平状态。
 在 rst_n 为 0 期间,即使在有时钟或信号 d 发生变化的情况下 q 仍然保持为低电平。
 在 rst_n 由 0 变成 1 撤消复位后,q 没有立刻发生变化。
 在第 4 个时钟上升沿时,此时 rst_n 等于 1,而 d 等于 1,因此 q 变成了 1。
 第 5 个时钟上升沿,仍然是同样情况,rst_n=1,d=1,因此 q=1。
 在第 6 个时钟上升沿,rst_n=1,d=0,因此 q=0。
 第 7~10 个时钟沿也是按同样方式判断。对应的功能是:如果管脚 rst_n 为高电平,则观察管脚 clk,在 clk 由 0 变 1 即上升沿的时候,将现在 d 的值赋给 q。若 d 是低电平,q 也是低电平;若 d 是高电平,q也是高电平。

6.2.3 D 触发器代码

首先,观察如下这段时序逻辑的代码:
在这里插入图片描述
从语法上分析该段代码的功能为:该段代码总是在“时钟 clk 上升沿或者复位 rst_n 下降沿”的时候执行一次。具体执行方式如下:

  1. 如果复位 rst_n=0,则 q 的值为 0;
  2. 如果复位 rst_n=1,则将 d 的值赋给 q(注意,前提条件是时钟上升沿的时候)。
    上例的功能与本案例的功能是相同的:当给管脚 rst_n 给低电平,也就是赋值为 0 时,输出管脚q 就处于低电平状态。如果管脚 rst_n 为高电平则观察管脚 clk,在 clk 由 0 变 1 即上升沿的时候,将现在 d 的值赋给 q,d 是低电平,q 也是低电平,d 是高电平,q 也是高电平。
    因此可以看出这段代码的功能与 D 触发器的功能是一样的,即该代码其实就是在描述一个 D 触发器,也就是 D 触发器的代码。前文中已经讲过在 FPGA 设计中可以用原理图的形式来设计,也可以用硬件描述语言来设计。
    当用原理图来设计时几个 D 触发器还可以忍受,但如果出现几千几万个 D 触发器则必定是头晕眼花,而用硬件描述语言 Verilog 则不存在这一问题。
6.2.4 怎么看FPGA波形

下面来讨论如下图所示的波形,先观察在第 4 个时钟上升沿的时刻,思考一下此时看到的信号 q的值是多少?是 0 还是 1?或者观察到的是 q 的上升沿?
在这里插入图片描述
首先明确一点:Verilog 代码对应的是硬件,因此应该从硬件的角度来分析这个问题。再来理清一下代码的因果关系:先有时钟上升沿,此为因,然后再将 d 的值赋给 q,这才是结果。这个因果是有先后关系的,对于硬件来说这个“先后”无论是多么地迅速,也一定会占用一定时间,所以 q 的变化会稍后于 clk 的上升沿。例如下图就是硬件的实际变化情况。
在这里插入图片描述
图 1.3- 40 中就很容易看出,第 4 个时钟上升沿时刻对应的 q 值为 0,也就是变化前的值。上面的波形虽然更将近于实际,但这样画图使这一过程非常复杂,且非必要操作。因此建议只需掌握这种看波形规则,即时钟上升沿看信号,是看到变化之前的值。
所以第 4 个时钟上升沿时,看到 q 值为 0;在第 6 个时钟上升沿时,看到 q 值为 1;在第 7 个时钟上升沿时,看到 q 值为 0;在第 8 个时钟上升沿时,看到 q 值为 1;在第 10 个时钟上升沿时,看到 q 值为 0。注意一下,复位信号是在系统开始时刻或者出现异常时才使用,一般上电后就不会再次进行复位,也可以认为复位是一种特殊情况。
下面考虑正常使用的情况:**无论是从功能上还是波形上,都可以看到信号 q 只在时钟上升沿才变化,而绝对不会在中间发生变化。在一般的数字系统中大部分信号之间的传递都是在同一个时钟下进行的,即大部分都是同步电路。跨时钟的电路占比非常小,属于特殊的异步电路。**在本教材中,如果没有提前说明所有的案例、练习都默认为同步电路。
下面具体分析每个时钟下 q 信号的情况:
在 rst_n 由 1 变 0 时,q 立刻变成 0。
在第 2 个时钟上升沿,看到 rst_n 为 0。按代码功能,q 仍然为 0。
在第 3 个时钟上升沿,看到 rst_n 为 0。按代码功能,q 仍然为 0。
在第 4 个时钟上升沿,看到 rst_n 为 1,d 值为 1,q 值为 0。按代码功能,q 变成 1。
在第 5 个时钟上升沿,看到 rst_n 为 1,d 值为 1,q 值为 1。按代码功能,q 变成 1。
在第 6 个时钟上升沿,看到 rst_n 为 1,d 值为 0,q 值为 1。按代码功能,q 变成 0。
在第 7 个时钟上升沿,看到 rst_n 为 1,d 值为 1,q 值为 0。按代码功能,q 变成 1。
在第 8 个时钟上升沿,看到 rst_n 为 1,d 值为 0,q 值为 1。按代码功能,q 变成 0。
在第 9 个时钟上升沿,看到 rst_n 为 1,d 值为 0,q 值为 0。按代码功能,q 变成 0。
在第 10 个时钟上升沿,看到 rst_n 为 1,d 值为 1,q 值为 0。按代码功能,q 变成 1。

6.3 时钟

时钟信号是每隔固定时间上下变化的信号。本次上升沿和上一次上升沿之间占用的时间就是时钟周期,其倒数为时钟频率。高电平占整个时钟周期的时间,被称为占空比。
FPGA 中时钟的占空比一般是 50%,即高电平时间和低电平时间一样。其实占空比在 FPGA 内部没有太大的意义,因为 FPGA 使用的是时钟上升沿来触发,设计师们更加关心的是时钟频率。
如果时钟的上升沿每秒出现一次,说明时钟的时钟周期为 1 秒,时钟频率为 1Hz。如果时钟的上升沿每 1 毫秒出现一次,说明时钟的时钟周期为 1 毫秒,时钟频率为 1000Hz,或写成 1kHz。
现在普通 FPGA 器件所支持的时钟频率范围一般不超过 150M,高端器件一般不超过 700M(注意,该值为经验值,实际时钟的频率与其具体器件和设计电路有关),所对应的时钟周期在纳秒级范围。因此在本教材中所有案例的时钟频率一般选定范围是几十至一百 M 左右。
下面列出本教材常用到的时钟频率以及所对应的时钟周期,方便进行换算。
在这里插入图片描述
时钟是 FPGA 中最重要的信号,其他所有信号在时钟的上升沿统一变化,这就像军队里的令旗, 所有军队在看到令旗到来的时刻执行已经设定好的命令。
时钟这块令旗影响着整体电路的稳定。首先,时钟要非常稳定地进行跳动。就如军队令旗,如果时快时慢就会让人无所适从,容易出错。而如果令旗非常稳定,每个人都知道令旗的指挥周期,就可以判断令旗到来前是否可以完成任务,如果无法完成则进行改正(修改代码),从而避免系统出错。其次,一个高效的军队中令旗越少越好,如果不同部队对标不同的令旗,那么部队协作就容易出现问题,整个军队无法高效的完成工作,容易出现错误。同样的道理,FPGA 系统的时钟必定是越少越好,最好只存在一个时钟。以上就是要求不要把信号放在时序逻辑敏感列表的原因。

6.4 时序逻辑代码和硬件

先来分析一下下面这段代码:
在这里插入图片描述
仍然从语法上分析该段代码的功能。该段代码总是在“时钟 clk 上升沿或者复位 rst_n 下降沿”的时候执行一次。具体执行方法如下:

  1. 如果复位 rst_n=0,则 q 的值为 0;
  2. 如果复位 rst_n=1,则将(a+d)的结果赋给 q(注意,前提条件是时钟上升沿的时候)。
    假设用信号 c 表示 a+d 的结果,则第 2 点可改为:如果复位 rst_n=1,则将 c 的值赋给 q(注意,前提条件是时钟上升沿的时刻)。很明显这是一个 D 触发器,输入信号为 d,输出为 q,时钟为 clk,复位为 rst_n,其电路示意图如下图所示:
    在这里插入图片描述
    可知 c 是 a+d 的结果,因此其自然是通过一个加法器实现,画出上面代码所对应的电路结构图,可以看出在 D 触发器的基础上增加了一个加法器。
    在这里插入图片描述
    很容易分析出上面电路的功能:信号 a 和信号 b 相加得到 c,c 连到 D 触发器的输入端。当 clk出现上升沿时,将 c 的值传给 q。这与代码功能是一致的。下面是代码和硬件所对应的波形图。
    在这里插入图片描述先看信号 c 的波形:c 的产生只有与 a 和 d 有关,与 rst_n 和 clk 无关。c 是 a+d 的结果,按照二进制加法:0+0=0,0+1=1,1+1=0 可以画出 c 的波形。

在第 1 个时钟期间,a=0,d=0,所以 c=0+0=0;
在第 2 个时钟期间,a=1,d=0,所以 c=1+0=1;
在第 3 个时钟期间,a=1,d=1,所以 c=1+1=0;
在第 4 个时钟期间,a=0,d=1,所以 c=0+1=1;
在第 5 到第 6 个时钟期间,a=0,d=0,所以 c=0+0=0;
在第 7 个时钟期间,a=1,d=1,所以 c=1+1=0;
在第 8 个时钟期间,a=0,d=1,所以 c=0+1=1;
在第 9 个时钟期间,a=0,d=0,所以 c=0+0=0;
在第 10 个时钟期间,a=0,d=1,所以 c=0+1=1。
再看信号 q 的波形:q 是 D 触发器的输出,其只在 rst_n 的下降沿或者 clk 的上升沿才变化,其他时刻不变化,即 a、d、c 发生变化时,q 不会立刻发生改变。
在这里插入图片描述下面具体分析每个时钟下 q 信号的情况:
在 rst_n 由 1 变 0 时,q 立刻变成 0。
在第 2 个时钟上升沿,看到 rst_n 为 0。按代码功能,q 仍然为 0。
在第 3 个时钟上升沿,看到 rst_n 为 0。按代码功能,q 仍然为 0。
在第 4 个时钟上升沿,看到 rst_n 为 1,c 值为 0,q 值为 0。按代码功能,q 变成 0;
在第 5 个时钟上升沿,看到 rst_n 为 1,c 值为 1,q 值为 0。按代码功能,q 变成 1;
在第 6 个时钟上升沿,看到 rst_n 为 1,c 值为 0,q 值为 1。按代码功能,q 变成 0;
在第 7 个时钟上升沿,看到 rst_n 为 1,c 值为 0,q 值为 0。按代码功能,q 变成 0;
在第 8 个时钟上升沿,看到 rst_n 为 1,c 值为 0,q 值为 0。按代码功能,q 变成 0;
在第 9 个时钟上升沿,看到 rst_n 为 1,c 值为 1,q 值为 0。按代码功能,q 变成 1;
在第 10 个时钟上升沿,看到 rst_n 为 1,c 值为 0,q 值为 1。按代码功能,q 变成 0;
在第 11 个时钟上升沿,看到 rst_n 为 1,c 值为 1,q 值为 0。按代码功能,q 变成 1。
在讨论时序逻辑的加法器时 对加法器的输出 c 和 D 触发器的输出 q 分开进行讨论,就像两块独立的电路。同样的道理,在设计 Verilog 代码时也可以将其分开来进行编写。先将下面的硬件电路用 Verilog 描述出来
在这里插入图片描述
该电路对应的电路可以写成:
在这里插入图片描述上面的两段代码,都是描述同一加法器硬件电路。接着用 Verilog 对触发器进行描述:

在这里插入图片描述
其代码的写法如下:
在这里插入图片描述
最后可以看到,两段代码都有信号 c,说明这两段代码是相连的,利用硬件连接起来可以变成如
下图所示的电路。
在这里插入图片描述
由此可见,下面两段代码所对应的硬件电路是一模一样的。
在这里插入图片描述
那么这两种代码哪一种比较好呢?答案是这两段代码并无区别,因为两者的硬件是相同的。由此也可以得知评估 verilog 代码好坏的最基本标准,即不是看代码行数而是看硬件。

6.5 阻塞语句和非阻塞赋值

在 always 语句块中,Verilog 语言支持两种类型的赋值:阻塞赋值和非阻塞赋值。阻塞赋值使用“=”语句;非阻塞赋值使用“<=”语句。
阻塞赋值:在一个“begin…end”的多行赋值语句,先执行当前行的赋值语句,再执行下一行的赋值语句。
非阻塞赋值:在一个“begin…end”的多行赋值语句,在同一时间内同时赋值。
在这里插入图片描述
上面两个例子中,1 到 4 行部分是阻塞赋值,程序会先执行第 2 行,得到结果后再执行第 3 行。6 至 9 行这一段是非阻塞赋值,第 7 行和第 8 行的赋值语句是同时执行的。
具体分析一下这两段代码这件的区别:假设当前 c 的值为 0,d 的值为 0,a 的新值为 1。
阻塞赋值的执行过程和结果为:程序先执行第 2 行,此时 c 的值将更新为 1,然后再执行 3 行,此时 c+a 也就是相当于 1+1=2,即 d 的值为 2。

非阻塞赋值的执行过程和结果为:程序同时执行第 7 行和 8 行。需要特别注意是,在执行第 8行的时候,第 7 行还并未执行,这也就意味着 c 的值还没有发生变化,即此时 c 的值为 0。同时执行的结果是,c 的值为 1,d 的值为 1。

根据规范要求,组合逻辑中应使用阻塞赋值“=”,时序逻辑中应使用非阻塞赋值“<=”。可以将这个规则牢牢记住,按照这一规则进行设计绝对不会发生错误。制定这个规范的原因并不是考虑语法需要,而是为了正确的进行硬件描述。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Verilog HDL 硬件描述语言 目 录 译者序 前言 第1章 简介 1 1.1 什么是Verilog HDL? 1 1.2 历史 1 1.3 主要能力 1 第2章 HDL指南 4 2.1 模块 4 2.2 时延 5 2.3 数据流描述方式 5 2.4 行为描述方式 6 2.5 结构化描述形式 8 2.6 混合设计描述方式 9 2.7 设计模拟 10 第3章 Verilog语言要素 14 3.1 标识符 14 3.2 注释 14 3.3 格式 14 3.4 系统任务和函数 15 3.5 编译指令 15 3.5.1 `define和`undef 15 3.5.2 `ifdef、`else 和`endif 16 3.5.3 `default_nettype 16 3.5.4 `include 16 3.5.5 `resetall 16 3.5.6 `timescale 16 3.5.7 `unconnected_drive和 `nounconnected_drive 18 3.5.8 `celldefine 和 `endcelldefine 18 3.6 值集合 18 3.6.1 整型数 18 3.6.2 实数 19 3.6.3 字符串 20 3.7 数据类型 20 3.7.1 线网类型 20 3.7.2 未说明的线网 23 3.7.3 向量和标量线网 23 3.7.4 寄存器类型 23 3.8 参数 26 第4章 表达式 28 4.1 操作数 28 4.1.1 常数 28 4.1.2 参数 29 4.1.3 线网 29 4.1.4 寄存器 29 4.1.5 位选择 29 4.1.6 部分选择 29 4.1.7 存储器单元 30 4.1.8 函数调用 30 4.2 操作符 30 4.2.1 算术操作符 31 4.2.2 关系操作符 33 4.2.3 相等关系操作符 33 4.2.4 逻辑操作符 34 4.2.5 按位操作符 35 4.2.6 归约操作符 36 4.2.7 移位操作符 36 4.2.8 条件操作符 37 4.2.9 连接和复制操作 37 4.3 表达式种类 38 第5章 门电平模型化 39 5.1 内置基本门 39 5.2 多输入门 39 5.3 多输出门 41 5.4 态门 41 5.5 上拉、下拉电阻 42 5.6 MOS开关 42 5.7 双向开关 44 5.8 门时延 44 5.9 实例数组 45 5.10 隐式线网 45 5.11 简单示例 46 5.12 2-4解码器举例 46 5.13 主从触发器举例 47 5.14 奇偶电路 47 第6章 用户定义的原语 49 6.1 UDP的定义 49 6.2 组合电路UDP 49 6.3 时序电路UDP 50 6.3.1 初始化状态寄存器 50 6.3.2 电平触发的时序电路UDP 50 6.3.3 边沿触发的时序电路UDP 51 6.3.4 边沿触发和电平触发的混合行为 51 6.4 另一实例 52 6.5 表项汇总 52 第7章 数据流模型化 54 7.1 连续赋值语句 54 7.2 举例 55 7.3 线网说明赋值 55 7.4 时延 55 7.5 线网时延 57 7.6 举例 57 7.6.1 主从触发器 57 7.6.2 数值比较器 58 第8章 行为建模 59 8.1 过程结构 59 8.1.1 initial 语句 59 8.1.2 always语句 61 8.1.3 两类语句在模块中的使用 62 8.2 时序控制 63 8.2.1 时延控制 63 8.2.2 事件控制 64 8.3 语句块 65 8.3.1 顺序语句块 66 8.3.2 并行语句块 67 8.4 过程性赋值 68 8.4.1 语句内部时延 69 8.4.2 阻塞性过程赋值 70 8.4.3 非阻塞性过程赋值 71 8.4.4 连续赋值与过程赋值的比较 72 8.5 if 语句 73 8.6 case语句 74 8.7 循环语句 76 8.7.1 forever 循环语句 76 8.7.2 repeat 循环语句 76 8.7.3 while 循环语句 77 8.7.4 for 循环语句 77 8.8 过程性连续赋值 78 8.8.1 赋值—重新赋值 78 8.8.2 force与release 79 8.9 握手协议实例 80 第9章 结构建模 83 9.1 模块 83 9.2 端口 83 9.3 模块实例语句 83 9.3.1 悬空端口 84 9.3.2 不同的端口长度 85 9.3.3 模块参数值 85 9.4 外部端口 87 9.5 举例 89 第10章 其他论题 91 10.1 任务 91 10.1.1 任务定义 91 10.1.2 任务调用 92 10.2 函数 93 10.2.1 函数说明部分 93 10.2.2 函数调用 94 10.3 系统任务和系统函数 95 10.3.1 显示任务 95 10.3.2 文件输入/输出任务 97 10.3.3 时间标度任务 99 10.3.4 模拟控制任务 99 10.3.5 定时校验任务 100 10.3.6 模拟时间函数 101 10.3.7 变换函数 102 10.3.8 概率分布函数 102 10.4 禁止语句 103 10.5 命名事件 104 10.6 结构描述方式和行为描述方式的 混合使用 106 10.7 层次路径名 107 10.8 共享任务和函数 108 10.9 值变转储文件 110 10.9.1 举例 111 10.9.2 VCD文件格式 112 10.10 指定程序块 113 10.11 强度 114 10.11.1 驱动强度 114 10.11.2 电荷强度 115 10.12 竞争状态 116 第11章 验证 118 11.1 编写测试验证程序 118 11.2 波形产生 118 11.2.1 值序列 118 11.2.2 重复模式 119 11.3 测试验证程序实例 123 11.3.1 解码器 123 11.3.2 触发器 124 11.4 从文本文件中读取向量 126 11.5 向文本文件中写入向量 127 11.6 其他实例 128 11.6.1 时钟分频器 128 11.6.2 阶乘设计 130 11.6.3 时序检测器 132 第12章 建模实例 136 12.1 简单元件建模 136 12.2 建模的不同方式 138 12.3 时延建模 139 12.4 条件操作建模 141 12.5 同步时序逻辑建模 142 12.6 通用移位寄存器 145 12.7 状态机建模 145 12.8 交互状态机 147 12.9 Moore有限状态机建模 150 12.10 Mealy型有限状态机建模 151 12.11 简化的21点程序 153 附录 语法参考 157 参考文献 172

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值