Verilog HDL(Hardware Description Language)是在 C 语言的基础上发展起来的一种硬件描述语言(用它可以表示逻辑电路图、逻辑表达式、数字逻辑系统所完成的逻辑功能等)具有灵活性高、易学易用等特点。Verilog HDL 可以在较短的时间内学习和掌握,目前已经在 FPGA 开发/IC 设计领域占据绝对的领导地位。
逻辑电路中有四种值,即四种状态:
标识符(identifier)用于定义模块名、端口名、信号名等。Verilog 的标识符可以是任意一组字母、数字、$和_(下划线)符号的组合,但标识符的第一个字符必须是字母或者下划线,另外标识符是区分大小写。
在程序运行过程中,其值不能被改变的量称为常量,Verilog 常见的常量有数字、参数。
8'b10101100 //位宽为8的数的二进制表示, 'b表示二进制
8'ha2 //位宽为8的数的十六进制,'h表示十六进制
4'b10x0 //位宽为4的二进制数从低位数起第二位为不定值
4'b101z //位宽为4的二进制数从低位数起第一位为高阻值
12'dz //位宽为12的十进制数其值为高阻值
8'h4x //位宽为8的十六进制数其低四位值为不定值
-8'd5 //这个表达式代表-5的补码(用八位二进制数表示)
16'b1010_1011_1111_1010
在Verilog HDL中可以用parameter来定义常量,即用parameter来定义一个标识符代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可提高程序的可读性和可维护性。其说明格式如下:
parameter 参数名1=表达式,参数名2=表达式, …, 参数名n=表达式; //语法格式
parameter msb=7; //定义参数msb为常量7
parameter 参数可以定义在模块参数列表中,通过在例化模块时传入不同的值,以达到配置模块功能的目的。
与parameter类似,localparam也可以用来定义符号常量,但是localparam不能用在模块参数列表中。
Verilog中常用的变量类型有reg型、wire型
wire型即线型,可以把他理解为数字电路中的导线,只能在assign语句中对其赋值,其定义方法如下:
wire [n-1:0] 数据名1,数据名2,…数据名i; //共有i条总线,每条总线内有n位
wire [7:0] b; //定义了一个八位的wire型数据
wire [4:1] c, d; //定义了二个四位的wire型数据
wire型即寄存器类型,可以把他理解为数字电路中的寄存器(在综合时组合逻辑中的reg可能不会综合成寄存器),只能在always语句中对其赋值,其定义方法如下:
reg [n-1:0] 数据名1,数据名2,… 数据名i;
reg [3:0] regb; //定义了一个四位的名为regb的reg型数据
reg [4:1] regc, regd; //定义了两个四位的名为regc和regd的reg型数据
Verilog HDL通过对reg型变量建立数组来对存储器建模(可以将memory型理解为reg数组,在Verilog中只支持一维数组),数组中的每一个单元通过一个数组索引进行寻址,其定义方法如下:
reg [n-1:0] 存储器名[m-1:0]; //reg[n-1:0]定义了存储器中每一个存储单元的大小,[m-1:0]定义了该存储器中有多少个这样的寄存器([n-1:0]决定位宽,[m-1:0]决定个数)
reg [7:0] mema[255:0]; //定义了一个名为mema的存储器,该存储器有256个8位的寄存器,存储单元索引范围是0到255
mema[3]=0; //给memory中的第3个存储单元赋值为0。
在Verilog中wire和reg默认是无符号(unsigned)类型的,可以通过关键字signed定义有符号的wire和reg型变量(有符号变量负数采用补码表示),其定义方法如下:
reg signed [3:0] regb; //定义了一个四位的有符号reg型数据
wire signed [7:0] wireb; //定义了一个八位的有符号wire型数据
在使用有符号变量时需要注意以下几点:
在Verilog HDL语言中,算术运算共有下面几种:
Verilog HDL作为一种硬件描述语言,提供了以下五种位运算符:
在Verilog HDL语言中提供了三种逻辑运算符:
Verilog HDL语言中关系运算符共有以下四种:
Verilog HDL语言中有四种等式运算符:
在Verilog HDL中有两种移位运算符:
在Verilog HDL语言有一个特殊的运算符:拼接运算符{}。用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作,其使用方法如下:
{信号1的某几位,信号2的某几位,…,…,信号n的某几位}
Verilog 和 C 语言类似,都因编写需要定义了一系列保留字,叫做关键字(或关键词),如下是Verilog的所有关键字
其中常用的关键字如下
关键字 | 含义 |
---|---|
module | 模块开始定义 |
input | 输入端口定义 |
output | 输出端口定义 |
inout | 双向端口定义 |
parameter | 参数常量定义 |
localparam | 参数常量定义 |
wire | 线型变量定义 |
reg | 寄存器变量定义 |
always | 产生reg型变量语句的关键字 |
assign | 产生wire型变量语句的关键字 |
begin | 顺序块起始标志,当出现多条语句时需要写在begin end之间 |
end | 顺序块结束标志 |
posedge | 表示上升沿敏感 |
negedge | 表示下降沿敏感 |
case | 多条件分支语句起始标记 |
default | 多条件分支语句的默认分支 |
endcase | 多条件分支语句结束标记 |
if | f/else语句标记 |
else | if/else语句标记 |
for | for语句标记 |
endmodule | 模块结束定义 |
Verilog模块以以module开始,endmodule结束,其格式如下:
//模块开始
module 模块名 #(
//模块参数,模块参数非必选项
parameter 参数1,
parameter 参数2,
......,
parameter 参数n
)
(
//输入参数
input 输入端口1,
input 输入端口2,
......,
input 输入端口n,
//双向参数
inout 双向端口1,
inout 双向端口2,
......,
inout 双向端口n,
//输出参数
output 输出端口1,
output 输出端口2,
......,
output 输出端口n
)
//内部参数变量定义
//内部功能定义
//模块结束
endmodule
1、
if(表达式)
语句
2、
if(表达式)
语句1
else
语句2
3、
if(表达式1)
语句1;
else if(表达式2)
语句2;
else if(表达式3)
语句3;
........
else if(表达式m)
语句m;
else
语句n;
在if和else后面可以包含一条语句,也可以包含多条语句,当包含多条语句时需要用用begin和end将多条语句组合成一个复合语句。
if(a>b) begin
out1<=int1;
out2<=int2;
end
else begin
out1<=int2;
out2<=int1;
end
case(控制表达式)
条件1 : 语句1;
条件2 : 语句2;
条件3 : 语句3;
......
条件n : 语句n;
default : 语句n+1;
endcase
当控制表达式的值与分支条件的值相等时(分支条件必须是常量,且不能与其他分支相同,位宽也必须与控制表达式的位宽一致),就执行分支条件后面的语句,如果所有分支都不匹配,就执行default后面的语句(如果没有default则此时case不执行任何分支的语句)
在Verilog HDL中存在着四种类型的循环语句,用来控制执行语句的执行次数,不过Verilog在综合时只能综合有限次数的循环语句。
1、
//表达式用于指定循环次数,一般为常量
repeat(表达式)
语句;
2、
repeat(表达式) begin
多条语句
end
1、
while(表达式)
语句
2、
while(表达式) begin
多条语句
end
1、
for(表达式1; 表达式2; 表达式3)
语句
2、
for(表达式1; 表达式2; 表达式3) begin
语句
end
其执行流程相当于下面的while语句:
表达式1;
while(表达式2) begin
语句;
表达式3;
end
assign语句和always语句是Verilog中的两个基本语句,使用频率很高。
assign语句使用时不能带时钟,只能生成组合逻辑电路,在assign语句中只能对wire型变量赋值。
always语句可以带时钟,也可以不带时钟,不带时钟时生成组合逻辑,带时钟时生成时序逻辑,在always语句中只能对reg型变量赋值
task和function说明语句分别用来定义任务和函数,利用任务和函数可以把一个很大的程序模块分解成许多较小的任务和函数便于理解和调试
function [automatic] [返回值宽度] 函数名;
输入端口列表;
[变量声明语句];
begin
语句;
......
end
endfunction
function:表示函数开始。
automatic:声明函数未可重入,表示函数内定义的变量默认为动态的,在调用函数时单独分配,可以避免在多个地方同时调用时出现结果未知的情况,但是会消耗更多资源。
[返回值宽度]:它是是可选的,若未指定则默认未1bit。
函数名:函数名是函数的名字,调用时通过此名字来调用,同时还会在函数内部定义一个同名的变量用于返回函数的返回值。
输入端口列表:一个函数至少包含一个输入端口,不能有输出端口和双向端口。
[变量声明语句]:用于声明函数内部使用的变量。
endfunction:表示函数结束。
function的调用格式如下:
函数可以在always中被调用,也可以在assign被调用,函数调用时只能做右值,不能单独作为一条语句
变量 = 函数名(表达式1[, 表达式2, ......])
注意:
在函数中使能实现组合逻辑,不能出现延时语句(如#200),不能出现敏感事件控制语句(如wait),不能出现过程块语句(如always)。
task [automatic] 任务名;
[输入端口列表];
[输出端口列表];
[双向端口列表];
[变量声明语句];
begin
语句;
......
end
endtask
task:表示任务开始
[automatic]:表示任务内定义的变量默认为动态的,在调用任务时单独分配
任务名:任务的名称,在always中可以通过任务名调用任务
[输入端口列表]:任务可以包含0个或多个输入端口
[输出端口列表]:任务可以包含0个或多个输出端口
[双向端口列表]:任务可以包含0个或多个双向端口
[变量声明语句]:声明任务内部所需要使用的变量
endtask:表示任务结束
任务调用格式如下:
任务只能在过程语句中被调用(如always),其调用格式如下:
任务名([表达式1], [表达式2], ......]);
注意:
虽然在任务中可以使用延时语句(如#200),敏感事件控制语句(如wait),但是使用了这些语句的任务无法综合,因为Verilog只能综合组合逻辑电路的任务。
在Verilog中有两种赋值方式:
下面以一个LED流水灯为例展示Verilog 的程序框架
`timescale 1ns / 1ns
//module表示模块开始
module flow_led #(
//参数列表
parameter LED_CHANNEL = 4, //LED数量
parameter COUNT_WIDTH = 25, //内部计数器宽度
parameter COUNT_PERIOD = 25_000_000 //计数器最大周期,决定LED多久变化依次
)
(
//输入参数列表,输入参数列表只能式wire
input sys_clk, //时钟
input sys_rst_n, //复位,低电平有效
//双向(inout)参数列表,双向参数只能是wire,这里暂未使用
//输出参数列表,输出参数可以是wire可以是reg,默认是wire
output reg [LED_CHANNEL-1:0] led //LED
);
//周期计数器
reg [COUNT_WIDTH-1:0] count;
//一个周期计数器,计数范围为0~COUNT_PERIOD-1,即周期为COUNT_PERIOD
//always用于产生一个对reg型变量进行赋值的语句块
//posedge表示关注信号上升沿
//@表示敏感变量表条件成立执行一次
//begin/end产生一个顺序块
//<=用于赋值时是非阻塞式赋值,执行时先计数右值,并不会立即赋给变量,需要等语句块结束后才赋给变量,因此需要在下一个时钟沿才能读取到这时钟沿赋的值
always @(posedge sys_clk) begin
if(!sys_rst_n)
count <= 0;
else if(count < (COUNT_PERIOD - 1))
count <= count + 1;
else
count <= 0;
end
//控制LED输出
always @(posedge sys_clk) begin
if(!sys_rst_n)
led <= 1;
else if(count == (COUNT_PERIOD - 1))
led <= {led[LED_CHANNEL-2:0],led[LED_CHANNEL-1]};
end
//endmodule是模块结束标记
endmodule