Verilog HDL基础篇—基本语法
admin 于 2020年08月29日 发表在 FPGA 开发

1. Verilog HDL基本结构

module 模块名( 端口1, 端口2, 端口3, ... )

1.1 I/O声明

模块的I/O声明用来声明各端口信号流动方向,包括输入(input),输出(output)和双向(inout)。如下:

1.1.1 输入声明

input[msb:lsb]  端口1, 端口2, 端口3, ...

1.1.2 输出声明

output[msb:lsb] 端口1, 端口2, 端口3, ...

1.1.3 输入、输出声明

inout[msb:lsb]  端口1, 端口2, 端口3, ...

1.2 信号类型声明

常用的信号类型有连线型(wire)、寄存器型(reg)、整形(integer)、实型(real)、时间型(time)等。

1.3 功能描述

功能描述可以用assign语句、实例化元件、always块语句、initial块语句等实现。

1.3.1 assign语句

assign后面加一个赋值语句即可。如:

assign a = b & c;

1.3.2 实例化元件

利用Verilog HDL 提供的元件库来实现一个逻辑关系。如:

and ul(q, a, b);

1.3.3 always块语句

可以实现各种逻辑,常用于时序和组合逻辑的功能描述。表示一个带有异步清零端的D触发器的实现。如下:

always @(posedge clk or posedge clr)
    begin
        if(clr) q <= 0;
        else q <= d;
    end

1.3.4 initial块语句

在程序中initial语句只执行一次,常用于电路的初始化,该过程不需要任何仿真时间,即在0ns时间内便可完成。initial块可生成激励波形作为电路的测试仿真信号,如下:

initial
    begin
            inputs = 'b0000;
        #10 inputs = 'b0001;
        #10 inputs = 'b0010;
        #10 inputs = 'b0011;
        #10 inputs = 'b0100;
        ...
    end

1.4 module模块的引用

在引用模块时,其端口可以用两种方法连接,如下:

(1)严格按照模块的定义的端口顺序来连接,不用标明原模块定义的规定的端口名,如:

模块名( 连接端口1信号名, 连接端口2信号名, 连接端口3信号名, ......)

(2)在引用时用 "." 符号,标明原模块是定义时规定的端口名,如:

模块名( .端口1名(连接信号1名), .端口2名(连接信号2名), ......)

2. Verilog HDL数据类型

2.1 常量

2.1.1 整形常量

<位宽>'<进制><数值>

位宽:对应的二进制宽度。当定义的位宽大于实际的位宽时,在常数的左边自动补0,但如果常数的最左边一位是x或z时,那么就在左边自动填补x或z;当定义的位宽比常数实际小时,在最左边的相应位就被截断。

进制:整型数有四种进制形式,如:二进制(b或B)、十进制(d或D)、八进制(o或O)、十六进制(h或H)。

数值:二进制数值可以用四种基本的值表示,如下:

0: 逻辑0或"假"
1: 逻辑1或"真"
x: 未知
z: 高阻
6'B10X1Z0     //位宽为6的二进制数,从低位数起第2位为高阻,第四位为不定值
-8'D76        //8位十进制值,即-76,符号必须写在最前面

另外,在不指定位宽时,缺省位宽由机器系统决定,但至少是32位;如果数值中既无位宽,也无进制,则缺省为十进制数。

2.1.2 实型常量

实型数可以用十进制计数法和科学计数法两种格式表示。在表示小数时,小数点两边必须都有数字,否则为非法的表示形式。

7.56    //十进制计数法6E-2    //科学计数法,其值为0.06

2.1.3 字符串常量

字符串是用双引号括起来的字符序列,必须写在一行,不能分行书写。字符串每个字符都是以其ASCII码进行存放的。如下:

"hello!"    //按照字母顺序存放,每个字母为8位ASCII码

2.1.4 参数常量(parameter

使用parameter来定义常量。参数常量定义格式如下:

parameter 标识符1 = 表达式1, 标识符2 = 表达式2, 标识符n = 表达式n;

例如:
parameter PI = 3.14, A = 8'B10110101, WORD_LENGTH = 16;

2.2 变量

2.2.1 wire型

网络数据类型之一,表示结构实体之间的物理连接。网络类型的变量不仅不能存储值,而且必须受到驱动器的驱动。如果没有驱动器连接到网络型的变量之上,那么其他为高阻值。

wire型变量常用来表示以assign语句生成的组合逻辑信号,输入/输出信号在默认情况下自动定义为wire型。wire型信号可作为任何语句中的输入,也可作为assign语句和实例化元件的输出。

wire[msb:lsb] 变量1, 变量2, ..., 变量n;

例如:
wire a, b;
wire[7:0] m, n;//定义两个8位wire变量m、n,最低位位第0位,最高位为第7位
wire[8:1] x, y;

2.2.2 reg型

reg是寄存器类型,是数据存储单元的抽象,其对应的是具有状态保持功能的电路元件,如触发器、锁存器等。

reg型只能在always 和 initial块中赋值,通过赋值语句改变reg型变量的值;若reg型变量未被赋值初始化,则其值为未知值x。

wire型变量需要持续地驱动,而reg型变量保持最后一次的赋值。

2.2.3 memory型

memory型是存储器类型,是通过建立reg型数组来描述的,可以描述RAM存储器、ROM存储器和reg文件。如下:

reg memory[1023:0];      //存储器为1024个单元,每个单元为1位
reg[7:0] memory[15:0];   //存储器为16个单元,每个单元为8位

2.2.4 integer型 (不可综合)

integer型是32位带符号整型变量,用于对循环控制变量说明,典型应用是高层次的行为建模。

2.2.5 time型(不可综合)

time类型用于存储和处理时间,是64位无符号数。

2.2.6 real型(不可综合)

real是64位带符号实型变量,用于存储和处理实型数据。

3. Verilog HDL运算符

3.1 算术运算符

+

加法运算符或正值运算符,如x+y,+8

-

减法运算符或负值运算符,如x-y,-90

*

乘法运算符,如x*y

/

除法运算符,如x/y

%

取模运算符,如x%y

3.2 逻辑运算符

&&

逻辑与,如:A&&b

||

逻辑或,如:a||b

!

逻辑非,如:!a

3.3 关系运算符

小于

<=

小于等于

大于

>=

大于等于

3.4 等值运算符

==

逻辑相等

如果操作数中的某些位可能存在不定值x或高阻值z,这时逻辑相等在进行比较时,结果为不定制x;而全等运算符按位比较,结果只能为1(真),或者0(假);

!=

逻辑不等

===

全等

!==

非全等

3.5 位运算符

~

 

 

位运算符是对两个操作数按位进行逻辑运算符的。当两个操作数的位数不同时,自动在位数少的操作数的高位补0

&

~&

与非

|

~|

或非

^

异或

^~或~^

同或

3.6 缩减运算符

&

 

 

缩减运算符与逻辑运算符的法则一样,但缩减运算符是对单个操作数按位进行逻辑递推运算的,运算结果为1位二进制数。

~&

与非

|

~|

或非

^

异或

^~

同或

3.7 移位运算符

<< 

左移

左移和右移运算符是对操作数进行逻辑移位操作的,空位用0进行补位。

>> 

右移

3.8 条件运算符

 ?:

条件运算符是唯一一个三目运算符,即条件运算符需要三个操作数,格式:条件 ? 表达式1 : 表达式2

3.9 拼接运算符

 

{}

拼接运算符用来将两个或多个数据的某位拼接起来。拼接运算符格式:{数据1的某位,数据2的某位,…,数据n的某位}

例子:X={a[7:4], b[3],   c[2:0]}

3.10 运算符优先级

运算符

                      优先级

+(正) -(负) ! ~


                   高优先级

                         .

                         .

                         .

                         .

                         .

                         .  

                         .

                   低优先级

*  /  %

+(加) –(减)

<<  >>

<  <=  >    >=

== != === !==

& ~&

^  ^~  ~^

|  ~|

&&

||

?:

4. Verilog HDL基本语法

4.1 赋值语句

4.1.1 连续赋值语句

连续赋值语句用来驱动wire型变量,这一变量必须事先定义过。使用连续赋值语句时,只要输入端操作数的值发生变化,该语句就重新计算并刷新赋值结果。连续赋值语句用来描述组合逻辑。如下:

assign #(延时量) wire 型变量名 = 赋值表达式

4.1.2 过程赋值语句

过程赋值语句是在initial或always语句块内赋值的,用于对reg型、memory型、integer型、time型和real型变量赋值。这些变量在下一次过程赋值之前保持原来的值。包含:阻塞赋值和非阻塞赋值。

4.1.2.1 阻塞赋值

使用 "=" , 它在该语句结束时就完成赋值操作。如:b = a ; 

(1)赋值语句执行完成后,块才能结束;

(2)b的值在赋值语句执行完成后立刻就改变的;

(3)在时序逻辑中的使用时,可能产生意想不到的结果;

4.1.2.2 非阻塞赋值

使用 "<=" , 它在块结束时才能完成赋值操作。如:b <= a ;

(1)在语句块中,上面语句所赋值的变量值不能理解为下面的语句所用;

(2)块结束后才能完成这次赋值操作,而所赋变量值是上一次赋值得到的;

(3)在编写可综合的时序逻辑模块时,这是最常用赋值方法; 

4.2 块语句

块语句的作用是将多条语句并成一组,使它们像一条语句那样。块语句包括两种类型:顺序块和并行块。

4.2.1 顺序块

关键字 begin ... end 将多条语句组成顺序块,格式如下:

begin
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
end

或

begin: 块名
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
end

顺序块具有以下三个特性:

(1)块内的语句是顺序执行的,即只有上面的一条语句执行完成后下面的语句才能执行;

(2)每条语句的延迟时间都是相对于前一条语句的仿真时间而言的;

(3)直到最后一条语句执行完,程序流控制才跳出语句块; 

4.2.2 并行块

由关键字 fork ... join 声明,格式如下:

fork
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
join

或

fork: 块名
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
join

并行块具有以下四个特性:

(1)块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句则开始同时并行地执行;

(2)块内每条语句的延迟时间是相对于程序流程控制进入到块内的仿真时间的;

(3)延迟时间是用来给赋值语句提供执行时序的;

(4)当按照时间序排序在最后的语句执行完或一个 disable 语句执行时,程序流程控制跳出该程序块;

4.2.2 块语句的特点

块语句具有三个特点:嵌套块、命令块和命名块的禁用。

4.2.2.1 嵌套块

块可以嵌套使用,顺序块和并行块可以混合一起使用。格式如下:

initial
begin: 
    语句 1 ;
    语句 2 ;
    ...
    fork
        语句 1 ;
        语句 2 ;
        ...
        语句 n;
    join
end

4.2.2.2 命令块

在Verilog HDL语言中,可以给每个块取一个名字,只需要将名字加在关键字 begin 或 fork 后面即可。格式如下:

initial
begin: 块名1    //名字为“块名1”的顺序命名块
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
end

initial
fork: 块名2    //名字为“块名2”的并行命名块
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
join

块可以具有自己的名字,这称为命名块,具有以下特点:

(1)命名块中可以声明局部变量;

(2)命名块是设计层次的一部分,命名块中声明的变量可以通过层次名引用进行访问;

(3)命名块可以被禁用,例如停止其执行;

4.2.2.3 命名块禁用

Verilog HDL通过关键字 disable 提供了一种中止命名块执行的方法。disable可以用来从循环中退出、处理错误条件以及根据控制信号来控制某些代码段是否被执行。

在Verilog HDL语言中,可以给每个块取一个名字,只需要将名字加在关键字 begin 或 fork 后面即可。格式如下:

fork: 块名
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
    disable 块名;
join

或

begin: 块名
    语句 1 ;
    语句 2 ;
    ...
    语句n ;
    disable 块名;
end

具有以下四个特点:

(1)可以在块内定义局部变量,即只在块内使用的变量;

(2)可以允许块被其他语句调用,如 :disable 可以用来从循环中退出、处理错误条件以及根据控制信号来控制某些代码是否被执行,类似C语言的break;

(3)在Verilog语言里,所有的变量都是静态的,即所有的变量都只有一个唯一的存储地址,因此进入或跳出块并不影响存储在变量内的值。

4.3 条件语句

用于根据某个条件来确定是否执行其后的语句,使用关键字if和else表示。

4.3.1 if_else 语句

用来判断给定的条件是否满足,根据判断结果为真或假决定执行的操作。提供三种形式:

(1)if 语句

if(表达式) 语句

(2)if ... else 语句

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

(3)if .. else if .. else 语句

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

4.3.2 case 语句

一种多种分支语句,常常应用于需要多分支选择的地方。case语句格式如下:

case(控制表达式)    
    分支项表达式1 : 语句1    
    分支项表达式2 : 语句2    
    ...    
    分支项表达式m : 语句m    
    default: 语句n
endcase

4.4 循环语句

Verilog HDL语言中存在4种类型的循环语句,用来控制执行语句的执行次数,分别为:forever语句、repeat语句、while语句、for语句。

4.4.1 forever 语句

forever循环语句常用产生周期性波形。它与initial语句不同之处在于它不能独立写在程序中,而必须写在initial块中。forever语句常用于产生仿真测试信息。如下:

initial
begin    
    clock = 0;    
    #5 forever    
    #10 clock = ~clock 
end

4.4.2 repeat 语句

用于执行指定循环次数的过程语句,格式如下: repeat(表达式) 语句

如果循环计数表达式的值不确定,即为x或z时,那么循环次数按0处理。例子:

initial
begin    
    s = 0;    
    i = 1;    
    repeat(100)
    begin        
        s = s + i;        
        i = i + 1;    
    end
end

4.4.3 while 语句

执行while时,先对条件进行判断,如果条件为真,那么执行该语句;如果条件为假,那么退出循环;如果条件为x或Z,那么按0(假)处理。例子如下:

reg[7:0] memory[0:255];
initial
begin    
    reg[7:0] i;    
    i = 0;    
    while(i<=255)    
    begin        
        memory[i] = 0;        
        i = i + 1;    
    end
 end

4.4.4 for 语句

按照指定的次数重复执行过程赋值语句若干次。例子:

reg[15:0] x, y;
reg[31:0] s;
initial
begin    
    reg[3:0] i;    
    s = 0;    
    for(i = 0; i <= 15; i=i+1)    
        if(y[i]) s = s + (x << i);
end

4.5 结构说明语句

Verilog HDL语言中的任何过程模块都从属于四种结构说明语句:initial说明语句、always说明语句、task说明语句、function说明语句。

4.5.1 initial 说明语句

常用于对各变量的初始化。一个程序模块中可以有多个initial语句,所有initial语句在程序一开始时同时执行,并且只初始化一次。如下:

initial
begin    
    reset = 1;    
    #3 reset = 0;    
    #5 reset = 1;
end

4.5.2 always 说明语句

与initial语句一样,一个程序中可以有多个always语句,always语句也是在程序一开始时立即被执行的,不同的是 always 语句不断地重复运行。但always语句后跟的语句是否执行,需要看其敏感事件列表是否满足,若有条件满足,则运行一次;如不断满足,则不断循环执行。语句格式如下:

always @(敏感事件列表) 语句

reg[7:0] count
always @(posedge clk)
begin    
    count = count + 1'b1; 
end

4.5.3 task 说明语句

用来定义任务,类似于高级语言中的字程序,用来单独完成某项任务,并被其他模块或其他任务调用。语句格式如下:

task 任务名;   
    端口声明语句;    
    类型声明语句; 
    ...   
    语句n;
endtask

例子(红绿黄交通灯):
module traffic_lights;
    reg clock, red, amber, green;
    parameter on = 1, off = 0, red_tics = 350, amber_tics=30, green_tics=200;
    
    // 初始化
    initial red = off;
    initial amber = off;
    initial green = off;
    
    // 时序控制
    always
        begin
            red = on;
            light(red, red_tics);
            green = on;
            light(green, green_tics);
            amber = on;
            light(amber, amber_tics);
        end
    
    // 交通灯开启时间任务
    task light;
        output color;
        input[31:0] tics;
        begin
            repeat(tics) 
                @(posedge clock);
            color = off; 
        end
    endtask
    
    // 产生时钟脉冲的always块
    always
        begin
            #100 clock = 0;
            #100 clock = 1;
        end
            
endmodule

注意以下几点:

(1)任务的定义和调用必须同一模块内。任务定义不能出现在任何一个过程块内部,任务的调用应在always块、initial块或另外一个任务中;

(2)任务定义时,task语句后没有端口名列表,输入输出端口名是通过端口声明语句进行顺序声明的;一个任务也可以没有输入输出端口。

(3)当任务被调用时,任务被激活。如果一个任务有输入输出端口,调用时需列出端口名列表,其顺序和类型应该与任务定义中的完全一致。

(4)进行任务调用时,参数的传递是按值传递的,不能按址传递;

(5)一个任务可以调用别的任务或函数,可调用的任务和函数的个数不受限制;

4.5.4 function 说明语句

类似高级语言中的函数,用来单独完成某项具体的操作。函数可以作为表达式的一个操作数,也可以被模块、任务或其他函数调用,函数调用时有一个返回值。语句格式如下:

function (返回值的类型或范围) 函数名    
    端口声明语句;    
    类型声明语句;    
    ...
    语句n;
endfunction

例子(偶校验计算):
module parity;
    reg[31:0] addr;
    reg parity;
    initial
    begin
        addr = 32'h3456_789a;
        #10 addr = 32'hc4c6_78ff;
        #10 addr = 32'hff56_ff9a;
        #10 addr = 32'h3faa_aaaa;        
    end
    
    // 当地址值发生变化,计算新的校验值
    always @(addr)
    begin
        parity = calc_parity(addr);
    end
    
    // 定义偶校验函数
    function calc_parity;
        input[31:0] address;
        begin
            calc_parity = ^address;
        end
    endfunction
    
endmodule

注意以下几点:

(1)函数只能有一个返回值,而任务不返回值。函数的返回值只是通过函数名返回的,而任务的返回值则通过输出端口传递的。

(2)函数至少有一个输入变量,而任务可以没有或有多个任何类型的变量;

(3)函数只能与主模块共用一个仿真时间,而任务可以定义自己的仿真时间单位;

(4)函数不能启动任务,而任务能够启动其他任务和函数;

4.6 编译预处理语句

4.6.1 宏定义(`define 和 `undef)

`define 指令是用一个指定标识符代替一个字符串,格式如下:

`define 宏名 字符串

在预编译处理时,把程序中在该命令以后所有的 “宏名” 替换成 “字符串”。可以用一个简单的名字代替一个长的字符串,也可以用一个有含义的名字代替没有含义的数字和符号。

注意以下几点:

(1) `define 命令可以出现在模块定义里面,也可以出现在模块定义外面。宏名的有效范围为定义命令之后到原文件结束;

(2)在引用已定义的宏名时,必须在宏名前面加上符号 " ` ",表示该名字是一个宏定义名字;

(3)宏定义不是Verilog HDL语句,不必在行末加分号。如果加了分号会连分号一起进行置换;

4.6.2 文件包含(`include)

`include语句用来实现文件的包含操作,它可以将一个源文件的全部内容包含到本文件中。语法格式如下:

`include "文件名"

注意以下几点:

(1)一个 `include 命令只能指定一个被包含的文件,如果要包含n个文件,要用n个 `include 命令;

(2) `include 命令可以出现在Verilog HDL源程序的任何地方,被包含文件名可以是相对路径名,也可以是绝对路径名;

(3) 在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。

4.6.3 时间尺度(`timescale)

在Verilog HDL中,`timescale 命令用来说明跟在该命令后的模块的时间单位和时间精度。使用`timescale命令可以在同一个设计里面包含采用不同的时间单位模块。格式如下:

`timescale 时间单位/时间精度

该命令中,时间单位参量是用来定义模块中仿真时间和延迟时间的基准单位的。时间精度参量,被用来相对延迟时间值进行取整操作(仿真前),因此该参量又可以被称为取整精度。

4.6.4 条件编译(`ifdef 、`else 、`endif)

条件编译命令,当条件满足时才进行编译,条件不满足时不编译这些语句。一般有两种形式:

(1)第一种

当宏名已经被定义过,则对程序段1进行编译,程序端2将被忽略;否则编译程序段2,程序段1将被忽略。

`ifdef 宏名(标识符)
程序段1
`else
程序段2
`endif

(2)第二种

`ifdef 宏名(标识符)
程序段1
`endif

“宏名”是一个Verilog HDL的标识符,“程序段”可以是Verilog HDL语句组,也可以是命令行。

参考来源:

[1] 《数字电路设计及Verilog HDL实现》    康磊            西安电子科技大学出版社

[2] 《Verilog数字系统设计教程》               夏宇闻         北京航空航天大学出版社

注意:本站所有文章除特别说明外,均为原创,转载请务必以超链接方式并注明作者出处。 标签:FPGA,Verilog,HDL语法