verilog语法

综合:编译RTL代码,从库里选择用到的门器件,把这些器件按照“逻辑”搭建成电路

不可综合:找不到对应的器件来实现相应的代码
在设计的时候要确保所写的代码是可以综合的,

下面是不可综合或者不推荐使用的代码

代码 要求
initial 严禁在设计中使用,只能在测试文件中使用
task/function 不推荐在设计中使用,在测试文件中可用
for 在设计中、测试文件中均可使用。但在设计中多数会将其用错,所以建议在初期设计时不使用,熟练后按规范使用
while/repeat/forever 严禁在设计中使用,只能在测试文件中使用,产生激励信号
integer(整数) 不推荐在设计中使用
三态门 内部模块不能有三态接口,三态门只有顶层文件中才使用。三态门目的是为了节省管脚
casex/casez (设计代码内部不能有X态和Z态,因此casez、casex设计时不使用)待定
force/wait/fork 严禁在设计中使用,只能在测试文件中使用
#n(延迟多久) 严禁在设计中使用,只能在测试文件中使用

推荐使用的代码及其说明

代码 备注
reg/wire 设计中所有的信号类型定义,只有reg和wire,always中使用reg assign中使用wire
parameter 设计代码中所有的位宽、长度、状态机命名等、建议都用参数表示,阅读方便并且修改容易
使用方式如下:
module a (#parameter SIZE = 6,WIDTH = 8)(input .. ,output ..);
endmodule
assign/always 程序块主要部分
组合逻辑格式为:
always@(*) begin
end
或者用 assign

时许逻辑格式为:
always@(posedge clk or negedge rst) begin
if(rst==1’b0) begin
/*CODE*/
end
else begin
/*CODE*/
end
end
时许逻辑中,敏感列表一定是clk的上升沿和复位的下降沿,最开始必须判断复位
if else 和case alway里面的语句,使用if else 和case两种方法用来作选择判断,可以完成全部设计
算术运算符
(+、-、*、/、%)
可以直接综合出相对应的电路。但除法和求余数运算的电路面积一般比较大,不建议直接使用除法和求余数
乘法电路是由多个加法电路实现的,乘法电路比加法电路更大
赋值运算符
(=,<=)
时序逻辑用“<=”,组合逻辑用“=”(时序逻辑也可以用,但是会阻碍),其他情况不存在
关系运算符
(==,!=,>,<,>=,<=)
逻辑运算符
(&&,
位运算符
(~,|,^,&)
移位运算符
(<<,>>)
拼接运算符
({})

模块

模块(module)是verilog的基本描述单元,模块相当于一个原理图,例化相当于制造这个模块
例化模块:推荐按名字关联,按位置关联

模块A
1
2
3
module A(...);

endmodule
模块B例化模块A
1
2
3
4
5
6
`include "A"
module B(...);

A a();

endmodule

信号类型

verilog HDL的信号类型有很多种,主要包括两种数据类型:线网类型、寄存器类型。在进行工程设计的过程也只会使用到这两个类型的信号。

信号位宽

wire [7:0] aaa;//位宽为8

线网类型 wire

通常用assign进行赋值
如果没有定义线宽范围,缺省值为1
信号数据类型没有定义,默认wire类型

寄存器类型reg

wire和reg的区别

reg 型信号并不一定生成寄存器。如下面代码1所示;
可以这么使用,在模块中使用·always设计的信号都定义为reg型,其他信号都定义为wire型;

代码1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
always @(\*)begin	
if(cnt2==0) begin
x = 475_000;
end
else if(cnt2 == 1) begin
x = 425_000;
end
else if(cnt2 == 2) begin
x = 350_000;
end
else if(cnt2 == 3) begin
x = 250_000;
end
else if(cnt2 == 4) begin
x = 100_000;
end
else if(cnt2 == 5) begin
x = 90_000;
end
else if(cnt2 == 6) begin
x = 80_000;
end
else if(cnt2 == 7) begin
x = 40_000;
end
else if(cnt2 == 8) begin
x = 20_000;
end
else begin
x = 10_000;
end
end

综合成组合电路,对X进行设计,综合出的电路里面是没有寄存器的,但是他在always中使用,所以还是要reg定义,虽然没有生成寄存器,但是还是要使用寄存器类型,这也是语法识别出来必须这样子。

代码2
1
2
3
4
5
ass ass1(
.clk (clk),
.rst (rst),
.out (out)
);

代码2是例化代码,out是例化模块输出,由于顶层模块out不是always产生的,而是例化产生的,因此顶层out要定义成wire型

程序语句

描述功能语句就会使用 assign always

assign语句

assign 语句是连续赋值语句,一般是将一个变量的值不间断的赋值给另一个变量,两个变量之间就类似于被导线链接在了一起,习惯上当做连线用。
assign与assign之间是并行的,独立的

always(总是)语句

always语句是条件循环语句,执行机制是:对一个称为敏感变量表(敏感列表)发生条件满足事件后驱动程序执行
当敏感事件的条件满足时,就执行一次程序语句,敏感列表中信号,一定会包含当前整个always用到的输入信号。
当always很复杂,有很多敏感信号,可能会遗漏,为了避免这种情况可以用“*”来代替,这个“*”是指“程序语句”中所有的条件信号,如下代码,“*”代表 a、b、d、sel,不包含c

1
2
3
4
5
6
7
8
always @(*) begin
if(sel == 0) begin
c = a + b;
end
else begin
c = a + d;
end
end

这种条件信号变化结果称为“组合逻辑”

always有时钟信号,由1变0或者由0变1的瞬间执行一下程序代码,输出才变化,其他时间输出不变的,称为“时序逻辑电路”
信号边沿触发,既信号上升沿或下降沿才变化的always,被称为“时序逻辑”
注意:识别信号是不是时钟不是看名字,而是看这个信号放在哪里 ,只有放在敏感列表并且是边沿出发的才是时钟。而信号rst是复位信号,同样也不是看名字来判断,而是放在敏感列表中并且同样边沿出发。

设计时需要注意以下几点(有待参考)
组合逻辑的always语句中敏感变量必须写全,或者用“*”代替。
组合逻辑器件的赋值采用阻塞赋值“=”,时许逻辑器件赋值语句采用非阻塞赋值“<=”,原因 阻塞赋值和非阻塞赋值

数字进制

verilog中数字表示方式,最常用的格式是:<位宽>’<基数><数值>,如 4'b1001;
位宽:描述数值转换成二进制数后存储需要占用的位数,位宽是一个十进制的整数,例如4'b1001中4就是位宽。如果没有该项可以通过常量值的进行推断,例如 'b10010 可推断位宽为 5。

基数:表示数值是用多少进制数显示出来。 基数的表达形式有: b、B、o、O、d、D、h、H 分别表示 二进制、十进制、八进制、十六进制。如果没有此项默认为十进制数,例如,二进制的4'b1001 可以写成十进制 4'd9等,还可以不写基数直接写成9。 只要底层二进制相同,无论携程十进制、八进制、十六进制都是表示同样的数字

数值:是由基数(表示用什么进制表示)所决定的表示常量真实值的一串ASCII码
如果基数定义为b或B,数值可以是0、1、x、X(不定态)、z、Z(高阻态)
如果基数定义为o或O,数值可以是0、1、2、3、4、5、6、7
如果基数定义为h或H,数值可以是0、1、2、3、4、5、6、7、9、A、B、C、D、E、F、a、b、c、d、e、f
如果基数是d或D,数值符可以是任意的十进制数0到9,但不可以是x或z

如果出现位宽小于数值,那么数据大小以位宽为准,例如:
4'b001001 =>4'b1001

如果出现位宽大于数值,则高位补0 例如:
32'h 12 => 32'h00000012

二进制是基础

二进制表示小数,转到其他文章

不定态

数字电路只有高电平和低电平,分别是1和0.但代码中经常能看到x和z,如1’bz。x和z是什么电平,没有实际的电平来对应两者,x和z更多地是用来表示设计者的意图或者用于仿真目的** (转换成实际电路就会变成1或0)。告诉仿真器和综合器如何解释这段代码。
X态:不定态,常用于判断条件,告诉综合工具设计者不关系它的电平是多少,0或1都可以

代码,这里只是语法上面的设计意图
1
2
3
4
5
6
7
8
9
10
11
always(posedge clk or negedge rst) begin
if(rst == 1'b0) begin
dout <= 0;
end
else if(din == 4'b10x0) begin //4'b1010 或 4'b1000 都可以满足条件
dout <= 1;
end
else begin

end
end
仿真器或综合器生成如下代码,再去生实际电路
1
2
3
4
5
6
7
8
9
10
11
always(posedge clk or negedge rst) begin
if(rst == 1'b0) begin
dout <= 0;
end
else if(din == 4'b1010 || din == 4'b1000) begin //4'b1010 或 4'b1000 都可以满足条件
dout <= 1;
end
else begin

end
end

仿真过程中有些信号产生了不定态,设计者就要认真分析这个不定态是否合理。如果真的不关心是0还是1,那么可以不解决。建议所有信号都处于已知状态。可以减少思考时间

高阻态

Z态,高阻态。表示不驱动该信号(既不给0也不给1)(FPGA内部信号不会这样,内部一般都是固定值),通常用于三态门接口当中。
三态门做的接口,既做输入也做输出,是双向接口,一般硬件电路中会将该线接上一个上拉电阻(弱上拉)或下拉电阻(弱下拉)。
FPGA设计中是如何做到“不驱动”这一行为呢?FPGA内部有三态门。

在verilog中三态门代码实现

1
2
assign data = (wr_en == 1)?wr_data:1'bz;
assign rd_data = data;

综合器看到这两行代码则知道要合成三态门了,高阻z的作用在在于此,硬件上用三态门是为了减少管脚,而在FPGA内部没有必要减少连线,所以使用三态信号是没有意义的。
建议设计时不要在FPGA内部使用高阻态“z”,没有必要给自己添加“思考”麻烦。如果设计中使用了高阻态也不会报错,可以实现功能。
高阻态“z”表示不驱动总线,实际电路就是高电平或者低电平。
高阻态在语法上表现一个行为,硬件上有对应电路

算术运算符

分类 功能 代码 电路示意图 备注
加法器 always@(*) begin
c = a+b;
end
逻辑运算都是有与门、或门等门电路搭建起来的电路,
1位加法器 C = A ^ B cout(进位) = A && B
减法器 always@(*) begin
c = a-b;
end
C = A & B
乘法器 always@(*) begin
c = a*b;
end
二进制运算中,乘法运算实质上就是加法运算,
1111\*111 = (1111) + (11110) + (1111000)
所以乘法器会比加法器消耗的资源多
除法器 always@(*) begin
c = a/b;
end
二进制运算中,除法和求余涉及到加法、减法和移位等运算,
所以除法和求余数电路资源都非常大,设计时要经历避免除法和求余。
一定要用到除法,尽量让除数为2的N次方,如2、4、8、16等。
a/2就是a向右移动1位
a/4就是a向右移动2位
a/8就是a向右移动3位
移位运算是不消耗资源的
求余器 余数 always@(*) begin
c = a%b;
end

可以直接写的算术运算符:+-*(对应硬件电路比较小);除法和求余就不能直接写,对应得硬件电路比较大;慎用除法

加法运算符号

verilog代码中可以直接使用符号“+”;组合逻辑

1
assign C = A + B;

减法运算符

1
C = A - B;

乘法运算符

1
C = A *B

多位二进制的乘法与十进制计算相同

1
2
3
4
5
6
7
8
      1 1
x 1 0 1
------------
1 1
0 0
1 1
------------
1 1 1 1

求余和除法运算

没有直接的除法器,仿真测试中,是可以使用除法和求余,不用综合成电路,实际设计中,尽量不写

信号位宽

写代码时。需要注意信号的位宽,最终的结果取决于 “=” 号左边信号的位宽,保存低位,丢弃高位

1
2
3
4
5
6
7
8
9
wire c;
wire [1:0] d;
wire [2:0] e;
wire [2:0] f;

assign c = 1'b1 + 1'b1; //c = 1'b0
assign d = 1'b1 + 1'b1; //d = 2'b10
assign e = 1'b1 + 1'b1; //e = 3'b010
assign f = 1 + 1; //f = 3'b010

信号c的位宽为 1 位,运算结果最终保留 1 位,c = 1'b0
信号d的位宽为 2 位,运算结果最终保留 2 位,d = 2'b10;
信号e的位宽为 3 位,运算结果最终保留 3 位,e = 3'b010;
“1”默认是32位,1 + 1 的结果也是32位,由于f的位宽只有3位,所以运算的结果可以保留低3位,f = 3'b010

减法运算也是相同道理

1
2
3
4
5
6
7
8
9
wire c;
wire [1:0] d;
wire [2:0] e;
wire [2:0] f;

assign c = 0 - 1; //c = 1'b0 0-1 未写位宽默认是32位位宽
assign d = 0 - 1; //d = 2'b10
assign e = 0 - 1; //e = 3'b010
assign f = 0 - 1; //f = 3'b010

0 - 1得到的二进制是1111111...,保存结果取决 ”=“ 号左边信号的位宽。
c的位宽有1位,保留低1位,c的值1'b1;
d的位宽有2位,保留低2位,d的值2'b11;
e的位宽有3位,保留低3位,e的值3'b111;
f的位宽有4位,保留低4位,e的值4'b1111;

乘法也是如此,结果取决于等号左边信号的位宽,保存低,丢弃高位。

1
2
3
wire [4:0] h;

assign h = 2'b11 * 3'b101; //h结果 5’b01111

2'b11 * 3'b101值是4'b1111
h该信号有5位,4'b1111赋给5位信号,结果高位补0,结果位5'b01111

补码由来

加法出现进位的时候,计算的结果是不正确的,只有保留了进位计算的结果才是正确的

二进制减法

1
2
3
4
5
、 、 
0 0//二进制减法
- 0 1
------
1 1 1

二进制减法结果是负数结果,就要用取反加一变成正数,得到负数也是取反加1,负数的表示方式是从:
1111:-1
1110:-2
1101:-3
1100:-4
1011:-5
1010:-6
1001:-7
1000:-8
10111:-9
10110:-10

逻辑运算符

类型 情况 功能 verilog代码 电路示意图 备注
与门 1位逻辑与
(符号:&&)
A和B都为1,
C位1,否则0
reg A,B;
always@(*)begin
C = A&&B;
end
以后换成链接 注意:FPGA支持多输入的与门,
例如四输入与门,输入可为ABCD,
输出为E,当ABCD同时为1时,
E为1。
与门 多位逻辑与
(符号&&)
A或B都不为0时
C为1,否则为0;
reg [2:0] A,B,C;
always@(*) begin
C = A&&B
end
综合电路会是如下操作:
A是多位,会先对A的多位
进行逻辑或,B也是如此
然后再对A和B进行与运算
以后换成链接 多位信号之间的逻辑与,
很容易引起歧义,设计最好不要用
多位数的逻辑与。
如果要实现上面功能,
建议代码改为如下:
always@(*)begin
C=(A!=0)&&(B!=0);
end
或门 1位逻辑或
(符号||)
A和B其中1个为1,
C为1;否则C为0;
reg A,B;
always@(*) begin
C = A||B;
end
注意:FPGA支持多输入的或门
例如四输入或门,输入可为ABCD,
输出为E,当ABCD任意一个输入
为1时,E为1;
或门 多位逻辑或
(符号:||)
A和B其中1个非0,
C为1;否则C为0;
reg[2:0]A,B,C;
always@(*)begin
C = A||B;
end

verilog HDL语言中逻辑运算符,分别是:

&&:逻辑与;
||:逻辑或;
!:逻辑非。

与、或:分为1位的逻辑与、或和多位的逻辑与、或;