Verilog HDL

  硬件描述语言

Posted by     Keyon                      on June 9, 2018

Verilog HDL基本结构

基本要求

  • Verilog HDL程序是由模块构成的。每个模块嵌套在moduleendmodule声明语句中。模块是可以进行层次嵌套的。
  • 每个Verilog HDL源文件中只有一个顶层模块,其他为子模块。
    • 每个模块要进行端口定义,并说明输入输出端口,然后对模块的功能进行行为逻辑描述。
  • 程序书写格式自由,一行可以写几个语句,一个语句也可以分多行写。
  • 除了endmodule语句、begin_end语句和fork_join语句外,每个语句和数据定义的最后必须有分号。
  • 可用/*…..*///…对程序的任何部分作注释。加上必要的注释,以增强程序的可读性和可维护性。

模块结构

Verilog的基本设计单元是“模块(block) ” 。

Verilog 模块的结构由在moduleendmodule关键词之间的4个主要部分组成:

  • 端口定义
  • I/O说明
  • 信号类型声明
  • 功能描述

逻辑功能定义

在Verilog模块中有3种方法可以描述电路的逻辑功能:

  • 用assign语句
  • 用元件例化(instantiate)
  • 用“always”块语句

关键字

事先定义好的确认符,用来组织语言结构;或者用于定义Verilog HDL提供的门元件(如andnotorbuf)。与C语言一样,用户程序中的变量、节点等名称不能用关键字。

注:用小写字母定义。

标识符

任何用Verilog HDL语言描述的“东西”都通过其名字来识别,这个名字被称为标识符。 如源文件名、模块名、端口名、变量名、常量名、实例名等。

标识符可由字母、数字、下划线和$符号构成;但第一个字符必须是字母或下划线,不能是数字或$符号。

在Verilog HDL中变量名是区分大小写的。

数据类型及常量、变量

数据类型

数据类型是用来表示数字电路中的数据存储和传送单元。

Verilog HDL中共有19种数据类型,其中3个最基本的数据类型为:

  • parameter型
  • reg型
  • wire型

常量

在程序运行过程中,其值不能被改变的量,称为常量

  • 数字(包括整数,x和z值,负数)
  • parameter常量(或称符号常量)

整数型常量(即整常数)的4种进制表示形式:

  • 二进制整数(b或B)
  • 十进制整数(d或D)
  • 十六进制整数(h或H)
  • 八进制整数(o或O)

整常数的3种表达方式:

表达方式 说明
<位宽>’<进制><数字> 完整的表达方式
<进制><数字> 缺省位宽,则位宽由机器系统决定,至少32位
<数字> 缺省进制为十进制,位宽默认为32位

注:这里位宽指对应二进制数的宽度。

x和z值:x表示不定值,z表示高阻值。

  • 每个字符代表的二进制数的宽度取决于所用的进制。
  • 当用二进制表示时,已标明位宽的数若用x或z表示某些位,则只有在最左边的x或z具有扩展性。为清晰可见,最好直接写出每一位的值。
  • ?是z的另一种表示符号,建议在case语句中使用?表示高阻态z。

负数:在位宽前加一个减号,即表示负数。

注:减号不能放在位宽与进制之间,也不能放在进制与数字之间。

parameter常量(符号常量) :用parameter来定义一个标识符,代表一个常量——称为符号常量。

parameter 参数名1 = 表达式,参数名2 = 表达式, ......; 
  • 每个赋值语句的右边必须为常数表达式,即只能包含数字或先前定义过的符号常量。
  • 常用参数来定义延迟时间和变量宽度。
  • 可用字符串表示的任何地方,都可以用定义的参数来代替。
  • 参数是本地的,其定义只在本模块内有效。
  • 在模块或实例引用时,可通过参数传递改变在被引用模块或实例中已定义的参数。

模块实例引用时参数的传递——方法之一:利用defparam定义参数声明语句。

defparam 例化模块名.参数名1 =常数表达式,
         例化模块名.参数名2 =常数表达式, ......;
  • defparam语句在编译时可重新定义参数值。
  • 提示:若不能综合时可用#号后跟参数的语法来重新定义参数。

模块实例引用时参数的传递——方法之二:利用特殊符号#

被引用模块名 # (参数1,参数2,…)例化模块名(端口列表);

变量

在程序运行过程中,其值可以改变的量,称为变量

其数据类型有19种,常用的有3种:

  • 网络型(nets type)
  • 寄存器型(register type )
  • 数组(memory type)

net型变量:输出始终随输入的变化而变化的变量,例如硬连线。 表示结构实体(如门)之间的物理连接。

常用nets型变量:

  • wire,tri:连线类型(两者功能一致)
  • wor,trior:具有线或特性的连线(两者功能一致)
  • wand,triand:具有线与特性的连线(两者功能一致)
  • tri1,tri0:上拉电阻和下拉电阻
  • supply1,supply0:电源(逻辑1)和地(逻辑0)

wire型变量:最常用的nets型变量,常用来表示以assign语句赋值的组合逻辑信号。 模块中的输入/输出信号类型缺省为wire型。 可用做任何方程式的输入,或“assign”语句和实例元件的输出。

wire 数据名1,数据名2, ......,数据名n; 

wire[n-1:0] a//宽度为n的总线a;
或 wire[n-1:0] 数据名1,数据名2, ......,数据名m;//m条总线 

register型变量:对应具有状态保持作用的电路元件(如触发器、寄存器等),常用来表示过程块语句(如initialalwaystaskfunction)内的指定信号 。

常用register型变量:

  • reg:常代表触发器
  • integer:32位带符号整数型变量
  • real:64位带符号实数型变量
  • time:无符号时间变量

  • register型变量与nets型变量的根本区别是:register型变量需要被明确地赋值,并且在被重新赋值之前一直保持原值。
  • register型变量必须通过过程赋值语句赋值,不能通过assign语句赋值。
  • 在过程块内被赋值的每个信号必须定义成register型。

reg型变量:在过程块中被赋值的信号,往往代表触发器,但不一定就是触发器(也可以是组合逻辑信号)。

reg 数据名1,数据名2, ......,数据名n; 

reg[n-1:0] 数据名1,数据名2, ......,数据名m; 
或 reg[n:1] 数据名1,数据名2, ......,数据名m; 

memory型变量——数组:由若干个相同宽度的reg型向量构成的数组。Verilog HDL通过reg型变量建立数组来对存储器建模。memory型变量可描述RAM、ROM和reg文件。

memory型变量通过扩展reg型变量的地址范围来生成:

reg[n-1:0] 存储器名[m-1:0]; 
或 reg[n-1:0]存储器名[m:1]; 

运算符

算术运算符

算术运算符 说明
+
-
*
/
% 求模
  • 进行整数除法运算时,结果值略去小数部分,只取整数部分。
  • %称为求模(或求余)运算符,要求%两侧均为整型数据。
  • 求模运算结果值的符号位取第一个操作数的符号位。
  • 进行算术运算时,若某操作数为不定值x,则整个结果也为x。

逻辑运算符

逻辑运算符把它的操作数当作布尔变量:

  • 非零的操作数被认为是真(1‘b1)
  • 零被认为是假(1‘b0)
  • 不确定的操作数(记为1’bx)
逻辑运算符 说明
&&(双目) 逻辑与
||(双目) 逻辑或
!(单目) 逻辑非

注:进行逻辑运算后的结果为布尔值(为1或0或x)。

&&||的优先级除高于条件运算符外,低于关系运算符、等式运算符等几乎所有运算符;逻辑非!优先级最高。

位运算

位运算符 说明
~ 按位取反
& 按位与
| 按位或
^ 按位异或
^~,~^ 按位同或
  • 位运算其结果与操作数位数相同。位运算符中的双目运算符要求对两个操作数的相应位逐位进行运算。
  • 两个不同长度的操作数进行位运算时,将自动按右端对齐,位数少的操作数会在高位用0补齐。

关系运算符

关系运算符 说明
< 小于
<= 小于或等于
> 大于
>= 大于或等于
  • 运算结果为1位的逻辑值1或0或x。关系运算时,若关系为真,则返回值为1;若声明的关系为假,则返回值为0;若某操作数为不定值x,则返回值为x。
  • 所有的关系运算符优先级别相同。
  • 关系运算符的优先级低于算术运算符。

等式运算符

等式运算符 说明
== 等于
!= 不等于
=== 全等
!== 不全等
  • 运算结果为1位的逻辑值1或0或x。
  • 等于运算符(= =)和全等运算符(= = =)的区别:
    • 使用等于运算符时,两个操作数必须逐位相等,结果才为1;若某些位为x或z,则结果为x。
    • 使用全等运算符时,若两个操作数的相应位完全一致(如同是1,或同是0,或同是x,或同是z),则结果为1;否则为0。
  • 所有的等式运算符优先级别相同。
  • = = =和= =运算符常用于case表达式的判别。

缩减运算符

缩减运算符 说明
&
~& 与非
|
~| 或非
^ 异或
^~,~^ 同或
  • 运算法则与位运算符类似,但运算过程不同。
  • 对单个操作数进行递推运算,即先将操作数的位0与位1进行与、或、非运算,再将运算结果与第三位进行相同的运算,依次类推,直至最高位。
  • 运算结果缩减为1位二进制数。

移位运算符

移位运算符 说明
>> 右移
<< 左移

将操作数右移或左移n位,同时用n个0填补移出的空位。

条件运算符

  • 条件运算符为?:
  • 用法:信号 = 条件?表达式1:表达式2;
  • 当条件为真,信号取表达式1的值;为假,则取表达式2的值。

位拼接运算符

  • 位拼接运算符为{ }
  • 用于将两个或多个信号的某些位拼接起来,表示一个整体信号。
  • 用法:{信号1的某几位,信号2的某几位,……,信号n的某几位}

注:

  • 可用重复法简化表达式
  • 还可用嵌套方式简化书写
  • 在位拼接表达式中,不允许存在没有指明位数的信号,必须指明信号的位数;若未指明,则默认位32位二进制数。

运算符的优先级

Cb1FMQ.png

语句

赋值语句和块语句

  • 连续赋值语句assign语句,用于对wire型变量赋值,是描述组合逻辑最常用的方法之一。
  • 过程赋值语句——用于对reg型变量赋值, 有两种方式:
    • 非阻塞(non-blocking)赋值方式:赋值符号为<=
    • 阻塞(blocking)赋值方式:赋值符号为=
  • 非阻塞赋值在块结束时完成赋值操作,块内的语句同时执行
  • 阻塞赋值在该语句结束时就完成赋值操作,块内前面的赋值语句没有完成之前,后面的语句就不能被执行

条件语句

  • 条件语句分为两种:if-else语句和case语句
  • 它们都是顺序语句,应放在“always”块内

if-else语句:判定所给条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一。

if-else语句有3种形式:

  • 其中“表达式”为逻辑表达式或关系表达式,或一位的变量。
  • 若表达式的值为0、或z,则判定的结果为“假”;若为1,则结果 为“真”。
  • 语句可为单句,也可为多句;多句时一定要用begin_end语句 括起来,形成一个复合块语句。

方式1:

if(表达式) 语句1; 

方式2:

if(表达式1) 语句1; 
else 语句2; 

方式3:

if(表达式1) 语句1; 
else if(表达式2)语句2; 
		...
else if(表达式n)语句n; 

case语句

  • 当敏感表达式取不同的值时, 执行不同的语句。
  • 功能:当某个(控制)信号取不同的值时,给另一个(输出) 信号赋不同的值。常用于多条件译码电路(如译码器、数据选 择器、状态机、微处理器的指令译码)。
  • case语句有3种形式:casecasezcasex

  • 在case语句中,分支表达式每一位的值都是确定的(或者为0,或者为1)
  • 在casez语句中,若分支表达式某些位的值为高阻值z,则不考虑对这些位的比较
  • 在casex语句中,若分支表达式某些位的值为z或不定值x,则 不考虑对这些位的比较

注:在分支表达式中,可用?来标识x或z。

case(敏感表达式) 
	值1:语句1; 
	值2:语句2; 
		... 
	值n:语句n; 
	default: 语句n+1; 
endcase 

for语句

一般形式:

for (表达式1;表达式2;表达式3)语句 

简单应用形式:

for(循环变量赋初值;循环执行条件;循环变量增值) 
	执行语句 

repeat语句:连续执行一条或多条语句n次

repeat(循环次数表达式) 
	begin
	......
	end

while语句:有条件地执行一条或多条语句

首先判断循环执行条件表达式是否为真。若为真,则执行后面的语句或语句块;然后再回头判断循环执行条件表达式是否为真,若为真,再执行一次后面的语句;如此不断,直到条件表达式不为真。

while(循环执行条件表达式) 
	begin
	......
	end

结构说明语句

always块语句

  • 包含一个或一个以上的声明语句(如:过程赋值语句、 任务调用、条件语句和循环语句等),在运行的全过程中,在定时控制下被反复执行。
  • 在always块中被赋值的只能是register型变量(如regintegerrealtime)。
Always @(<敏感信号表达式>) 
	begin 
		// 过程赋值语句
		// if语句
		// case语句
		// while,repeat,for循环 
		// task,function调用 
	end 
  • 敏感信号表达式又称事件表达式或敏感表,当其值改变时,则执行一遍块内语句;典型的敏感信号是时钟。
  • 在敏感信号表达式中应列出影响块内取值的所有信号。
  • 敏感信号可以为单个信号,也可为多个信号,中间需用关键字or连接。
  • 敏感信号不要为x或z。

  • always的时间控制可以为沿触发,也可为电平触发。
  • 关键字posedge表示上升沿;negedge表示下降沿。

由两个沿触发的always块:

always@ (posedge clock or posedge reset)
begin 
	……
end

由多个电平触发的always块:

always@ (a or b or c) 
begin
...... 
end

always块语句是用于综合过程的最有用的语句之一,但又常常是不可综合的。为得到最好的综合结果, always块程序应严格按以下模板来编写:

always @ (Inputs) //所有输入信号必须列出,用or隔开 
begin
	...... //组合逻辑关系
end
always @ (Inputs) //所有输入信号必须列出,用or隔开 
if(Enable)
begin
	...... //锁存动作
end
always @ (posedge Clock) // Clock only
begin
	...... //同步动作
end
always @ (posedge Clock or negedge Reset) 
// Clock and Reset only
begin
	if (! Reset) //测试异步复位电平是否有效
	...... //异步动作
	else
	...... //同步动作
end //可产生触发器和组合逻辑

注意事项:

  • 当always块有多个敏感信号时,一定要采用if - else if语句,而不能采用并列的if语句。否则易造成一个寄存器有多个时钟驱动,将出现不能综合的错误。
  • 通常采用异步清零。只有在时钟周期很小或清零信号为电平信号时(容易捕捉到清零信号)采用同步清零。

initial语句

initial 
	begin 
	语句1; 
	语句2; 
	……
	语句n; 
end 

task和function语句

  • task和function语句分别用来由用户定义任务和函数。
  • 任务和函数往往是大的程序模块中在不同地点多次用到的相同的程序段。
  • 利用任务和函数可将一个很大的程序模块分解为许多较小的任务和函数,便于理解和调试。
  • 输入、输出和总线信号的值可以传入、传出任务和函数。

task(任务)

  • 当希望能够对一些信号进行一些运算并输出多个结果(即有多个输出变量)时,宜采用任务结构。
  • 常常利用任务来帮助实现结构化的模块设计,将批量的操作以任务的形式独立出来,使设计简单明了。
task <任务名>; 
	端口及数据类型声明语句; 
	其他语句; 
endtask 
调用方法 
<任务名>(端口1,端口2,......); 

function(函数)

  • 函数的目的是通过返回一个用于某表达式的值,来响应输入信号。适于对不同变量采取同一运算的操作。
  • 函数在模块内部定义,通常在本模块中调用,也能根据按模块层次分级命名的函数名从其他模块调用。而任务只能在同一模块内定义与调用。
function <返回值位宽或类型说明> 函数名; 
	端口声明;
	局部变量定义; 
	其他语句; 
endfunction 
调用:<函数名>(<表达式> <表达式>) 
  • 函数的调用是通过将函数作为调用函数的表达式中的操作数来实现的。
  • 函数在综合时被理解成具有独立运算功能的电路,每调用一次函数,相当于改变此电路的输入,以得到相应的计算结果。

函数使用规则 :

  • 函数的定义不能包含任何时间控制语句——用延迟#、事件控制@或等待wait标识的语句。
  • 函数不能启动(即调用)任务。
  • 定义函数时至少要有一个输入参量。且不能有任何输出或输入/输出双向变量。
  • 在函数的定义中必须有一条赋值语句,给函数中的一个内部寄存器赋以函数的结果值,该内部寄存器与函数同名。

编译预处理语句

  • “编译预处理”是Verilog HDL编译系统的 一个组成部分。编译预处理语句以西文符号开头。
  • 在编译时,编译系统先对编译预处理语句进行预处理,然后将处理结果和源程序一起进行编译。

‵define语句

宏定义语句——用一个指定的标志符(即宏名)来代表一个字符串(即宏内容)。

‵define 标志符(即宏名)字符串(即宏内容) 
  • 宏展开——在编译预处理时将宏名替换为字符串的过程。
  • 在进行宏定义时,可引用已定义的宏名,实现层层置换。

‵include语句

文件包含语句——一个源文件可将另一个源文件的全部内容包含进来。

‵include “文件名” 

‵timescale语句

时间尺度语句——用于定义跟在该命令后模块的时间单位和时间精度。

‵timescale <时间单位> / <时间精度>
  • 时间单位——用于定义模块中仿真时间和延迟时间的基准单位;
  • 时间精度——用来声明该模块的仿真时间和延迟时间的精确程度。
  • 在同一程序设计里,可以包含采用不同时间单位的模块。此时用最小的时间精度值决定仿真的时间单位。
  • 时间精度至少要和时间单位一样精确, 时间精度值不能大于时间单位值。