1.Verilog HDL 简介
VerilogHDL是一种硬件描述语言,以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻功能。
Verilog语法分为可综合的语法和不可综合的语法,可综合的意思是可以综合成实际电路,同理不可综合就是不能形成实际的电路。
Verilog HDL 与 VHDL 的对比:
Verilog HDL | VHDL |
---|---|
语法自由,易学易用 | 语法严谨,上手较难 |
适合算法级,门级设计 | 适合系统级设计 |
代码简洁 | 代码冗长 |
发展较快 | 发展缓慢 |
2.Verilog HDL 语法基础
1.逻辑值
- 0:逻辑低电平,条件为假
- 1:逻辑高电平,条件为真
- z:高阻态,无驱动
- x:未知逻辑电平
2.算术运算符
- +, (加法,如assignc=a+b;即把a与b的和赋值给c)
- -,(减法,如assignc=a-b;即把a减b的差赋值给c)
- *,(乘法,如assignc=a*3;即让a和3相乘,结果赋值给c,但是一般不用乘号)
- /(除法,如assignc=a/2;即让a和2相除,结果赋值给c,一般也不用除号)
- %,(求模,或称为求余,要求%两的均为整型数据,5%3的值为2,一般用在测试文件)
注意
:在FPGA中不能直接用组合逻辑设计除法器、取余运算电路,结合时序逻辑,可能需要多个时钟周期。
常见位运算符和逻辑运算符的区别:
1.非门【~】【!】
- ~:位运算符【按位取反】 结果为十进制数(多bit数据) 【~的对象为进制数】
- !:逻辑运算符 结果为True 或False(单bit0或1)【!的参数为进制数 or 公式】
注意
~为按位 取反,意思是~110=> 001,而!是逻辑取反,要么为真,要么为假,只有0和1,逻辑取反是将2进制转换为十进制看是0 还是1,然后再取反,如果~11=>00,而!11=!3 = 0
2.与门【&】【&&】
- &:位运算符【按位与】 结果为十进制数(多bit数据) 【&的对象为进制数】
- &&:逻辑运算符 结果为True 或False(单bit0或1) 【&&的参数为进制数 or 公式】
3.或门【|】 【||】
- |:位运算符【按位或】 结果为十进制数(多bit数据) 【|的对象为进制数】
- ||:逻辑运算符 结果为True 或False(单bit0或1)【||的参数为进制数 or 公式】
3.常量与变量
3.1常量
格式
:[换算为二进制后位宽的总长度]['][数值进制符号][与数值进制符号对应的数值],例如,8'd171:位宽是8bit,十进制的171。
**[数值进制符号]**中如果是[h]则表示十六进制,如果是[o]则表示八进制,如果是[b]则表示二进制,[d]表示十进制,例如,
-
8'hab表示8bit的十六进制数ab,
-
8'0253表示8b的八进制数253;
-
8'b1010_1011表示8b的二进制数1010_1011(下划线增强可读性)。
[换算为过进制后位宽的总长度]:可有可无,verilog会为常量自动匹配合适的位宽。
当总位宽大于实际位宽,则自动在左边补0 ,总位宽小于实际位宽,则自动截断左边超出的位数。例如
'd7与8'd7:表示相同数值,8'd7换算为二进制就是8'b0000_0111,前面5位补0;
2'd7换算为二进制就是2'b11,超过2位宽的部分被截断。
如果直接写参数,例如100,表示位宽为32b的十进制数100,
3.2 变量
3.2.1 wire型
Wire 类型变量,也叫网络类型变量,用于结构实体之间的物理连接,如门与门之间,不能储存值,用连续赋值语句assign赋值,定义为wire [n-1:0] a ;
3.2.2 reg型
Reg 类型变量,也称为寄存器变量,可用来储存值,必须在always语句里使用。其定义为reg [n-1:0] a ; 表示n位位宽的寄存器,如reg [7:0] a;
3.2.3 memory型
可以用memory类型来定义RAM,ROM等存储器,其结构为reg [n-1:0] 存储器名[m-1:0],意义为m个n位宽度的寄存器。例如,reg [7:0] ram [255:0]表示定义了256个8位寄存器,256也即是存储器的深度,8为数据宽度。
4.规约运算符和按位运算符
以"&"操作符号为例子,"&"操作运算符有两种用途.既可以作为一元运算符(仅有一个参与运算的量),也可以作为 二元运算符(有两个参与运算的量)
当"&"作为一元运算符时表示规约与,&m,表示是将m中所有比特相与,最后的结果为1bit.
例如:
&4'b1111=1&1&1&1=1'b1
&4'b1101=1&1&0&1=1'b0
当"&"作为二元运算符时表示按位与,m&n是将m的每个比特与n的相应比特相与,在运算的时候要保证和n的比特数相等,最后的结果和m(n)的比特数相同。
例如;
4'b1010&4'b0101=4'b0000
4'b1101&4'b1111=4'b1101
因此,"~&","^","~^","|","~|"同理
5.逻辑运算符
以"&&"操作符为例,"&&"表示逻辑与,运算规则:逻辑与运算符号两边只有真或者假,非零表示真,零表示假,逻辑运算符两边都不为零则结果为1,否则为0。
6.关系运算符
a<b a小于b
a>b a大于b
a<=b a小于或等于b
a>=b 大于或等于b
注:关系运算符一般在条件判断时用到,例如if的判断语句,如果if后面接的判断语句是真,则返回1,否则返回0
7.移位运算符
移位运算符是二元运算符,左移符号为"<<"右移符号为">>",将运算符左边的操作数左移或右移指定的位数,用0来补充空闲位。
b<=<<1;即让的每一位都往左移动1位,结果赋值给b;
b<=>>2;即让的每一位都往右移动2位,结果赋值给b;
在应用以为运算符的时候一定要注意它的这个特性,那就是空闲位用0来填充,也就是说,一个二进制数不管原数值是多少,只要一直移位,最终全部会变为0。
例如:4'b1000>>3后的结果为4'b0001,4'b1000>>4的结果为4'b0000。
移位运算符在使用时,可代替乘法和除法,左移一位可以看成是乘以2,右移一位可以看成是除以2,但要注意位宽的拓展。
8.位拼接运算符
位拼接运算符由一对花括号加逗号组成"{,}",拼接的不同数据之间用",隔开。
将8b的、3bit的b、5bit的c按顺序拼接成一个16位的d,表示方法为:d:{a,b,c};
9.条件运算符
条件运算符,"? :",是一个三元运算符,即有三个参与运算的量,条件表达式的一般形式为:表达式1?表达式2:表达式3
执行过程是:当表达式1为真,则表达式2作为条件表达式的值,否则以表达式3作为条件表达式的值。
10.优先级
总的先级关系为:归约运算符>算数运算符>移位运算符>关系运算符>"=="和"!=">按位运算符>"&&"和"||">条件运算符
总的来说是一元运算符>二元运算符>三元运算符。
如果在编写代码的时候对这些关系容易混淆,最好的方式就是使用"()"增加优先级。
3.Verilog关键字
3.1.module
module关键字,代表一个模块 ,我们的代码写在这个关键字中间
module()
...
endmodule
3.2.input output
input关键字,模块的输入信号,比如input Clk,Clk是外面关键输入的时钟信号
output关键字,模块的输出信号,比如output[3:0]Led,其中[3:0]表示0~3共4路信号
inout模块输入输出双信号,数总线的通信中这种信号被广泛应用
wire 关键字,线性号 ,例如:wi re C1_CLk,其中C1_CLk就是wire类型的信号
reg 关键字,寄存器,和线信号不同,它可以在always中被赋值,经常用于时序逻辑中,比如reg[3:0]Led表示了一组寄存器
3.3. always
always()括号里面是敏感信号.这里的always@(posedg Clk)敏感信号是posedg Clk含义是在上升沿的时候有效,敏感信号 还可以negedg Clk含义是下降沿的时候有效, 这种形式一般时序电路都会用到.还可以是*这个符号,如果是一个 * 则表示一直是敏感的,一般是用于组合逻辑.
verilog
always(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 8'd0;
else if(cnt == CNT_MAX)
cnt <= CNT_MAX;
else
cnt <= cnt + 8'd1;
3.4. assign
assign是 用来给output,inout以及wire这些类型进行连线的, assign相当于一条连线, 将表达式右边的电路直接通过wire(线)连接到左边,左边信号必须是wire型(output和inout属于wire型).当右边变化了左边立马发生变化,方便用来描述简单的组合逻辑,如:
wire a,b,y:
assign y = a&b:
3.5. if...else...
if-else条件分支语句的作用是根据指定的判断条件是否满足来确定下一步要执行的操作。它在使用时可以采用如下三种形式:
(1)
verilog
if (<条件表达式>)
语句或语句块;
在if-else条件语句的这种使用形式中没有出现else项,这种情况下条件分支语句的执行过程是:如果指定的<条件表达式>成立(也就是这个条件表达式的逻辑值为"1"),
则执行条件分支语句内给出的"语句或语句块",然后退出条件分支语句的执行;如果<条件表达式>不球立(也就是条件的表达式的逻辑值为"0""x","z"),则不执行条
件分支语句内给出的"语句或语句块",而是直接退出条件语句的执行。这种写法如果在always块中表达组合逻辑时会产生latch,所以不推荐这种写法。
(2)
verilog
if (<条件表达式1>)
语句或语句块1;
else if (<条件表达式2>)
语句或语句块2;
......
else
在执行这种形式的if-else条件分支语句时,将按照各分支项的排列顺序对各个条件表达式是否成立做出判断,当遇到某一项的条件表达式成立时,就执行这一项所指定的语句或语句块;如果所有的条件表达式都不成立,则执行最后的else项。这种形式的if-else条件分支语句实现了一种多路分支选择控制。这种写法是我们在使用根据波形写代码的方法中最常用的一种写法。
(3)verilogHDL允许if-else条件分支语句的嵌套使用,但是不要嵌套太多层,也不推荐这种嵌套的写法,因为嵌套会有优先级的问题,最后导致逻辑混乱,if和else的结合混乱,代码也不清晰,如果写代码时遇到这种情况往往是可以将其合并的,最终写成(2)的形式。
verilog
if (<条件表达式1>)
if(<条件表达式2>)
语句或语句块1;
else
语句或语句块2;
else
语句或语句块3;
3.6. case...endcase
case分支语句是另一种用来实现多路分支控制的分支语句。与使用if-else条件分支语句相比,采用case分支语句来实现多路控制将显得更为方便与直观。case分支语句通常用于对微处理器指令译码功能的描述以及对有限状态机的描述。case分支语句有"case",casez,casex三种形式。
verilog
case (<控制表达式>)
<分支语句1>:语句块1;
<分支语句2>:语句块2;
<分支语句3>:语句块3;
...
<分支语句n>:语句块n;
default:语句块n+1;
endcase
<控制表达式>代表着对程序流向进行控制的控制信号:各个<分支表达式>则是控制表达式的某些具体状态取值,在实际使用中这些分支项表达式通常是一些常量表达式:
3.7. begin...end
3.8. parameter &localparam
- parameter :除了能在模块内部,还能作为实例化中采纳数传递的接口
- localparam:只能使用在模块内部,。Localparam多用于状态机状态的定义,如果没定义位宽,那么默认的32个bit。
例子:
verilog
module counter
#(
parameter CNT_MAX = 25'd24_999_999,
)
(
//端口列表
input wire sys_clk,
input wire sys_rst_n,
output wire led_out
);
//参数的定义
parameter CNT_MAX = 25'd24_999_999;//除了能在模块内部,还能作为实例化中采纳数传递的接口
localparam CNT_MAX = 25'd24_999_999;//只能使用在模块内部
//定义变量
reg [24:0] cnt;
//但参数的模块实例化例子
module counter
#(
.CNT_MAX (100),
)
counter_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.led_out(led_out)
);
endmodule
3.9. include&define
include 和 define 都是预处理命令,用于常量阐述定义的定义
3.10. 符号部分
3.Verilog中数值表示的方式
如果我们要表示一个十进制是180的数值,在Verilog中的表示方法如下:
二进制:8'b1011_0100;//其中"_"是为了容易观察位数,可以不写
十进制:8'd180
16进制:8'HB4
4.阻塞赋值"="和非阻塞赋值"<="
对于阻塞赋值和非阻塞赋值,是很多初学者容易困惑的地方.
学习FPGA和单片机最大的区别在于,学习FPGA时,必须时刻都有着时钟的概念,而在单片机中时钟相关性的比较差.
首先说非阻塞赋值,这个在时序逻辑电路中随处可见:
verilog
reg A;
reg B;
always@(posedg clk)
begin
A < = 1'b1;
B < = 1'b1
/**或者**
B < = 1'b1;
A < = 1'b1;
*****/
end
以上这段程序里,A和B是同时被赋值的,具体是说在时钟的上升沿来的时刻,A和B同时被置为1.而调换A和B的赋值顺序,也同样输出相同的结果.
接着来看阻塞赋值,表示会阻塞住,接下来看如下代码:
verilog
always@(posedg clk)
begin
A = 1'b1;
B < = 1'b1
end
看到,上面这个程序是阻塞和非阻塞的混合使用,一般教材是极力反对这种写法的。其实只要你理解了,有的时候这种用法还能帮上大忙。只不过,不理解的话乱用会导致时序违规。回到正题,
我们这么写是为了更好的理解阻寒赋值: 当时钟上升沿来临的时刻,首先 A 会被置 1,然后 B 寄存器再置 1。区别就是A和B 不再同时置 1。A 要比B 提前零点几纳秒。这样就出现了先后顺序。
这个过程还是在一个时钟内完成的,但是数据到达 B 寄存器相比上面两段程序晚了那么零点几纳秒!
当我们的时钟跑的比较慢的时候,比如 50M,一个周期有 20ns,那么这么短暂的延时基本可以忽略不计,但是随着设计的复杂,以及时钟速度的提高,这样的语句就要小心。
4.Verilog系统函数
Verilog语言中预先定义了一些任务和函数,用于完成一些特殊的功能,它们被称为系统任务和系统函数,这些函数大多数都是只能在Testbench仿真中使用,让我们更方便的进行验证.
'timescale 1ns/1ns //时间尺度预编译指令 时间单位/时间精度
时间单位和时间精度由值1、10和100以及单位s、ms、us、ns、和组成。
时间单位:定义仿真过程所有与时间相关量的单位。
仿真中使用"#数字"表示延时相应时间单位的时间,例#10表示延时10个单位的时间,即10ns。
时间精度:决定时间相关量的精度及仿真显示的最小刻度。
`timecale 1ns/10ps 精度0.01, #10.11表示延时10110ps
下面这种写法就是错误的,因为时间单位不能比精度小.
`timescale 100ps/1ns
主要的函数有如下,在支持verilog语法的编辑器中都会显示为高亮关键字
verilog
$display //打印信息,自动换行
$write //打印信息
$srobe //打印信息,自动换行,最后执行
$monitor //检测变量
$stop //暂停仿真
$finish //结束仿真
$time //时间函数
$random //随机函数
$readmemb //读文件函数
下面单独介绍它们的功能,并在ModelSim的Transcript界面中打印这些信息。
4.1 $display用于输出,打印信息
格式:
$display("%b+%b=%d",a,b,c);
//%b+%b=%d,格式控制,未指定时,默认十进制
//a,b,c 输出列表,需要输出信息的变量
//每次打印信息自动换行
%h或%H://以十六进制的形式输出%d或%D://以十进制的形式输出
%o或%O://以八进制的形式输出
%b或%B://以二进制的形式输出
4.2 $write 输出,打印信息
使用格式为
$write("%b+%b=%d\n",a,b,c) //%b+%b=%d,格式控制,未指定时,默认十进制,\n换行
4.3 $strobe输出,打印信息
verilog
`timescale 1ns/1ns
module tb_test()
reg [3:0] a;
reg [3:0] b;
reg [3:0] c;
initial begin
$strobe("strobe:%b+%b=%d",a,b,c);
a = 4'd4;
$display("display:%b+%b=%d",a,b,c);
b = 4'd6;
c = a+b;
end
endmodule
4.4 $monitor用于持续监测变量
使用格式:
$monitor("%b+%b=%d",a,b,c) //被监测变量发生变化触发打印操作,自动换行
verilog
`timescale 1ns/1ns
module tb_test()
reg [3:0] a;
reg [3:0] b;
reg [3:0] c;
initial begin
a = 4'd4;
#100;
b = 4'd6;
#100;
c = a+b;
end
initial $monitor("%b+%b=%d",a,b,c);
endmodule
4.5 finish 结束仿真,stop用于暂停仿真
ver
`timescale 1ns/1ns
module tb_test();
initial begin
$display("Hello ")
$display("word")
#100;
$display("stop simulation");
$stop; //暂停仿真
$display("Continue Simulation");
#100;
$display("Finish Simulation");
$finish; //结束仿真
end
endmodule
4.6 time 为时间函数, 返回64位当前仿真时间;random用于产生随机函数.返回随机数
verilog
`timescale 1ns/1ns
module tb_test();
reg [3:0] a;
always #10 a = $random;
initial $monitor("a=%d @time %d",a,$time);
endmodule
4.7 readmemb;读取二进制文件;readmemh:读取十六进制文件函数
使用格式:
$readmemb("<数据文件名>",<存储器名>);
$readmemh("<数据文件名>",<存储器名>);
verilog
`timesacle 1ns/1ns
module tb_test();
integer i;
reg [7:0] a [13:0];
initial begin
$readmemb("Test.txt",a)
for(i=0;i<=20;i=i+1) begin
#10
$write("%s",a[i]);
end
end
endmodule