全文总结:本文总结了Verilog的模块与程序结构,并详细阐述了:
1、连续赋值语句和过程赋值语句的基本格式和特点
2、阻塞赋值和非阻塞赋值的区别与使用场景
3、底层模块的命名法调用
4、最后简单介绍了Verilog中的两种数据类型及使用场景
一、Verilog模块与程序结构
Verilog模块结构主要分为模块说明部分和功能描述部分,所有的模块必须以module开头 - endmodule结尾。在功能实现时主要用到的语句有assign连续赋值语句、always语句块及过程赋值语句、底层模块的调用语句。
以2选1多路器的Verilog描述为例,将其与verilog模块对应,如下所示:
在模块说明部分,其以module开头,endmodule结尾,模块名为MUX21a,一共有4个端口分别为a,b,s,y,之后对端口信号的类型进行了声明。
在功能描述部分,只有一条assgin连续赋值语句,利用了条件赋值语句对输出端口y进行赋值。
1.1 模块说明部分
- 模块名是指电路的名字,由用户指定,最好与文件名一致
- 端口列表是指电路的输入/输出信号名称列表,信号名由用户指定,各名称间用逗号隔开
- 端口信号声明要说明端口信号的输入输出属性、信号的数据类型以及信号的位宽;输入输出属性有input、output、inout三种;常用信号的数据类型有wire和reg两种,缺省则为wire型;信号的位宽用[n1,n2]表示,缺省则为1bit
- 参数声明要说明参数的数据类型、名称和初值
1.2 功能描述部分
1.2.1 assign连续赋值语句
基本格式:assgin 赋值目标 = 表达式
与门实现:assign y = a&b;
- 连续赋值语句是指其总是处于激活状态,只要表达式中的操作数(a或b)有变化,立即进行计算和赋值
- 赋值目标(y)的数据类型必须是wire型的,wire表示电路的连线
1.2.2 表达式运算
(1)算术型
乘法 | 除法 | 加法 | 减法 | 求余 | 求幂 |
---|---|---|---|---|---|
* | / | + | - | % | ** |
(2)逻辑型
逻辑非 | 逻辑与 | 逻辑或 |
---|---|---|
! | && | || |
- 逻辑型运算的结果只有三种可能,1(逻辑真),0(逻辑假),x(不确定),对于x(不确定)的运算,可以将x视为0,1各运算一次,若两次结果不一样,则结果为x
(3)关系运算符
大于 | 小于 | 大于等于 | 小于等于 |
---|---|---|---|
> | < | >= | <= |
(4)等价运算符
等于 | 不等于 | case等于 | case不等 |
---|---|---|---|
== | != | === | !=== |
- 等于和不等于的结果只有三种可能,1(逻辑真),0(逻辑假),x(不确定)
- case等于和case不等的结果只能是1或0,只有操作数的每一位都相同结果才为1
(5)按位运算符
按位非 | 按位与 | 按位或 | 按位异或 | 按位同或 |
---|---|---|---|---|
~ | & | | | ^ | ~^ |
- 如果多个操作数之间的位宽不同,位宽小的会自动左添0补齐
(6)缩减运算符
缩减与 | 缩减与非 | 缩减或 | 缩减或非 | 缩减异或 | 缩减同或 |
---|---|---|---|---|---|
& | ~& | | | ~| | ^ | ~^ |
- 按位运算符和缩减运算符的符号是相同的,其不同点在于按位运算符中按位非操作数只有一个,其余按位运算的操作数有两个及以上;缩减运算符的操作数只有一位,将该数的各位自左至右进行逻辑运算
(7)移位运算符
右移 | 左移 | 算术右移 | 算术左移 |
---|---|---|---|
>> | << | >>> | <<< |
- 左移和右移自动补零,算术左移和算数右移不补零
eg. Y = 4’b1010 >> 1; 结果为0100
Y = 4’b1001 >>> 1; 结果为1100
(8)拼接复制运算符
拼接 | 复制拼接 |
---|---|
{},格式:{操作数1,操作数2,…} | {n{}},格式:{n{操作数1,操作数2,…}} |
复制运算符和拼接运算符可以组合使用,但是要注意组合使用的格式,以复制n次a,并拼接b为例:{{n{a}}, b}
,需要强调的是,在复制n次之后外面还需要加花括号{}之后才能正确拼接
(9)条件运算符
条件赋值 |
---|
? : |
1.2.3 always语句块,过程赋值语句
1.基本格式:
always @(敏感信号条件表) begin
各类顺序语句;
end
- always语句本身不是一个单一的有意义的一条语句,而是和下面的语句一起构成一个语句块,称之为过程块;过程块中的赋值语句成为过程赋值语句
- 该语句块不是总处于激活状态,当满足激活条件时从才能被执行,否则被挂起,挂起时即使操作数有变化,也不执行赋值,赋值目标值保持不变
- 赋值目标必须为reg型
2.敏感信号条件表:边沿敏感和电平敏感
-
边沿敏感:
(posedge 信号名) ,信号上升沿到来时执行语句块中内容
(negedge 信号名),信号下降沿到来时执行语句块中内容
(posedge 信号名1 or negedge 信号名2) ,信号1上升沿到来时或者信号2下降沿到来时执行语句块中内容
例如:(posedge clk)、(negedge reset)、(posegde clk or negedge reset)
-
电平敏感:
(信号名列表),信号列表中的任何一个信号有变化,执行语句块中的内容
1.2.4 阻塞赋值和非阻塞赋值
阻塞赋值:
右边表达式的计算和对左边寄存器变量的赋值是一个统一的原子操作中的两个动作,这两个动作之间不能再插入其他任何动作
简单概括就是,表达式计算完毕后立即将值赋给赋值目标
非阻塞赋值:
首先按顺序计算右边表达式的值,但并不马上赋值,而是要等到过程结束时再按顺序赋值
简单概括就是,等所有表达式计算完毕之后同时赋值给赋值目标
解答:对于阻塞赋值,M1=A=1,M2=B&M=1&1=1,Q=M1|M2=1|1=1;对于非阻塞赋值M1=A=1,M2=B&M=1&0=1,Q=M1|M2=0|0=1
!注意:在一个always语句块中不能混合使用阻塞赋值和非阻塞赋值语句,设计组合电路时常用阻塞赋值,设计时序电路时常用非阻塞赋值。
1.2.5 底层模块的调用
在顶层模块调用底层模块时需要对底层模块进行例化,其格式如下:
底层模块名 例化名 (端口映射);
端口映射的方法有命名法和顺序法,通常只使用命名法
module DFF(CLK,D,Q)
input CLK,D;
output reg Q;
always@(posedge CLK)
Q <= D;
endmodule
例如,上述是一个已经编写好的D触发器模块,若需要在顶层对其进行调用,如下所示
module example(clkk,d,q)
input clk,d,a;
output q; //注意这里q的数据类型是wire型,这是因为在顶层模块并没有对其进行过程赋值
DFF dff(.CLK(clk), .D(d), .Q(q)) //对底层模块的调用,命名法
endmodule
1.3 Verilog中的数据类型
net型用于连续赋值的赋值目标或门原语的输出,仿真时不需要分配内存空间,最常用的是wire
wire(线型) | |||
---|---|---|---|
wand(线与) | tri(三态) | tri0(下拉电阻) | supply0(地) |
wor(线或) | triand(三态与) | tri1(上拉电阻) | supply1(电源) |
trior(三态或) | trireg(电容性线网) |
variable用于过程赋值的赋值目标,仿真时需要分配内存空间,最常用的是reg
reg(寄存器型) | |
---|---|
integer(整型) | time(时间型) |
real(实型) | realtime(实时间型) |
对于端口信号,input和inout信号必须定义成net型,output信号可以是net型也可以是variable型,取决于使用哪种赋值语句对其赋值