FPGA学习记录

一、语法

1、运算符

2、常用关键字


3、赋值语句

  • RHS:赋值等号右边的表达式或变量可以写作 RHS 表达式或 RHS 变量;
  • LHS: 赋值等号左边的表达式或变量可以写作 LHS 表达式或 LHS 变量;

a、阻塞赋值

在同一个always中,一条阻塞赋值语句如果没有执行结束,那么该语句后面的语句就不能被执行,即被“阻塞”。也就是说 always 块内的语句是一种顺序关系,这里和 C 语言很类似。符号“=”用于阻塞的赋值(如:b = a;), 阻塞赋值“=”在 begin 和 end 之间的语句是顺序执行,属于串行语句。

阻塞赋值的执行可以认为是只有一个步骤的操作,即计算 RHS 的值并更新 LHS,此时不允许任何其他语句的干扰,所谓的阻塞的概念就是值在同一个 always 块中,其后面的赋值语句从概念上来讲是在前面一 条语句赋值完成后才执行的。

b、非阻塞赋值

非阻塞赋值是由时钟节拍决定,在时钟上升到来时,执行赋值语句右边,然后将 begin-end 之间的所有赋值语句同时赋值到赋值语句的左边,注意:是 begin-end 之间的所有语句,一起执行,且一个时钟只执行一次,属于并行执行语句。

(1) 赋值开始的时候,计算 RHS;
(2) 赋值结束的时候,更新 LHS。
所谓的非阻塞的概念是指,在计算非阻塞赋值的 RHS 以及 LHS 期间,允许其它的非阻塞赋值语句同时计算 RHS 和更新 LHS。

c、总结

在描述组合逻辑电路的时候,使用阻塞赋值,比如 assign 赋值语句和不带时钟的 always 赋值语句,这种电路结构只与输入电平的变化有关系。

在描述时序逻辑的时候,使用非阻塞赋值,综合成时序逻辑的电路结构,比如带时钟的 always 语句;这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化

4、assign和always的区别

assign 语句和 always 语句是 Verilog 中的两个基本语句,这两个都是经常使用的语句。assign 语句使用时不能带时钟。always 语句可以带时钟, 也可以不带时钟。 在 always 不带时钟时,逻辑功能和 assign 完全一致,都是只产生组合逻辑。比较简单的组合逻辑推荐使用 assign 语句,比较复杂的组合逻辑推荐使用 always 语句。

5、带时钟和不带时钟的always

always 语句可以带时钟, 也可以不带时钟。 在 always 不带时钟时,逻辑功能和 assign 完全一致,虽然产生的信号定义还是 reg 类型,但是该语句产生的还是组合逻辑。

在 always 带时钟信号时,这个逻辑语句才能产生真正的寄存器,如下示例counter 就是真正的寄存器。

6、latch–锁存器

latch 是指锁存器,是一种对脉冲电平敏感的存储单元电路。锁存器和寄存器都是基本存储单元,锁存器是电平触发的存储器,寄存器是边沿触发的存储器。两者的基本功能是一样的,都可以存储数据。锁存器是组合逻辑产生的,而寄存器是在时序电路中使用,由时钟触发产生的。

latch 的主要危害是会产生毛刺(glitch),这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不易查出。因此,在设计中,应尽量避免 latch 的使用。

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

注意,只有不带时钟的 always 语句 if 或者 case 语句不完整才会产生 latch, 带时钟的语句 if或者 case 语句不完整描述不会产生 latch。

7、状态机

  • Mealy 状态机:组合逻辑的输出不仅取决于当前状态,还取决于输入状态。
  • Moore 状态机:组合逻辑的输出只取决于当前状态。

a、Mealy状态机

米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F, F 是当前状态和输入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由一组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的, G 也是当前状态和输入信号的函数。

b、Moore状态机

摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。

c、三段式状态机

使用三个 always 模块,一个 always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。

三段式状态机的基本格式是:
第一个 always 语句实现同步状态跳转;
第二个 always 语句采用组合逻辑判断状态转移条件;
第三个 always 语句描述状态输出(可以用组合电路输出,也可以时序电路输出)。

8、模块化设计

在进行模块化设计中,对于复杂的数字系统,我们一般采用自顶向下的设计方式。可以把系统划分成几个功能模块,每个功能模块再划分成下一层的子模块;每个模块的设计对应一个 module,一个 module 设计成一个 Verilog 程序文件。因此,对一个系统的顶层模块,我们采用结构化的设计,即顶层模块分别调用了各个功能模块。

下图是模块化设计的功能框图,一般整个设计的顶层模块只做例化(调用其它模块),不做逻辑。顶层下面会有模块 A、模块 B、模块 C 等,模块 A/B/C 又可以分多个子模块实现。

a、模块例化

FPGA 逻辑设计中通常是一个大的模块中 包含了一个或多个功能子模块, Verilog 通过模块调用或称为模块实例化的方式来实现这些子模块与高层模 块的连接, 有利于简化每一个模块的代码,易于维护和修改。

上图右侧是例化的数码管静态显示模块,子模块名是指被例化模块的模块名,而例化模块名相当于标识,当例化多个相同模块时,可以通过例化名来识别哪一个例化,我们一般命名为“u_”+“子模块名”。信号列表中“.”之后的信号是数码管静态显示模块定义的端口信号,括号内的信号则是顶层模块声明的信号,这样就将顶层模块的信号与子模块的信号一一对应起来,同时需要注意信号的位宽要保持一致。

b、参数例化

参数的例化是在模块例化的基础上,增加了对参数的信号定义,如下图所示:

在对参数进行例化时,在模块名的后面加上“#”,表示后面跟着的是参数列表。计时模块定义的MAX_NUM 和顶层模块的 TIME_SHOW 都是等于 25000_000,当在顶层模块定义 TIME_SHOW=12500_000时,那么子模块的 MAX_NUM 的值实际上也等于 12500_000。当然即使子模块包含参数,在做模块的例化时也可以不添加对参数的例化,这样的话,子模块的参数值等于该模块内部实际定义的值。值得一提的是, Verilog 语法中的 localparam 代表的意思同样是参数定义,用法和 parameter 基本一致,区别在于 parameter 定义的参数可以做例化,而 localparam 定义的参数是指本地参数,上层模块不可以对localparam 定义的参数做例化。