SystemVerilog和Verilog中的表达式位宽
Verilog和SystemVerilog作为一种“松散类型”的语言已经被很多工程师广泛的用于设计验证领域,但是这并不是说各种电路结构或者验证环境中就可以肆无忌惮的随意使用,特别是在不同位宽的信号进行计算时,结果经常出现与自己表达式预期的结果不一致,以至于怀疑自己数学是不是体育老师教的,为此,本文将示例说明表达式不同位宽信号进行运算时,结果到底应该是什么。
首先我们先看一个运算的各位数位宽一致,但是计算结果与期望不一致的示例。
示例中,(sig1+sig2)结果应该为“1_1110_0011”,以为后的结果应该包含进位位“1”,但是实际result中并没有包含进位位。这是为什么呢?这是因为表达式中(sig1+sig2)进位操作时会产生一个中间结果,这个中间结果的位宽与sig1和sig2最宽的那一个数保持一致。示例中因为sig1和sig2都是8位宽,所以这个中间结果并没有包含进位位,因此进行移位操作的这个中间结果实际上只包含了(sig1+sig2)“和”的部分,因此移位后的结果进位位“消失”了,跟我们期望的结果不一致。为了解决这样的问题,我们需要保持中间结果中能够有效地把和的进位位保留下来,那么如何可以实现进位结果保留下来并且能够参与到移位操作中呢?我们一块看看下面几个示例。
【示例】方法一:加入宽位宽数据拓展结果
示例中,对sig1和sig2进行加法操作时,增加了一个9位宽的0(可以指定任何一个大于8位的0),这样子(sig1+sig2+9’b0_0000_0000)进行加法操作时,所有的操作数都被扩展为9位进行计算,(sig1+sig2+9’b0_0000_0000)运算的结果(中间结果)中也将包含进位位,在对这个中间9位宽结果移位时,就会将其进位位一并进行移位,所以最终的结果中包含了我们期望的(sig1+sig2)的进位位。除了这个方法外,我们还可以通过类型转换实现操作数位宽的扩展,如下例。
【示例】方法二:使用静态类型转换
【仿真结果】
仿真结果同上例。
示例中,通过将sig1和sig2中任意一个使用”int'”转换成32位,此时sig1和sig2进行操作时,所有的操作数都会拓展为32位后进行计算,移位操作时就会对这个32位的中间结果进行移位,此时的最终结果result中就会包含加法操作的进位位。
其实除了上述两种方法外,最稳妥的操作方式是我们在进行操作时,对于要进行操作的各种结果的位宽要有足够的估计,如下例。
【示例】方法三:扩展赋值表达式中相关操作数的位宽
示例中,将result的位宽声明为9位宽,那么当sig1和sig2进行运算时,会被扩展为9位宽,此时sig1+sig2的结果也为9位,即此时两个数相加结果的进位位被保留下来,所以此时进行移位操作后输出结果包含进位位。
在Verilog和SystemVerilog中之所有会出现上述示例中的问题是因为Verilog和SystemVerilog在对表达式进行解析时,对于操作数的位宽以及表达式结果的位宽的处理方式不是很了解导致的,其实在Verilog和SystemVerilog中对于表达式的位宽是会按照一定的规则进行确定的,而这个规则的依据就是具体表达式中参与的各种运算符以及操作数位宽,并且将表达式分为了两类:self-determined和context-determined。
self-determined表达式的位宽仅由表达式中操作数的位宽决定,这样的表达式的位宽不会受到表达式上下文的影响,也就是不会受到表达式其他部分的影响;
context-determined表达式的位宽既由表达式决定,也由整个语句的其他部分表达式位宽决定;
这两种说法可能比较抽象,下表中对经常遇到的运算操作后表达式的位宽进行汇总分类如下,用以帮助大家具体运算时理解(参考IEEE1800 2012 Chapter 11),其中黄色部分为self-determined表达式常用操作符。
下面我们针对上述运算符结合示例进行说明。
【示例】
示例结果分析:
1st和2nd:因为&sig1和&sig2并未出现在赋值表达式中,并且符合“& op”条件,所有操作数位宽self-determined,通过上表分析&sig1和&sig2的结果均为1位,与仿真结果一致。
3rd:因为&sig1和&sig2并未出现在赋值表达式中,&sig1和&sig2分别计算完成后的结果均为1位,并且这两个&运算的结果位于“+”左右两侧,两个1位数相加的结果仍为1位,所以仿真结果为0。
4th和5th:因为“&sig1+sig2”和“sig1+&sig2”并未出现在赋值表达式中,所以所有操作数的位宽self-determined,sig1和sig2均位于“+”运算符两侧,此时“+”操作结果位宽取决于“+”操作符操作数最大位宽,所以在4th中显示的结果与sig1的位宽相同,而5th中显示的结果与sig2位宽相同。
6th:“sig1+sig2”的结果产生的原因与4th和5th相同,“sig1+sig2”结果由sig1和sig2位宽最宽的那个数决定,所以显示结果位宽同sig2。
7th:在“result = (&sig1 + &sig2)>>1”中,因为&sig1和&sig2符合“& op”条件,所以先会计算&sig1和&sig2的结果,应该为1位,但是作为赋值表达式(赋值表达式右侧操作数位宽由context-determined决定,即由赋值表达式左右两侧位宽最宽的数决定)右侧的操作数“&sig1”和“&sig2”的位宽由赋值表达式两侧的操作数共同决定,因为表达式左侧的result位宽为10位,所以&sig1和&sig2进行“+”操作时,位宽会被扩展为10位宽,所以最终的计算结果如仿真结果所示:’h00_0000_0001。
8th和9th:与7th计算过程类似,此处不再赘述。
10th:在“result = (sig1 + sig2) >>1”中,sig1和sig2在进行计算时的位宽由赋值表达式两侧的操作数共同决定,因为表达式左侧的result位宽为10位,所以sig1和sig2进行“+”操作时,位宽会被扩展为10位宽,所以最终的计算结果如仿真结果所示:’h01_0111_11111。
11th、12th、13th和14th与上述计算过程类似,此处不再赘述。
那么,当赋值表达式左侧操作数的位宽小于右侧某个操作数的位宽时,结算结果还是上述示例那样吗?请看下例。
【示例】
示例中,result的位宽为7位,所以7th-14th计算结果相较于上例均发生了高位截断情况。可见,当操作数位于赋值表达式左右两侧时,赋值表达式最终结果的位宽由赋值表达式赋值表达式左侧操作数决定,但是其中的各种运算及中间结果位宽需要对赋值表达式两侧数的位宽都进行考虑,即context-determined。所以,为了获得期望的结果,在使用各种操作数进行运算时,需要注意以下几点:
Ø 操作数的符号特性,是否为有符号数,因为牵扯到数据拓宽时高位补位;
Ø 操作数的位宽,特别是在赋值表达式中位于赋值表达式两侧操作数的位宽;
Ø 操作表达式所使用的运算符(self-determined);
Ø 表达式是否位于赋值表达式中,决定了是self-determined还是context-determined。
而在实际过程中,我们的表达式往往即包含了self-determined运算又包含了context-determined,那么我们在具体运算时可以参考如下步骤:
第一步:先确定赋值表达式左右两侧的self-determined运算,并对其完成解析;
第二步:根据赋值表达式两侧最大的位宽对赋值表达式左右两侧表达式的位宽进行扩展;
第三步:左右两侧拓展后的操作数进行相关运算,但是需要注意,赋值表达式的最终结果将不会被拓展;