1、操作符
1.1、Verilog支持的操作符
操作符 | 用途 | 备注 |
---|---|---|
{} 、{{}} | 拼接、复制 | 在位拼接表达式中不允许存在没有指明位数的信号,因为在计算拼接信号的位宽大小时必须知道其中每个信号的位宽。同时在进行复制操作时,用于表示重复的表达式必须是常数表达式,例如{7{w}}中的7。 |
+、- | 正号、负号 | 单目运算符(可用于实数运算) |
+、-、*、/ | 加、减、乘、除 | 在Verilog中算术运算符又称为二进制运算符,在进行算数运算操作时,如果有一个操作数有不确定的值x,则整个结果也为不定值x(包括%运算符)。关于除法运算,在做整数除时向零方向舍去小数部分,就是只取整数部分。(可用于实数运算,%不可) |
% | 取模 | 求余数,要求%两侧均为整数类型,同时进行取模运算时,结果值的符号位采用模运算式里第一个操作数的符号位。只可用于整数运算。 |
** | 指数 | (可用于实数运算) |
>、>=、<、<= | 大于、大于等于、小于、小于等于 | (可用于实数运算) |
!、&&、|| | 逻辑非、逻辑与、逻辑或 | |
==、!= | 逻辑相等、逻辑不等 | 由于操作数中某些位可能是不定值x和高阻值z,结果可能为不定值x,即x、z不参与运算。例如:if(A == 1'bx) $display("Aisx") (当A等于x时,这个语句不执行)。(可用于实数运算) |
===、!== | 逻辑全等、逻辑不全等 | 它在对操作数进行比较时对某些位的不定值x和高阻值也进行比较,即0、1、x、z都参与比较,两个操作数必须完全一致,其结果才是1,否则为0。这两个运算符常用于case表达式的判别,所以又称为“case等式运算符”.例如:if(A === 1'bx) $display("Aisx") (当A等于x时,这个语句执行)。 |
~、&、|、^、^~ | 按位取反、按位与、按位或、按位异或(也称为XOR运算符)、按位同或(异或非) | 不同长度的数据进行位运算时:两个长度不同的数据进行位运算时,系统会自动地将两者按右端对齐,位数少的操作数会在相应的高位用0填满,以使两个操作数按位进行操作。0与任何位值相与都等与零(包括x),1与任何位值相或都等于1(包括x),x与任何位值相异或、同或都等于x。除了~是单目运算符,其他的都为双目运算符,位运算的操作数是几位数,其结果也是几位数。 |
&、|、^、^~ | 缩减(归约)运算符 | 都是单目运算符,即对单个操作数进行运算,其运算结果是1位的二进制数。例如:reg[3:0] B; reg C; C = &B 相当于 C = ((B[0] & B[1]) & B[2]) & B[3]。 |
<<、>> | 逻辑左移、逻辑右移 | |
<<<、>>> | 算术左移、算术右移 | 根据unsigned和signed操作,对于有符号数右移高位补符号位 |
?: | 条件运算符 | 三目运算符 |
1.2、操作符的优先级
操作符 | 优先级 |
---|---|
+、-、!、~、&、|、^、^~ (单操作数) | Highest precedence |
** | |
*、/、% | |
+、-(双操作数) | |
<<、>>、<<<、>>> | |
<、<=、>、>= | |
==、!=、===、!== | |
&(双操作数) | |
^、^~(双操作数) | |
|(双操作数) | |
&& | |
|| | |
?:(条件操作符) | |
{}、{{}} | Lowest precedence |
PS:表格中每行的操作符具有相同的优先级,行间从上到下降序排列。
2、常数
2.1、整数
1)整数可以用十进制(decimal)、十六进制(hexadecimal)、八进制(octal)、二进制 (binary)的形式表示,表现形式为:<null/+/-><size><sign:s/S><base:d/D/h/H/o/O/b/B><0~9/0~f/0~7/0~1/x/z>,其中size、sign、base是可选的。所以最简单的整数是没有size、sign和base的十进制数,只用0~9,可选+或-,表示的是符号数(signed integer)。
2) sign必须和base一起使用,不能单独使用。当base前面有sign标志时,表示的是符号数;当base前面没有sign标志时,表示的是无符号数。例如:8‘d6 =>就是用8位二进制数表示的数值6,无符号数;-8’d6 => 表示的是6的二进制补码,位宽为8,等价于 -(8‘d6) ,转换为补码形式为’1111_1010‘;4’shf => 表示为4位的‘1111’,因为是有符号数且最高位为1,所以它应该被解释为二进制的补码,即它表示的原始的数据是‘-1’;-4’sd15 => 首先不管符号,4‘sd15本身就是一个有符号数,此处是4位的补码表示形式,即‘1111’,还原成原码为-4’d1,所以-4’sd15表示的实际数值为-(-4’d1) = '0001'。
3)负数以2的补码形式表示,其实在FPGA中有符号数一般都是用补码表示。
4)x表示不可知值(unknown),z表示高阻值(HiZ),在十进制数中不能使用x和z。其中x可以用?代替,在使用casex和casez时,为了便于理解常用?代替z。当z作为逻辑门的输入或者在表达式中出现时,通常把z当做x处理,但是当z出现在MOS的原语中,还是当做z,因为MOS可以传送高阻。
5)如果无符号数的位数小于size,那么就在左边扩展。如果最左边的位是0或1,左端就补0扩展;如果最左边的位是x,左端就补x扩展;如果最左边的位是z,左端就补z扩展。如果无符号数的位数大于size,那么就在左端截去多余的位。
6)在Verilog-2001中,对于没有size限定的数,那么就在左端按照表达式的size根据最左边的位进行扩展(0、x或z),扩展多少位都没有问题。但是在Verilog-1995中,如果最左边位是x或z,,那么x或z最多只能扩展到32位,超出的位按0扩展。
7)对于<sign>、<base>、a~f、x和z,大写和小写都可以使用(case insensitive)。
8)注意:当把带有size的负常数赋给一个reg类型的变量时,不管这个变量是否是signed,都会对这个负常数做符号扩展。例如:reg signed [15:0] h;reg [15:0] m;h = -12'h123;h = 12'shEDD;m = -12'h123;m = 12'shEDD;那么在FPGA中,h和m的值都表示为16‘FEDD。
2.2、实数
实数常数定义采用双精度浮点数,有两种表示方式:十进制法和科学计数法。例如:1.2、2365.8421、1.3e11、1.5E-2、36e10、235.147_254_e-11等。当把实数赋给一个整数变量时,按四舍五入转换后赋值。例如:36.5和36.7都转换为37,而35.2转换为35,-1.5转换为-2,而1.5转换为2。
2.3、字符串
1)字符串是包含在两个“(双引号)之间的字符。
2)字符串在表达式中或者在赋值时,被当做一个由8-bit ASCII码序列组成的无符号数。
3)字符串中可以使用如下的特殊字符:\n(换行)、\t(Tab键)、\\(反斜杠)、\"(双引号)和\ddd(用3位八进制数表示ASCII 值)。
4)使用reg变量操作字符串时,每8bit存一个字符。
5)因为字符串被当做无符号数,所以也用整数的补齐和截去原则,就是如果字符串的位长小于变量的位长,那么字符串做右对齐存放在变量的右侧,变量的左侧补0;如果字符串的位长大于变量的位长,那么字符串做右对齐放在变量的右侧,多余的位截去。
3、数据类型
Verilog中主要有两种数据类型,变量(variable)和线网(net)。这两种数据类型主要区别在于它们的赋值(assign)和保持(hold)方式,它们代表了不同的硬件结构。
3.1、线网
线网用于表示结构体(如逻辑门)之间的连接。除了trireg之外,所有其他的线网类型都不能保存值,线网的值是由driver决定的,例如有连续赋值驱动或由逻辑门驱动。如果driver没有驱动线网,那么线网的值是z,但是tri0、tri1、trireg除外,tri0将是0,tri1将是1,而trireg将保持driver之前驱动的值。线网有以下数据类型:wire、wand、wor、tri、triand、trior、tri0、tri1、trireg、uwire、supply0、supply1。其中wire是我们最常使用的线网数据类型,常用于连续赋值语句(assign),端口信号的连接等。
3.2、变量
变量是数据存储单元的抽象,具有如下的特性:
1)变量将保持每次赋给它的值,直到下一次赋值给它。当过程块被触发时,过程块中的赋值就会改变变量的值。
2)reg、time和integer的初始值是x,real和realtime的初始值是0.0。如果使用变量声明赋值,即reg A = 1'b0;,其实就相当于在在initial块中使用阻塞赋值。
3)对reg的赋值是过程赋值(例如always块中,阻塞赋值和非阻塞赋值),因为reg能够保持每次赋的值,所以它能模型硬件寄存器(例如:边沿敏感的触发器或电平敏感的锁存器)。但是reg不止用于模型硬件寄存器,它也用于模型组合逻辑。
4)除了用于模型硬件,变量也有其他的用途。虽然reg很通用,但是integer和time可以提供更大的方便性和可读性。time变量常和$time函数一起使用。
相关注意事项:
1)可以把赋值赋给线网和变量,只有integer、real、realtime、reg signed和net signed才能保持符号标志(所以记住,integer表示的是一个有符号数,其等价于reg signed[31:0]),而time(等价于reg unsigned[63:0])、reg unsigned和net unsigned则把赋给他们的数值都当做无符号数处理。
2)real和realtime是等价的,都是64-bit双精度浮点数,只不过realtime变量常和$realtime函数一起使用。
3)不能对real和realtime使用位索引和部分索引。
3.3、线网和变量的区别
在我们最开始学习使用verilog的时候,最让人感觉困惑的就是reg和wire的使用,不知道什么时候该用什么样的数据类型。其实规则很简单,在verilog中,任何过程赋值的左侧变量必须声明为reg,除此之外使用的变量必须声明为wire,没有其他例外的情况。通俗来讲,就是在过程赋值语句中,例如常用的always块中,包括时序逻辑和组合逻辑,赋值语句的左侧的信号必须声明为reg类型,其他用途的信号(包括连续赋值语句,模块之间信号的连接等)都声明为wire类型。
关于为什么verilog要区分reg和wire类型,其实和数据类型检查有关,因为数据类型检查是对同一变量识别错误赋值(就是对同一变量既有连续赋值又有过程赋值)的最容易方法。
3.4、数组
有关于数组(array)的几点说明如下:
1)数组的元素可以是标量(没有范围声明的1-bit线网或reg)也可以是向量(带有范围声明的多bit线网或reg),也就是线网和寄存器变量都可以声明为数组。
2)数组的维数可以是一维、二维······多维。多维数组是可以综合的。
3)数组的引用可以针对某一个元素或者某一个元素的一部分。
4)通常把一维数组称为memory。例如:$readmemh和$readmemb就把数据加载到memory中。
5)在常规的仿真条件下,波形文件不存储数组。为了在波形文件中保存数组,需要使用内嵌函数和相应的命令行选项。
6)不能通过引用数组的一部分来初始化数组的一部分,也不能引用整个数组来初始化整个数组(在Verilog-2001中,引用数组中的多个元素(多于一个全字)依旧是非法的)。同时数组不需要一定先初始化才能使用。
7)一维数组必须用一个或两个索引变量访问,二维数组必须用两个或三个索引变量访问,以此类推。
4、表达式
4.1、自己之前疑惑的表达式分析
自己之前在看书中,遇到如下几个表达式,当时看的稀里糊涂,这里自己再重新分析下,也帮助和我有类似情况的朋友提供点帮助。
首先书中对于在表达式中用不同方式表示的整数给出了如下解释:
a)An unsized, unbased integer --被当做符号数 eg:11,-12
b)An unsized, signed, based integer --被当做符号数 eg:'sd16、'shff
c)An sized, signed, based integer --被当做符号数 eg:16'sd7、8'sha
d)An unsized, unsigned, based integer --被当做无符号数 eg:'d16、'hff
e)An sized. unsigned, based integer --被当做无符号数 eg:16'd7、8'ha
几个表达式如下:
integer IntA
1)IntA = -12 / 3;
2)IntA = -'d12 / 3;
3)IntA = -'sd12 / 3;
4)IntA = -4'sd12 / 3;
解释如下:
1)第一个最常见的整数除整数,结果显而易见是-4(32为有符号数,补码表示)。
2)第二个可能就有点疑惑了,前面讲整数的时候提到过类似的,其实主要不理解的是-’d12表示的是一个什么类型多少数值的数。先说答案,-'d12被解释为一个无符号数,它不会当做负数处理,而是当做一个大的正数。因为虽然-12和-’d12具有同样的2的补码位,但是在表达式中-‘d12失去了作为符号负数的特性。我是这样理解的,-’d12可以看做为-('d12),因为这里没有指定位宽,所以默认为32位,即-(32'd12) => -1*(32'd12),根据运算法则可知,无符号数乘以有符号数结果还是为无符号数,所以-‘d12是一个无符号数,-‘d12表示为补码形式:4294967283,所以被当做一个较大的正数,然后除以3,2)式结果便为1431655761。
3)第三个也可以像2) 式那样理解,-’sd12 => -(32'sd12) => -1*(32'd12),有符号数*有符号数,所以结果-‘sd12也是有符号数。因为’sd12中s表示12是一个有符号数,默认32位,即0000_0000_0000_0000_0000_0000_0000_1100,这是补码形式,则其原码为1111_1111_1111_1111_1111_1111_1111_0100,因为-(32'sd12) 最外层还有一个负号,所以再次对该原码进行求补码(绝对值取反加1),即为1000_0000_0000_0000_0000_0000_0000_1100 => -12,所以-'sd12 / 3 = -12/3 = -4。
4)第四个其实和第三个类似,只是位宽换成了4位,-4'sd12 => -(1100) => -(-4) = 4,所以-4'sd12 / 3 = 1(整数除法向零取整)。
还有几个式子是“divide minus twelve by three”的不同方式,在表达式中使用integer和reg数据类型:
integer intA;
reg [15:0] regA;
reg signed [15:0] regS;
1)intA = -4'd12; // intA在FPGA表示为 FFFF_FFF4
regA = intA / 3; // 表达式结果是-4,regA为65532
2)regA = -4'd12; // regA为65524
intA = regA / 3; // 表达式结果是21841
3)intA = -4'd12 / 3; // 表达式结果是1431655761
4)regA = -12 / 3; // 表达式结果是-4,regA在FPGA表示为65532
5)regS = -12 / 3; // 表达式结果是-4
regS = -4'sd12 / 3; // 表达式结果是1
分析如下:
1)intA表示为FFFF_FFF4,是因为intA是一个整数类型,这里默认为32位,所以再进行计算- 4'd12的补码时,需要先将4'd12扩展为32位,即为32’d12,然后计算再计算-32‘d12的补码;由前面的分析可知,在表达式中-32‘d12是失去作为符号负数的特性的,即它表示的是一个大的正数,所以intA的值为FFFF_FFF4,但同时intA为一个有符号数,所以intA将FFFF_FFF4转换为一个有符号数,所以有符号数intAFFFF_FFF4的原码为-12(除最高位符号位外,其它位取反然后加1),又因为表达式 regA = intA / 3,其中intA 和 3都是有符号数,所以结果为-12/3 = -4,表示为补码为FFFC。又因为regA 是一个无符号数,所以regA的值就是-4的补码65532。
2)因为regA 是一个无符号数据类型,然后在表达式中- 4'd12又失去了作为符号负数的特性,所以regA 表示的值为- 4'd12的补码形式(先位扩展再进行补码转换),即FFF4(65524);表达式intA = regA / 3中regA 为无符号数,3为有符号数,所以结果为无符号数21841。
3)表达式intA = - 4‘d12 / 3中,因为intA整数类型,默认为32为,所以 - 4‘d12需要先进行位扩展然后转换为补码,所以 - 4‘d12表示的值为4294967284,然后除以3结果为1431655761。
4)表达式为有符号数除以有符号数,所以结果为-4,但是因为regA为16位的无符号数,所以regA的值为-4的补码,即65532。
5)表达式regS = -12 / 3的操作数都为有符号数,所以结果为-4,又因为regS 也为有符号数且位宽满足,所以regS 的值因为-4;表达式regS = - 4'sd12 / 3中,操作数也都为有符号数,首先看4'sd12,其表示的为一个有符号数,其原码为-4,所以- 4'sd12为4,所以表达式结果为1(整数除法向零取整)。
4.2、表达式的位长分析
表达式的位长有如下几条需要注意的规则:
1)表达式的位长(或者表达式的size)由表达式中的操作数和表达式所处的上下文决定。
2)自决定表达式就是表达式的位长完全由表达式自己决定,例如用于表示延迟的表达式。
3)上下文决定表达式就是表达式的位长既由表达式本身的位长决定,也由这样的事实决定(表达式本身是另一个表示式的一部分)。例如赋值RHS的位长既依赖于其自身,也依赖于LHS的位长。
4)如果不想让乘法丢失溢出的位,那么就要把结果赋值给一个位长足够大的变量,这样才能够保存运算的最大结果。
相关例子如下:
A、在计算表达式时,中间结果就取操作数的最大位长(如果是赋值,也包含LHS),所以计算时要防止中间结果丢失。
例子:中间结果没有保存进位。
reg [15:0] a, b, answer;
answer = (a + b) >> 1; //error 这样做是有问题的,因为所有的操作数都是16bit,所以(a + b)就只产生16bit的中间结果,这样进位在做右移1位之前就被舍弃。
解决办法是强制(a + b)按17bit计算,改正方法有3种:
1)把(a + b)改为(a + b + 0),这样就按32bit计算,因为0是32bit的数。
2)把(a + b)改为(a + b + 17‘b0),这样就按17bit计算。
3)把reg [15:0] answer 改为reg [16:0] answer,这样也按17bit计算。
B、根据上下文来决定表达式的位长。
module bitlength();
reg [3:0] a,b,c;
reg [4:0] d;
initial begin
a = 9;
b = 8;
c = 1;
$display("answer = %b", c ? (a&b) : d);
end
endmodule
运行结果为:
answer = 01000;
分析如下:
虽然 (a&b) 的位长是4-bit,但是它所处的上下文是条件表达式,而且d的位长是5,所以 (a&b) 就要使用最大的位长5。
C、在表达式中注意因为 { } 的误用而导致一些表达式的位长出现问题
reg [3:0] a;
reg [5:0] b;
reg [15:0] c;
initial begin
a = 4'hF;
b = 6'hA;
$display("a*b=%h", a*b);
c = {a**b};
$display("a**b=%h", c);
c = a**b;
$display("c=%h", c);
end
运行结果为:
a*b = 16;
a**b = 1;
c = ac61;
分析如下:
1)a*b是一个自决定表达式,所以结果位长为最大6bit,a*b结果本来应该为’h96,但是因为位宽为6所以被截断了,所以结果为‘h16。
2)因为表达式c = {a**b}使用了{ },所以表达式a**b就变成了一个自决定表达式,不取决于c,就是和c没有关系,所以a**b结果取决于a,为4bit。
3)表达式c = a**b的位长取决于c。
D、在比较表达式中,中间结果的位长是以操作数的最大位长为准。
module test
reg [8:0] a = 510, b = 25;
reg [7:0] c = 12, d = 45;
initial begin
if((a+b) > (c+d))
$display("large");
else
$display("less or equal");
if((a+b+0) > (c+d+0))
$display("large");
else
$display("less or equal");
if((a+b+10'b0) > (c+d+10'b0))
$display("large");
else
$display("less or equal");
end
endmodule
分析如下:
1)对于(a+b) > (c+d),这是因为(a+b)中的a和b的位长是9bit,那么(a+b)的中间结果就按9bit计算,应该是535,但是按9bit就是23了;(c+d)中的c和d的位长是7bit,那么(c+d)的中间结果就按7bit计算,应该是57,按7bit还是57。所以(a+b) > (c+d)为false。
2)对于(a+b+0) > (c+d+0),因为0是32bit,所以(a+b+0)和(c+d+0)的中间结果是按照32bit表示,就是535>57,所以(a+b+0) > (c+d+0)为true。
3)对于(a+b+10’b0) > (c+d+10’b0),因为10’b0是10bit,所以(a+b+10’b0)和 (c+d+10’b0)的中间结果按照10bit表示,就是535>57,所以(a+b+10’b0) > (c+d+10’b0)为true。
~OVER~