以下内容学习自黑金 FPGA 教程以及正点原子 FPGA 教程。
目录
一、数据类型
1.1 常量
-
0 和 1 :0 表示低电平,即对应电路的 GND;1 表示高电平,即对应电路的 VCC。
-
x 和 z :x 代表不定值,有可能是低电平也有可能是高电平;z 代表高阻值,外部没有激励信号,是一个悬空状态。
5‘b00x11,第三位不定值;3’b00z,最低位为高阻值。
- 整数:可用二进制 b 或 B,八进制 o 或 O,十进制 d 或 D,十六进制 h 或 H 表示。
8‘b00001111 表示 8 位位宽的二进制整数,4’ha 表示 4 位位宽的十六进制整数。
- 下划线:仅在位数过长时用来分割位数,提高程序可读性。
前面的 8‘b00001111 可以写成 8‘b0000_1111。
- 标识符:用于定义模块名、端口名和信号名等(这东西存在于任何描述语言中)。同样是必须以字母或者下划线开头,且区分大小写。
含义清晰、简洁易懂的标识符,clk_50,clk_cpu,sdram_addr。
- 参数 parameter:parameter 可以用标识符定义常量,运用时只使用标识符即可。
定义 parameter width = 8; 又定义寄存器 reg [width - 1 : 0] a; 就是定义了 8 位宽度的寄存器
parameter 就和 C 语言中的函数形参类似,是支持传递参数和修改参数值的,具体形式记录在后面,这里先以增加认知为主。
1.2 变量
-
Wire型:也叫网络类型变量,用于结构实体之间的物理连接,如门与门之间,不能储存值,其值由驱动它的元件所决定,用连续赋值语句 assign 赋值,定义为 wire [n - 1 : 0] a; 其中 n 代表位宽,如定义 wire a; assign a = b; 是将 b 的结点连接到连线 a 上。
-
Reg型:也叫寄存器变量,可用来储存值,其默认初始值为不定值 x,只能在 always 语句里和 initial 语句中被赋值。其定义为 reg [n - 1 : 0] a; 表示 n 位位宽的寄存器。对于 reg 的赋值过程的描述,如果是时序逻辑,则 always 语句中带有时钟信号,该 reg 变量对应为触发器;如果是组合逻辑,则 always 语句中不带时钟信号,该 reg 变量对应为硬件连线。比如下面的 reg q 就是 D 触发器的输出,reg Mux 则是数据选择器的输出。
-
Memory型:用来定义 RAM,ROM 等存储器,其结构为 reg [n - 1 : 0] 存储器名 [m - 1 : 0] ,表示 m 个 n 位宽度的寄存器。比如 reg [7:0] ram [255:0] 定义了 256 个 8 位寄存器,256 即存储器深度,8 为数据宽度。
二、运算符
2.1 算术运算符(+ 、- 、* 、/ 、%)
+ 、- 、* 、/ 、% 这些符号的定义和 C 语言完全一致,即加、减、乘、除、取余。
2.2 赋值运算符( = 、<=)
Verilog 中有两种赋值方式,分别为 =(阻塞赋值)和 <=(非阻塞赋值)。阻塞赋值为执行完一条赋值语句,再执行下一条(顺序执行),且赋值是立即执行;非阻塞赋值则不考虑赋值顺序(并行执行),在 always 块语句执行完成后,才进行赋值。说得更具体一些,就是:阻塞赋值的赋值语句是在前一条赋值语句结束后开始赋值的,而非阻塞赋值的赋值语句则是先计算出语句块内部所有右边表达式的值,然后并行完成对左边寄存器变量的赋值操作。
一般情况下,在时序逻辑电路中使用非阻塞赋值,可以避免仿真时出现竞争冒险现象;在组合逻辑电路中使用阻塞赋值,执行赋值语句后立即改变;在 assign 语句中必须使用阻塞赋值。
2.3 关系运算符(> 、< 、>= 、<= 、== 、!=)
这里的运算符也可以视作 C 语言中的符号(大于、小于、大于等于、小于等于、等于、不等于)。
2.4 逻辑运算符(&& 、|| 、!)
这里对应的是逻辑与、或、非,与 C 语言相同。
2.5 条件运算符(?:)
与 C 语言相同,属于条件判断的简化写法。
2.6 位运算符(~ 、& 、| 、^)
按位取反、按位与、按位或、按位异或。
2.7 移位运算符(<< 、>> )
左移和右移。
2.8 拼接运算符({ })
这个算是特有的,作用是将多个信号按位拼接。例如 {a [3 : 0] , b [1 : 0]} 表示将 a 的低 4 位,b 的低 2 位拼接成 6 位数据;{n{a [3 : 0]}} 表示将 n 个 a [3 : 0] 拼接。
2.9 运算符优先级
这个也是老规矩了,大概记一个 位操作 > 算术 > 移位 > 关系运算 > 逻辑运算 > 条件运算 就成。
三、程序框架
3.1 注释
Verilog 和 C 语言一样有两种注释方式,即 “//” 单行注释与 “/* */” 多行注释。
3.2 关键字
常用的关键字见下表(这里列出的并非全部):
关键字 | 含义 |
---|---|
module | 模块开始定义 |
input | 输入端口定义 |
output | 输出端口定义 |
inout | 双向端口定义 |
parameter | 信号参数定义 |
wire | wire 信号定义 |
reg | reg 信号定义 |
always | 产生 reg 信号语句的关键字 |
assign | 产生 wire 信号语句的关键字 |
begin | 语句起始标志 |
end | 语句结束标志 |
edge/posedge/negedge | 时序电路标志 |
case | case 语句起始标志 |
default | case 语句默认分支标志 |
endcase | case 语句结束标志 |
if | if/else 语句标志 |
else | if/else 语句标志 |
for | for 语句标志 |
endmodule | 模块结束定义 |
3.3 模块
Verilog 的基本设计单元就是 “模块”。一个模块由两部分组成,一部分描述接口,另一部分描述逻辑功能。如之前在介绍 Reg 型变量时引用的 D 触发器中,第一句定义了模块名称及信号接口的名称与个数,第二句到第四句定义了信号接口的输入输出属性,这四句为前半部分;后半部分则由 always 语句描述该模块的逻辑功能是 D 触发器。
模块例化:前面提到的模块还只是一个很小很简单的模块,而 FGPA 逻辑设计中通常是一个大的模块中包含了一个或多个功能子模块,verilog 通过模块调用或称为模块实例化的方式来实现这些子模块与高层模块的连接,有利于简化每一个模块的代码,易于维护和修改(翻译:类似于 C++ 中类的实例化)。模块例化方法如下图所示:
3.4 结构语句
这里正式介绍一下 initial 语句和 always 语句。
- initial 语句在模块中只会执行一次。它常用于测试文件的编写,用来产生仿真测试信号(激励信号),或者用于对存储器变量赋初值。
- always 语句则一直在不断地重复活动。但是只有和一定的时间控制结合在一起才有作用。结合之前的时序电路标志关键字说明,always 的时间控制可以是沿触发(描述时序逻辑行为),也可以是电平触发(描述组合逻辑行为);可以是单个信号,也可以是多个信号,多个信号中间要用关键字 or 连接。always 语句后紧跟的过程块是否运行,要看它的触发条件是否满足。always @(*) 表示对后面语句块中所有输入变量的变化都是敏感的。
四、有限状态机
4.1 状态机概念理解
有限状态机(Finite State Machine,FSM)是指在有限个状态之间按一定规律转换的时序电路。一图理解:
PS:状态机思想实际上在编程算法中很常见,只是没有像 Verilog 这样明确定义出来而已。
4.2 状态机设计——四段论
- 状态空间定义:确定好要划分出来的状态数量,定义状态名称;
- 状态跳转:确定各状态之间的跳转递进关系;
- 下个状态判断:判断各状态下进行状态跳转的时间点;
- 各个状态下的动作:每个不同的状态下都有不同的动作(判断状态跳转时间点 + 其他事情)。
以一个四状态的 Moore 有限状态机举例说明。
在程序中设计了 8 位的移位寄存器,在 Idle 状态下,判断 shift_start 信号是否为高,如果为高,进入 Start 状态,在 Start 状态延迟 100 个周期,进入 Run 状态,进行移位处理,如果 shift_stop 信号有效了,进入 Stop 状态,在 Stop 状态,清零 q 的值,再跳转到 Idle 状态。
首先定义好模块名称以及状态机的状态:
module top
(
input shift_start,
input shift_stop,
input rst,
input clk,
input d,
output reg [7:0] q
);
parameter Idle = 2'd0 ; //Idle state
parameter Start = 2'd1 ; //Start state
parameter Run = 2'd2 ; //Run state
parameter Stop = 2'd3 ; //Stop state
reg [1:0] current_state ; //statement
reg [1:0] next_state ;
reg [4:0] delay_cnt ; //delay counter
之后开始逐个状态的跳转设计。首先是状态机复位设计如下:
//First part: statement transition
always @(posedge clk or negedge rst)
begin
if (!rst)
current_state <= Idle ;
else
current_state <= next_state ;
end
然后是四大状态之间的跳转设计如下:
//Second part: combination logic, judge statement transition condition
always @(*)
begin
case(current_state)
Idle : begin
if (shift_start)
next_state <= Start ;
else
next_state <= Idle ;
end
Start : begin
if (delay_cnt == 5'd99)
next_state <= Run ;
else
next_state <= Start ;
end
Run : begin
if (shift_stop)
next_state <= Stop ;
else
next_state <= Run ;
end
Stop : next_state <= Idle ;
default: next_state <= Idle ;
endcase
end
最后状态机的输出设计如下:
//Last part: output data
always @(posedge clk or negedge rst)
begin
if (!rst)
delay_cnt <= 0 ;
else if (current_state == Start)
delay_cnt <= delay_cnt + 1'b1 ;
else
delay_cnt <= 0 ;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
q <= 0 ;
else if (current_state == Run)
q <= {q[6:0], d} ;
else
q <= 0 ;
end
endmodule