购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第四讲

出生命名自有准则
千丝万缕布尔逻辑变量类型

矛盾只有一个来源,那就是无疑或可以骗倒人的模棱两可。

——《迈向基地》

有句电影台词说得好:“太极拳只重其义,不重其招。你忘记所有招式,就可以练成太极拳了。”

本想学学上仙的话,故作深沉地说:“Verilog是硬件语言,绝对并行。你忘记所有C语言之流,就可以学成Verilog语言了。”但是,这有狗尾续貂的嫌疑。关于硬件语言的前一句自然没有问题,但是后面关于“忘记”的言论就值得商榷了。首先是关于《葵花宝典》的类似吐血说法:即便忘 C,未必成功。不付出努力就获得成功的事情,你信,老衲我也不信。要想学好Verilog,还是老老实实地慢慢看这本传奇吧。其次,万一读者不仅仅做FPGA或ASIC呢?万一还想做SOC,而且还是DSP、CPU和MPU的内容呢?为了不被痛扁,老僧想了想还是收回这句话为妙。

前文书给大家介绍了有关常量表示的问题,这个是基础不得不学。但是,光掌握这个知识,顶多就会用线连连电源和数字地。那些表达式再复杂,也不过最终算出一串“0”和“1”来,然后没有任何变化。这叫作静态连线,显然不符合老板出薪水叫你做系统的初衷。

一、世事无常变为宗

俗话说:“办事不由东,辛苦也是空,”为了达到让老板满意的目的,在这一回书里给大家介绍变量这个东西。变量者,变化之量也。计算机科学中,对于变量的说法是:“在程序设计中,变量(Variable /Scalar)是指一个包含部分已知或未知数字或逻辑(即一个值)之存储位址,以及对应之符号名称。”在Verilog语言相关的领域中,必须把这个描述修正为:“在电路设计中,变量是指一个包含部分已知或未知数字、信号或逻辑(即一个值)之信号节点,以及对应之符号名称。”

请诸位注意“信号节点”这个说法,所谓“信号节点”是指模块/基本单元的管脚,或者电路连线上的任何一个点。简单来说,画过或调试过PCB的工程师都可以理解,信号节点就是示波器等仪表可以引出信号进行测试的点。图2.8给出了一个例子,这是一个记录计算?目前输入最大值的电路,当然,电路功能在目前还无关紧要。图中,各个英文名称都是相应的信号节点。例如,D 触发器的时钟管脚为“CLK”,输入管脚叫作“select_value”,而输出管脚被命名为“Maximum_Value”。图中,为了演示,专门定义了一个在连线上叫作“Observation”的节点,其实这个点的值和 D 触发器的输出“Maximum_Value”是一样的。另外,比较器的输出管脚“_is_input_greater”和选择器输入“select_flag”是一根连线的不同位置,这两个信号的值也将会是一致的。一般在设计代码时,要避免一根连线在不同位置有不同名称的情况,这一点施主们需要注意。

图2.8 信号节点示意图

与数学中的量不同,工程设计所用的变量和常量通常都采用多字符的名字,如count或size。而单个字符的名字一般仅用于辅助性的变量,如i,j,k常作为索引的变量。

Verilog 是大小写敏感的。所谓标识符就是程序中信号的名称。也就是说,counter和 Counter 会被当作两个不同的变量。为了保证不产生混淆,一般建议对变量首字母的大小写进行一定的规范,如模块内部变量采用小写字母开头,同时模块端口使用大写字母开头。

一些命名规范是作为Verilog语法在语言层面强制执行的。

首先,不能用关键词给变量命名。这是非常一般的要求,这里不再详细论述。

其次,Verilog语法规定,标识符(包括这里的变量,还包含一些后面才会提到的其他内容)应该以字母、下画线(“_”)开头,中间可以包含字母、数字及下画线和美元符号(“$”)的字符(串)。例2.6给出了一些合法与非法的变量命名。

【例2.6】变量命名

在Verilog语言语法基本的限制以外,进一步的命名风格规范也很有必要。在电路层面是不会使用变量名的,因此电路并不关心是否采用了准确的名字。正因为如此,变量名完全是作为工程师的工具而存在的,借助这个工具工程师能更容易地编写和理解程序。工程师通常创建编码规范,并且坚持这些规范,以帮助对变量命名甚至提供精确的命名规划。较短的名字便于输入,但是描述能力较差;较长的名字使程序更容易读懂,变量的意图更容易理解。尽管如此,冗长的变量名也可能会导致更难理解的代码。

二、线网寄存双飞燕

Verilog所用到的所有变量都属于两个基本的类型:线网类型(net/wire)和寄存器类型(register)。一般的说法是:

“线网与我们实际使用的电线类似,它的数值一般只能通过连续赋值(continuous assignment),由赋值符右侧连接的驱动源决定。线网在初始化之前的值为x(trireg类型的线网是一个例外,它相当于能够储存电荷的电容器)。如果未连接驱动源,则该线网变量的当前数值为z,即高阻态。线网类型的变量有以下几种:wire、tri、wor、trior、wand、triand、tri0、tri1、supply0、supply1、trireg,其中wire作为一般的电路连线使用得最为普遍,而其他几种用于构建总线,即多个驱动源连接到一条线网的情况,或搭建电源、接地等。

寄存器与之不同,它可以保存当前的数值,直到另一个数值被赋值给它。在保持当前数值的过程中,不需要驱动源对它进行作用。如果未对寄存器变量赋值,它的初始值则为x。Verilog中所说的寄存器类型变量与真实的硬件寄存器是不同的,它是指一个储存数值的变量。如果要在一个过程(initial过程或always过程)里对变量赋值,这个变量必须是寄存器类型的。寄存器类型的变量有以下几种:reg(普通寄存器)、integer(整数)、time(时间)、real(实数),其中reg作为一般的寄存器使用得最为普遍。

此外,利用寄存器变量的数组,还可以对ROM进行建模。”

最初学习Verilog语言时,在下有一个粗陋的理解:wire类型对应的是组合电路,reg类型对应的则是时序电路。然后,就是范玮琪的《最初的梦想》里唱的:“如果骄傲没被现实大海冷冷拍下……”。老衲败了,而且败得很惨。现在,鄙人可以举出很多反例来证明当年的无知。这个话题,后文书也会介绍的,这里先留个话头。现在,你只需要记住:这种说法是错的,大错特错。

先看线网型变量,虽然有很多种类,但是在做逻辑设计中,工程师只会遇到一个“大虾”:wire。例2.7中给出了几个声明wire类型变量的例子。值得说明的是,这些变量都是1比特宽度的。同样的位宽(如上所述,例子里的所有变量都符合这个要求),可以在一行里一起定义。另外,Verilog不允许初始化wire型变量,这和reg型的情况完全不同。

【例2.7】wire类型变量的声明

wire _select_flag;

wire is_greater,is_equal_all_0;

关于其他关键词的情况,这里作为冷知识介绍给大家。

“tri”和“wire”称为连接单元。按照规定,“wire”仅仅可以由一个门驱动,而“tri”可以连接多个门。这个对应前面介绍高阻“z”时的状况,对应其物理器件。

“wor、trior、wand、triand”叫作线上单元,对线上信号进行逻辑运算。例如,“wor”和“trior”这两个以“or”结尾的哥们就是一个逻辑或的操作。

“tri0、tri1、supply0、supply1、trireg”叫作带驱动的单元,在物理上与上拉/下拉电阻等有关。

所有这些类型的关系及真值表,请参考 IEEE 协议,和逻辑设计的关系不大,这里就不啰唆了。

回过头来说说“reg”型变量。望文生义,它总是被人误解成“register”——触发器。后文将会说到,组合逻辑也有被定义成“reg”的。结论:到目前为止,不知道为什么用这三个字母来命名这种类型。我们只能抱着“难得糊涂”的态度来处理这个问题,到底这种类型的变量是唯一可以综合的,不用活计没法做了。它基本类似于例2.7的情况:例2.8中给出了几个声明reg类型变量的例子。也值得说明的是,这些变量都是1比特宽度的。同样的位宽(如上所述,例子里的所有变量都符合这个要求),可以在一行里一起定义。另外,在 Verilog 2001(注意这个版本信息)中,允许初始化 reg 型变量,这和wire型的情况完全不同。

关于初始值这个问题,一般对于FPGA会起作用,对于ASIC则完全会被忽略。

【例2.8】reg类型变量的声明

reg _float_flag;

reg last_state,crc_link_0;

reg original_flag=1'b0;

关于其他关键词的情况,这里也作为冷知识介绍给大家。

大多数的矢量类型(reg或net)都被默认为无符号数。integer和real是个例外,它们被默认为有符号数。通常,real 类型是不可综合的。假设在没有溢出的情况下,不管是无符号数还是有符号数,它们都是二进制的一串数值而已;当这个值被当作某种类型比较时,有符号数的MSB被用来表示这个数字的符号,而无符号数的MSB则是位权最高的那一位。无论采用什么样的二进制格式,一个无符号数永远也不能成为负值。

除非特别声明为有符号数,还有就是integer的位宽为宿主机的字的位数,但最小为32位。这里很容易引起混淆,因此在下不喜欢使用。

“time”是时间的格式,一般在仿真里使用。例如,报告在仿真的什么时刻,发生了什么故障之类的。

最后说明一点:所有变量在使用之前必须声明。

三、位宽数组可定义

前面一回说道,1比特位宽的信号是不能很好描述这个世界的,因此需要多比特的信号。对于电路而言,高比特就是并行的多个连接线、管脚或触发器,这也就是为什么有“Verilog语言是并行执行”的说法了。

对于一个单个的多比特信号,需要引入“向量”的概念。

向量形式的数据在硬件描述语言中十分重要。在Verilog中,标量的意思是只具有1比特位宽的变量,而向量表示具有多个比特位宽的变量。如果没有特别指明位宽,系统默认它为标量。

在真实的数字电路,如将两个四位二进制数相加的进位加法器中,我们可以发现,其中一个数是通过四条电线(每条线表示四位中的某一位)连接到加法器上的。我们可以用一个向量来表示这个多位数,分别用这个向量的各个分量来表示“四条电线”,即四位中的某一位。这样做的好处是可以方便地在 Verilog 代码的其他地方选择其中的一位(位选)或多位(域选)。当然,如果没有进行位选或域选,则这个多位数整体将被选择。

向量的表示需要使用方括号,方括号里的第一个数字为向量第一个分量的序号,第二个数字为向量最后一个分量的序号,中间用冒号隔开。向量分量的序号不必从0开始,不过为了和数字电路里二进制数高低位的表示方法一致,我们常常让最低位为0(即对于四位二进制数,其最高位为第3位,次高位为第2位,次低位为第1位,最低位为第0位)。当然,这只是一种习惯。例如,上面提到的四位二进制数用向量表示如例2.9所示。

【例2.9】4比特wire型向量的声明

wire[3:0]input_add;  //声明名为input_add的4位wire型向量

wire[4:1]input_add1;  //也是4位wire型向量,但是分量序号从4到1

wire[0:3]input_add2;  //也是4位wire型向量,但是分量序号从0到3

上面的向量声明之后,我们就可以方便地选择其中的某几个分量进行操作。请注意用于域选的方括号的位置在向量名称之后,方括号内的数字为所需的位数。例如,我们可以进行如例2.10所示的操作。

【例2.10】对于向量个别位的操作

input_add[3]=1'b1; //将1赋值给input_add向量的第3位(最高位)

input_add[1:0]=2'b01;

//将0和1分别赋值给input_add向量的第1、0位(最低两位)

当对向量进行赋值时,如果右边的数值位宽大于左边的变量,则多出来的位被丢弃;如果右边的数值位宽小于左边的变量,则不够的位用0填补。

对于reg型向量,在Verilog 2001中,在声明时也可以定义初始值,见例2.11。

【例2.11】reg型向量的声明与初始值

reg[7:0]counter=8'h0; //8比特计数器声明,同时定义初始值0

reg[15:0]_statement,next_statement;

另一个容易和向量混淆的概念就是数组。向量设计的本意——上面说过——是为了表述多比特位宽的单个信号。数组设计是为了方便地实现 RAM 这种器件。需要说明的是,在真实电路设计千万不要用数组来实现很大的 RAM,尤其是在 FPGA 里。芯片中的RAM一般是用特殊电路实现的[例如,FPGA里的块RAM(Block RAM,BRAM)],同时具有规定的接口时序。这个问题的源头是生产工艺和成本等方面,按照老衲的一贯作风——不多说。

Verilog 中的几种寄存器类型的数据,包括 reg、integer、time、real,以及由这几种数据构成的向量,都可以构成数组。声明数组时,方括号位于数组名的后面,括号内的第一个数字为第一个元素的序号,第二个数字为最后一个元素的序号,中间用冒号隔开。如果数组是由向量构成的,则数组的其中某个元素是向量。同样的,出于习惯考虑,我们一般让数组第一个元素的序号为0,后面元素的序号依次递增。此外,和C语言类似,用户可以声明多维数组。例2.12是一些数组声明的例子。最后说一下,二维数组是Verilog 2001的特性,估计是为了满足方兴未艾的图像处理的需求。

【例2.12】数组的声明与引用

integer number[0:100]; //声明一个有101个元素的整数数组

number[25]=1234; //将1234赋值给25号(第26个)元素

reg[7:0]my_input[65535:0]; //声明一个有65 536个元素的8位向量寄存器

my_input[97]=8'b10110101;

//将1011 0101分别赋值给97号(第2个)元素的7至0位

reg my_reg[0:3][0:4]; //声明一个具有20个元素的二维寄存器数组

my_reg[1][2]=1'b1; //将1赋值给上述二维数组的第2行、第3列元素

上面的例子中,第三行的例子是65 536个8位向量组成的向量数组,它可以用于描述一个64 KB的存储器。

当表示数组的某个元素时,允许使用变量来表示元素的索引,但是表示一个向量的一位或几位时,只允许使用数字来表示位的索引;此外,使用数组时一次只能对一个元素进行操作,而不能如向量那样同时对连续的几个位进行操作。

由于数组和向量的表示都使用了方括号,因此使用时需要注意这个变量或向量的名称在最初被声明为何种类型的数据。由于数组的索引和向量的索引都用了方括号对“[]”,所以对于向量数组的引用采用先数组索引,后向量索引的顺序,这个需要特别强调。例2.13给出了一个同时索引数组和向量的情况,可供参考。

【例2.13】向量数组的索引

my_input[65535][7:4]=4'b1010;

//将一个4比特二进制数赋值给第65 536个元素的高四位

这正是:

“设计信号要可见,变量引入有概念。语法定义看着烦,够用即可例子现。声明需在应用前,向量为了高位宽。数组存储浪费钱,引用顺序大圆满。” JfbmAD8tLh3PAcULLWBf7/7AuofZYjMUcal2V9SqZ8GcfbaLwGDi9woZL8Drlobj

点击中间区域
呼出菜单
上一章
目录
下一章
×