文章目录
- Verilog 简单介绍
- 数据类型介绍
- 变量
- 运算符及表达式
- 非阻塞赋值和阻塞赋值
- 条件语句
- 循环语句
- 顺序块和并行块
- 结构说明语句
- assign 语句
- 打印信息
- 宏定义
Verilog 简单介绍
Verilog HDL是硬件描述语言的一种,用于数字电子系统设计。该语言允许设计者进行各种级别的逻辑设计,进行数字逻辑系统的仿真验证、时序分析、逻辑综合,它是目前应用最广泛的一种硬件描述语言。
Verilog模型可以是实际电路的不同级别的抽象,这些抽象的级别和它们所对应的模型类型共有5种:
(1)系统级(system evel):用语言提供的高级结构能够实现待设计模块的外部性能的模型。
(2)算法级(algorithm level):用语言提供的高级结构能够实现算法运行的模型。
(3)RTL级(register transfer level):描述数据在寄存器之间的流动和如何处理、控制这些数据流动的模型。
(4)门级(gate level):描述逻辑门以及逻辑门之间连接的模型。
(5)开关级(switch level):描述器件中三极管和储存节点以及它们之间连接的模型。
数据类型介绍
数字
二进制整数(b),八进制整数(o),十进制整数(d),十六进制整数(h)。
格式:<位宽><进制><数字>
如 4‘b 0001,位数多了可以用下划线提高可读性,如 8‘d 1000_0000。
在数字电路中,x代表不定值,z代表高阻值。
参数型(parameter)
在Verilog HDL中用parameter来定义常量,即用parameter来定义一个标识符代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可提高程序的可读性和可维护性。
格式: parameter 参数名1=表达式,参数名2=表达式,…,参数名n=表达式;
变量
wire型
wire型数据常用来表示用以assign关键字指定的组合逻辑信号,Verilog程序模块中输入、输出信号类型默认时自动定义为wire型。wire型信号可以用做任何方程式的输入,也可以用做assign语句或实例元件的输出。
格式:wire [n-1:0] 数据名1,数据名2,…,数据名n;
reg型
寄存器是数据储存单元的抽象,寄存器数据类型的关键字是reg。通过赋值语句可以改变寄存器储存的值,其作用与改变触发器储存的值相当。Verilog HDL语言提供了功能强大的结构语句,使设计者能有效地控制是否执行这些赋值语句,这些控制结构用来描述硬件触发条件,例如时钟的上升沿和多路器的选通信号。
reg类型数据的默认初始值为不定值x,reg型数据常用来表示always模块内的指定信号,常代表触发器。通常在设计中要由 always模块通过使用行为描述语句来表达逻辑关系。在always模块内被赋值的每一个信号都必须定义成reg型。
格式:reg [n-1:0] 数据名1,数据名2,…,数据名n;
对于reg型数据,其赋值语句的作用就如同改变一组触发器的存储单元的值。在Verilog中有许多构造用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件时用时钟的上升沿,或用来描述判断逻辑的细节。reg型数据的默认初始值是不定值,reg型数据可以赋正值,也可以赋负值,但当一个reg型数据是一个表达式中的操作数时,它的值被当作是无符号值,即正值。例如,当一个4位的寄存器用做表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。
运算符及表达式
Verilog HDL语言的运算符范围很广,其运算符按其功能主要分为以下几类:
算术运算符 (+,-,×,/,% )
赋值运算符 (=,<= )
关系运算符 (>,<,>=,<= )
逻辑运算符 (&&,|| ,!)
条件运算符 (?:)
位运算符 (~,| ,^,& , ^~) (^~为按位同或,即异或非)
移位运算符 (<<,>> )
拼接运算符 ({ })
按照其所带操作数的个数可分为:单目、双目、三目运算符。
单目: a=~a; 取反操作
双目:c=a | b; 按位或操作
三目:d=a ? b : c; 条件运算
各运算符的优先级别如下图所示。
非阻塞赋值和阻塞赋值
非阻塞赋值:如a<=b;
非阻塞赋值方式:在语句块中,上面语句所赋的变量值不能立即就为下面的语句所用;块结束后才能完成这次赋值操作,而所赋的变量值是上一次赋值得到的;在编写可综合的时序逻辑模块时,这是最常用的赋值方法。
阻塞赋值:如a=b;
阻塞赋值方式:赋值语句执行完后,块才结束;a的值在赋值语句执行完后立刻就改变的;在时序逻辑中使用时,可能会产生意想不到的结果。
阻塞赋值的执行可以认为是只有一个步骤的操作,即计算等号右边的表达式并更新左边的变量,此时不能允许有来自任何其他Verilog语句的干扰。所谓阻塞的概念是指在同一个always块中,即使语句间不设定延迟,其后面的赋值语句从概念上是在前一句赋值语句结束后再开始赋值的。
非阻塞赋值的操作过程可以看作两个步骤:(1)在赋值开始时刻,计算非阻塞赋值等号右边的表达式;(2)在赋值结束时刻,更新非阻塞赋值等号左边的表达式。
非阻塞赋值允许其他的Verilog语句同时进行操作。 在计算非阻塞赋值等号右边表达式和更新等号左边表达式期间,其他的Verilog非阻塞赋值语句都能同时计算等号右边表达式并更新等号左边表达式。
非阻塞赋值操作只能用于对寄存器类型变量进行赋值,因此只能用在initial块和always块等过程块中,always模块内的reg型信号都是采用非阻塞赋值。非阻塞赋值不允许用于连续赋值。
条件语句
条件语句必须在过程块语句中使用,所谓过程块语句是指由initial和always语句引导的执行语句集合,除这两种块语句引导的begin end块中可以编写条件语句外,模块中的其他地方都不能编写。
应当注意if与else的配对关系,else总是与它上面的最近的if配对。如果if与else的数目不一样,为了实现程序设计者的企图,可以用begin end块语句来确定配对关系。
case语句是一种多分支选择语句,if语句只有两个分支可供选择,而实际问题中常常需要用到多分支选择,Verilog语言提供的case语句直接处理多分支选择。case语句通常用于微处理器的指令译码,它的一般形式如下:
case(表达式) <case分支项> endcase
casez(表达式) <case分支项> endcase
casex(表达式) <case分支项> endcase格式:
分支表达式 :语句;
default :语句;
case语句与if else语句的区别主要有两点:(1)与case语句中的控制表达式和多分支表达式这种比较结构相比,if else结构中的条件表达式更为直观一些。(2)对于那些分支表达式中存在不定值x和高阻值z的位时,case语句提供了处理这种情况的手段。
循环语句
在Verilog HDL中存在着4种类型的循环语句,用来控制执行语句的执行次数。
(1)forever 语句:连续的执行语句。forever循环语句常用于产生周期性的波形,用来作为仿真测试信号,它与always语句的不同之处在于不能独立写在程序中,而必须写在initial块中。
(2)repeat 语句:连续执行一条语句n次。
(3)while 语句:执行一条语句直到某个条件不满足。如果一开始条件就不满足, 则语句一次也不能被执行。
(4)for 语句:①先给控制循环次数的变量赋初值;②判定控制循环的表达式的值,如为假,则跳出循环语句,如为真,则执行指定的语句;③执行一条赋值语句来修正控制循环变量次数的变量的值,然后返回第②步。
顺序块和并行块
关键字begin end用于将多条语句组成顺序块,顺序块具有以下特点:
(1)顺序块中的语句是一条接一条按顺序执行的,只有前面的语句执行完成之后才能执行后面的语句(除了带有内嵌延迟控制的非阻塞赋值语句)。
(2)如果语句包括延迟或事件控制,那么延迟总是相对于前面那条语句执行完成仿真时间的。
并行块由关键字fork join声明,并行块具有以下特性:
(1)并行块内的语句并发执行;
(2)语句执行的顺序是由各自语句内延迟或事件控制决定的;
(3)语句中的延迟或事件控制是相对于块语句开始执行的时刻而言的。
下面的代码反映了顺序块和并行块的不同。
initial
begin
x=1 b0; //在仿真时刻0完成
#5 y=1'b1; //在仿真时刻5完成
#10 z={x,y}; //在仿真时刻15完成
#20 w={y,x};//在仿真时刻35完成
end
initial
fork
x=1 b0; //在仿真时刻0完成
#5 y=1'b1; //在仿真时刻5完成
#10 z={x,y}; //在仿真时刻10完成
#20 w={y,x};//在仿真时刻20完成
join
结构说明语句
Verilog语言中的任何过程模块都从属于以下4种结构的说明语句:initial 说明语句;always 说明语句;task说明语句;function说明语句。
一个程序模块可以有多个initial和always过程块,每个initial和always说明语句在仿真的一开始同时立即开始执行。initial语句只执行一次,而always语句则是不断地重复活动着,直到仿真过程结束。always语句后跟着的过程块是否运行,则要看它的触发条件是否满足,如满足则运行过程块一次,再次满足则再运行一次,直至仿真过程结束。在一个模块中,使用initial和always语句的次数是不受限制的,它们都是同时开始运行的。task和function语句可以在程序模块中的一处或多处调用。
initial 语句的格式:
initial
begin
语句1;
语句2;
…
语句n;
end
一个模块中可以有多个initial块,它们都是并行运行的,initial块常用于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。
always语句的格式:
always <时序控制> <语句>
always语句由于其不断活动的特性,只有和一定的时序控制结合在一起才有用。如果一个always语句没有时序控制,则这个always语句将会使仿真器产生死锁。
例如:always clk = ~clk;
上面这个always语句将会生成一个0延迟的无限循环跳变过程,这时会发生仿真死锁。
给其加上时序控制如下。
always #period clk = ~clk;
这时则生成一个周期为2倍period的无限延续的信号波形。
always 的时间控制可以是沿触发也可以是电平触发,可以单个信号也可以多个信号,如果是多个信号中间需要用关键字or连接。逗号可以代替关键字or,如果输入变量较多且对所有的输入变量都是敏感的,可以用 always@(*) 语句代替关键字or并列的一系列输入。
沿触发的always块常常描述时序行为,如有限状态机。如果符合可综合风格要求,则可通过综合工具自动地将其转换为表示寄存器组和门级组合逻辑的结构,而该结构应具有时序所要求的行为;电平触发的always块常常用来描述组合逻辑的行为。如果符合可综合风格要求,可通过综合工具自动将其转换为表示组合逻辑的门级逻辑结构或带锁存器的组合逻辑结构,而该结构应具有所要求的行为。
一个模块中可以有多个always块,它们都是并行运行的,如果这些always块是可以综合的,则表示的是某种结构,如果不可综合,则是电路结构的行为,因此,多个always块并没有前后之分。
assign 语句
wire类型的信号需要连续赋值,在Verilog中,连续赋值是通过assign语句实现的,任何线或其他类似线的数据类型都可以用一个值来连续驱动,这个值可以是常数,也可以是一组信号组成的表达式。
使用 assign 语句时, 需要遵循以下规则:①左值应该始终是wire类型的标量或矢量网络,或者标量或矢量网络的串联, 而绝对不能是reg类型的标量或矢量寄存器;②右值可以包含标量或矢量寄存器和函数调用;③只要等号右侧任何操作数的值发生变化, 左值就会使用新值进行更新;④assign语句也称为连续赋值,并且始终处于活动状态。
下面是一个assign语句的应用例子,每当a,b,c,d中的任何一个输入变量发生变化时,输出o都会发生更新。
module combo ( input a, b, c, d,
output o);
assign o = ~((a & b) | c ^ d);
endmodule
reg类型的变量不能使用assign进行连续赋值,这是因为reg类型的变量可以存储数据,并且不需要连续驱动,因此只能在initial以及always块内对reg类型变量进行赋值。
打印信息
输出(打印)信息有以下两种方式。
$display(“%d,%h”,x,y);
$write(“%d,%h”,x,y);
其中display自动地在输出后进行换行,write则不是这样, 如果想在一行里输出多个信息,可以使用write。
宏定义
格式: `define 宏名 宏内容
这种方法能够用一个简单的名字代替一个长的字符串,也可以用一个有含义的名字代替没有含义的数字或符号。
①宏名可以用大写字母表示,也可以用小写字母表示,建议使用大写字母,以与变量名相区别。
②宏命令可以出现在模块定义里面,也可以出现在模块定义外面。宏名的有效范围为定义命令之后到原文件结束,通常宏命令写在模块定义的外面,作为程序的一部分,在此程序内有效。
③在引用已定义的宏名时,必须在宏名的前面加上符号 `,表示该名字是一个经过宏定义的名字。
④使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量,而且记住一个宏名要比记住一个无规律的字符串容易,这样在读程序时能立即知道它的含义,当需要改变某一个变量时,可以只改变define命令行,一改全改,由此可见使用宏定义,可以提高程序的可移植性和可读性。
⑤宏定义是用宏名代替一个字符串,也就是做简单的置换,不做语法检查。预处理时照样代入,不管含义是否正确,只有在编译已被宏展开后的源程序时才报错。
⑥宏定义不是Verilog HDL语句,不必在行末加分号,如果加了分号会连分号一起进行置换。
⑦在进行宏定义时,可以引用已定义的宏名,层层置换。
`define a b+c
`define d e+`a //d=b+c+e
⑧宏名和宏内容必须在同一行中进行声明。
使用下面的语句就可以在当前文件引入已存在的文件a.v。
`include “a.v”
时间尺度命令的格式如下。
`timescale <时间单位>/<时间精度>
举一个例子如下。
`timescale 1ns/1ps
如果是上面的命令,则在这个命令之后,模块中所有的时间值都表示是1ns的整数倍,# 50 就表示延迟50ns,因为在命令中定义了时间单位是1ns。模块中的延迟时间可表达为带3位小数的实数,因为命令中定义的时间精度为1ps。
以上就是 Verilog 基础知识的所有内容了!
参考资料:
Verilog 数字系统设计教程/夏宇闻编著
Verilog assign statement