起因
最开始我希望做一个16位小数乘16位小数的运算,考虑到整数部分(16位)为零,于是我直接取低16位作为变量来进行计算(以节约电路面积)。这相当于我把一个小数左移了16bit然后取高16位。
a,b为16bit的小数<<<16得到的值,例如a=0.1<<<16=16’b0001100110011001
我需要做如下的计算:
a x b,(1-a) x b,a x (1-b),(1-a) x (1-b)
对应的rtl如下:
temp_r1_ab <= decimal_a * decimal_b;
temp_r1_1ab <= (~decimal_a) * decimal_b;//1-a,a是正小数部分<<<16得到的整数,因此1是溢出的部分不用管,1-a=-a,即a取反
temp_r1_a1b <= decimal_a * (~decimal_b);
temp_r1_1a1b <= (~decimal_a) * (~decimal_b);
注释中解释了为什么1-a直接用~a来表示。
但是结果很奇怪!a x b的结果是正常的约等于0.1,但是(1-a) x b即~a x b 得到的却是0.8999而非0.1。(a=0.5无论如何取反,得到的结果都靠近0.5,因此结果应该是0.1)
这里解释一下,选中变量右键可以设置数据的表示形式。
因为a,b一共16位都是小数,因此选择16(从右往左数16位作为小数点的位置),因此波形显示的是0.5以及0.19999。
做简单验证试验
由于位数太多难以判断出错的原因,因此重新写了一个简单的例子:
波形如下:
对变量e增加位宽后:
可以看到e变量的高位一直在扩展1。寻思没有得到结果,因为d和e之间并没有什么区别,只是~a经过了一个新的连线然后接到d上,从理论上来说不应该有区别的。其次,两个正数(无符号数,没有特别的处理)4‘b0100=4’d4 ,4’b0111=4’d7相乘,结果应该是4’d28=8’b0001_1100,不应该在高位不停扩展1(这应该是算术左移才能在高位扩展1,否则都是扩展的0)
此外,如下代码也会出现自动扩展1的情况(16bit的decimal_a = 0.5=16’b1000_0000_0000_0000)
temp_r1_1ab <= (~decimal_a) * 32'd1;
虽然修改了中间给一级赋值,但结果仍然高位扩展了1。而上述中间赋值wire[3:0] temp_a =(~a[3:0]);位数相同,因此结果不会出现问题。
wire[31:0] decimal_b_n = (~decimal_b[15:0]);
如下修改,结果仍然不对。
temp_r1_1ab <= (~decimal_a[15:0]) * decimal_b
小结
两个取反相乘有问题,特别做了一个实验。中间给一个相同位宽的变量寄存/赋值~a,就能正常得到结果。
解释出现这个问题的原因,再总结一下
根据以上现象推测,在计算过程中遇到取反时,如果左边变量的位宽比右边的变量位宽大,等号右边的变量会先自动扩展到左边变量的位宽(因为取反)然后进行计算。解决办法如下:
1.过一级赋值(assign),把取反的过程独立出来。如下所示:
wire[15:0] decimal_b_n = (~decimal_b[15:0]);
always……
temp_r1_1ab[31:0] <= decimal_b_n[15:0] * decimal_b[15:0];
取反的过程独立,这样送入dsp的变量不会自动扩展。
2.在取反后把高位补零然后乘法。如下所示:
temp_r1_1ab[31:0] <= {16'b0,(~decimal_a[15:0])} * decimal_b[15:0];//可以不用列出位宽,此处是为了说明而列出变量位宽
送入dsp的数无法自动扩展。