FPGA学习 阶段一:Verilog HDL 简介与基本语法(1)
FPGA硬件开发路线学习规划:
由于想学习FPGA的相关内容,同时给数字电路打点基础,于是借助着B站上西安电子科技大学的视频开始学习Verilog HDL这门硬件描述语言,在学完语言后,将按照**《FPGA设计技巧与案例开发详解》**这本书上的相关内容在一块核心为Altera Cyclone IV的开发板上完成基本例程,之后在网上寻找相关项目学习、开发
1. Verilog的基本概念
- Verilog是一门类C语言,语法非常接近C,将电路设计转变为程序设计
- Verilog的代码对应的是现实中的一个硬件,这个过程叫“综合”,类似于C语言的编译
- Verilog的代码是并行执行的
2. Verilog HDL基础知识
2.1 Verilog HDL语言要素
2.1.1 空白符
空白符包括空格符\b
、制表符\t
、换行符\n
、和换页符\f
。在编译和综合时,空白符被忽略:
initial begin a = 3'b100; b = 3'b010; end
equals to:
initial
begin
a = 3'b100;
b = 3'b010;
end
2.1.2 注释符
注释与C语言相同,单行注释用//
,多行注释用/* */
2.1.3 标识符
标识符命名规则与C语言基本相同,只允许出现字母、数字、$符号和_ 下划线,第一个字符必须是字母或 _下划线
2.1.4 数值
- Verilog HDL有四种基本的逻辑数值状态:
状态 | 含义 |
---|---|
0 | 低电平、逻辑0、“假” |
1 | 高电平、逻辑1、“真” |
x或X | 不确定或未知的逻辑状态 |
z或Z | 高阻态 |
备注:高阻态High impedance
表示电路中的某个节点具有相对电路中其他点相对更高的阻抗
电路分析时高阻态可做开路理解。可以把它看作输出(输入)电阻非常大。它的极限状态可以认为悬空(开路)。
当门电路的输出上拉管导通而下拉管截止时,输出为高电平;反之就是低电平;如上拉管和下拉管都截止时,输出端就相当于浮空(没有电流流动),其电平随外部电平高低而定,即该门电路放弃对输出端电路的控制 。
- 整数及其表示
+/-(size>'<base_format><number>
数制 | 基数符号 | 合法标识符 |
---|---|---|
二进制 | b或B | 0、1、x、X、z、Z、?、_ |
八进制 | o或O | 0~7、x、X、z、Z、?、_ |
十进制 | d或D | 0~9、_ |
十六进制 | h或H | 09、af、A~F、x、X、z、Z、?、_ |
//正确的表示
8'b10001101 //位宽为8位的二进制数10001101
8'ha6 //位宽为8位的十六进制数a6
5'o35 //位宽为5位的八进制数35
4'd6 //位宽为4位的十进制数6
4'b1x_01 //位宽为4位的二进制数1x01
//错误的表示
4'd-4 //数值不能为负,有负号应放在最左边
3' b001 //'和基数b之间不能有空格
(4+4)'b11 //位宽不能是表达式
- 实数及其表示
和C语言相同:
a.十进制表示法,小数点两边必须都有数字
b.科学计数法
2.7 //十进制计数法
5.2e8 //科学计数法
3.5E-6 //e和E意义相同
5_4582.2158_5896 //使用下划线可提高可读性
6. //非法表示
.3e5 //非法表示
2.2 物理数据类型
物理数据类型有连线型、寄存器型和储存器型
2.2.1 信号强度
信号强度表示数字电路中不同强度的驱动源,用来解决不同驱动强度存在下的赋值冲突:
标记符 | 名称 | 类型 | 强弱程度 |
---|---|---|---|
supply | 电源级驱动 | 驱动 | 最强 |
strong | 强驱动 | 驱动 | 强 |
pull | 上拉级驱动 | 驱动 | 较强 |
large | 大容性 | 存储 | 中强 |
weak | 弱驱动 | 驱动 | 中 |
medium | 中性驱动 | 存储 | 较弱 |
small | 小容性 | 存储 | 弱 |
highz | 高容性 | 高阻 | 最弱 |
2.2.2 连线型
连线型数据类型 | 功能说明 |
---|---|
wire,tri | 标准连线(缺省视为该类型) |
wor,trior | 多重驱动时,具有线或 特性的连线型 |
wand,trand | 多重驱动时,具有线与 特性的连线型 |
trireg | 具有电荷保持特性的连线型数据(特例) |
tri1 | 上拉电阻 |
tri0 | 下拉电阻 |
supply1 | 电源线,用于对电源建模,为高电平1 |
supply0 | 电源线,用于对“地”建模,为低电平0 |
- 单连线之间的逻辑wire和tri
wire/tri | 0 | 1 | x | z |
---|---|---|---|---|
0 | 0 | x | x | 0 |
1 | x | 1 | x | 1 |
x | x | x | x | x |
z | 0 | 1 | x | z |
- 含有
线或
时连线之间的逻辑
wor/trior | 0 | 1 | x | z |
---|---|---|---|---|
0 | 0 | 1 | x | 0 |
1 | 1 | 1 | 1 | 1 |
x | x | 1 | x | x |
z | 0 | 1 | x | z |
2.2.3 连续型数据类型的声明
<net_declar><drive_strength><range><delay><list_of_variables>;
- net_declar:包括wire、tri等等连线型中的任意一种
- dirve_strength:用来表示连线变量的驱动强度
- rang:用来指定数据位标量或矢量。若该项默认,表示数据类型为1位的标量,超过1位则为矢量
- delay:指定仿真延迟时间
- list_of_variables:变量名称,一次可定义多个名称,用逗号分开
2.2.4 寄存器型
reg型是数据储存单元的抽象类型,其对应的硬件电路原件具有状态保持作用,能够存储数据,如触发器、锁存器等
reg型变量常用于行为级描述,由过程赋值语句对其进行赋值
//reg型变量简单例子:
reg a; //定义一个1位的名为'a'的reg变量
reg[3:0]b; //定义一个4位的名为'b'的reg变量
reg[8:1]c,d,e; 定义三个名称分别为c、d、e的8位reg型变量
//reg型变量一般为无符号数,若将一个负数赋给reg型变量,则自动转换成二进制补码形式。例如:
reg signed[3:0]rega;
rega = -2; //rega的值为1110(14),是2的补码
2.2.5 寄存器型数据类型的声明
reg<range><list_of_register_variables>;
- range:可选项,它指定了reg型变量的位宽,缺省时为1位
- list_of_register_variables:为变量名称列表,一次可以定义多个名称,之间用逗号分开
2.2.6 物理数据类型声明的例子
reg rega; //定义一个1位寄存器型变量
reg[7:0] regb; //定义一个8位的寄存器型变量
tri[7:0] tribus; //定义一个8位的三态总线
tri0[15:0] busa; //定义一个16位连线型,处于三态时为下拉电阻
tri1[31:0] busb; //定义一个32位连线型,处于三态时为上拉电阻
reg scalared[1:4] b;//定义一个4位的标量型储存器矢量
wire(pull1,strong0) c = a + b; //定义一个1和0的驱动强度不同的1位连线型变量c
trireg(large) storeline; //定义一个具有大强度的电荷存储功能的存储线
/*在数字电路中,三态逻辑(英语:Three-state logic)允许输出端在0和1两种逻辑电平之外呈现高阻态,等效于将输出的影响从后级电路中移除。
这允许多个电路共同使用同一个输出线,例如总线结构
在总线连接的结构上。总线上挂有多个设备,设备与总线以高阻的形式连接。这样在设备不占用总线时自动释放总线,以方便其他设备获得总线的使用权.*/
2.2.7 存储器型
储存器型变量可以描述RAM型、ROM型储存器以及reg文件
储存器变量的一般声明格式为:
reg<range1><name_of_register><range2>
- range1和range2都是可选项,缺省都为1
- range1:表示存储器当中寄存器的位宽,格式为[msb:lsb]
- range2:表示寄存器的个数,格式为[msb:lsb],即拥有msb-lsb+1个
- name_of_registers:变量名称,一次可以定义多个名称,之间用逗号隔开
reg[7:0] mem1[255:0]; //定义了一个有256个8位寄存器的存储器mem1
//地址范围是0到255
reg[15:0] mem2[127:0],reg1,reg2;//定义了一个具有128个16位寄存器的储存器mem2和两个16位寄存器reg1和reg2
reg[n-1:0] a; //表示一个n位寄存器a
reg mem1[n-1:0] //表示一个由n个1位寄存器构成的存储器mem1
2.3 抽象数据类型
抽象数据类型主要包括整型integer
、时间型time
、实型real
及参数型parameter
2.3.1 整型
integer<list—of—register—variables>;
integer index; //简单的32位有符号整数
integer i[31:0]; //定义了整型数组,它有32个元素
2.3.2 时间型
时间型数据与整型数据相似,只是它是64位无符号数。时间型数据主要用于对模拟时间的储存与计算处理,常与系统函数$time
一起使用。
声明格式:
time<list_of_register_variables>
time a,b; //定义了两个64位的时间型变量
2.3.3 实数型
实数型数据在机器码表示法中是浮点型数值,可用于对延迟时间的计算
声明格式:
real<list_of_variables>
real stime; //定义了一个实数型数据
####2.3.4 参数型
属于常量,在仿真开始之前就被赋值,在仿真过程中保持不变,以提高程序的可读性和维护性
声明格式:
parameter<name_of_parameter>=<value>
parameter length = 32,weight = 16;
parameter PI = 3.14,LOAD = 4'b1101;
parameter DELAY=(BYTE+BIT)/2;
2.3 运算符和表达式
2.3.1 算术运算符
算术运算符包含加法+
;减法-
;乘法*
;除法/
;取模%
- 算术操作结果的算术表达式的结果由最长的操作数决定。在赋值语句下,算术操作结果的长度由操作左端目标长度决定
reg[3:0] A,B,C;
reg[5:0] D;
A = B + C; //4位
D = B + C; //6位
- 有符号数和无符号数的使用
module arith_tb;
reg[3:0] a;
reg[2:0] b;
initial
begin
a = 4'b1111; //15
b = 3'b011; //3
$display("%b",a*b) //乘法运算,结果等于4'b1101,高位被舍去
$display("%b",a/b); //除法运算,结果为4'b0101
$display("%b",a+b); //加法运算,结果为4'b0010
$diaplay("%b",a-b); //减法运算,结果为4'b0010
$diaplay("%b",a%b); //取模运算,结果为4'b0000
end
endmodule
/*注意:二进制的减法先补全位数,使两个数同位,然后再取补码,
正二进制数的补码是它本身,
负二进制数的补码是它取反后加1*/
2.3.2 相等关系运算符
- 等于“”、不等于“!=”、全等“=”、非全等“!==”
- 比较的结果有三种,真“!”、假“0”、不定值“x”
- “==”真值表
== | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | x | x |
1 | 0 | 1 | x | x |
x | x | x | x | x |
z | x | x | x | x |
- “===”真值表
=== | 0 | 1 | x | z |
---|---|---|---|---|
0 | 1 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 |
x | 0 | 0 | 1 | 0 |
z | 0 | 0 | 0 | 1 |
2.3.3 条件运算符
- 表达形式如下:
<条件表达式>?><表达式1>:<表达式2>
条件表达式的计算结果只有1
,0
和x
三种,结果为1
时执行表达式1;结果为0
时执行表达式2
module mux2(in1,in2,sel,out);
input [3:0] in1,in2;
input sel;
output [3:0] out;
reg [3:0] out;
assign out = (sel!)?in1:in2;
//sel为0时out等于in1,反之等于in2
endmodule
2.3.4 连接和复制运算符
连接运算符“{}”和复制运算符“{{}}”
-
连接运算符
{信号1的某几位,信号2的某几位,…,信号n的某几位} -
复制操作符
将一个表达式放入双重花括号中,复制因子放在第一层括号中
module con_rep_tb;
reg [2:0] a;
reg [3:0] b;
reg [7:0] c;
reg [4:0] d;
reg [5:0] e;
initial
begin
a = 3'b101;
b = 4'b1110;
c = {a,b};
d = {a[2:1],b[2:0]};//d取了a的第3位到2位与b的第3位到1位拼接
e = {2{a}};
$display("%b",c); //8'b01011110
$display("%b",d); //5'b10110
$display("%b",e); //6'b101101
end
endmodule
2.4 模块
2.4.1 模块的基本概念
- 模块(module)是Veril HDL语言的基本单元,它代表一个基本的功能块,用于描述某个设计的功能或结构以及与其他模块通信的外部接口
- 一个模块主要包括:
- 模块的开始与结束:以关键词module开始,以关键词endmodule结束的一段程序,其中模块开始语句必须要以分号结束
- 端口定义:用来定义端口列表里的变量哪些是输入(input)、输出(output)和双向端口(inout)以及位宽的说明。
- 数据类型说明:数据类型在语言上包括wire、reg、memory和parameter等类型,用来说明模块中所用到的内部信号、调用模块等的声明语句和功能定义语句
- 逻辑功能描述:用来产生各种逻辑(主要是组合逻和时序逻辑)。主要包括以下部分:initial语句、always语句、其它子模块实例化语句、门实例化语句、用户自定义原句(UDP)实例化语句、连续赋值语句(assign)、函数(function)和任务(task)
例:上升沿D触发器
module dff(din,clk,q);
input din,clk;
output q;
reg q;
always@(posedge clk) //posedge指D触发器上升沿触发
q <= din;
endmodule
2.4.2 端口
端口的定义:
模块的端口可以是输入口input
、输出端口output
或双向端口inout
模块的端口声明了模块的输入输出口。其格式如下:
module 模块名(口1,口2,…)
模块的端口表示的是模块的输入和输出口名,也就是说,它与别的模块联系端口的标识。
在模块被引用时,在引用的模块中,有些信号要输入到被引用的模块中,有的信号需要从被引用的模块中取出来。在引用模块时其端口可以用两种方法连接:
- 在引用时严格按照模块定义的端口顺序来连接,不用标明原模块定义时规定的端口名,例如:
模块名(连接端口1信号名,连接端口2信号名,...)
- 在引用时用“.”符号,标明原模块是定义时规定的端口名,例如:
模块名(.端口1名(连接信号1名),.端口2名(连接信号2名),...)
这样的好处在于可以用端口名与被引用模块的端口相对应,而不必严格按端口顺序对应,提高了程序的可读性和可移植性。