前言

Verilog数据类型,比较常用也就是reg以及wire了,但是其他的也要了解呀,下面根据《FPGA之道》的描述,来一起复习(学习)下。

Verilog数据类型

在Verilog语言中,主要有三大类数据类型,即寄存器数据类型、线网数据类型和参数数据类型。从名称中,我们可以看出,真正在数字电路中起作用的数据类型应该是寄存器数据类型和线网数据类型,它们共同遵守Verilog的四值逻辑系统。下面对它们分别进行介绍:

Verilog四值逻辑系统

在Verilog的逻辑系统中有四种值,也即四种状态:
逻辑1:表示逻辑高电平;
逻辑0:表示逻辑低电平;
不确定逻辑X:表示不可推断电平,一般是由于赋值冲突导致;
高阻逻辑Z:表示高阻态,相当于电路中的断路;
可以参看下图来帮助我们形象的去理解这四种逻辑状态:
FPGA之道(33)Verilog数据类型_赋值

寄存器数据类型

Verilog中规定,凡是在程序块中被赋值的变量,都必须是寄存器类型的。对应于实际的数字电路中,如果该程序块描述的是时序逻辑,则该寄存器变量对应为寄存器;如果该程序块描述的是组合逻辑,则该寄存器变量对应为硬件连线;如果该程序块描述的是不完全组合逻辑,那么该寄存器变量也可以对应为锁存器。由此可见,寄存器类型的变量不一定会综合为寄存器,这一点需要注意。寄存器数据类型中包括一些子类型,分别介绍如下:

reg

reg类型是寄存器数据类型中最主要、最常用的一种数据类型,使用语法如下:
reg <variable_name>;
其中,如果不写的话,默认是定义1位的reg类型。关于reg的例子如下:
reg a;
reg [7:0] b;
如果有多个同样位宽的reg变量需要声明,也可以写成这样:
reg [3:0] c, d, e;

integer

integer是整数类型,对应32位二进制表示。
需要说明的是,虽然Verilog语法中有整数类型,但是我们并不用它来处理真正的数据计算,一般都是用reg和wire类型来实现数据的处理的。因为整数类型抽象层级过高,不适合在FPGA中使用作为主要数据类型使用,不过Verilog终究是一种编程语言,它是在PC机上进行编程的,编译环境也跑在PC机上,所以有些时候,整数类型也是很必要的,例如,循环语句中的循环变量等参数。整数类型的示例如下:
integer i;

real

real是实数类型,这个数据类型的抽象级别比较高,不利于开发者对FPGA的资源掌控,而且编译器还不一定能够很好的支持。并且在数字的世界里以对数形式或位宽比较大的reg或wire再配合上描述小数点位置的reg或wire类型就可以达到比较高精度的运算。因此一般不建议使用该类型。

线网数据类型

Verilog中规定,模块的input和inout端口必须是线网类型;连续赋值语句的被赋值对象必须是线网类型。对应于实际的数字电路,线网类型实际上就对应着硬件的连线,起到连接作用。 线网数据类型包括很多个子类型,分别介绍如下:

wire

wire是线网中最常用的一种数据类型,使用语法如下:
wire <variable_name>;
其中,如果不写的话,默认是定义1位的wire类型。关于wire的例子如下:
wire a;
wire [7:0] b;
如果有多个同样类型的wire变量需要声明,也可以写成这样:
wire [3:0] c, d, e;

tri

tri其实和wire在用法上是一摸一样的,不过有时候,我们需要定义一些会被三态门驱动的硬件连线,用tri来命名会让代码更具有可读性,让人一看就知道这根连线上会出现Z状态,仅此而已。

supply1/supply0

这两个线网类型分别表示强行上拉到逻辑1和强行下拉到逻辑0,可以理解为电源线和地线,在Verilog中可以当做常数来使用。

wand/triand

这两个线网类型都是表示线与逻辑,跟wire与tri一样,wand与triand之间也仅仅是名字不同而已。线与逻辑一般用于集电极开路的电路中,此时的连线上,即使有多个驱动源也不会出现X状态,而是通过多个输出端的直接互连就可以实现“与”的逻辑功能。在硬件上,可用OC门或三态门(ST门)来实现。用OC门实现线与,应同时在输出端口应加一个上拉电阻,因为OC门本身不具有输出高电平的能力。
注意,一般FPGA内部不具有这种功能结构,所以建议大家做简要了解即可。

wor/trior

这两个线网类型都是表示线或逻辑,它们也仅仅是名字不同而已。线或逻辑一般用于射极耦合电路,此时的连线上,即是有多个驱动源也不会出现X状态,而是通过多个输出端的直接互连就可以实现“或”的逻辑功能。
注意,一般FPGA内部不具有这种功能结构,所以建议大家做简要了解即可。

tri1/tri0/ trireg

这三个线网类型分别表示当连线被置为高阻态时,进行上拉还是下拉还是保持之前的值,上拉即上拉到逻辑1,下拉即下拉到逻辑0,保持即之前线上是什么现在还是什么,主要指电容特性。此三种类型也仅作了解即可。

参数数据类型

parameter

parameter关键字是用来定义module里的一些参数,在之前Verilog基本程序框架一小节中,我们已经做过基本了解,知道它可以声明在两个地方,一是模块的接口部分,一是模块实现的声明部分。并且在模块实例化的时候,可以重新定义parameter的值,来实现模块的复用。这里我们再多补充一点,那就是使用parameter关键字定义参数时,即使是不同类型的参数,也可以一并定义,例如:
parameter a = 15, b=3’b110, c = a+7;

localparam

localparam关键字也是用来定义module里的一些参数,它与parameter功能类似,唯一的不同就是在上层模块中的例化时不能对其取值进行重新定义。不过一种变通的方法就是在localparam的赋值等式中使用parameter参数,例如:
parameter N = 15;
localparam M = N+1;
这样便可以通过修改N的值来达到改变M的值。

specparam

Verilog中有一种特殊的语法块,叫做specify,它主要用于定义模块的时序模型。而specify语法块中也可以拥有自己的参数,为了区别与module内的参数,所以使用了specparam的关键字。举例如下:
specparam in_to_out = 9;

如何定义数组

Verilog中只支持一维数组,声明语法如下:
type <type_range> <array_name><array_range>;
例如,要定义一个32bit位宽,可存储512个数据的存储器,可以声明如下:
reg [31:0] myRam[511:0];
注意,Verilog中的数组只支持到元素的访问,即以下操作是没有问题的:
wire [31:0] dOut;
assign dOut = myRam[256];
而以下操作是不允许的:
wire signBit;
assign signBit = myRam[128][31]; //will cause compile error!
那么,若要查看myRam中存储的某一个数据的某一个bit,需要用以下方法:
assign dOut = myRam[256];
assign signBit = dOut[31];

常量表示方法

reg和wire是Verilog中两种最重要的逻辑变量类型,可以说代码中有了这两个类型的变量,就几乎能完成所有功能。它们中存储的数据都是逻辑数据,即遵守Verilog四值逻辑系统,那么,当我们需要给它们赋一些常数值时,需要遵循什么样的表达方式呢?

二进制表示法

数字的世界就是二进制的世界,所以二进制数据有着极大的用武之地,因此,二进制表示法是逻辑常数最重要的一种表示法。它的语法如下:
<bit_width>'b //注b与B均可
例如:
wire a;
assign a = 1’b1;
wire [7:0] b;
assign b = 8’b11001110;
如果需要设置高阻状态,也可以用z或Z代替,例如:
wire [7:0] bus;
assign bus = 8’bzzzzZZZZ;
注意,没有关于x状态的常量赋值,因为x状态是由于赋值冲突产生的,而不是被单独赋予的,所以像 a = 1’bx; 这样的赋值,没有人知道这代表什么意义。
二进制表示法的优点就是物理意义十分清晰,不过缺点就是当位宽较大时容易让人看花眼,对此,Verilog引入了下划线‘_’作为分隔符,该分隔符没有任何意义,仅仅起视觉辅助作用,例如:
reg [7:0] m;
m <= 8’b1110_1100; //must in process
有时候,常数部分超出了位宽指示部分的宽度,例如:
reg [3:0] n;
n = 4’b00111; //must in process, will give out a warning!
此时编译器一般会给出warning,建议编程时最好不要出现不匹配的情况,保持一个良好的编程习惯!

八进制表示法

八进制是二进制更高一级的抽象,它相比于二进制在书写的时候简洁一些,因为1位八进制数代表3位二进制数。缺点就是对于位宽不是3的整数倍的常量不能很完美的表达。八进制的表示语法如下:
<bit_width>'o //注o与O均可
例如:
wire [2:0] a;
assign a = 3’o5;
如果需要设置高阻状态,也可以用z或Z代替,例如:
wire [5:0] bus;
assign bus = 6’ozZ;
与二进制一样,八进制中也支持下划线‘_’作为分隔符。
对于八进制来说,不匹配现象是很容易碰见的,此时,编译器一般会将多余的高位截掉, 例如:
wire [3:0] c;
assign c = 4’o35;
相当于
assing c = 4’b1101;

十六进制表示法

相比于八进制,十六进制表示法要更常用一些。1位十六进制的数可以表示4位二进制的数。缺点就是对于位宽不是4的整数倍的常量不能很完美的表达。十六进制的表示语法如下:
<bit_width>'h //注h与H均可
例如:
wire [7:0] a;
assign a = 8’hF7;
如果需要设置高阻状态,也可以用z或Z代替,例如:
wire [15:0] bus;
assign bus = 16’hZzZz;
与二进制、八进制一样,十六进制中也支持下划线‘_’作为分隔符。
对于十六进制来说,不匹配现象也是很容易碰见的,此时,编译器一般会将多余的高位截掉, 例如:
wire [5:0] c;
assign c = 6’hFA;
相当于
assing c = 6’b11_1010;

十进制表示法

十进制表示法是我们人类最好理解的表示法,但是却不是数字电路中最好理解的表示法,因为1位十进制的数据约对应3.322位二进制数,所以我们无法快速的通过一个十进制数确定物理上的位数以及每位的状态。因此十进制表示法对任何位宽的变量都无法完美的表达,此时不匹配现象随处可见,因此正确的书写位宽参数成为必要,编译器一般会将十进制数转换为二进制数,多余的高位将被截掉,缺少的高位将会补零。十进制的表示语法如下:
<bit_width>'d //注d与D均可
例如:
wire [7:0] a;
assign a = 8’d255;
如果需要设置高阻状态,也可以用z或Z代替,但是注意,此时,无论位宽是多少,都只能写一个z,例如:
wire [15:0] bus;
assign bus = 16’dz;
若要写成16’dzzz,程序铁定会报错。谁能告诉我这第二个z对应到bus的哪几位?

整数表示法

整数表示法是使整数类型变量integer的常量表示法,但是,也可以被wire和reg所用,例如:
wire [7:0] a;
assign a = 128;
此时,我们可以把这种形式看做简化版的十进制表示法。
但是有一点是整数表示法可以而十进制表示法不行的,那就是负常量的赋值,例如:
assign a = -32; //ok

assign a =16’d-32; //error
还有一点是十进制表示法可以而整数表示法不行的,那就是高阻状态的赋值,例如:
assign a = 16’dz; //ok

assign a = z; //not expect
因为此时z被当做了一个变量,如果z未定义,则会报错,如果之前有定义变量z,则这里会将z的值赋给a。

命名规则

无论是变量还是实例或者是模块,都必须有自己的名字,又称标识符,而Verilog语法中标识符的正确命名规则如下:
1、必须以字母或者下划线开头,即a-z,A-Z或‘’;
2、标识符中可以包含字母、数字、下划线和串号,即a-z,A-Z,0-9,‘
’和‘$’;
3、如果以斜杠符号‘\’开头,以空格结尾,则可以包含任何字符,例如:
wire ^df* ;
assign ^df* = 1’b0;
虽然Verilog语法的命名规则非常灵活,但是建议大家还是少玩些花样,将重心放在程序架构设计和功能代码编写上,而不要舍本逐末的花心思在定义一些奇怪的变量上面。