FPGAVerilog语法入门
Posted 独独白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FPGAVerilog语法入门相关的知识,希望对你有一定的参考价值。
学习板:ZYNQ7020
一、可编程逻辑器件
1、概念
对于常见的89C51、STM32、Arduino等等单片机来讲,内部电路连接是固定的,即内部的逻辑功能是固定不变的,要想改变它们的逻辑功能,就必须重新设计电路,改变内部各单元电路的连接。
而对于可编程逻辑器件(Programmable Logic Device)(PLD),用户可以自行修改内部连接,这种PLD内部电路结构可以通过写入编程数据来设置,当然写入后也可以擦除重写。用户可以根据自己的需求来设置内部电路的逻辑功能。
2、常见的可编程逻辑器件
常见的可编程逻辑器件有:
(1)CPLD
复杂可编程逻辑器件(Complex Programmable Logic Device)
(2)FPGA
现场可编辑门阵列(Field Programmable Gate Array)
俩者的差异本质差异在于电路结构不同:
CPLD:基于乘积项的与或逻辑阵列
FPGA:基于“查找表”的CLB阵列
3、FPGA与单片机的区别
FPGA是一种可通过编程来改变其逻辑功能的数字集成电路;而对单片机编程并不能改变其电路结构。
通过FPGA编程后,生成电路,通过逻辑功能达到我们的目的;而对单片机编程后,会转换指令,单片机处理软件指令:取指、译码、执行…取指、译码、执行…一步步执行下去
4、HDL
HDL(Hardware Description Language),硬件描述语言。用于描述数字电路结构和功能的语言。
HDL 可以在不同的层次对数字电路的结构、功能和行为进行描述。它所描述的电路可以通过综合工具(如Vivado软件)将其转换为门级电路网表,然后将门级电路网表与基本元件一一对应起来,再通过布局布线工具转换为电路布线结构。
5、Verilog与C的区别
Verilog语言是硬件描述语言,编译下载到FPGA后,会生成电路,而不同模块的电路执行可以独立执行,所以可以说verilog语言是并行执行的。
C语言是软件编程语言,编译下载到单片机后,生成的是指令。单片机必须通过:取指、译码、执行…取指、译码、执行…来完成功能,所以可以理解为是串行执行。
即可以说Verilog语言是并行执行,也可以说FPGA是并行执行;既可以C语言是串行执行,也可以说单片机是串行执行。这既是Verilog与C的区别,也是FPGA与单片机的区别。
二、逻辑值
逻辑 0:表示低电平,也就是对应我们电路的 GND;
逻辑 1:表示高电平,也就是对应我们电路的 VCC;
逻辑 X:表示未知,有可能是高电平,也有可能是低电平;
逻辑 Z:表示高阻态,外部没有激励信号时是一个悬空状态。
三、符号
Verilog程序由各种符号流构成,这些符号包括:空白符(White Space)、运算符(Operator)、数字(Number)、字符串(String)、注释(Comment)、标识符(Identifier)、关键字(Key Word)等
1、空白符(White Space)
Verilog语言中的空白符包括:空格、Tab、换行符
verilog语言可以不分行,例如:
initial begin ina=3'b001;inb=3'b011;inc=3'b111;end
等效为:
initial
begin
ina=3'b001;
inb=3'b011;
inc=3'b111;
end
空白符只是为了使代码错落有致,阅读起来更加方便,在代码被综合时空白符会被忽略。
2、注释(Comment)
注释与c语言一样:
(1)单行注释
以“//”开始,到本行结束
(2)多行注释
以“/*”开始,到 “ */”结束
3、标识符(Identifier)
标识符用于用户在编程时给Verilog对象、模块、端口和实例起名字
标识符由 字母、数字、“$”、和"_"(下划线)组成。并且第一个字符必须是字母或者下划线。标识符最长可包含1023个字符 。
还有一种标识符为转义标识符,它与C语言的转义标识符一样,以反斜杠“\\”开头,空白符结尾。
4、关键字(Key Word)
Verilog的关键字有很多,1995年发布标准后,在2001年又进行了修订,目前最常见关键字有
关键字 | 含义 |
---|---|
module | 模块开始定义 |
input | 输入端口定义 |
output | 输出端口定义 |
inout | 双向端口定义 |
parameter | 信号的参数定义 |
wire | 线性变量定义 |
reg | 寄存器型数据定义 |
always | 产生reg信号语句的关键字 |
assign | 产生wire信号语句的关键字 |
begin | 语句的起始标志 |
posedge/negedge | 时序电路的标志 |
case | Case语句起始标记 |
default | Case语句的默认分支标志 |
endcase | Case语句结束标记 |
if/else | if/else语句标记 |
for | for语句标记 |
end | 语句的结束标志 |
endmodule | 模块结束定义 |
5、运算符(Operator)
与C语言类似(本博八中详细总结)
四、常量
Verilog的常量主要有三种:整数、实数、字符串
1、整数(Integer)
整数的书写格式:
(+或-)(位宽)’(进制符)(值)
进制符与C语言一致:
①二进制:b或B
②八进制:o或O
③十进制:d或D
④十六进制:h或H
例如:
8'b11000111 //位宽为8的二进制数11000111
8'Hd5 //位宽为8的十六进制数d5 变为二进制:11010101
5'O23 //位宽为5的八进制数23 变为二进制:10011
5'd6 //位宽为5的十进制数6,变为二进制:00110
在位宽 与 '之间、进制数与数值之间允许出现空格,格式:
(+或-)(位宽)<空格>’(进制符)<空格>(值)
例如上面的例子,中间插了空格
8 'b 11000111 //位宽为8的二进制数11000111
8 'H d5 //位宽为8的十六进制数d5 变为二进制:11010101
5 'O 23 //位宽为5的八进制数23 变为二进制:10011
5 'd 6 //位宽为5的十进制数6,变为二进制:00110
其它地方出现空格是非法的!
书写整型变量时,注意以下几条规则:
(1)较长的数值可以用下划线分开,不要求每隔4位加一个下划线,可以任意加在某几个位置,它本身没有意义,只是为了代码的可读性(注意第一位不能是下划线)。例:
16'B 1001_1011_1111_0000
13'b 1_0011_0100_1110
13'b 1_001_101_001_110
(2)如果没有定义位宽,则默认为32位,例:
'b 111 //为二进制 0000 0000 0000 0000 0000 0000 0000 0111
'd 123 //10进制数,变为二进制 0000 0000 0000 0000 0000 0000 0111 1011
(3)如果定义的位宽比数值的长度长,需要在前面补零。例:
5'b 111 //为二进制 00111
10'd 123 //为二进制 00 0111 1011
(4)如果定义的位宽比数值长度短,则左边的部分被截掉。例:
5'b 101_1111 //为二进制 11111
3'd123 //为二进制 011
(5)未知态x、高阻态z在二进制中代表1位x或z;在八进制中代表三位x或z;在16进制中代表四位x或z。例:
5'b111zz //为二进制111zz
8'O23z //为二进制10011zzz
18'O12z3z //为二进制 000 001 010 zzz 011 zzz
16'Hz5Ax //为二进制 zzzz 1001 1010 xxxx
(6)整数可以带正负号,并且正负号必须写在最前面。负数通常表示为二进制补码的形式
(7)当位宽和进制符默认时,认为是十进制、32位数。例:
32 //为十进制数32,二进制数:0000 0000 0000 0000 0000 0000 0010 0000
123 //为十进制数123 ,二进制数:0000 0000 0000 0000 0000 0000 0111 1011
(8)在位宽 与 '之间、进制数与数值之间允许出现空格,格式:
(+或-)(位宽)<空格>’(进制符)<空格>(值)
上面总结过了。
(9)在2001修订后,扩展了带符号的整数定义。例:
8'sh3a //一个十六进制带符号整数 3a
2、实数(real)
rerilog不像c语言那般,有float、double类型。对于浮点型数据,都是定义为实数(real)类型。
例:
real a,b;
initial begin
a=16/10;
b=16/1.1;
end
实数的表示方法有以下几种:
(1)十进制表示法
例如:
1.0
3.1415
0.1
需要注意的是小数点俩边都必须有数字(与c语言不同),如以下表示是错的:
3.
0.
.3
//三者都是错误表示法
(2)科学计数法
12_345.56E3 //值为123456.0
314159E-5 //值为3.14159
3、字符串(String)
字符串是双引号内的字符序列,字符串不能分成多行书写。
Verilog中采用reg类型变量来储存字符串,例如:
reg [8*12:1] stringval;
initial begin
stringval="Hello world!" ;
end
4、字符串中的一类特殊字符
特殊字符 | 含义 |
---|---|
\\n | 换行 |
\\t | Tab键 |
\\ | 符号\\ |
" | 符号" |
\\ddd | 八进制数ddd的ASCII字符 |
例如:
\\123 //八进制数123对应的ASCII字符,即字符S
五、数据类型
Verilog中主要有两种数据类型:net型和Variable
1、net型
net型数据相当于电路中的各种物理连接,其特点是输出的值随着输入的值的变化而变化。
net型数据不能储存值,它的值由驱动它的元件所决定。net型变量有两种驱动方式:
①在结构描述中将其连接到一个门元件或模块的输出端;
②用持续赋值语句assign对其进行赋值。
如果没有驱动元件连接到ne类型的变量上,该变量的值为高阻Z(trireg除外)
常用的net型变量:
类型 | 功能 | 可综合性 |
---|---|---|
wire、 tri | 连线功能 | 可综合 |
wor、trior | 具有线或线特性的多重驱动连线 | |
wand、triand | 具有线或线特性的多重驱动连线 | |
tri1、tir0 | 分别为上拉电阻和下拉电阻 | |
supply1、supply0 | 分别为电源(逻辑1)和地(逻辑0) | |
trireg | 具有电荷保持作用的连线 | 可综合 |
着重总结一下最常用的两种net型数据类型
(1)wire型
Verilog模块中的输入和输出信号没有明确指定数据类型时都被默认为wire型。
wire型信号可用作任何表达式的输入,也可以用作assign语句和实例元件的输出。
wire型取值可为:0、1、x、z,如果wire没有连接到驱动,其值为高阻态z。
wire型变量定义格式:
wire 数据名1,数据名2....数据名n;
例如定义两个位宽为1的wire型变量a,b:
wire a,b;
如果定义多位宽的wire型变量,例如总线,则格式:
wire[n-1:0] 数据名1,数据名2....数据名n;
或者
wire[n:1] 数据名1,数据名2....数据名n;
例:
wire[7:0] databus; //定义位宽为8的数据总线
wire[15:0] addrbus //定义位宽为16的地址总线
或
wire[8:1] databus; //定义位宽为8的数据总线
wire[16:1] addrbus //定义位宽为16的地址总线
(2)tri型
tri与wire功能和使用方法完全一致。对应Verilog的综合器来讲,对tri型数据和wire型数据的处理是完全相同的。定义为tri型只是为了增加程序的可读性,可以更清楚地表示该信号综合后的电路连接具有三态的功能。
2、variable型
variable型变量必须放在过程语句中,如initial、always,通过赋值语句进行赋值。而且在initial、always等过程块内被赋值的信号必须定义为variable型。在综合器综合时,根据其被赋值的具体情况确定是映射连线还是映射成为存储元件(触发器、寄存器)
常见的variable型变量:
类型 | 功能 | 可综合性 |
---|---|---|
reg | 寄存器型变量 | 可综合 |
integer | 32位带符号整型变量 | 可综合 |
real | 64位带符号实型变量 | 不可综合 |
time | 64位带无符号时间变量 | 不可综合 |
上表中的real、time变量是纯数学的描述,不对应具体的硬件电路,不可被综合。
time主要用于对模拟时间的存储和处理,real表示实数寄存器,主要用于仿真。
(1)reg型
reg型变量定义格式:
reg 数据1,数据2,…数据n;
如定义两个位宽为1的reg型变量 a、b:
reg a,b;
定义多位宽的reg型变量:
reg[n-1:0] 数据1,数据2,…数据n;
reg[n:1]数据1,数据2,…数据n;
例如定义位宽为8的reg型变量a:
reg[7:0] a;
或
reg[8:1] a;
(2)integer型
integer型变量定义与reg型变量相同,integer型变量多用于表示循环变量,用来表示循环次数等。
如下定义位宽为1的integer型变量a、b和位宽为32的integer型变量c、d:
integer a,b;
integer[31:0] c;
integer[32:1] d;
六、参数
1、参数parameter
用参数parameter来定义一个常亮,通常用来表示时延和变量的宽度,并且只能被赋值一次 。类似于C语言中的#define宏定义。
parameter型参数声明格式:
parameter 参数名1=表达式1,参数名2=表达式2,…参数名n=表达式n;
注意有等于号!与c语言的宏定义区分 。并且参数名最好用大写字母。
如:
parameter SUM=100,SIZE=100;
parameter NUM=10, SIZE=NUM*4;
parameter PI=3.14159;
2、参数localparam
localparam用于定义局部参数,作用范围仅限于本模块内,不能用于参数传递。即在实例化是不能通过层次引用来重新定义,常用于状态机参数的定义。
localparam参数的定义与parameter参数的定义相同。
七、向量
1、标量与向量
宽度为1的变量称为标量,如果在变量声明中没有指定位宽,则默认为标量。
如:
wrie a; //线型变量a,为标量
reg clk; //reg型标量clk,位宽为1,为标量
位宽大于1的变量称为向量,格式是[MSB:LSB],MSB表示最高有效位,LSB表示最低有效位。
如:
wire[7:0] bus;//位宽为8的wire型变量 bus,为向量
reg[7:0] a;//位宽为8的reg型变量a,最高有效位和最低有效位分别是a[7]、a[0]
reg[0:7] b;//位宽为8的reg型变量a,最高有效位和最低有效位分别是b[0]、b[7]
2、位选择和域选择
在表达式中任意选中向量的一位称为位选择,选中向量的多位称为域选择。
如:
a=A[2];//位选择 将A的位2赋值给a变量
b=B[5:3] //域选择 将B的位5:3赋值给b变量
reg[7:0] a,b;
reg[3:0] c;
reg d;
c=a[3:0]+b[7:4];//域选择
d=a[7]&b[7]; //位选择
上面代码的c=a[3:0]+b[7:4];//域选择
等效为:
c[3]=a[3]+b[7];
c[2]=a[2]+b[6];
c[1]=a[1]+b[5];
c[0]=a[0]+b[4];
如果不想向量支持位选择和域选择,只作为一个统一的整体进行操作,则在定义时用关键字vectored说明,此时向量变为向量类向量
如果未做说明,则默认为标量类向量,可以进行位选择和域选择。当然也可以用关键字scalared说明 。
如:
wire vectored [7:0] databus; //向量类向量,不能进行位选择和域选择
3、储存器
储存器是由一组宽度相同的寄存器构成的阵列,定义储存器时需要定义储存器的容量和字长。
容量表示储存器储存单元的数据量
字长表示每个储存单元的数据宽度
如:
reg[5:0] mymenory[63:0]; //mymenory表示容量为64,前面的部分表示字长为6
//能存储64个数,每个数的位宽为6
八、运算符
Verilog的运算符与C语言的基本相同。
1、算术运算符
符号 | 含义 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模 |
2、逻辑运算符
符号 | 含义 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑非 |
3、位运算符
符号 | 含义 |
---|---|
~ | 按位取反 |
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
^~ 或者~^(等价) | 按位同或 |
4、关系运算符
符号 | 含义 |
---|---|
< | 小于 |
<= | 小于或等于 |
> | 大于 |
>= | 大于或等于 |
5、等式运算符(有个重要知识点)
符号 | 含义 |
---|---|
== | 等于 |
!= | 不等于 |
=== | 全等 |
!== | 不全等 |
相等和全等的区别:
相等比较(==)参与比较的两个操作数必须逐位相等,结果才能为1。但是如果某些位为不定态x和高阻态z,则比较的结果为不定值。
而全等比较(===)则对这些不定值x或高阻值z也进行比较,两个操作数必须完全一致,其结果才为1 。
所以等于(==)一般用于比较不含不定态的操作数,全等( ===)可用于比较含不定态的操作数。
下面是等于(==)的真值表:
等号左边\\结果\\等号右边 | 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 |
例:
a=5'b 11xz0;
b=5'b 11xz0;
//a==b的值为不定值x
//a===b的值为1
6、缩减运算符
符号 | 含义 |
---|---|
& | 与 |
~& | 与非 |
| | 或 |
~| | 或非 |
^ | 异或 |
^~、 ~^ | 同或 |
缩减运算符与位运算符的逻辑一样,但缩减运算符是对单个操作数进行与、或、非的递推运算,它放在操作数的前面,将向量缩减为一个标量。
例:
reg[3:0] a;
b=&a; //等效为b=(a[0]&a[1]&a[2]&[3])
A=5'b11001;
//则有以下结果:
&A=0; //1&1&0&0&1=0
~&A=1; //!(1&1&0&0&1)=1;
|A=1; //1|1|0|0|1=1
~|A=0; //!(1|1|0|0|1)=0
^A=1; //1^1^0^0^1=1;
~^A=1; //1~^1~^0~^0~^1=1;
7、移位运算符
符号 | 含义 |
---|---|
>> | 右移 |
<< | 左移 |
>>> | 算术右移 |
<<< | 算术左移 |
A>>n 可以把操作数A右移n位,A<<n可以把操作数A左移n位。
如:
A=6'b 101101;
//A>>2的值为6'b 001011
//A<<2的值为6'b 110100
算术左移和算术右移是为了区分带符号的操作数,有以下规则:
左移:
算术左移与逻辑左移不论操作数有无符号,两者结果都一样,都是低位补0。
右移:
操作数为正整数时,算术右移与逻辑右移一样,都是高位补零。
操作数为负整数时,算术右移高位补1,逻辑右移高位补0 。
也就是常说的:有符号算术右移,高位需依次补足符号位的数。
例:
A=6'sb 101101; //定义6位的有符号操作数
//A<<2的值为6'b 110100
//A<<<2的值为6'b 110100
//两者一样
//A>>2的值为6'b 001011
//A>>>2的值为6'b 111011
//两者不一样,注意>>>时,符号位为1,所以高2位都要补1
8、指数运算符
指数运算符用两个星号“**”表示,一般底数为2。
例:
parameter WIDTH=16;
parameter DEPTH=8;
reg[WIDTH-1:0] memory[0:(2**DEPTH)-1]
//定义一个储存器,该储存器的每个元素的位宽都为16位
//可储存2^8个元素(256)
9、条件运算符
这个与c语言一样,用符号?:表示。
用法:
值=条件?表达式1:表达式2
条件成立则:值=表达式1
条件不成立则:值=表达式2
例:
integer a=1,b=2,c;
c=a>b?1:0;
//结果为c=0
10、位拼接运算符
通过两个花括号来进行位拼接,花括号里面的部分用逗号隔开用法:
信号1的某几位,信号2的某几位…信号n的某几位
例:
ina=3'b 111;
inb=4'b 1011;
inc=5'b 11001;
output[7] sum;
sum=ina[2:0],inb[2:0],inc[0];
//拼接后,sum=111_011_1
拼接运算符可以嵌套。例:
ina=3'b 111;
inb=4'b 1011;
inc=5'b 11001;
ina,inb,inc,ina//结果:111_1011_11001_111
4ina,inb[2:0],2inc[4:3],inb[2]
//等价与:ina,ina,ina,ina,inb[2:0],inc[4:3],inb[2],inc[4:3],inb[2]
//结果为:111_111_111_111_011_11_0_11_0
可以用位拼接来实现移位操作
我们知道一个整数乘2,就是整体左移1位;乘4就是整体左移2位;除2就是整体右移1位…
注意:
不是移位运算,移位运算位数不变,这里整体左移2位,则低位补2个零,总位数+2;整体右移2位,则舍去低位的两个0(整除2,则原来数的位1:0一定都是0),可以在高位补2个0让位数保持不变,或者可以不要管,直接让位数-2 。
则有:
input[5:0] a,b;
f=a*4+a/8;
//则可以用以下方法实现:
f=a,2'b00+3'b000,a[5:3]
11、运算优先级
优先级从高到底排序:
类别 | 运算符 |
---|---|
单目运算符 | +、-、!、~ 、&、~ &、|、~ |、^、~ ^、 ^~ |
指数运算 | ** |
算术运算符 | * 、/ 、% |
移位运算符 | << 、 >> 、 <<< 、>>> |
关系运算符 | <、<=、>、>= |
等式运算符 | ==、!=、 = ==、!= = |
位运算符 | & 、| |
逻辑运算符 | &&、|| |
条件运算符 | ?: |
位拼接运算符 | 、 |
以上是关于FPGAVerilog语法入门的主要内容,如果未能解决你的问题,请参考以下文章