Verilog自学

参考教材:《Verilog数字系统设计教程(第二版)》(夏宇闻 编著)

Verilog自学真困难。

Verilog语言练习网站:HDLBits

第一章 模块

1.1 概述

Verilog的基本设计单元是模块。一个模块由两部分构成,一部分描述接口,一部分描述逻辑功能。

例1 设计一个2输入(a,b)、2输出(c,d)的模块block,输出c为输入相与,输出d为输入相或。

module block(a,b,c,d)
input a,b;
output c,d;

assign c = a | b;
assign d = a & b;
endmodule

1.2 模块端口的声明

模块的端口声明了模块的输入输出口,格式如下:

module 模块名(1,2,3,4,...);

1.3 模块内容

模块的内容包括I/O说明、内部信号声明和功能定义。

  • I/O说明

输入口:

input [信号位宽-1:0] 端口名1;
input [信号位宽-1:0] 端口名2;
...

输出口:

output [信号位宽-1:0] 端口名1;
output [信号位宽-1:0] 端口名2;
...

I/O说明也可以放在端口声明的语句里,格式如下:

module module_name(input port1,input port2,...
            output port1,output port2,...);
  • 内部信号声明

内部信号用reg或wire表示。每个逻辑门输入输出都需要指定wire或reg类型。wire表示直通,即只要输入有变化,输出马上无条件地反映;reg表示一定要有触发,输出才会反映输入。在模块内用到的和端口有关的wire和reg类型变量格式如下:

reg [width-1:0] R变量1,R变量2,...;
wire [width-1:0] W变量1,W变量2,...;
  • 功能定义

模块中最重要的是逻辑功能定义部分。有3种方法:

1、用assign声明语句

assign a=b&c;//a为b与c

2、用实例元件

and #2 u1(q,a,b);

说明:上面语句表示设计中用到一个跟与门(and)一样的,名为u1的与门,输入为a,b,输出为q,#2表示输出延迟为2个单位时间。要求每个实例元件的名字是唯一的。

3、用always语法块

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

说明:always块既可以描述组合逻辑,又可以描述时序逻辑。上面的例子生成了一个带有异步清除端的D触发器(目前还看不懂,提前感受下)。

1.4 模块的引用

类似于程序设计语言里面函数的调用。比如,引用4个可扩展4位比较器来构建16位比较器。

引用方式有两种:

1、严格按照模块定义的端口顺序连接,不必标明原模块的端口名。即

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

2、用“.”注明原模块是定义时规定的端口名。即

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

第二章 数据类型、常量和变量

2.1 常量

  • 数字

1、整数

有3种表达:

(1) <位宽> <进制> <数字>

这是最全面的描述方式。如:

8'b10101100  //8:位宽  'b:二进制   即,8位二进制数10101100
8'ha2        //'h:十六进制   即,8位十六进制数a2(位宽是对二进制而言的)

(2) <进制> <数字>

采用默认位宽,由机器决定,至少32位。

(3) <数字>

采用默认进制十进制。

2、x值和z值

x代表不定值,z代表高阻态。x可以用来代表十六进制中的4位二进制数的状态,也可以代表八进制中的3位二进制数的状态,二进制数中的1位。z类似。z也可写作”?”。(有啥用?)

4'b10x0
4'b101z

3、负数

负号必须写在数字表达式的最前面。

-8'd5   //表示5的补码,用8位二进制表示

4、下划线

用来分隔开数的表达,提高程序的可读性。

16'b1010_1011_1100_0011
  • parameter型(参数型)
parameter 参数名1=表达式,参数名2=表达式,参数名3=表达式,...;

参数型常用来定义延迟时间和变量宽度。在模块或实例引用时,可通过参数传递改变在被引用模块中已经定义的参数(目前不懂)。

2.2 变量

wire表示直通,即只要输入有变化,输出马上无条件地反映;reg表示一定要有触发,输出才会反映输入。

  • wire型

wire型数据常用来表示用以assign关键字指定的组合逻辑信号。格式:

wire [位宽-1:0] 数据名1,数据名2,数据名3,...;
  • reg型

就是寄存器,是数据存储单元的抽象。通常,在设计中要用always模块使用行为描述语句来表达逻辑关系。常用reg来表示always模块内的指定信号,代表触发器。格式:

reg [位宽-1:0] 数据名1,数据名2,数据名3,...;
  • memory型

Verilog通过对reg型变量建立数组来对存储器建模,可以描述ROM等存储器。Verilog中没有多维数组,memory格式如下:

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

如:

reg [7:0] mema[255:0];//存储器名为mema,有256个8位存储单元(寄存器),地址范围是0-255

第三章 运算符与表达式

3.1 与大部分程序设计语言含义相同的运算符

  • 算术运算符:+,-,*,/,%

  • 位运算符:~,&,|,^(异或),^~(同或)

  • 逻辑运算符:&&,||,!(逻辑非)

  • 关系运算符:<,>,<=,>=

  • 移位运算符:>>,«

3.2 等式运算符

==,!=:逻辑等式运算符,结果由两个操作数的值决定,由于操作数中某些位可能是不定值x和高阻态z,因此结果可能为不定值x。只要其中一个操作数中含有x或z,结果即为x。

===,!==:case等式运算符(因为它们常用于case表达式的判别),对不定值x和高阻态z也进行比较,两个操作数必须完全一致才为1,否则为0。

3.3 拼接运算符

{}

使用方法:

{信号1的某几位,信号2的某几位,信号3的某几位,...}

{a,b[3:0],w,3'b101}

重复信号,如:

{4{w}}          //相当于{w,w,w,w}

嵌套表达,如:

{b,{3{a,b}}}    //相当于{b,a,b,a,b,a,b}

3.4 缩减运算符

功能:多个与、或、非运算的缩减。如:

reg [3:0] B;
reg C;
C=&B;

相当于

C=((B[0] & B[1]) & B[2]) & B[3];

3.5 赋值语句

<=:非阻塞式赋值,并行执行。一般用于时序电路。

=:阻塞式赋值,后边的语句必须在这句执行完毕才能执行(顺序执行)。一般用于assign语句。

举例说明:

例2(非阻塞式赋值)

always @ (posedge clk)
    begin
        b <= a;
        c <= b;
    end

事实上实现了下面的电路图:

例2

例3(阻塞式赋值)

always @ (posedge clk)
    begin
        b = a;
        c = b;
    end

事实上实现了下面的电路图:

例3

第四章 控制流

块语句、条件语句、循环语句、生成语句

4.1 块语句

4.1.1 顺序块

特点:块内语句顺序执行;每条语句的延迟时间是相对于前一条语句的仿真时间而言的;直到最后一条语句执行完,程序流程控制才跳出该语句块。

格式:

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

可以自行添加两语句间的时间延迟(此处每条语句的延迟时间是相对于前一条语句的仿真时间而言的):

begin
    areg=breg;
    #10 creg=areg;
    //在两条赋值语句之间延迟10个时间单位
end

4.1.2 并行块

特点:块内语句同时执行;块内每条语句的延迟时间是相对于程序流程控制进入到块内的仿真时间的。

格式:

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

4.1.3 命名块的禁用

命名块:块可以有自己的名字,例如:

begin: block1 //该语句块的名字为block1
    语句1;
    语句2;
    ...
    语句n;
end

命名块的一大好处是可以用disable关键字终止执行。这一点非常类似于C语言中用break语句来退出循环的执行。

务必注意:在阅读下面的例题之前,请先学习4.2条件语句和4.3循环语句。

例4 从寄存器flag的低有效位开始,查找第一个值为1的位。

reg [15:0] flag;
integer i;//用于计数的整数

initial
begin
    flag=16'b0010_0000_0000_0000;
    i=0;
    begin: block1
        while(i<16)
        begin
            if(flag[i])
            begin
                $ display("Encountered a TRUE bit at element number %d",i);
                disable block1; //在标志寄存器flag中找到了值为1的位,禁用block1
            end
            i=i+1;
        end
    end
end

4.2 条件语句

过程块语句:initial和always语句引导begin end块。

条件语句必须在过程块语句中使用。

条件语句分为:if else语句,case语句

4.2.1 if else语句

与一般程序设计语言相同,3种形式:

(1)

if(条件表达式)
    语句;

(2)

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

(3)

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

如果if语句中含有多条语句,应当用beginend将它们包含起来。如:

if(a>b)
    begin
        out1<=int1;
        out2<=int2;
    end
else
    begin
        out1<=int2;
        out2<=int1;
    end

表达式可以简写:

if(expression)

等价于

if(expression==1)

4.2.2 case语句

一般形式:

case(表达式)
    <case分支项>
endcase

case分支项的一般形式:

分支表达式:       语句;
default:         语句;

举个例子:

reg [15:0] rega;
reg [9:0] result;
case(rega)
    16'd0: result=10'b0111111111;
    16'd1: result=10'b1011111111;
    16'd2: result=10'b1101111111;
    16'd3: result=10'b1110111111;
    16'd4: result=10'b1111011111;
    16'd5: result=10'b1111101111;
    16'd6: result=10'b1111110111;
    16'd7: result=10'b1111111011;
    16'd8: result=10'b1111111101;
    16'd8: result=10'b1111111110;
    default: result=10'bx;
endcase

注意:

执行完case分支项的语句后,会跳出该case语句结构(与C语言不同!)。

case语句所有表达式值的位宽必须相等,这样才能比较。

不当使用条件语句会导致锁存器的产生。例如

always @ (al or d)
    begin
        if (al) q=d
    end

这里if语句保证了只有当al=1时,q才取d的值,但是没有给出al=0的值,因此这时q会延续之前的取值,也就是说会形成一个锁存器。

可以使用末尾的else或者default分支来避免锁存器的产生。

4.3 循环语句

Verilog中提供了四种类型的循环语句:forever语句,repeat语句,while语句,for语句。forever以后再说。

4.3.1 repeat语句

格式:

repeat(循环次数表达式)
    begin
        语句1;
        语句2;
        ...
        语句n;
    end

其中,循环次数表达式用于指定循环次数,可以是一个整数、变量或者数值表达式。

例5 使用repeat循环、加法和移位语句实现乘法器。

parameter size=8, longsize=16;
reg [size:1] opa,opb;
reg [longsize:1] result;

begin: mult    //这里mult是该语句块的名称,通过begin加冒号来定义
    reg [longsize:1] shift_opa, shift_opb;
    shift_opa = opa;
    shift_opb = opb;
    result=0;
    repeat(size)
        begin
            if(shift_opb[1])
                result=result+shift_opa;
            
            shift_opa=shift_opa<<1;
            shift_opb=shift_opb>>1;
        end
end

4.3.2 while语句

一般形式:

while(表达式)
    begin
        语句1;
        语句2;
        ...
        语句n;
    end

与C语言一致,不再赘述。

4.3.3 for语句

一般形式:

for(循环变量赋初值;循环结束条件;循环变量增值)
    begin
        语句1;
        语句2;
        ...
        语句n;
    end

与C语言一致,不再赘述。

4.4 生成语句

生成语句是用来生成一些代码的。它分为循环生成语句、条件生成语句和case生成语句。

循环生成语句可以用来减少一些相似代码的重复编写,简化代码。条件生成语句的目的是左右编译器的行为,类似于C语言中的条件选择宏定义,根据一些初始参数来决定载入哪部分代码来进行编译。case生成与条件生成作用相似。

生成语句的介绍请参见这篇CSDN博文:verilog generate 生成语句

第五章 结构说明语句

initial说明语句,always说明语句

initial语句只执行一次,用于初始化;always语句不断地重复活动,直到仿真过程结束。

5.1 initial说明语句

语法:

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

例6 用initial 块对存储器变量赋初值。

initial begin
    areg=0;
    for(index=0;index < size;index=index+1)
        memory[index] = 0;
end

5.2 always说明语句

语法:

always <时序控制> begin
    语句1;
    语句2;
    ...
end