FPGA 入门 —— 代码规范与模块结构

FPGA 入门 —— 代码规范与模块结构

不可综合或不推荐使用的代码

代码 要求
initial 严谨在设计中使用,只能在测试文件中使用
task/function 不推荐在设计中使用,在测试文件中使用
for 在设计中、测试文件中均可以使用,但在设计中多数会将其用错,所以建议在初期设计时不使用,熟练后按规范使用
while/repeat/forever 严禁在设计文件中使用,只能在测试文件中使用
integer 不推荐在设计中使用
三态门 内部模块不能有三态接口,三态门只有顶层文件才使用,三态门目的时为了节省管脚,FPGA 内部完全没有必要使用
casex/casez 设计代码内部不能有 X 态和 Z 态,因此 casez,casex 设计时不使用
force/wait/fork 严禁在设计中使用,只能在测试文件中使用
#n 严禁在设计中使用,只能在测试文件中使用

推荐使用的设计代码

代码 备注
reg/wire 设计中所有的信号类型定义,只有 reg 和 wire 两种
parmeter 设计代码中所有的位宽、长度、状态机命名等,建议都用参数表示,阅读方便并且修改容易
assign/always 程序块主要部分,至简设计法对 always 使用有严格规范
组合逻辑格式为:
always@(*)begin
代码语句
end
或者使用 assign
时序逻辑格式为:
always@(posedge clk or negedgerst_n) begin
if(rst_n == 1'b0)begin
代码语句:
end
else begin
代码语句
end
end
end
时序逻辑中,敏感列表一定时 clk 的上升沿和复位的下降沿、最开始必须判读复位
if else 和 case always 里面的语句,使用 if else 和 case 两种方法用来作选择判断,可以完成全部设计
算数运算符(+,-,x,/,%) 可以直接综合出相对应的电路。但除法和求余运算的电路面积一般比较大,不建议直接使用除法和求余
赋值运算符(=,<=) 时许逻辑用“<=”,组合逻辑用“=”,其他情况不存在
关系运算符(==,!,=,>,<,>=,<=)
逻辑运算符(&&,
位运算符(~, ,^,&)
位移运算符(<,>,>>) fpga常用运算符
拼接运算符({}) fpga常用运算符

模块结构

模块时 Verilog 的基本描述单位,是用于描述某个设计的功能或结构及与其他模块通信的外部端口,采用模块化设计让程序看起来更有提条理性,也便于仿和调试

模块就等同于一个器件,就如同调用通用器件(与门,三态门等),或通用宏单元(计数器、ALU、CPU 等),一个模块可以在另一个模块中进行调用,一个电路可以由多个模块组合而成

整个项目的设计思路就是模块套模块,自顶向下依次展开,在一个工程设计里,每个模块实现特定的功能,模块间可进行层次的嵌套,对大型的数字电路进行设计时,可以将其分割成大小不一的小模块,每个小模块实现特定的功能,最后通过由顶层模块调用子模块的方式来实现整体功能,这就是 Top-Down 思想

模块主要有五个部分:

  • 端口定义

  • 参数定义(可选)

  • I/O 说明

  • 内部信号声明

  • 功能定义

模块总是以关键词 module 开始,以关键词 endmodule 结尾,一般语法结构如下:

注意:这里的信号宽度都是定义为 [width-1 : 0] ,这是由于如果我们使用的是 8 位输入,那么我们的输入一般是 0-7 ,刚好是 8 位,所以这里是这样定义的

这里的端口定义、单数定义、信号定义都比较好理解,这里主要介绍信号类型和功能定义

信号类型

reg

reg[width-1 : 0]:reg 型表示的寄存器类型,用于 always 模块内被赋值的信号,必须定义为 reg 型,reg 表示一定要有触发,输出才会反映输入的状态。根据触发条件的不同,模块可以建模不同的硬件结构:如果这个条件是时钟的上升沿或下降沿,那么这个硬件模型就是一个触发器(时序逻辑);如果这个条件是某一信号的高电平或低电平,那么这个硬件模型就是一个锁存器(组合逻辑);如果这个条件是赋值语句右侧任意操作数的变化,那么这个硬件模型就是一个组合逻辑。

reg 型相对复杂些,其综合后的输出主要还看具体使用的场景:当在组合电路中使用 reg,合成后的仍然是 net 网络;当在时序电路中使用 reg 合成后的才是 register。

wire

wire[width-1 : 0]:wire 的本质是一条没有逻辑的连线,也就是说输入时什么输出也就是什么。wire 型数据常用来表示以 assign 关键字指定的组合逻辑。输入输出端口类型都默认为 wire 型,wire 相当于物理连线,默认初始值是 z(高组态)

如果你把 wire 定义的变量用在时序逻辑的语句中就会出现综合错误:

例如:

在 always 语句中使用 wire 型定义的变量赋值,综合器就会报错。

reg 与 wire 对比

从仿真分析角度来说

wire 对应于连续赋值,如 assign

reg 对应于过程赋值,如 always,initial

  1. wire 型数据常用来表示以 assign 关键字指定的组合逻辑信号,模块的输入输出端口类型都默认为 wire 型,wire 相当于物理连线,默认初始值是 z,reg 型数据表示寄存器模型,用于 always 块、initial 语句中被赋值的变量

  2. wire 使用在连续赋值语句中,reg 用在过程赋值语句(always、initial)中

  3. wire 若无驱动器连接其值为z,reg 默认初始值为不定值 x

  4. wire 表示直通,即输入有变化,输出马上无条件地反映(如与、非门的简单连接),reg 表示一定要有触发,输出才会反映输入的状态

  5. wire 一般用在组合逻辑中,reg 一般用在时序逻辑中

  6. reg 变量在 always 中有两种情况:

  • always @(a or b or c)形式的,即不带时钟边沿的,综合出来还是组合逻辑;

  • always @(posedge clk)形式的,即带有边沿的,综合出来一般是时序逻辑,会包含触发器(Flip-Flop)

  1. always 块不止能实现时序逻辑,还能实现组合逻辑:
  • 如果这个条件是时钟上升沿或下降沿,那硬件模型就是一个触发器,只有是指定了 always@(posedge or negedge)才是触发器

  • 如果这个条件是某一信号的高低电平,那这个硬件模型就是一个锁存器

  • 如果这个条件是赋值语句右侧任意操作数的变化,那这个硬件模型就是一个组合逻辑

  1. 对组合逻辑输出变量,可以直接用 assign。即如果不指定为 reg 类型,那么就默认为 1 位 wire 类型,故无需指定 1 位 wire 类型的变量。当然专门指定出 wire 类型,可能是多位或为使程序易读

  2. reg 型数据保持最后一次的赋值,而 wire 型数据需要持续的驱动

在连续赋值语句 assign 中,表达式右侧的计算结果可以立即更新到表达式的左侧,可以理解为逻辑之后直接连接了一条线,这个逻辑对应于表达式的右侧,这条线对应于 wire

在过程赋值语句中,表达式右侧的计算结果在某种条件的触发下放到一个变量当中,这个变量可以声明成 reg 型

  1. reg和wire类似于C、C++的变量,但若此变量要放在begin...end之内,则该变量只能是reg型;在begin...end之外,则用wire型

什么时候使用 reg 或 wire ?

  1. assign 语句中变量需要定义成 wire 型,使用 wire 必须搭配 assign

  2. 元件例化时候的输出必须用 wire

  3. input、output 和 inout 的预设值都是 wire

  4. 变量放在 begin……end 之内必须使用 reg 变量

  5. 在 initial 语句中使用

功能描述

全部由 always 、 assign 和例化模块组成,assign ,连续赋值,always ,敏感赋值

连续赋值,就是无条件全等,敏感赋值,就是有条件相等,assign 的对象是 wire ,always 的对象是 reg ,这就是语法约束

功能差异

assign 对应电路下连线操作。always 对应插⼊敏感控制连线。这⾥容易混淆的就是 assign 综合的⼀定是组合电路,但是 always 综合的不⼀定是时序电路。always 的敏感列表使⽤*号就可以等效综合为组合电路。如果使⽤的是电平触发,也使会综合成相应的组合电路。只有出现边沿触发时,才会综合成时序电路。⽽时序电路,基础就是时钟和使能两个关键信号。时钟在 always 模块中不再出现(时钟信号在敏感信号作⽤下的值⼀直相等,没有使⽤在内部的意义,当然可以转化后使⽤)。⽽对于使能信号,则是会有⼀个 if 判断语句,⽽且处于第⼀优先级。这就是异步复位。如果不在敏感列表⽽处于第⼀优先级,则是同步复位信号

从上⾯的描述可以看到: always 可以实现两种电路,是不是可以不⽤ assign 来实现设计?理论上可以,但是会加⼤设计的难度。⾸先要明确的是,always 只能对 reg 变量赋值,这导致 wire 变量赋值困难。如果没有 assign,每个 wire 变量都要加⼊⼀个 reg 缓冲。可以简单理解:开始时只有 always,可以实现基本的功能,然后将其中的组合逻辑提取出来构成 assign。同时引⼊了 wire。也就是 assign 是 always 的补充(只是⽅便理解,没有根据)

主要就是一下几点:

assign 语句使用时不能带时钟

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

带时钟和不带时钟的 always:

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

在 always 带时钟信号时,这个逻辑语句才能产生真正的寄存器

猜你喜欢

转载自blog.csdn.net/m0_59161987/article/details/129434751