笔记来自正点原子官方教学视频
时间:2025/2/1
更新时间:2025/4/18
【第一期】手把手教你学领航者&启明星ZYNQ之FPGA开发篇【真人出镜】FPGA教学视频教程_哔哩哔哩_bilibili
补充内容为博主后续学习中遇到的新内容,更新方式为遇到一点更新一点。
逻辑值
逻辑0:表示低电平,对应电路的GND;
逻辑1:表示高电平,对应电路的VCC;
逻辑X:表示未知电平,可能是高电平,也可能是低电平;
逻辑Z:表示高阻态,外部无激励信号,是一个悬空状态。
数字进制格式
二进制表示如下:4’b0101 表示4位二进制数字0101
十进制表示如下:4’d2 表示4位十进制数字2(二进制0010)
十六进制表示如下:4’ha 表示4位十六进制数字a(二进制1010)
一般来说,当未指明数据的位宽时,默认为32位宽。
16’b1001_1010_1010_1001 = 16’h9AA9 这里的下划线没有含义,只是为了更好的可读性。
标识符
标识符( identifier)用于定义模块名、端口名、信号名等。
命名规则如下:
①、标识符可以是任意一组字母、数字、$符号和_(下划线)符号的组合;
②、但标识符的第一个字符必须是字母或者下划线;
③、标识符是区分大小写的;
标识符推荐写法
1、不建议大小写混合使用;
2、普通内部信号建议全部小写;
3、信号命名最好体现信号的含义,简洁、清晰、易懂;
以下是一些推荐的写法:
①、用有意义的有效的名字如 sum 、cpu_addr等。
②、用下划线区分词,如cpu_addr。
③、采用一些前缀或后缀,比如时钟采用clk前缀:clk_50,clk_cpu;
数据类型
在 Verilog 语言中,主要有三大类数据类型: 寄存器数据类型、线网数据类型 和 参数数据类型。
从名称中,我们可以看出,真正在数字电路中起作用的数据类型应该是:寄存器数据类型和线网数据类型。
寄存器类型
寄存器类型:寄存器表示一个抽象的数据存储单元,通过赋值语句可以改变寄存器储存的值。寄存器数据类型的关键字是 reg,reg 类型数据的默认初始值为不定值 x 。
//reg define
reg [31 : 0 ] delay_cnt;
reg key_reg; // 位宽为1
reg [7 : 0 ] example[13:0]; // example是一个包含14个 8位寄存器 的数组,如 example[0] 表示数组中的第一个元素,这个元素是一个 8位寄存器
// 定义方法
// 数据类型 位宽 变量名
// 若没有指明位宽,则默认位宽为1
reg 类型的数据只能在 always 语句和 initial 语句中被赋值。
如果该过程语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应为触发器;
如果该过程语句描述的是组合逻辑,即always语句不带有时钟信号,则该寄存器变量对应为硬件连线;
当未指定数据类型时,默认为wire类型。
线网类型
线网数据类型表示结构实体(例如门)之间的物理连线。线网类型的变量不能储存值,它的值是由驱动它的元件所决定的。
驱动线网类型变量的元件有门、连续赋值语句、assign等。 如果没有驱动元件连接到线网类型的变量上,则该变量就是高阻的,即其值为z。
线网数据类型包括 wire 型和 tri 型,其中最常用的就是 wire 类型。
// wire define
wire key_flag
参数类型
参数其实就是一个常量,在 Verilog HDL 中用 parameter 定义常量。我们可以一次定义多个参数,参数与参数之间需要用逗号隔开。每个参数定义的右边必须是一个常数表达式。
// parameter define
parameter H_SYNC = 11'd41;
parameter H_BACK = 11'd41;
parameter H_DISP = 11'd41;
parameter H_FRONT = 11'd41;
parameter H_TOTAL = 11'd41;
参数型数据常用于定义状态机的状态、数据位宽和延迟大小等。
采用标识符来代表一个常量可以提高程序的可读性和可维护性。
在模块调用时,可通过参数传递来改变被调用模块中已定义的参数。
运算符
Verilog中的操作符按照功能可以分为下述类型:
1、算术运算符
2、关系运算符
3、逻辑运算符
4、条件运算符
5、位运算符
6、移位运算符
7、拼接运算符
算术运算符
符号 | 使用方法 | 说明 | 备注 |
+ | a + b | a 加上 b | |
- | a - b | a 减去 b | |
* | a * b | a 乘以 b | |
/ | a / b | a 除以 b | 这是整除 |
% | a % b | a 模除 b |
关系运算符
符号 | 使用方法 | 说明 |
> | a > b | a 大于 b |
< | a < b | a 小于 b |
<= | a >= b | a 大于等于 b |
>= | a<= b | a 小于等于 b |
== | a == b | a 等于 b |
!= | a != b | a 不等于 b |
逻辑运算符
符号 | 使用方法 | 说明 |
! | !a | a的非 如果a为0,那么a的非是1。 |
&& | a && b | a 与上 b 如果a和b都为1,a&&b结果才为1,表示真。 |
|| | a || b | a 或上 b 如果a或者b有一个为1,a||b结果为1,表示真。 |
条件运算符
符号 | 使用方法 | 说明 |
? : | a ? b : c | 如果 a 为真,就选择 b,否则选择 c |
举例:
result = (a >= b) ? a : b;
位运算符
符号 | 使用方法 | 说明 |
~ | ~a | 将 a 的每个位进行取反 |
& | a & b | 将 a 的每个位与 b 相同的位进行相与 |
| | a | b | 将 a 的每个位与 b 相同的位进行相或 |
^ | a ^ b | 将 a 的每个位与 b 相同的位进行异或 |
移位运算符
符号 | 使用方法 | 说明 |
<< | a << b | 将 a 左移 b 位 |
>> | a >> b | 将 a 右移 b 位 |
两种移位运算都用0来填补移出的空位。
左移时,位宽增加。例:4’b1001 << 2 = 6’b100100;
右移时,位宽不变。例:4’b1001 >> 1 = 4’b0100;
拼接运算符
符号 | 使用方法 | 说明 |
{} | { a,b} | 将 a 和 b 拼接起来,作为一个新信号 |
举例:
c = { a, b[3:0] };
若a为8位,则拼接后的c为12位宽,高8位为a,低4位为b。即:
c[11:0] = { a[7:0], b[3:0] };
位拼接操作符
符号 | 使用方法 | 说明 |
+: | 信号名[起始位 +: 宽度] | 表示从起始位开始,向高位方向提取子向量 |
-: | 信号名[起始位 -: 宽度] | 表示从起始位开始,向低位方向提取子向量 |
例子:
wire [63:0] axil_awaddr;
wire [31:0] sub_axil_awaddr;
assign sub_axil_awaddr = axil_awaddr[32 +: 32];
// axil_awaddr[32 +: 32] 表示从第 32 位开始,提取 32 位的子向量,即 axil_awaddr[63:32]
Verilog程序框架
Verilog注释
Verilog中有两种注释方式。一种是以 // 开头的语句,它表示以//开始到本行结束都属于注释语句。
另一种是以“/*”符号开始,“*/” 结束,在两个符号之间的语句都是注释语句,因此可扩展到多行。
Verilog关键字
以下只列出常用关键字:
关键字 | 含义 |
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 | 模块结束定义 |
Verilog程序框架
Verilog 的基本设计单元是“模块”(block)。 一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能。
每个Verilog程序包括4个主要的部分: 端口定义、IO说明、内部信号声明、功能定义。
模块的结构
功能定义部分有三种方法:
①、assign语句 描述组合逻辑
②、always语句 描述组合/时序逻辑
③、例化实例元件 如:and #2 u1(q,a,b);
上述三种逻辑功能是并行的。
注意: 在always块中,逻辑是顺序执行的。 而多个always块之间是并行的。
模块的调用
在模块调用时,信号通过模块端口在模块之间传递。
端口连接时注意位宽一致。
注:输出端口只能连接wire类型数据。
结构语句
initial 和 always。
initial 语句它在模块中只执行一次。 它常用于测试文件的编写,用来产生仿真测试信号(激励信号),或者用于对存储器变量赋初值。
上图中,#20的含义为:延时20个单位时间后,再对sys_rst_n进行赋值。
always 语句一直在不断地重复活动。 但是只有和一定的时间控制结合在一起才有作用。
always 的时间控制可以是沿触发,也可以是电平触发; 可以是单个信号,也可以是多个信号,多个信号中间要用关键字 or 连接。 always 语句后紧跟的过程块是否运行,要看它的触发条件是否满足。
以下是沿触发的always块:
沿触发的 always 块常常描述时序逻辑行为。
由关键词 or 连接的多个事件名或信号名组成的列表称为“敏感列表”。
以下是电平触发的always块:
电平触发的 always 块常常描述组合逻辑行为。
如果组合逻辑块语句的输入变量很多,那么编写敏感列表会很烦琐并且容易出错。
以上两个图中的代码是等价的,@( * )表示对后面语句块中所有输入变量的变化都是敏感的。
赋值语句
Verilog HDL 语言中,信号有两种赋值方式:
①、阻塞赋值(blocking),如 b = a;
②、非阻塞赋值(Non_Blocking),如 b <= a;
阻塞赋值只有一个步骤:计算 RHS 并更新 LHS 。
非阻塞赋值有两个步骤:
①、赋值开始的时候,计算 RHS ;
②、赋值结束的时候,更新 LHS 。
以下以阻塞赋值为例:
所谓阻塞的概念是指,在同一个always块中,后面的赋值语句是在前一句赋值语句结束后才开始赋值的。
以下以非阻塞赋值为例:
所谓非阻塞的概念是指,在计算非阻塞赋值的RHS以及更新LHS期间,允许其他的非阻塞赋值语句同时计算RHS和更新LHS。
非阻塞赋值只能用于对寄存器类型的变量进行赋值,因此只能用在initial块和always块等过程块中。
总结
在描述组合逻辑的 always 块中用阻塞赋值 = ,综合成组合逻辑的电路结构; 这种电路结构只与输入电平的变化有关系。
在描述时序逻辑的 always 块中用非阻塞赋值 <=,综合成时序逻辑的电路结构; 这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。
注意:在同一个always块中不要既用非阻塞赋值又用阻塞赋值,不允许在多个always块中对同一个变量进行赋值!
条件语句
if 条件语句
条件语句必须在过程块中使用。
过程块语句是指由initial和always语句引导的块语句。
if 语句有如下注意事项:
①、允许一定形式的简写,如: if(a) 等同于 if(a == 1) if(!a)等同于 if(a != 1)。
②、if语句对表达式的值进行判断,若为0,x,z,则按假处理;若为1,按真处理。
③、if和else后面的操作语句可以用begin和end包含多个语句。
④、允许if语句的嵌套。
case 语句
1、分支表达式的值互不相同;
2、所有表达式的位宽必须相等; 不能用 ’bx 来代替 n’bx。
3、casez 比较时,不考虑表达式中的高阻值;
4、casex 不考虑高阻值z 和 不定值x。
状态机(State Machine)
有限状态机(Finite State Machine,简称FSM),指在有限个状态之间按一定规律转换的时序电路。
下面介绍两种有限状态机:
Mealy 状态机
状态寄存器由一组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳变沿。
状态是否改变、如何改变,取决于组合逻辑F的输出,F是当前状态和输入信号的函数。
状态机的输出是由输出组合逻辑G提供的,G也是当前状态和输入信号的函数。
Moore 状态机
状态机的设计
状态机的设计可以遵循四段论:
①、状态空间定义
②、状态跳转
③、下个状态判断
④、各个状态下的动作
状态空间定义
此步骤需定义状态空间和当前状态以及下一状态。注意位宽保持一致。
左右两种方式都可以,右边的定义方式称为:独热码。 即每个状态只有一个寄存器位置位,译码逻辑简单。
状态跳转
下一个状态判断(使用组合逻辑)
各个状态下的动作
有以下两种写法:
一种是直接使用assign:
另一种是使用组合逻辑: