临近期末,刚考完组原,还有一天半复习verilog,记录一下个人认为需要知道的Verilog重点吧(本人所选的verilog课时很少)
本文主要采摘了Verilog HDL-巴斯克中的内容
一、简介
Verilog HDL是一种硬件描述语言,用于从算法级、门级到开关级的多种抽象设计层次的数字系统建模。
Verilog HDL不仅定义了语法,而且对每个语法结构都定义了清晰的模拟、仿真语义。语言从C编程语言中继承了多种操作符和结构。
所以很多操作符看起来那么熟悉。
Verilog HDL支持三种不同方式或混合方式对设计建模。包括:行为描述方式–使用过程化结构模块;数据流方式–采用连续赋值语句方式建模;结构化方式–使用门和模块化实例语句描述建模。(重点)
Verilog HDL两种数据类型:线网数据类型、寄存器数据类型。线网类型表示构件间的物理连线,而寄存器类型表示抽象的数据存储元件。
对高级编程语言结构,例如条件语句、情况语句和循环语句,语言中都可以使用。
二、HDL指南
1、模块
模块是Verilog的基本描述单位,用于描述某个设计的功能或结构及其与其他模块通信的外部端口。
一个设计的结构可以使用开关级原语、门级原语和用户定义的原语方式描述;设计的数据流行为使用连续赋值语句进行描述;时序行为使用过程结构描述。
一个模块的基本语法:
module module_name (port_list) ;
Declarations: //说明部分:定义不同的项
reg, wire, parameter,
input, output, inout,
function, task, . . .
Statements: //语句:定义功能和结构
Initial statement
Always statement
Module instantiation
Gate instantiation
UDP instantiation
Continuous assignment
endmodule
说明部分
用于定义不同的项,例如模块描述中使用的寄存器和参数。语句
定义设计的功能和结构。说明部分和语句可以散布在模块中的任何地方;但是变量、寄存器、线网和参数等的说明部分必须在使用前出现。为了是模块化描述清晰和具有良好的可读性,最好将所有的说明部分放在语句前。
如果没有定义端口位数,则端口大小为1位。
如果没有端口的数据类型说明,则为线网数据类型。
2、时延
assign #2 Sum = A ^ B; // #2 指2个时间单位
使用编译指令将时间单位与物理时间相关联,这样的编译器指令需要在模块描述前定义,如下:
`timescale 1ns /100ps
以反引号开头的第一条语句是编译器指令,这条语句说明时延时间单位为1ns,并且时间精度为100ps,所以上面的连续赋值语句中 #2代表2ns。
3、数据流描述方式
用数据流描述方式对一个设计建模的最基本的机制就是使用连续赋值语句。在连续赋值语句中,某个值被指派给线网变量。
assign [delay] LHS_net = RHS_ expression
右边表达式使用的操作数无论何时发生变化,右边表达式都重新计算,并且在指定的时延后变化值被赋予左边表达式的线网变量。
时延定义了右边表达式操作数变化与赋值给左边表达式之间的持续时间。(个人认为就理解为滞后或者时延)。
连线类型是线网类型的一种。
连续赋值语句是并发执行的,也就是说各语句的执行顺序与其在描述中出现的顺序无关。
4、行为描述方式
(1)initial语句:此语句只执行一次。
(2)always语句:此语句总是循环执行,或者说此语句重复执行。
只有寄存器类型数据能够在这两种语句中被赋值。寄存器类型数据在被赋新值前保持原有值不便。所有的初始化语句和always语句在0时刻并发执行。
reg类型是寄存器数据类型的一种。
always语句中有一个与事件控制(紧跟在@后面的表达式)相关联的顺序过程(begin-end对)。
在顺序过程中的语句顺序执行,并且在顺序执行后被挂起,再次等待事件控制。
在顺序过程中出现的语句是过程赋值模块化的实例。过程赋值可以有一个可选的延迟。
时延分为两种类型:
//语句间时延的示例
Sum = (A ^ B) ^ Cin;
#4 T1 = A & Cin;
//语句内实验的示例
Sum = #3 (A ^ B) ^ Cin;
initial语句包含一个顺序过程,所有语句全部执行完毕后,initial语句永远挂起。
举例:
initial
begin
Pop = 0; //0ns
Pid = 0; //0ns
Pop = #5 1; //5ns
Pid = #3 1; //8ns
Pop = #6 0; //14ns
Pid = #2 0; //16ns
end
5、结构化描述形式
Verilog HDL可以使用如下方式描述结构:
- 内置门原语(在门级)
- 开关级原语(在晶体管级)
- 用户定义的原语(在门级)
- 模块实例(创建层次结构)
通过使用线网相互连接。
举例:
module FA_Str(A,B,Cin,Sum,Cout);
input A,B,Cin;
output Sum,Cout;
wire S1,T1,T2,T3;
xor X1(S1,A,B),X2(Sum,S1,Cin);
and A1(T3,A,B),A2(T2,B,Cin),A3(T1,A,Cin);
or O1(Cout,T1,T2,T3);
endmodule
门实例由线网类型变量互联。门实例语句可以以任何顺序出现。xor、and和or是内置门原语,紧跟在每个门后的信号列表是它的互联。列表中的第一个是门的输出,余下的是输入。
module FourBitFA(FA,FB,FCin,FSum,FCout);
parameter SIZE = 4;
input [SIZE:1] FA,FB;
output [SIZE:1] FSum;
input FCin;
input FCout;
wire [1:SIZE-1] FTemp; //书上这么写的,个人认为[1:SIZE-1]或者[SIZE-1:1]都可以
FA_Str
FA1 (.A(FA[1]),.B(FB[1]),.Cin(FCin),.Sum(FSum[1]),.Cout(FTemp[1])),
FA2 (.A(FA[2]),.B(FB[2]),.Cin(FTemp[1]),.Sum(FSum[2]),.Cout(FTemp[2])),
FA3 (FA[3],FB[3],FTemp[2],FSum[3],FTemp[3]),
FA4 (FA[4],FB[4],FTemp[3],FSum[4],FCout);
endmodule
前两个实例采用命名关联方式,后两个实例使用位置关联将端口和线网关联。
6、混合设计描述方式
模块描述中可以包含实例化的门、模块实例化语句、连续赋值语句以及always语句和initial语句的混合,它们之间可以相互包含。
来自always和initial语句的值能够驱动门或开关,而来自门或连续赋值语句的值能够反过来用于除法always和initial语句。
三、Verilog语言要素
1、标识符
任意一组字母、数字、$符号和_下划线符号的组合,标识符第一个字符必须是字母或者下划线,标识符是区分大小写的。
转义标识符可以在一条标识符中包含任何可打印字符,转移标识符以\符号开头,以空白结尾。
eg: \OutGate 和OutGate相同
Verilog HDL定义了一系列保留字,叫做关键词。注意只有小写的关键词才是保留字。(Verilog区分大小)。
但是转移标识符与关键词并不完全相同。\initial和initial不同。
个人认为使用正常的标识符就可以了,现阶段没必要使用转义标识符等,而且标识符和高级语言定义类似,符合编程习惯。
2、注释
两种形式:和C语言一样
/*第一种形式,多行*/
//第二种形式,单行
3、格式
Verilog区分大小写,自由格式,即结构可以跨越多行编写,也可以在一行内编写。
4、系统任务和函数
以$字符开头的标识符表示系统任务或者系统函数。
任务可以返回0个值或多个值,函数除了只能返回一个值以外与任务相同。
函数在0时刻执行,不允许延迟,任务可以带有延迟。
$display ("Hi you have reached LT today!");
//在新的一行中显示
$time
//返回任务系统当前的模拟时间
5、编译指令
以反引号开始的某些标识符是编译器指令。在Verilog语言编译时,特定的编译器指令在整个编译过程中有效,直到遇到其他的不同的编译器程序指令。
`define //用于文本替换,很像C语言中的define指令
`undef //取消前面定义的宏
`ifdef //如果定义了某名字的宏
`else
`endif
`include //编译器指令用于嵌入内嵌文件的内容
`timescale //将指令时间单位与实际时间相关联,用于定义时延的单位和时延精度
6、值集合
0:逻辑0或“假” ; 1:逻辑1或“真” ; x:未知 ; z:高阻
下划线_可以随意用在整数或者实数中,它们就数量本身没有意义,用来提高易读性,唯一的限制是下划线符号不能用作首字符。
(1)整型数
-
简单的十进制格式 32 ,-15
可以带可选的"+"、“-”表示正负。
-
基数表示法
5'037 //5位八进制数
4'D2 //4为十进制数
4'B1x_01 //4位二进制数
7'Hx //7位(十六进制)x,即xxxxxxx
4'hZ //4位(十六进制)z,即zzzz
4'd-4 //非法:数值不能为负
8'h 2 A //允许出现空格
3' b001 //非法:在'和基数b之间不能出现空格
基数格式计数形式通常为无符号数。
如果没有定义一个整型数的长度,数的长度为相应值中定义的位数。
'o721 //9位八进制数
'hAF //8位十六进制数
如果定义的长度比为常量指定的长度长,通常在左边填 0补位。但是如果数最左边一位为x或z,就相应地用x或z在左边补位。
如果长度定义得更小,那么最左边的位相应地被截断。
(2)实数
- 十进制计数法 2.0 , 0.1
- 科学计数法23_5.1e2 , 3.6E2
实数通过四舍五入被转换为最相近的整数。
(3)字符串
字符串是双引号内的字符序列。字符串不能分成多行书写。
用8位ASCII值表示的字符可看做无符号整数。因此字符串是8位ASCII值得序列。
反斜线 \ 用于对确定的特殊字符转移
\\ //字符\本身
\t //制表符
\n //换行符
\" //字符"
\206 //八进制数206对应的字符
7、数据类型
(1)线网类型
net type 表示Verilog结构化元件间的物理练连线。它的值由驱动元件的值决定,如果没有驱动元件连接到线网,线网的缺省值为z。
net_kind [msb,lsb] net1,net2,...;
msb和lsb是用于定义线网范围的常量表达式;范围定义是可选的,如果没有定义范围,缺省的线网类型是1位。
-
连线wire和tri线网
用于连接单源的连线是最常见的线网类型。连线与三态线网语法一致;三态线可以用于描述多个驱动源驱动同一根线的线网类型;并且没有其他特殊的意义。
-
wor和trior线网
线或指如果某个驱动源为1,那么线网的值也为1。线或和三态线或在语法功能上是一致的。
-
wand和triand线网
线与网指如果某个驱动源为0,那么线网的值为0。线与和三态线与网在语法和功能上是一致的。
-
trireg线网
此线网存储数值,类似于寄存器,并且用于电容结点的建模。三态寄存器线保存作用在线网上的最后一个值,此外,三态寄存线网的缺省初始值为x。
-
tri0和tri1
tr0(tri1)线网的特征是若无驱动源驱动,它的值为0(tri1为1)。
-
supply0和supply1
supply0对地建模,即低电平0;supply网用国语对电源建模,即高电平1。
在定义向量线网时可以选用关键词scalared、vectored,如果一个线网定义使用了关键字vectored,那么就不允许选择和部分选择该线网,scalared允许部分选择该线网。
(2)寄存器类型
register type表示一个抽象的数据存储单源,只能在always和initial语句中被赋值,并且从它的值从一个赋值到另一个赋值被保存下,寄存器类型的变量具有x的缺省值。
-
reg
reg [msb:lsb] reg1,reg2...;
msb,lsb定义了范围,并且必须是常数值表达式。范围定义是可选的,如果没有定义范围,缺省值为1位寄存器。
寄存器可以取任意长度。寄存器中的值常被解释为无符号数。
-
存储器
reg [msb,lsb] memory1 [upper1:lower1], memory2 [upper2:lower2],...;
存储器属于寄存器数组类型,线网数据类型没有相应的存储器类型。存储器赋值不能在一条语句中完成,在存储器被赋值时,需要定义一个索引。
reg Bog[1:5];
Bog = 5’b11011; 这样的赋值是错误的,存储器不能这样赋值。
为存储器赋值的另一种方法是使用系统任务。
(1)$readmemb(加载二进制值)
(2)$readmemb(加载十六进制值)
这些系统任务从指定的文本文件中读取数据并加载到存储器。文本文件必须包含相应的二进制或者十六进制数。
reg [1:4] RomB [7:1]; $readmemb ("ram.patt",RomB);
Romb是存储器,文件“ram.patt”必须包含二进制值。文件也可以包含空白和注释。
系统任务 r e a d m e m b 促 使 从 索 引 7 即 R o m b b 的 最 左 边 的 字 索 引 , 开 始 读 取 值 。 如 果 只 加 载 存 储 器 的 一 部 分 , 值 域 可 以 在 readmemb促使从索引7即Rombb的最左边的字索引,开始读取值。如果只加载存储器的一部分,值域可以在 readmemb促使从索引7即Rombb的最左边的字索引,开始读取值。如果只加载存储器的一部分,值域可以在readmemb方法中显式定义。
$readmemb ("ram.patt",Romb,5,3);
这种情况下,只有RomB[5],RomB[4],RomB[3]这些字从文件头开始被读取。
文件也可以包含显式的地址形式。
@hex_address value eg: @5 11001
当这种情况下,值被读入存储器指定的地址。
当只定义开始值时,连续读取直至到达存储器右端索引边界。
$readmemb ("rom.patt",RomB,6); //从地址6开始,并且持续到1(右边界) $readmemb ("rom.patt",RomB,6,4); //从地址6读到地址4
-
integer寄存器
整数寄存器包含整数值,整数寄存器可以作为普通寄存器使用。
一个整数最少容纳32位。
**整数不能作为位访问,不能读取整数的某几位。**一种截取位置的方式是将整数赋值给一般的reg类型变量, 然后从中选取相应的位。
赋值总是从最右端的位向最左边的位进行,任何多余的位被截断。
-
time
tiem类型的寄存器用于存储和处理时间。,如果未定义界限,每个标识符存储一个至少64位的时间值。时间类型的寄存器只存储无符号数。
-
real和realtime类型
实数寄存器
8、参数
参数是一个常量。参数经常用于定义时延和变量的宽度。
parameter param1 = const_expr1,param2 = const_expr2,...;
参数值也可以在编译时被改变。改变参数值可以使用参数定义语句或通过在模块初始化语句中定义参数值。
四、表达式
1、操作数
不允许对存储器变量值部分选择或未选择。
memory [word_address]
reg [1:8] Ack,Dram [0:63];
Ack = Dram[60];
Dram[60][2] //不允许
Dram[60][2:4] //也不允许
//将存储器单元赋值给寄存器变量,然后对该寄存器变量采用部分选择或位选择操作。
表达式中可以用函数调用。
$time + SumOfEvent(A,B)
//$time是系统函数,SumOfEvent(A,B)是在别处定义的用户自定义函数
2、操作符
除了条件操作符从右向左关联外,其余所有操作符都自左向右关联。
A + B - C //等价于( A + B ) - C
A?B:C?:D:F //等价于A ? B : ( C ? D : F )
-
算数操作结果的长度
算术表达式结果的长度由最长的操作数决定。在赋值语句下,算数操作结果的长度由操作符左端目标长度决定。
{cout,sum} = a + b + cin;
在较大的表达式中,中间结果的长度应取最大操作数的长度。
-
无符号数、有符号数
无符号数存储在:线网、一般寄存器、基数格式表示形式的整数
有符号数存储在:整数寄存器、十进制形式的整数
回顾一下基数格式表示形式的整数:eg: -4’d12
-4’d12 / 4 ! = -12 / 4
-
关系操作符
关系操作符的结果为1或0(真或假),如有有一位是x或z,那么结果为x。
操作数长度不同,长度较短的操作数在最重要的位方向添0补齐。
== 逻辑相等
!= 逻辑不等
=== 全等
!== 非全等
Data = 'b11x0; Addr = 'b11x0; Data == Addr //不定,也就是说值为x,但: Data === Addr //为真,也就是说值为1。
-
逻辑操作符
|| && !(和C语言类似)
如果任意一个操作数包含x,则结果也为x
-
按位操作符(和C语言类似)
-
归约操作符
归约操作符在单一操作数的所有位上操作,并产生一位结果
&归约与,~&归约与非
-
移位操作符
-
条件操作符
cond_expr ? expr1 : expr2
wire [0:2] Student = Marks > 18 ? Grade_A : Grade_C; always #5 Ctr = (Ctr != 25) ? (Ctr + 1) : 5;
-
连接和复制操作
-
将小表达式合并形成大表达式
{expr1,expr2,…,exprN}
由于非定长常数的长度未知,不允许连接非定长常数。
-
复制通过指定重复次数来执行操作
{ repetirion {expr1,expr2,…,exprN}}
-
3、表达式种类
常量表达式
标量表达式
五、门电平模型化
1、内置基本门
多输入门:and、nand、or、nor、xor、xnor
多输出门:buf、not
三态门:bufif0,bufif1,notif,notif1
上拉、下拉电阻:pullup、pulldown
MOS开关:cmos、nmos、pmos、rcmos、rnmos、rpmos
双向开关:tran,tranif0,tranif1,rtran,rtranif0,rtranif1
2、多输入门
and、nand、nor、or、xor、xnor
这些逻辑门只有单个输出,1个或多个输入。
and A1(Out1,In1,In2);
and RBX(Sty,Rib,Bro,Qit,Fix);
xor (Bar,Bud[0],Bud[1],Bud[2]);
第一个端口是输出,其他端口是输入。
3、多输出门
buf、not
这些门都只有单个输入,一个或多个输出。
最后的端口数输入端口,其余的所有端口为输出端口。
buf B1(Fan[0],Fan[1],Fan[2],Fan[3],Clk);
not N1(phA,phB,Ready);
4、三态门
bufif0,bufif1,notif0,notif1
这些门有一个输出、一个数据输入和一个控制输入。
第一个端口是输出端口,第二个端口是数据输入,最后一个是控制输入
bufif1 BF1(Dbus,MemData,Strobe);
notif0 NT2(Addr,Abus,Probe);
5、上拉、下拉电阻
pullup、pulldown
6、MOS开关
cmos,pmos,nmos,rcmos,rpmos,rnmos
7、双向开关
tran、rtran、tranif0、rtranif0、tranif1,、rtranif1
8、门延时
and #(3,5) (Out1,In1,In2,In3);
//上升时延为3,下降时延为5,转换到x的时延是3和5中间的最小值3
notif1 #(2,8,6) (Dout,Din1,Din2);
//上升时延为2,下降时延为8,截止时延为6,转换到x的时延是2,8和6中的最小值
9、实例数组
nand Gang [3:0] (Out,InA,InB);
10、隐式网络
在同一模块中,实例名不能与线网名相同。
六、用户定义的原语
UDP的定义不依赖于模块定义,因此出现在模块定义以外,也可以在单独的文本文件中定义UDP。
UDP只能有一个输出或多个输入。第一个端口必须是输出端口。UDP的行为以表的形式描述。
- 组合电路UDP
primitive MUX2x1 (Z,Hab,Bay,Sel);
output Z;
input Hab,Bay,Sel;
table
// Hab Bay Sel : z 本行仅做注释
0 ? 1 : 0 ;
1 ? 1 : 1 ;
? 0 0 : 0 ;
? 1 0 : 1 ;
0 0 x : 0 ;
1 1 x : 1 ;
endtable
endprimitive
输入端口的次序必须和表中各项的次序匹配。
module MUX4x1(Z,A,B,C,D,Sel);
input A,B,C,D;
input [2:1] Sel;
output Z;
parameter tRIZE = 2, tFALL = 3;
MUX2x1 #(tRIZE,TFALL)
(TL,A,B,Sel[1]),
(TP,C,D,Sel[1]),
(Z,TL,TP,Sel[2]);
endmodule
- 时序电路UDP
一种模拟电平触发行为;另一种模拟边沿触发行为。
电平触发的时序电路UDP:D锁存器
primitive Latch(Q,Clk,D);
output Q;
reg Q;
input Clk,D;
table
//Clk D Q(State) Q(next)
0 1 : ? : 1 ;
0 0 : ? : 0 ;
1 ? : ? : - ;
endtable
endprimitive
"-"表示无变化。
边沿触发的时序电路UDP
primitive D_Edge_FF(Q,Clk,Data);
output Q;
reg Q;
input Clk,Data;
initial Q = 0;
table
//clk Data Q(state) Q(next);
(01) 0 : ? : 0 ;
(01) 1 : ? : 1 ;
(0x) 1 : 1 : 1 ;
(0x) 0 : 0 : 0 ;
// 忽略时钟负边沿:
(?0) ? : ? : - ;
// 忽略在稳定时钟上的数据变化:
? (??): ? : - ;
endtable
endprimitive
表项(01)表示从0转换到1,表项(?0)表示从任意值转换到0.
module Reg4(Clk,Din,Dout);
input Clk;
output [0:3] Din;
output [0,3] Dout;
D_Edge_FF
DLAB0(Dout[0],Clk,Din[0]),
DLAB1(Dout[1],Clk,Din[1]),
DLAB2(Dout[2],Clk,Din[2]),
DLAB3(Dout[3],Clk,Din[3]);
endmodule
边沿触发和电平触发的混合行为:
同一个表中能够混合电平触发和边沿触发,在这种情况下,边沿变化在电平触发之前处理,即电平触发项覆盖边沿触发项。
异步清零的D触发器
primitive D_Async_FF(Q,Clk,Clr,Data);
output Q:
reg Q;
input Clr,Data,Clk;
table
//Clk Clr Data Q(state) Q(next)
(01) 0 0 : ? : 0 ;
(01) 0 1 : ? : 1 ;
(0x) 0 1 : 1 : 1 ;
(0x) 0 0 : 0 : 0 ;
// 忽略时钟负边沿:
(?0) 0 ? : ? : - ;
(??) 1 ? : ? : 0 ;
? 1 ? : ? : 0 ;
endtable
endprimitive
三位表决电路的UDP描述
primitive Majority3(Z,A,B,C);
input A,B,C;
output Z;
table
//A B C : Z
0 0 ? : 0 ;
0 ? 0 : 0 ;
? 0 0 : 0 ;
1 1 ? : 1 ;
1 ? 1 : 1 ;
? 1 1 : 1 ;
endtable
endprimitive
七、数据流模型化
组合逻辑电路的行为最好使用连续赋值语句建模。
1、连续赋值语句
连续赋值语句将值赋给线网(连续赋值不能为寄存器赋值)
assign LHS_targer = RHS_expression;
只要在右端表达式的操作数上有时间发生,表达式即被计算,如果结果值有变化,新结果就赋给左边的线网。
连续赋值的目标类型如下:标量线网、向量线网、向量的常数型位选择、向量的常数型部分选择、上述类型的任意的拼接运算结果。
wire Cout,Cin;
wire [3:0] Sum,A,B;
assign {Cout,Sum} = A + B + Cin;
//目标是一个向量线网和一个标量线网得到拼接结果
因为A和B是4位宽,加操作的结果最大能够产生5位结果。左端表达式的长度指定为5位。赋值语句因此促使右端表达式最右边的4位的结果赋给Sum,第5位赋给Cout。
2、线网说明赋值
连续赋值可作为线网说明本身的一部分,这样的赋值被称为线网说明赋值。
wire [3:0] Sum = 4'b0;
wire Clear = 'b1;
//等价于 wire Clear; assign Clear = 'b1;
不允许在同一个线网上出现多个线网说明赋值,如果多个赋值是必需的,则必须使用连续赋值语句。
3、时延
如果在连续赋值语句中没有定义实验,则右端表达式立即赋给坐断表达式,时延为0。
assign #6 Ask = Quiet || Lata;
上式规定表达式结果的计算到其赋给左边目标需要经过6个时间单位的时延。
如果右端在传输给左端之前变化,应用最新的变化值。(用新值)
上升时延、下降时延、关闭时延
assign #(rise,fall,turn-off) LHS_target = RHS_expression;
assign #4 Ask = Quiet || Late; //上升、下降、截止、传递到x的时延都是4
assign #(4,8) Ask = Quick; //上升为4,下降为8,传递到x和z的时延是4和8中的最小值4
assign #(4,8,6) Arb = &DataBus;//上升为4,下降为8,截止为6,传递到x的时延是4,8,6中的最小值。
assign Bus = MemAddr [7:4]; //没有时延
4、线网时延
时延在线网说明中定义
wire #5 Arb;
表明Arb驱动源改变与线网Arb本身间的时延。
如果时延在线网说明赋值中出现,那么时延不是线网时延,而是赋值时延。
八、行为建模
1、过程结构
一个模块中可以包含任意多个initial或always语句,这些语句相互并行执行,即这些语句的执行顺序与其在的模块中的顺序无关。所有的initial和always语句在0时刻开始并行执行。
- initial语句
initial语句在模拟的0时刻开始执行。
parameter SIZE=1024;
reg[7:0] RAM[0:SIZE-1];
reg RibReg;
initial
begin: SEQ_BLK_Q
integer Index;
RibReg = 0;
for(Index = 0; Index<SIZE;Index=Index+1)
RAM[Index]=0;
end
由begin和end定界,包含顺序执行的进程语句。如果没有局部说明部分,则不要求有SEQ_BLK_Q。
parameter APPLY_DELAY = 5;
reg [0:7] port_A:
initial
begin
Port_A = 'h20;
#APPLY_DELAY Port_A='hF2;
#APPLY_DELAY Port_A='h41;
#APPLY_DELAY Port_A='h0A;
end
initial语句主要用于初始化和波形生成。
- always语句
always语句重复执行。
always
#5 Clk=~Clk; //产生一个周期为10个时间单位的波形
由事件控制的顺序过程的always语句。
reg[0:5] InstrReg;
reg[0:5] Accum;
wire ExecuteCycle;
always @(ExecuteCycle)
begin
case(InstrReg[0:1])
2'b00: Store(Accum,InstrReg[2:5]);
2'b11: Load(Accum,InstrReg[2:5]);
2'b01: JUmp(InstrReg[2:5]);
2'b10: ;
endcase
end
//Store Load Jump是在别处定义的用户自定义的任务
只要有事件发生,就执行顺序过程中的语句。
一个模块中可以包含多条initial和always语句,每条语句启动一个单独的控制流,各语句在0时刻开始并行执行。
2、时序控制
有两种控制形式:时延控制,事件控制
- 时延控制
#3 Wave = 'b0111;
#3;
RefClk = 0;
上面这两种形式都可以。
时延控制中的时延可以是任意表达式,不必限定为某一常量。
# Strobe
compare = TX^ask;
# (PERIOD/2)
Clock = ~Clock;
如果时延表达式的值为x或者z,其与零时延郑晓,如果延时表达式计算结果为负值,那么其二进制的补码值被作为时延。
- 事件控制
包括:边沿触发事件控制、电平敏感事件控制
边沿触发时间控制
@ (posedge Clock)
Curr_State = Next_State;
time RiseEdge,OnDelay;
initial
begin
@(posedge ClockA);
RiseEdge = $time;
@(negedge ClockA);
Ondelay = $time-RiseEdge;
$display ("The on-period of clock is %t",OnDelay);
end
电平敏感事件控制
进程语句或进程中的过程语句一直延迟到条件变为真后才执行。
wait(Sum>32)
Sum=0;
3、语句块
语句块提供将两条或多条语句组合成语法结构上相当于一条语句的机制。
顺序语句块(begin…end)
并行语句块(fork…join)
4、过程性赋值
过程性赋值是在initial语句或者always语句内的赋值,它只能对寄存器数据类型的变量赋值。表达式的右端可以是任何表达式。
补充重复事件控制的语句内部时延表示形式。
Done = repeat (2) @(negedge ClkA) A_REG+B_REG;
当时钟ClkA上的两个负沿时,再将右端值赋给Done。
//等价于
begin
Temp=A_REG+B_REG;
@(negedge ClkA);
@(negedge ClkA);
Done=Temp;
end
- 阻塞性过程赋值和非阻塞性过程赋值:
阻塞性过程赋值使用“=”,非阻塞性过程赋值采用“<=”
<=执行次序变得彼此不相关。
//体会两者的区别
begin
Clr <= #5 1; //5T
Clr <= #4 0; //4T
Clr <= #10 0; //10T
end
begin
Clr = #5 1; //5T
Clr = #4 0; //9T
Clr = #10 0; //19T
end
- 过程赋值和连续赋值的差异
过程赋值:在always或initial语句内出现,驱动寄存器,使用 = 或 <= 赋值,无assign关键词
连续赋值:在一个模块内出现,驱动线网,使用 = 赋值,有assign关键词
5、if语句
if(Sum<60)
begin
Grade=C;
end
else if(Sum<75)
begin
Grade=B;
end
else
begin
Grade=A;
end
6、case语句
case语句是一个多路条件分支形式
module ALU(A,B,OpCode,Z);
input [3:0]A,B;
input [1:2]OpCode;
output [7:0] Z;
parameter
ADD_INSTR=2'b10,
SUB_INSTR=2'b11,
MULT_INSTR=2'b01,
DIV_INSTR=2'b00;
always
@(A or B or OpCode)
case(OpCode)
ADD_INSTR: Z = A+B;
SUB_INSTR: Z = A-B;
MULT_INSTR: Z = A*B;
DIV_INSTR: Z = A/B;
endcase
endmodule
如果case表达式和分支项表达式的长度不同,则会统一为这些表达式的最长长度。
可以用?字符来代替无关位
casex(Mask)
4'b1??? : Dbus[4]=0;
4'b01?? : Dbus[3]=0;
4'b001? : Dbus[2]=0;
4'b0001 : Dbus[1]=0;
endcase
7、循环语句
- forever
在该过程中必须使用某种形式的时序控制,否则,forever循环将在0时延后永远执行下去
initial
begin
Clock = 0;
#5 forever
#10 Clock=~Clock;
end
- repeat语句
这种循环语句执行指定循环次数的过程语句。
repeat (Count)
Sum = Sum + 10;
repeat (ShiftBy)
P_Reg = P_Reg<<1;
//注意区分以下两种表达
repeat(Count)
@(posedge Clk) Sum=Sum+1; //+1执行Count次,每次负沿都赋值
Sum = repeat(Count)@(posedge Clk) Sum+1; //+1执行1次,等待Count次负沿再赋值
- while、for循环
while (BY > 0)
begin
Acc = Acc << 1;
By = By - 1;
end
integer K;
for ( K=0 ; K < MAX_RANGE ; K = K + 1)
begin
if(Abus[K] == 0)
Abus[K] = 1;
else if(Abus[k] == 1)
Abus[K] = 0;
else
$display( "Abus[K] is an x or a z");
end
九、结构建模
module HA(A,B,S,C);
input A,B;
output S,C;
parameter AND_DELAY = 1,XOR_DELAY = 2;
assign #XOR_DELAY S = A ^ B;
assign #AND_DELAY C = A & B;
endmodule
module FA(P,Q,Cin,Sum,Cout);
input P,Q,Cin;
output Sum,Cout;
parameter OR_DELAY=1;
wire S1,C1,C2;
HA h1(P,Q,S1,C1);
HA h2(.A(Cin),.S(Sum),.B(S1),.C(C2));
or #OR_DELAY O1(Cout,C1,C2);
endmodule
模块的输入端悬空,值为高阻态。模块的输出端口悬空,表示该输出端口废弃不用。
- 不同的端口长度
当端口和局部端口表达式的长度不同时,端口通过无符号数的右对齐或者截断方式进行匹配。
- 模块参数值
两种方式:参数定义语句defparam,或者带参数值的模块引用
//第一种方式
module TOP(NewA,NewB,NewS,NewC);
input NewA,NewB;
output NewS,NewC;
defparam Ha1.XOR_DELAY = 5,
Ha1.AND_DELAY = 2;
HA Ha1(NewA,NewB,NewS,NewC);
endmodule
module TOP2(NewP,NewQ,NewCin,NewSum,NewCout);
input NewP,NewQ,NewCin;
output NewSum,NewCout;
defparam Fa1.h1.XOR_DELAY = 2,
Fa1.h1.AND_DELAY = 3,
Fa1.OR_DELAY = 3;
FA Fa1(NewP,NewQ,NewCin,NewSum,NewCout);
endmodule
//第二种方式
module TOP3(NewA,NewB,NewS,NewC);
input NewA,NewB;
output NewS,NewC;
HA #(5,2) Ha1(NewA,NewB,NewS,NewC);
endmodule
module TOP2(NewP,NewQ,NewCin,NewSum,NewCout);
input NewP,NewQ,NewCin;
output NewSum,NewCout;
defparam Fa1.h1.XOR_DELAY = 2,
Fa1.h1.AND_DELAY = 3;
FA #(3) Fa1(NewP,NewQ,NewCin,NewSum,NewCout);
endmodule
模块化实例参数值的顺序必须与较低层被引用的模块中说明的参数顺序匹配。
带参数的模块引用只能用于将参数值向下传递一个层次,但是参数定义语句能够替换层次中任意一层的参数值。
十、验证
编写测试程序(test bench),测试验证程序用于测试和验证设计的正确性。
1、编写测试验证程序
产生模拟激励,将输入激励加入到测试模块并收集其输出响应,将相应输出与期望值进行比较。
2、波形产生
- 值序列
initial
begin
Reset = 0;
#100 Reset = 1;
#80 Reset = 0;
#30 Reset = 1;
end
- 重复模式
module Gen_Clk_A(Clk_A);
output Clk_A;
reg Clk_A;
parameter tPERIOD = 10;
initial
Clk_A = 0;
always
# (tPERIOD/2) Clk_A = ~ Clk_A;
endmodule
另一种方式:可以在initial语句中使用forever循环语句。
module Gen_Clk_D(Clk_D);
output Clk_D;
reg Clk_D;
parameter START_DELAY=5,LOW_TIME=2,HIGH_TIME=3;
initial
begin
Clk_D = 0;
#START_DELAY;
forever
begin
#LOW_TIME
Clk_D=1;
#HIGH_TIME
Clk_D=0;
end
end
endmodule
另外,可以使用repeat产生确定数目的时钟脉冲。