2补方式数的表示与加减乘设计要点
2补方式数的表示
一个n位二进制数b[n-1:0]可以表示无符号数的范围为0~(2^n)-1 (2^n表示2的n次方)。其表示的十进制数d为:
d=b[0]*(2^0) + b[1]*(2^1) + b[2]*(2^2) + ... + b[n-2]*(2^(n-2)) + b[n-1]*(2^(n-1))
比如8位二进制数0001_0011表示的无符号十进制数为:
2^4 + 2^1 + 2^0 = 19
一个n位二进制数可以表示有符号数的范围为 -(2^(n-1))~(2^(n-1))-1 。比如8位二进制数可以表示有符号数范围为 -128~+127 。其对应的二进制及其表示的十进制数如下图所示。
图片来自《Verilog HDL 高级数字设计》
怎么得到一个有符号数其对应的二进制表示呢?方法如下:
1. 先根据2补方式表示的数,判断最少需要几位来表示我们需要表示的数。比如:-128 最少需要9位二进制来表示。
2. 如果待表示的数为正数,则正常用二进制数来表示即可;如果待表示的数为负数,则先得到其正数的二进制表示,再将其正数的二进制表示按位取反之后,最后再加上1。比如:18的8位二进制表示为0001_0010, 那么-18的二进制表示为:~(0001_0010)+ 1 = 1110_1101 + 1 = 1110_1110。
(
反观一下:1110_1110表示的无符号数是
2^7 + 2^6 + 2^5 + 2^3 + 2^2 + 2^1
= 128+64+32+8+4+2 = 238 = 256 - 18
为啥要反观一下呢,这里告诉大家,一个负数a的n位二进制表示,等于无符号数 2^n - |a| 的二进制表示。
8位二进制数1110_1110表示有符号数-18。我们可以发现:
-18 =128+64+32+8+4+2-256 = 238 - 256
= 64+32+8+4+2-128
=2^6 + 2^5 + 2^3 + 2^2 + 2^1 - 2^7
通过这个例子告诉大家一个现象就是n位二进制有符号数b[n-1:0]表示的十进制数为:
d = b[0]*(2^0) + b[1]*(2^1) + b[2]*(2^2) + ... + b[n-2]*(2^(n-2)) - b[n-1]*(2^(n-1))
提到这个是为了让大家更好理解有符号整数乘法器的设计原理。
)
加减法描述要点
问题:为什么采用2补方式数的表示?
答:就是为了利于加法的实现。比如两个 -18跟8的8位二进制数相加,如下(相同位相加等于2,则向高位产生进位):
1110_1110 + 0000_1000 = 1111_0110
8位二进制的加法结果1111_0110表示的数为:
d = 2^1 + 2^2 + 2^4 + 2^5 + 2^6 - 2^7 = -10
看到没有,其表示的结果正好正确,不管是做有符号数加法还是无符号数加法,无需做任何其他修改。
但是特别注意,当相加的两个数位宽不匹配,一定要先将位宽少的数做符号位扩充,否则就会出错。比如:
a[7:0] = 1110_1110,b[8:0] = 00000_1000
将a,b送入一个9位的加法器相加,一定要先对a做符号位扩充。因为不做符号位扩充,在写Verilog时,默认将a最高位补0,a变成了9位二进制数,其表示的是238:
01110_1110 = 238
自然运算结果就会出错。
当我们在写Verilog时,可以采用如下方式进行符号位填充(填充到与输出结果数据位宽一致的位宽)再加,同时避免加法结果产生溢出,其加法的结果信号可以定义成比最高位宽加数的位宽多1bit:
module add #( parameter D_W_A = 7, parameter D_W_B = 5)( input [D_W_A-1:0] a, input [D_W_B-1:0] b, output [D_W_A:0] c); wire [D_W_A:0] a_b = { {(1){a[D_W_A-1]}}, a}; wire [D_W_A:0] b_b = { {(D_W_A-D_W_B+1){b[D_W_B-1]}}, b}; assign c = a_b + b_b;endmodul
对于减法,减去一个数等于加上这个数的负数,也就是减去一个数,等于加上这个数的补码(取反加1)。a[D_W-1:0]-b[D_W-1:0]可以采用如下方式描述:
module div #( parameter D_W = 7)( input [D_W-1:0] a, input [D_W-1:0] b, output [D_W-1:0] c); wire b_b = ~b; assign c = a + b_b + 1'b1; //类似于带进位输入的加法器endmodule
二进制加法器的门级实现方式最常见的有逐位进位链、超前进位两种方式(笔试常见),具体实现电路可以参考书籍《Verilog HDL 高级数字设计》,或继续关注公众号接下来的文章。
乘法器设计与描述要点
无符号乘法器设计可以直接采用移位加操作即可。比如n位二进制数a[n-1:0]乘以m位二进制数b[m-1:0],其原理如下:
a*b = a * (b[0]*(2^0) + b[1]*(2^1) + b[2]*(2^2) + ... + b[n-2]*(2^(n-2)) + b[n-1]*(2^(n-1)) )
= a*b[0]*(2^0) + a*b[1]*(2^1) + a*b[2]*(2^2) + ... + a*b[n-2]*(2^(n-2)) + a*b[n-1]*(2^(n-1))
乘以2^n等于左移n位。
对于无符号数乘法可以直接采用*描述,我们知道一个D_W_A位的数乘以一个D_W_B位的数,其结果最多为D_W_A+D_W_B位,故可以将其结果定义成D_W_A+D_W_B位,以避免产生结果溢出。
module mul #( parameter D_W_A = 7, parameter D_W_B = 5)( input [D_W_A-1:0] a, input [D_W_B-1:0] b, output [D_W_A+D_W_B-1:0] c); assign c = a * b;endmodule
有符号数乘法原理如下(结合上面有符号数的表示来理解),比如n位有符号二进制数a[n-1:0]乘以m位有符号二进制数b[m-1:0]:
a*b = a * (b[0]*(2^0) + b[1]*(2^1) + b[2]*(2^2) + ... + b[n-2]*(2^(n-2)) - b[n-1]*(2^(n-1)) )
= a*b[0]*(2^0) + a*b[1]*(2^1) + a*b[2]*(2^2) + ... + a*b[n-2]*(2^(n-2)) - a*b[n-1]*(2^(n-1))
但是对于有符号数乘法,我们必须先将待乘数做符号位填充,填充到n+m位,因为乘法是基于加法操作,而有符号数加法操作不做符号位填充,其结果就会出错。当采用 * 描述乘法操作时,其描述方式可以如下(位宽分别为D_W_A、D_W_B的两个数据相乘):
module mul #( parameter D_W_A = 7, parameter D_W_B = 5)( input [D_W_A-1:0] a, input [D_W_B-1:0] b, output [D_W_A+D_W_B-1:0] c); wire [D_W_A+D_W_B-1:0] a_b = { {D_W_B{a[D_W_A-1]}}, a}; wire [D_W_A+D_W_B-1:0] b_b = { {D_W_A{b[D_W_B-1]}}, b}; assign c = a_b * b_b;endmodule
对于具体的乘法器的电路设计大家可以参考书籍《Verilog HDL 高级数字设计》的内容,或继续关注公众号接下来的文章。
推荐参考书籍: