Verilog中关于表达式计算的分析与总结

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~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Panda 皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值