Verilog HDL基础概念详解
Verilog HDL 基本要素
常量
Verilog 中的常量(Constant) 有三种: 整数、实数、字符串
一些整数常数:
8'b11000101 // 宽度为8位的二进制数,数值为 11000101, 等于十进制的197
8‘h8a // 宽度为8位的十六进制,数值为8a,等于十进制的138
5'027 // 宽度为5位的八进制,数值为27,等于十进制的23
4'd10 // 宽度为4位的十进制,数值为10
如果没有明确指明,那么默认是十进制数
变量声明与数据类型
图例
数据类型可以是 net 型、variable 型
net 型变量
net 型相当于硬件电路中各种物理连接,其特点是输出的值紧跟输入值的变化而变化
wire 是最常用的 net 型变量,Verilog HDL 模块中的输入、输出信号在没有明确指定数据类型时,都被默认为 wire型
wire 型信号可以用作任何表达式的输入,也可以用作 assign 语句和实例元件的输出,如Z,其中0表示低电平、逻辑0;1表示高电平、逻辑1
Z表示高阻态。如果没有赋值,默认为高阻态Z
variable 型变量
variable 型变量是可以保存上次写入数据的数据类型,一般对应硬件上的一个触发器或锁存器等存储元件,但并不是绝对的,在综合器综合的时候,会根据其被赋值来具体确定是映射成连线还是映射为存储元件
variable 型变量也包括多种类型
variable 型变量必须在过程语句(inital 或 always )中实现赋值,这种赋值方式称为过程赋值
向量
变量声明格式中的位狂如果为1,那么对应的变量为标量,如果不为1,那么对应的变量为向量,默认为标量。向量的位宽用下面的形式定义
[MSB:LSB]
冒号左边的数字表示向量的最高有效位MSB(Most Significant Bit),冒号右边的数字表示向量的最低有效位LSB(Least Significant Bit)
例如:
wire [3:0] bus; // 4位的 wire 型bus,其中bus[3]是最高位,bus[0]是最低位
reg [31:5] ra; // 27位的reg型向量ra,其中ra[31]是最高位,ra[5]是最低位
reg [0:7] rc; // 8位的reg型向量rc, 其中 rc[0]是最高位,rc[7] 是最低位
向量有两种,一种是向量类向量,另一种是标量类向量,可以使用关键字区分
wire vectored [7:0] databus; // 使用关键字 vectored,表示是向量类向量
reg scalared [31:0] rega; // 使用关键字 scalared,表示是标量类向量
如果没有明确指出,那么默认是标量类向量,对标量类向量可以任意选中其中的一位或相邻几位,分别称为位选择(bit-select) 或域选择(part-select)
A = rega[6]; // 位选择,将向量 rega 的其中一位赋值给变量A
B = rega[5:2]; // 域选择,将向量 rega 的第5、4、3、2 位赋值给变量B
存储器可看作二维的向量。如下就是一个存储器的定义,定义了一个深度为64,每个元素宽度为32bit的存储器
reg [31:0] mem[63:0]; // mem 是深度为64,字长为 32bit的存储器
运算符
Verilong HDL 中定义的运算符包括: 算术运算符、逻辑运算符、位运算符、关系运算符、等式运算符、缩位运算符、移位运算符、条件运算符和拼接运算符
算法运算符
运算符 | 含义 |
---|---|
+ | 加法 |
– | 减法 |
* | 乘法 |
/ | 除法 |
% | 求余 |
逻辑运算符
运算符 | 含义 |
---|---|
&& | 逻辑与 |
! | 逻辑非 |
位运算符
运算符 | 含义 |
---|---|
~ | 按位取反 |
& | 按位与 |
^ | 按位异或 |
~^ 或 ^~ | 按位同或 |
关系运算符
运算符 | 含义 |
---|---|
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
等式运算符
运算符 | 含义 |
---|---|
== | 等于 |
!= | 不等于 |
=== | 全等 |
!== | 不全等 |
缩位运算符
运算符 | 含义 |
---|---|
& | 与 |
~& | 与非 |
| | 或 |
~| | 或非 |
^ | 异或 |
~^ 或^~ | 同或 |
移位运算符
运算符 | 含义 |
---|---|
>> | 右移 |
<< | 左移 |
条件运算符
运算符 | 含义 |
---|---|
?: | 条件运算 |
位拼接运算符
运算符 | 含义 |
---|---|
{ } | 拼接 |
总结
(1) 等式运算符中的"" 与 "=" 的区别是: 对于"“运算,参与比较的两个操作数必须逐位相等,其结果才为1,如果某些值是不定态X或高阻态Z,那么得到的结果是不定值X;而对于”="运算,则要求对参与的操作数中为不定态X或高阻态Z的位与进行比较,两个操作数也必须完全一致,其结果才为1,否则结果为0
reg [4:0] a = 5'b11x01
reg [4:0] b = 5'b11x01
针对上面的 a、b,"ab"的返回结果为X,而"a=b"的返回结果为1
(2) 缩位运算符与位运算符的运算符号、逻辑运算法则都是一样的,但是缩位运算符是对单个操作数进行与、或、异或的递推运算,它放在操作数的前面,能够将一个矢量减为一个标量
reg [3:0] a;
b = &a; // 等效于 b = ((a[0] & a[1]) & a[2]) & a[3]
而位运算需要对两个操作数对应位分别进行逻辑运算,例如
wire [3:0] a = 4'b0011;
wire [3:0] b = 4'b0101;
那么 a & b = 4'b0001, a | b = 4'b0111
(3) 位拼接运算符: 用来将两个或多个信号的某些位拼接起来
{比特序列0,比特序列1,...}
例如,在进行加法运算时,可将和与进位输出拼接在一起使用
input [3:0] ina,inb; // 加法输入
output [3:0] sum; // 加法的和
output count; // 进位
assign{cout, sum} = ina + inb; // 将和与进位拼接一起
位拼接还可以用来重复信号的某些位,其格式如下
{重复次数{被重复数据}}
利用上面的功能,可以实现对信号的符号扩展,例如
// 将 Data 的 符号进行扩展, s_data = {Data[7], Data[7], Data[7], Data[7], Data[7], Data}
wire [7:0] Data;
wire [11:0] s_data;
s_data = {{4{Data[7]}}, Data};
(4) 运算符优先级
Verilog HDL 行为语句
过程语句
Verilog 定义的模块一般包括过程语句,过程语句有两种:inital、always
其中initial 常用于仿真中初始化,其中语句只执行一次,而always中语句则是不断重复执行。此外,always 过程语句是可以综合的,initial 过程语句是不可综合的
always 过程语句
always 过程语句通常带有触发条件的,触发条件写在敏感信号表达式中,敏感信号表达式又称为事件表达式或敏感信号列表,当该表达式中变量的值改变时,就会引发其中语句序列执行。因此,敏感信号表达式中应列出影响块内取值的所有信号
敏感信号表达式
如果有两个或两个以上的敏感信号时,它们之间使用 “or” 连接,此外还是以32位加法器为例,使用assign直接赋值,其实也可以使用always 过程语句实现
只要被加数in1、加数in2中的任何一个改变,都会触发always 过程语句,在其中进行加法运算。这里有两个敏感信号in1、in2,使用"or" 连接
modeule add32(input wire[31:0] in1, input wire[31:0] in2, output reg[31:0] out);
always @(in1 or in2) // 使用always 过程语句实现加法
begin
out = in1 + in2
end
endmodule
敏感信号列表中的多个信号可以使用逗号隔开,上面的32位加法器可以修改为如下形式
module add32(input wire[31:0] in1, input wire[31:0] in2, output reg[31:0] out);
always @ (in1, in2) // 多个敏感信号使用逗号分隔
begin
out = in1 + in2
end
endmodule
敏感信号列表也可以使用通配符"*", 表示在该过程语句中的所有输入信号变量,上面的32位加法器可以修改为如下形式:
module add32(input wire[31:0] in1, input wire[31:0] in2, output reg[31:0] out);
always @(*) // 使用通配符表示过程语句中的所有输入信号变量
begin
out = in1 + in2
end
endmodule
组合电路与时序电路
敏感信号可以分为两种类型
前一种一般对应组合电路,如上面给出的加法器的例子,后一种一般对应时序电路。对于时序电路,敏感信号通常是时钟信号
,Verilog HDL 提供了posedeg、negedge 两个关键字来描述时钟信号
posedeg 表示以时钟信号的上升沿作为触发条件,negedge 表示以时钟信号的下降沿作为触发条件。还是以32位加法器为例,可以为其添加一个时钟同步信号,如下
module add32(input wire clk, // 增加了一个时钟输入信号
input wire[31:0] in1,
input wire[31:0] in2,
output reg[31:0] out);
always @ (posedge clk) // 在时钟信号的上升沿会触发always 中的语句
begin
out = in1 + in2;
end
endmodule
在时钟信号的上升沿,才会进行加法运算,这一点与前面的加法器不同,也就是当被加数in1、加数in2变化时,并不会立即改变输出out,而是要等待时钟信号的上升沿
initial 过程语句
initial 过程语句的格式
initial
begin
// 语句序列
end
initial 过程语句不带触发条件,并且其中的语句序列只执行一次。initial 过程语句通常用于仿真模块中对激励向量的描述,或用于给寄存器赋初值,它是面向模拟仿真的过程语句,通常不能被综合
如下是initial 过程语句的一个例子,用于给存储器mem 赋初值
initial
begin
for (addr = 0; addr < size; addr = addr + 1) // for 是一种循环语句
end
赋值语句
持续赋值语句
assign 为持续赋值语句,主要用于对wire型变量的赋值。如上文中加法器的例子
过程赋值语句
在 always、initial 过程中的赋值语句称为过程赋值语句,多用于对reg型变量进行赋值,分为非阻塞赋值和阻塞赋值两种方式
非阻塞赋值(Non-Blocking)
赋值符号为"<=",例如
b <= a
非阻塞赋值在整个过程语句结束时才会完成赋值操作,即b的值并不是立刻改变的
阻塞赋值(Blocking)
b = a
阻塞赋值在该语句结束时就立即完成赋值操作,即b的值在这条语句结束后立刻改变
如果在一个块语句中,有多条阻塞赋值语句,那么在前面的赋值语句没有完成之前,后面的语句就不能被执行,仿佛被阻塞了一样,因此称为阻塞赋值方式
在always 过程块中,阻塞赋值可以理解为赋值语句是顺序执行,而非阻塞赋值可以理解为赋值语句是并发执行的。如下面,在一个过程块中,阻塞式赋值与非阻塞式赋值只能使用其中一种
条件语句
if-else 语句
if-else 语句的格式有如下3种
(1) if (表达式) 语句序列1; // 非完整性IF语句
(2) if (表达式) 语句序列1; // 二重选择的IF语句
else 语句序列2;
(3) if (表达式1) 语句序列1; // 多重选择的IF语句
else if (表达式2) 语句序列2;
else if (表达式3) 语句序列3;
....
else if (表达式n) 语句序列n;
else 语句序列n+1;
上述格式中的“表达式”一般为逻辑表达式或关系表达式,也可能是1位的变量。系统对表达式的值进行判断,如果为0、X、Z,则按"假"处理,如果为1,则按"真" 处理。语句序列可以是单句,也可以是多句,多句时需使用begin-end块语句括起来
还是以32位加法器为例,为其添加一个复位信号rst,如果rst为高电平,那么复位信号有效,输出out为0,反之,复位信号无效,输出out为两个输入信号之和
module add32(input wire rst, // 增加了一个复位信号
input wire[31:0] in1,
input wire[31:0] in2,
output reg[31:0] out);
always @(*)
begin
if (rst == 1'b1)
out <= 32'h0; // 如果复位信号有效,那么输出out为0
else
out <= in1 + in2; // 反之,输出out为两个输入信号之和
end
endmodule
case 语句
相对于if-else 语句只有两个分支而言,case语句是一种多分支语句,所以case语句多用于多条件译码电路,如译码器、数据选择器、状态机及微处理器的指令译码
case (敏感表达式)
值1: 语句序列1;
值2: 语句序列2;
....
值n: 语句序列n;
default: 语句序列 n + 1;
endcase
当敏感表达式的值等于"值1"时,执行语句序列1;当等于“值2”时,执行语句序列2;依次类推
如果敏感表达式的值上面列出的值都不符,那么执行default后面的语句序列。如下代码是一个简单的运算单元,可执行加法或减法运算,如果输入变量type的值为1,那么执行加法运算,如果type的值为0,那么执行减法运算
module add_sub32(input wire type, // type 决定运算类型
input wire[31:0] in1,
input wire[31:0] in2,
output reg[31:0] out);
always @(*)
begin
case (type)
1'b1: out <= in1 + in2; // type 为1,执行加法运算
1'b0: out <= in1 - in2; // type 为0,执行减法运算
deault: out <= 32'h0;
endcase
end
endmodule
case 语句中,敏感表达式与值 1 ~ n之间的比较是一种全等比较,必须保证两者的对应位全等
casz、casex 语句是 case语句的两种扩展
此外,还有一种表示X或Z的方式,即用表示无关值的符号"?"来表示,例如
case (a)
2'b1x: out <= 1; // 只有a等于2'b1x时,out才等于1
casez(a)
2'b1x: out <= 1; // a等于2'b1x、2'b1z时,out等于1
casex(a)
2'b1x: out <= 1; // a等于2'b10、2'b11、2'b1x、2'blz 时,out等于1
case(a)
2'b1? : out <= 1; // a等于2'b10、2'b11、2‘b1x、2'blz时,out等于1
循环语句
for 语句
for 语句的格式如下,这与C语言是相似的
for (循环变量赋初值; 循环结束条件; 修改循环变量)
执行语句序列;
一个使用for语句实现7人表决器的例子如下。通过for循环统计赞成的人数,若超过4(含4人)赞成则通过,其中vote[7:1]表示7个人的投票情况,vote[i]为1,表示第i个人投的是赞成票,反之是反对票,pass是输出,超过4个人赞成,pass为1,反之为0
module vote7(vote, pass);
input wire[7:1] vote;
output reg pass;
reg[2:0] sum;
integer i;
always @(vote)
begin
sum = 0;
for (i = 1; i < 7; i = i+1)
if (vote[i])
sum = sum + 1; // 如果vote[i] 为1,那么sum加1,注意此处使用阻塞赋值
if (sum[2] == 1'b1) // 如果sum大于等于4,那么输出pass为1
pass = 1;
else
pass = 0;
end
endmodule
forever 语句
forever begin
语句序列
end
forever 循环语句连续不断地执行其中的语句序列,常用来产生周期性的波形
repeat 语句
repeat (循环次数表达式) begin
语句序列
end
while 语句
while (循环执行条件表达式) begin
语句序列
end
while 语句在执行时,首先判断循环执行条件表达式是否为,若为真,则执行其中的语句序列,然后再次判断循环条件表达式是否为真,若为真,则再次执行其中的语句序列,如此反复,直到循环条件表达式不为真为止
编译指示语句
Verilog HDL 和 C 语言一样提供了编译指示功能,允许在程序中使用编译指示(Compiler Directives) 语句,在编译时,通常先对这些指示语句进行预处理,然后再将预处理的结果和源程序一起进行编译
宏替换 `define
`define 可以用一个简单的名字或有意义的标识(也称为宏名)代替一个复杂的名字或变量,其格式如下
`define 宏名 变量或名字
例如: 一般在时序电路中会有一个复位信号,当该复位信号为高电平时表示复位信号有效,当该复位信号为低电平时,表示复位信号无效。分别执行不同的代码
always @(clk)
begin
if (rst == 1'b1)
// 复位有效
else
// 复位无效
end
一种更为友好的书写方式,是使用宏定义
// 宏定义 RstEnable 表示复位信号有效
`deine RstEnable 1'b1
.....
always @ (clk)
begin
if (rst == `RstEnable) // 在编译的时候会自动将`RstEnable 替换成1'b1
// 复位有效
else
// 复位无效
end
`include 语句
`include 是文件包含语句,它可将一个文件全部包含到另一个文件中,使用格式如下
`include "文件名"
条件编译语句 `ifdef、`else、`endif
条件编译语句 `ifdef、`else、`endif 可以指定仅对程序中的部分内容进行编译,有两种使用形式
第一种使用形式如下。当执行的宏在程序中已定义,那么其中的语句序列参与源文件的编译,否则,其中的语句序列不参与源文件的编译`
`ifdef 宏名
语句序列
`endif
第二种使用形式如下。当指定的宏在程序中已定义,那么其中的语句序列1参与源文件的编译,否则,其中的语句序列2参与源文件的编译
`ifdef 宏名
语句序列1
`else
语句序列2
`endif
作者:Ivanqhz