Verilog HDL 基本概念

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值