本篇文章给大家讲解一下RTL代码中,一个绕不开的话题,即数据溢出。对于纯控制类型的电路,可能只涉及计数器、地址增减这种简单的运算。但是如果大家做过算法类型的电路,位宽确定就会复杂很多,位宽选择的少了,那么就容易溢出,位宽选择的多了,就容易浪费面积。
在不同的情况下选择多长的位宽呢?在什么样的情况下我们对溢出可以无视呢?对于有符号数无符号数又分别怎么处理呢?本篇文章我将和大家一起学习这一问题。
1、什么是溢出?
假如我们有一个3bit的数据,假设是记录输入1出现了多少次,那么它就会从000->001一直变化到111,到111的时候,如果再加1,就会变成000,这个时候就发生了溢出。即绕过了一圈又回到了原点,很多时候我们希望即使你无法继续记录,那好歹保持111不变,如果有这一机制,我们就称之为做了溢出保护。
其实上述情况,有个时候是我们设计的位宽不太合理,即可能出现11次,12次的情况,那么你应该多设置1bit,采取4bit就没这个问题了。就好像笔者之前体侧的时候,需要跑6圈,我索性在一圈的中间停了下来,然后等大家都快跑完了再跟上,这样只跑一圈就行了。这种情况就是典型的位宽设置不合理,即跑了几圈都没人记录,缺少高位的记录!
开个玩笑,接下来我们进入正题。
2、不同类型下的溢出保护
下面的例子中如果没有特别说明,则认为均为3比特。并且我们写Verilog的时候,都没有规定是有符号数。即没有声明signed。
2.1、无符号数的两个数相加
这种情况最简单,我想即使大家不看这篇文章,都知道该怎么做。因为两个无符号数相加,即两个正数相加,最大不会超过大的输入的那个数的两倍。所以我们用较宽比特的输入的位宽再加上1作为输出的位宽即可。
即我们算c=a+b的时候,假设a的位宽是x,b的位宽是y。则c的位宽应该是max(x,y)+1。
这样设置可以保证不发生溢出。
2.2、多个无符号数相加
这种情况就复杂一些了,大家就假定所有的输入是全1,其可能到达最大数值是多少即可。根据此数值即可判断相应的位宽。
2.3、两个无符号数相减
首先两个无符号数相减,是有可能减出负数的,这种情况实际上是会自动扩展符号位的(注意,扩展的是符号位,而不是0),因此我们设定的结果位宽应该是减法中最大位宽加上1,并且作为设计者的你要知道,最高比特是符号位。
下面我解释一下仿真的机制:以下的例子都是assign c=a-b;
第一种情况,假设a、b、c都为3bit,假设a为111,b为000, 我们减去0,其实就等于加上0的补码,0的补码还是0,所以结果是111,没什么问题。
第二种情况,假设a、b、c都为3bit,假设a为000,b为001, 我们减去1,其实就认为是加上其补码,即加上111,最终的结果为111,这个时候你实际上无法判断是正7还是负1的,因此我们需要将c定为我们规定的4bit,这个时候在运算过程中,实际上是用0000减去1即加上1111因此得到1111。这个时候我们就非常清楚的知道最终的结果是个负数,真实的情况是负1。
第三种情况:假设a、b是3bit,c是4bit。假设a是111,b是001。这个时候实际上是不会减出负数的,但实际上处理中,也是将输入扩展成4bit进行仿真,即用0111减去1即加上1111,因此得到的结果是0110,即6,完美符合我们的预期。(很多仿真器实际上是在运算的时候,将输入都扩展成32bit,然后结果再截位,这样运算得到的结果和上面的分析得到的结果是一样的,大家可以动手试一下每一种情况)。
再强调一下,作为设计者的你应该知道,哪些情况会出现负数,如果出现负数你怎么进行后续处理,这些都是你自己应该解决的问题,仿真工具无法帮你解决,它能做的就是认为减法是可能减出负数的,并且贴心的为你扩展符号位,而不是扩展0。(大家可以试验一下,实际上你不管结果设置为多少比特,在减法的情况下前面补的都是符号位,而加法的情况下补的都是0,甚至可以不用深究细节,记住上面的结论即可,因为补码就是这么神奇)。
2.4、无符号数的加减混合运算
这种情况也会出现负数,结果位宽应该也是可能出现的结果最大位宽再加一位符号位即可。考虑极限的情况,即能算到的最大值或者最小值来判断。其实上面的情况都是这样,考虑最大最小的情况即可。
2.5、有符号数的加减混合运算
这种情况就相对复杂一些了,实际上有符号数的加法和减法在判断位宽上是没有区别的,其都会在结果上扩展符号位。因为实际上都可能是加一个正数。实际上我们都认为是加法,大家用极限的方式分析即可。
2.6、对无符号数限制输出位宽的情况
比如我们的输入都是3bit的数据,此时限制输出也只可以是3bit,这种情况称为限制输出位宽的情况。此时我们需要用一个溢出位判断,下面的a,b均为3比特。c2也是3比特。相应的如果判断已经溢出了,我们就输出3比特可以输出的最大值,认为最大大到这个值,就不允许再增加了。这种在现实中其实也很常见,比如考试总分是100分,90的基础分和20的Bonus分数,你即便获得了20分,也认为你最高分只能是100。所以100~110最后体现在总分上是没什么区别的。
assign {overflow,c2}=a+b always@(*) begin if(~overflow) c = c2; else c=3'd7; end
2.7、对有符号数限制输出位宽的情况
这种情况是最复杂的情况,实际上这种情况不能够直接相加。因为两个正数相加和两个负数相加,最后在结果上看可能是相同的两个负数,比如010+010得100,在有符号数中我们认为它是-4,而110+110实际上也会得到100,也认为是-4,但实际上后面那个是真的-4,前面那个是假的。我们必须引入合适的机制去判断。
这个时候我们应该在运算之前先扩展额外的一份符号位,再进行运算。这样多了一比特,实际上不会出现正数相加得到负数的情况了,因为我们有额外的一个比特用于溢出的情况(次高比特)。这样算出来是正数和负数就可以直接用这个扩展的符号位去判断。
我们首先考虑判断符号位,首先考虑算出来是个正数,如果是个正数,再判断有没有溢出,如果溢出了,则限制最大为3'd3。如果是个负数,同样判断是否溢出,注意,负数的溢出其应该是越接近1000,负数的绝对值越大,点大家可以动手算一下。代码如下。
assign a2={a[2],a}; assign b2={b[2],b}; assign c2=a2+b2; assign c=!c2[3]?(c2[2]?3'd3:c2[2:0]):(!c2[2]?3'd4:c2[2:0])
上面的c2是4个bit,其本质是{sign,over,c2}。这样对照上述代码,应该更加直观。
2.8、两个无符号数乘
这种情况非常简单,比如输入一个是x比特,一个是y比特,则输出定为(x+y)比特就行。
2.9、两个有符号数乘
这种情况和上述一样,比如输入一个是x比特,一个是y比特,则输出定为(x+y)比特就行。
如果你需要限制位宽输出,则取决于你是有符号数还是无符号数相乘,对应于2.6或者2.7的方法,大家可以自己想一下,这里就不重复讲了。
2.10、多个数相乘
如果是4个数相乘,那么输出位宽应该是x+y+z+u-(4-2)。
2.11、简单的除法
首先除法一般是禁止直接用Verilog写的,如果真的这么写了,实际上会保留整数部分,即转换成整除运算,大家可以试一下,比如100除以111,你会仿真得到000。实际项目如果真的要实现除法运算,一般会调用IP,或者手写迭代的方式。
上面的乘法器和除法器只谈了位宽的声明,其具体的实现后面的文章再讲。