Verilog HDL 基本要素

常量

Verilog 中的常量(Constant) 有三种: 整数、实数、字符串

一些整数常数:

8'b11000101 // 宽度为8位的二进制数,数值为 11000101, 等于十进制的197
8‘h8a      // 宽度为8位的十六进制,数值为8a,等于十进制的138
5'027      // 宽度为5位的八进制,数值为27,等于十进制的23
4'd10      // 宽度为4位的十进制,数值为10

如果没有明确指明,那么默认是十进制数

变量声明与数据类型

图例

数据类型可以是 net 型、variable 型

net 型变量

net 型相当于硬件电路中各种物理连接,其特点是输出的值紧跟输入值的变化而变化

wire 是最常用的 net 型变量,Verilog HDL 模块中的输入、输出信号在没有明确指定数据类型时,都被默认为 wire型

wire 型信号可以用作任何表达式的输入,也可以用作 assign 语句和实例元件的输出,如Z,其中0表示低电平、逻辑0;1表示高电平、逻辑1

Z表示高阻态。如果没有赋值,默认为高阻态Z

variable 型变量

variable 型变量是可以保存上次写入数据的数据类型,一般对应硬件上的一个触发器或锁存器等存储元件,但并不是绝对的,在综合器综合的时候,会根据其被赋值来具体确定是映射成连线还是映射为存储元件

variable 型变量也包括多种类型

variable 型变量必须在过程语句(inital 或 always )中实现赋值,这种赋值方式称为过程赋值

向量

变量声明格式中的位狂如果为1,那么对应的变量为标量,如果不为1,那么对应的变量为向量,默认为标量。向量的位宽用下面的形式定义

[MSB:LSB]

冒号左边的数字表示向量的最高有效位MSB(Most Significant Bit),冒号右边的数字表示向量的最低有效位LSB(Least Significant Bit)

例如:

wire [3:0] bus; // 4位的 wire 型bus,其中bus[3]是最高位,bus[0]是最低位
reg [31:5] ra;  // 27位的reg型向量ra,其中ra[31]是最高位,ra[5]是最低位
reg [0:7] rc;   // 8位的reg型向量rc, 其中 rc[0]是最高位,rc[7] 是最低位

向量有两种,一种是向量类向量,另一种是标量类向量,可以使用关键字区分

wire vectored [7:0] databus;    // 使用关键字 vectored,表示是向量类向量
reg scalared [31:0] rega;       // 使用关键字 scalared,表示是标量类向量

如果没有明确指出,那么默认是标量类向量,对标量类向量可以任意选中其中的一位或相邻几位,分别称为位选择(bit-select) 或域选择(part-select)

A = rega[6];    // 位选择,将向量 rega 的其中一位赋值给变量A
B = rega[5:2];  // 域选择,将向量 rega 的第5、4、3、2 位赋值给变量B

存储器可看作二维的向量。如下就是一个存储器的定义,定义了一个深度为64,每个元素宽度为32bit的存储器

reg [31:0] mem[63:0];   // mem 是深度为64,字长为 32bit的存储器

运算符

Verilong HDL 中定义的运算符包括: 算术运算符、逻辑运算符、位运算符、关系运算符、等式运算符、缩位运算符、移位运算符、条件运算符和拼接运算符

算法运算符

运算符 含义
+ 加法
减法
* 乘法
/ 除法
% 求余

逻辑运算符

运算符 含义
&& 逻辑与
逻辑非

位运算符

运算符 含义
按位取反
& 按位与
^ 按位异或
~^ 或 ^~ 按位同或

关系运算符

运算符 含义
< 小于
<= 小于等于
> 大于
>= 大于等于

等式运算符

运算符 含义
== 等于
!= 不等于
=== 全等
!== 不全等

缩位运算符

运算符 含义
&
~& 与非
|
~| 或非
^ 异或
~^ 或^~ 同或

移位运算符

运算符 含义
>> 右移
<< 左移

条件运算符

运算符 含义
?: 条件运算

位拼接运算符

运算符 含义
{ } 拼接

总结

(1) 等式运算符中的"" 与 "=" 的区别是: 对于"“运算,参与比较的两个操作数必须逐位相等,其结果才为1,如果某些值是不定态X或高阻态Z,那么得到的结果是不定值X;而对于”="运算,则要求对参与的操作数中为不定态X或高阻态Z的位与进行比较,两个操作数也必须完全一致,其结果才为1,否则结果为0

reg [4:0] a = 5'b11x01
reg [4:0] b = 5'b11x01

针对上面的 a、b,"ab"的返回结果为X,而"a=b"的返回结果为1

(2) 缩位运算符与位运算符的运算符号、逻辑运算法则都是一样的,但是缩位运算符是对单个操作数进行与、或、异或的递推运算,它放在操作数的前面,能够将一个矢量减为一个标量

reg [3:0] a;
b = &a;     // 等效于 b = ((a[0] & a[1]) & a[2]) & a[3]

而位运算需要对两个操作数对应位分别进行逻辑运算,例如

wire [3:0] a = 4'b0011;
wire [3:0] b = 4'b0101;
那么 a & b = 4'b0001, a | b = 4'b0111

(3) 位拼接运算符: 用来将两个或多个信号的某些位拼接起来

{比特序列0,比特序列1,...}

例如,在进行加法运算时,可将和与进位输出拼接在一起使用

input [3:0] ina,inb;        // 加法输入
output [3:0] sum;           // 加法的和
output count;              // 进位
assign{cout, sum} = ina + inb;  // 将和与进位拼接一起

位拼接还可以用来重复信号的某些位,其格式如下
{重复次数{被重复数据}}

利用上面的功能,可以实现对信号的符号扩展,例如

// 将 Data 的 符号进行扩展, s_data = {Data[7], Data[7], Data[7], Data[7], Data[7], Data}
wire [7:0] Data;
wire [11:0] s_data;
s_data = {{4{Data[7]}}, Data};

(4) 运算符优先级

Verilog HDL 行为语句

过程语句

Verilog 定义的模块一般包括过程语句,过程语句有两种:inital、always

其中initial 常用于仿真中初始化,其中语句只执行一次,而always中语句则是不断重复执行。此外,always 过程语句是可以综合的,initial 过程语句是不可综合的

always 过程语句

always 过程语句通常带有触发条件的,触发条件写在敏感信号表达式中,敏感信号表达式又称为事件表达式或敏感信号列表,当该表达式中变量的值改变时,就会引发其中语句序列执行。因此,敏感信号表达式中应列出影响块内取值的所有信号

敏感信号表达式

如果有两个或两个以上的敏感信号时,它们之间使用 “or” 连接,此外还是以32位加法器为例,使用assign直接赋值,其实也可以使用always 过程语句实现

只要被加数in1、加数in2中的任何一个改变,都会触发always 过程语句,在其中进行加法运算。这里有两个敏感信号in1、in2,使用"or" 连接

modeule add32(input wire[31:0] in1, input wire[31:0] in2, output reg[31:0] out);
    always @(in1 or in2)    // 使用always 过程语句实现加法
        begin
            out = in1 + in2
        end
endmodule

敏感信号列表中的多个信号可以使用逗号隔开,上面的32位加法器可以修改为如下形式

module add32(input wire[31:0] in1, input wire[31:0] in2, output reg[31:0] out);
    always @ (in1, in2) // 多个敏感信号使用逗号分隔
        begin
            out = in1 + in2
        end
endmodule

敏感信号列表也可以使用通配符"*", 表示在该过程语句中的所有输入信号变量,上面的32位加法器可以修改为如下形式:

module add32(input wire[31:0] in1, input wire[31:0] in2, output reg[31:0] out);
    always @(*)     // 使用通配符表示过程语句中的所有输入信号变量
        begin
            out = in1 + in2
        end
endmodule
组合电路与时序电路

敏感信号可以分为两种类型

  • 一种为电平敏感型
  • 另一种为边沿敏感型
  • 前一种一般对应组合电路,如上面给出的加法器的例子,后一种一般对应时序电路。对于时序电路,敏感信号通常是时钟信号,Verilog HDL 提供了posedeg、negedge 两个关键字来描述时钟信号

    posedeg 表示以时钟信号的上升沿作为触发条件,negedge 表示以时钟信号的下降沿作为触发条件。还是以32位加法器为例,可以为其添加一个时钟同步信号,如下

    module add32(input wire clk,    // 增加了一个时钟输入信号
        input wire[31:0] in1,
        input wire[31:0] in2,
        output reg[31:0] out);
        
        always @ (posedge clk)      // 在时钟信号的上升沿会触发always 中的语句
            begin
                out = in1 + in2;
            end
    endmodule
    

    在时钟信号的上升沿,才会进行加法运算,这一点与前面的加法器不同,也就是当被加数in1、加数in2变化时,并不会立即改变输出out,而是要等待时钟信号的上升沿

    initial 过程语句

    initial 过程语句的格式

    initial
    begin
        // 语句序列
    end
    

    initial 过程语句不带触发条件,并且其中的语句序列只执行一次。initial 过程语句通常用于仿真模块中对激励向量的描述,或用于给寄存器赋初值,它是面向模拟仿真的过程语句,通常不能被综合

    如下是initial 过程语句的一个例子,用于给存储器mem 赋初值

    initial
        begin
            for (addr = 0; addr < size; addr = addr + 1)    // for 是一种循环语句
        end
    

    赋值语句

    持续赋值语句

    assign 为持续赋值语句,主要用于对wire型变量的赋值。如上文中加法器的例子

    过程赋值语句

    在 always、initial 过程中的赋值语句称为过程赋值语句,多用于对reg型变量进行赋值,分为非阻塞赋值和阻塞赋值两种方式

    非阻塞赋值(Non-Blocking)

    赋值符号为"<=",例如

    b <= a
    

    非阻塞赋值在整个过程语句结束时才会完成赋值操作,即b的值并不是立刻改变的

    阻塞赋值(Blocking)

    b = a

    阻塞赋值在该语句结束时就立即完成赋值操作,即b的值在这条语句结束后立刻改变

    如果在一个块语句中,有多条阻塞赋值语句,那么在前面的赋值语句没有完成之前,后面的语句就不能被执行,仿佛被阻塞了一样,因此称为阻塞赋值方式

    在always 过程块中,阻塞赋值可以理解为赋值语句是顺序执行,而非阻塞赋值可以理解为赋值语句是并发执行的。如下面,在一个过程块中,阻塞式赋值与非阻塞式赋值只能使用其中一种

    条件语句

    if-else 语句

    if-else 语句的格式有如下3种

    (1) if (表达式) 语句序列1;     // 非完整性IF语句
    
    (2) if (表达式) 语句序列1;     // 二重选择的IF语句
        else      语句序列2;
        
    (3) if (表达式1)           语句序列1;  // 多重选择的IF语句
        else if (表达式2)      语句序列2;
        else if (表达式3)      语句序列3;
        ....
        else if (表达式n)      语句序列n;
        else                 语句序列n+1;
    

    上述格式中的“表达式”一般为逻辑表达式或关系表达式,也可能是1位的变量。系统对表达式的值进行判断,如果为0、X、Z,则按"假"处理,如果为1,则按"真" 处理。语句序列可以是单句,也可以是多句,多句时需使用begin-end块语句括起来

    还是以32位加法器为例,为其添加一个复位信号rst,如果rst为高电平,那么复位信号有效,输出out为0,反之,复位信号无效,输出out为两个输入信号之和

    module add32(input wire rst,    // 增加了一个复位信号
        input wire[31:0] in1,
        input wire[31:0] in2,
        output reg[31:0] out);
        
        always @(*)
            begin
                if (rst == 1'b1)
                    out <= 32'h0;   // 如果复位信号有效,那么输出out为0
                else
                    out <= in1 + in2;   // 反之,输出out为两个输入信号之和
        end
    endmodule
    

    case 语句

    相对于if-else 语句只有两个分支而言,case语句是一种多分支语句,所以case语句多用于多条件译码电路,如译码器、数据选择器、状态机及微处理器的指令译码

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

    当敏感表达式的值等于"值1"时,执行语句序列1;当等于“值2”时,执行语句序列2;依次类推

    如果敏感表达式的值上面列出的值都不符,那么执行default后面的语句序列。如下代码是一个简单的运算单元,可执行加法或减法运算,如果输入变量type的值为1,那么执行加法运算,如果type的值为0,那么执行减法运算

    module add_sub32(input wire type,   // type 决定运算类型
        input wire[31:0] in1,
        input wire[31:0] in2,
        output reg[31:0] out);
        always @(*)
            begin
                case (type)
                    1'b1: out <= in1 + in2; // type 为1,执行加法运算
                    1'b0: out <= in1 - in2; // type 为0,执行减法运算
                    deault: out <= 32'h0;
                endcase
            end
    endmodule
    

    case 语句中,敏感表达式与值 1 ~ n之间的比较是一种全等比较,必须保证两者的对应位全等

    casz、casex 语句是 case语句的两种扩展

  • 在casez语句中,如果比较的双方某些位的值为高阻Z,那么对这些位的比较结果就不予考虑,只需考虑其他位的比较结果
  • 在casex语句中,如果比较的双方某些位的值为Z或X,那么对这些位的比较结果就不予考虑,只需考虑其他位的比较结果
  • 此外,还有一种表示X或Z的方式,即用表示无关值的符号"?"来表示,例如

    case (a)
        2'b1x: out <= 1;    // 只有a等于2'b1x时,out才等于1
    
    casez(a)
        2'b1x: out <= 1;    // a等于2'b1x、2'b1z时,out等于1
    
    casex(a)
        2'b1x: out <= 1;    // a等于2'b10、2'b11、2'b1x、2'blz 时,out等于1
    
    case(a)
        2'b1? : out <= 1;   // a等于2'b10、2'b11、2‘b1x、2'blz时,out等于1
    

    循环语句

    for 语句

    for 语句的格式如下,这与C语言是相似的

    for (循环变量赋初值; 循环结束条件; 修改循环变量)
        执行语句序列;
    

    一个使用for语句实现7人表决器的例子如下。通过for循环统计赞成的人数,若超过4(含4人)赞成则通过,其中vote[7:1]表示7个人的投票情况,vote[i]为1,表示第i个人投的是赞成票,反之是反对票,pass是输出,超过4个人赞成,pass为1,反之为0

    module vote7(vote, pass);
        input wire[7:1] vote;
        output reg pass;
        reg[2:0] sum;
        integer i;
        
        always @(vote)
            begin
                sum = 0;
                for (i = 1; i < 7; i = i+1)
                    if (vote[i])
                        sum = sum + 1;  // 如果vote[i] 为1,那么sum加1,注意此处使用阻塞赋值
                    if (sum[2] == 1'b1) // 如果sum大于等于4,那么输出pass为1
                        pass = 1;
                    else
                        pass = 0;
            end         
    endmodule
    

    forever 语句

    forever begin
        语句序列
    end
    

    forever 循环语句连续不断地执行其中的语句序列,常用来产生周期性的波形

    repeat 语句

    repeat (循环次数表达式) begin
        语句序列
    end
    

    while 语句

    while (循环执行条件表达式) begin
        语句序列
    end
    

    while 语句在执行时,首先判断循环执行条件表达式是否为,若为真,则执行其中的语句序列,然后再次判断循环条件表达式是否为真,若为真,则再次执行其中的语句序列,如此反复,直到循环条件表达式不为真为止

    编译指示语句

    Verilog HDL 和 C 语言一样提供了编译指示功能,允许在程序中使用编译指示(Compiler Directives) 语句,在编译时,通常先对这些指示语句进行预处理,然后再将预处理的结果和源程序一起进行编译

    宏替换 `define

    `define 可以用一个简单的名字或有意义的标识(也称为宏名)代替一个复杂的名字或变量,其格式如下

    `define 宏名 变量或名字
    

    例如: 一般在时序电路中会有一个复位信号,当该复位信号为高电平时表示复位信号有效,当该复位信号为低电平时,表示复位信号无效。分别执行不同的代码

    always @(clk)
        begin
            if (rst == 1'b1)
                // 复位有效
            else
                // 复位无效
    end
    

    一种更为友好的书写方式,是使用宏定义

    // 宏定义 RstEnable 表示复位信号有效
    `deine RstEnable 1'b1
    .....
    
    always @ (clk)
        begin   
            if (rst == `RstEnable)  // 在编译的时候会自动将`RstEnable 替换成1'b1
                // 复位有效
            else
                // 复位无效
    end
    

    `include 语句

    `include 是文件包含语句,它可将一个文件全部包含到另一个文件中,使用格式如下

    `include "文件名"
    

    条件编译语句 `ifdef、`else、`endif

    条件编译语句 `ifdef、`else、`endif 可以指定仅对程序中的部分内容进行编译,有两种使用形式

    第一种使用形式如下。当执行的宏在程序中已定义,那么其中的语句序列参与源文件的编译,否则,其中的语句序列不参与源文件的编译`

    `ifdef 宏名
        语句序列
    `endif
    

    第二种使用形式如下。当指定的宏在程序中已定义,那么其中的语句序列1参与源文件的编译,否则,其中的语句序列2参与源文件的编译

    `ifdef 宏名
        语句序列1
    `else
        语句序列2
    `endif
    

    作者:Ivanqhz

    物联沃分享整理
    物联沃-IOTWORD物联网 » Verilog HDL基础概念详解

    发表回复