目录
1.0 为什么需要补码
在计算机内,整数的长度是确定的,在字长为32位的计算机中,整数的长度就是32个二进制,这其中还包括了符号位(1表示正,0表示负)。这里面我们为了方便描述,就假设机器字长为8位。
例如,十进制整数23,二进制真值表示为10111,其原码表示为 0001 0111。
十进制整数-23,二进制真值表示为-10111,原码表示为 1001 0111。
简而言之,源码就是最高位为符号位,其他位表示该数的绝对值
如果计算机内部采用原码表示数,那么在进行加法和减法运算的时候,最终都转化为两个绝对值的加运算和减运算,因此,在设计计算器的时候就既需要设计加法运算器,又要设计减法运算器,这样会对电路的设计提出额外的需求,能不能只使用加法来统一表示加法和减法,因此就需要用到补码。
注意:不论是systemverilog、verilog、vhdl,其中的有符号二进制数都是该数据的补码表达方式,例如:
system verilog中的
wire signed [5:0] math = 6'b111000
在实际的自然数表达为6’b101000 (= -8)。
2.0 补码的本质
实际上的补码可以理解为以下的计算方式:
例如:有符号数4’b1010的补码是4’b1110,其实就是原码去掉符号位后010加上补码去除符号位后110:010+110=1000。
要将正数转成对应的负数,其实只要用0减去这个数就可以了。比如,-8其实就是0-8。
已知8的二进制是00001000,-8就可以用下面的式子求出:
因为00000000(被减数)小于0000100(减数),所以不够减。请回忆一下小学算术,如果被减数的某一位小于减数,我们怎么办?很简单,问上一位借1就可以了。
进一步分解,可以发现100000000 = 11111111 + 1,所以上面的式子可以拆成两个:
补码的两个转换步骤就是这么来的。(其中的 1111_1000 就是-8的补码,是由对 000_1000 取反得到111_0111 加1 最终得到 111_1000,最后加上符号位1就是1111_1000)。这就是补码计算规则的由来。
3.0 有符号数和无符号数运算以及截位方式
3.1 有符号数与无符号数
顾名思义,有符号数指的就是带有符号位的数据,其中最高位就是符号位(如果最高位为0,那么表示是正数,如果最高位为1,那么表示是负数);无符号数就是不带有符号位的数据。
考虑一个4位的整数4’b1011.如果它是一个无符号数据,那么它表示的值为:
如果它是一个有符号数,那么它表示的值为:
所以相同的二进制数把它定义为有符号数和无符号数表示的数值大小有可能是不同的。同时,这里也告诉大家,有符号数和无符号数转化为10进制表示的时候唯一的区别就是最高位的权重不同,拿上例来说,无符号数最高位的权重是而有符号数最高位的权重是。
正因为有符号数和无符号数最高位的权重不同,所以他们所表示的数据范围也是不同的。比如,一个4位的无符号整数的数据范围为0~15,分别对应二进制4’b0000~4’b1111,而一个4位的有符号整数的数据范围为-8~7,分别对应二进制4’b1000~4’b0111.
扩展到一般情况,一个位宽为m的无符号整数的数据范围为,而一个位宽为m的有符号整数的数据范围为。
3.2 有符号整数的符号位扩展
问题:如何把一个4位的有符号整数扩展成6位的有符号整数。
假设一个4位的有符号整数为4’b0101,显然由于最高位为0,所以它是一个正数,如果要把它扩展成6位,那么只需要在最前面加2个0即可,扩展之后的结果为:6’b000101。
在看另外一个例子,假设一个4位的有符号整数为4’b1011,显然由于最高位为1,所以它是一个负数,如果要把它扩展成6位,那么这里要千万注意了,前面不是添2个0,而是添2个1,扩展之后的结果为:6’b111011。为了确保数据扩位以后没有发生错误,这里做一个简单的验证:
显然扩位以后数据大小并未发生变化。
综上得出结论:对一个有符号整数进行扩位的时候为了保证数据大小不发生变化,扩位的时候应该添加的是符号位。
3.3 有符号小数
有了前面两小节的基础以后接下来研究一下有符号小数。前面已经规定了有符号小数的记法。
假设一个有符号小数为4’b1011,它的数据格式为4Q2,也就是说它的小数位为2位。那么看看这个数表示的10进制数是多少:
显然,小数的计算方法实际上和整数的计算方法是一样的,只不过我们要根据小数点的位置来确定对应的权重。
接下来看看有符号小数的数据范围。 就拿4Q2格式的数据来说,它的数据范围为,分别对应二进制4’b1000~4’b0111。扩展到一般情况,mQn格式数据的数据范围为。
最后再来看看有符号小数的数据扩展。假设一个有符号小数为4’b1011,它的数据格式为4Q2,现在要把这个数据用6Q3格式的数据存储。显然需要把整数部分和小数部分分别扩一位,整数部分采用上一节提到的符号位扩展,小数部分则在最后面添一个0,扩展以后的结果为6’b110110,接下来仍然做一个验证:
显然,扩位以后数据大小并未发生变化。
总结:有符号小数进行扩位时整数部分进行符号位扩展,小数部分在末尾添0。
3.4 两个有符号数的和
两个有符号数相加,为了保证和不溢出,首先应该把两个数据进行扩展使小数点对齐,然后把扩展后的数据继续进行一位的符号位扩展,这样相加的结果才能保证不溢出。
举例:现在要把5Q2的数据5’b100.01和4Q3的数据4’b1.011相加。
第一步:由于5Q2的数据小数位只有2位,而4Q3的数据小数点有3位,所以先把5Q2的数据5’b100.01扩位为6Q3的数据6’b100.010,使它和4Q3数据的小数点对齐
第二步:小数点对齐以后,然后把4Q3的数据4’b1.011进行符号位扩展成6Q3的数据6’b111.011
第三步:两个6Q3的数据相加,为了保证和不溢出,和应该用7Q3的数据来存储。所以需要先把两个6Q3的数据进行符号位扩展成7Q3的数据,然后相加,这样才能保证计算结果是完全正确的。
以上就是两个有符号数据相加需要做的一系列转化。回过头来思考为什么两个6Q3的数据相加必须用7Q3的数据才能准确的存储他们的和。因为6Q3格式数据的数据范围为;那么两个6Q3格式的数据相加和的范围为;显然如果和仍然用6Q3来存一定会溢出,而7Q3格式数据的数据范围为,因此用7Q3格式的数据来存2个6Q3格式数据的和一定不会溢出。
结论:在用Verilog做加法运算时,两个加数一定要对齐小数点并且最高位也要符号位扩展对其以后相加,两数之和的位宽要是两数之中最高位宽+1,这样才能保证不溢出。
3.5 两个有符号数的积
两个有符号数相乘,为了保证积不溢出,积的总数据位宽为两个有符号数的总位宽之和,积的小数数据位宽为两个有符号数的小数位宽之和。简单来说,两个4Q2数据相乘,要想保证积不溢出,积应该用8Q4格式来存。这是因为4Q2格式数据的范围为:,那么两个4Q2数据相乘积的范围为:,而8Q4格式的数据范围为:,一定能准确的存放两个4Q2格式数据的积。
结论: mQn和aQb数据相乘,积应该用(m+a)Q(n+b)格式的数据进行存储,这样才会保证积不会溢出。
3.6 四舍五入(round)截位
前面讲的都是对数据进行扩位,这一节说的是对数据截位时如何进行四舍五入以提高截位后数据的精度。
假设一个9Q6格式的数据为:9’b011.101101,现在只想保留3位小数位,显然必须把最后三位小数位截掉,但是不能直接把数据截成6’b011.101,这样是不精确的,工程上一般也不允许这么做,正确的做法是先看这个数据是正数还是负数,因为9’b011.101101的最高位为0,所以它是一个正数,然后再看截掉部分(此例中截掉部分是最末尾的101)的最高位是0还是1,在数据是正数的情况下,如果截掉部分的最高位为1,那么是需要产生进位的,所以,最终9’b011.101101应该被截成6’b011.110.
如果是负数则正好相反。假设一个9Q6格式的数据为:9’b100.101101,由于最高位是1,所以这个数是一个负数,然后再看截断部分的最高位以及除最高位的其他位是否有1,此例中截断部分(截断部分为末尾的101)的最高位为1,而且除最高位以外的其他位也有为1的情况,由于负数最高位的权重是(),所以对于这种情况是不需要进位的,与正数不同的是,负数不进位是需要加1的。因此最终9’b100.101101应该被截成6’b100.110。
注意:正数的round是四舍五入;负数的round则可理解为“五舍六入”。
假设a是一个9Q6格式的数据,要求把小数位截成3位。下面是Verilog代码:
assign carry_bit = a[8] ? (a[2] & (|a[1:0])) : a[2] ;
assign a_round = {a[8], a[8:3]} + carry_bit ;
上面的代码第一行是通过判断符号位a[8]和截断部分数据特征来确定是否需要进位,如果a[8]是0,计算得到的carry_bit为1,则表示是a是正数,且截断是需要进位;如果a[8]是1,计算得到的carry_bit为1,则表示是a是负数,且截断是不需要进位的,负数不进位需要加1。代码第二行为了保证进位后数据不溢出,所以扩展了一位符号位。
3.7 饱和(saturation)截位
所谓饱和处理就是如果计算结果超出了要求的数据格式能存储的数据的最大值,那么就用最大值去表示这个数据,如果计算结果超出了要求的数据格式能存储的数据的最最小值,那么就用最小值去表示这个数据。
例1:有一个6Q3的数据为6’b011.111,现在要求用4Q2格式的数据去存储它,显然6’b011.111转化为10进制如下:
而4Q2格式的数据能表示的数据的最大值为4’b01.11,转化为10进制为1.75,因此4Q2格式的数据根本无法准确的存放3.875这个数据,这样就是所谓的饱和情况。在这种情况下,饱和处理就是把超过了1.75的所有数据全部用1.75来表示,也就是说,6Q3的数据为6’b011.111如果非要用4Q2格式的数据来存储的话,在进行饱和处理的情况下最终的存储结果为:4’b01.11。
例2:有一个6Q3的数据为6’b100.111,现在要求用4Q2格式的数据去存储它,显然6’b100.111转化为10进制如下:
而4Q2格式的数据能表示的数据的最小值为4’b10.00,转化为10进制为-2,因此4Q2格式的数据根本无法准确的存放-3.125这个数据,这是另一种饱和情况。在这种情况下,饱和处理就是把小于-2的所有数据全部用-2来表示,也就是说,6Q3的数据为6’b100.111如果非要用4Q2格式的数据来存储的话,在进行饱和处理的情况下最终的存储结果为:4’b10.00。
4.0 工程实例
4.1 向下取整(floor)截位
定义a为位宽为n bit的有符号数,a[n-1:0],截取m bit向下取整,floor(a/2^m),Verilog实现如下:
// floor(a/2**m)
assign b[n-m-1:0] = a[n-1:m];
4.2 向上取整(ceil)截位
定义a为位宽为n b it的有符号数,a[n-1:0],截取m bit向上取整,ceil(a/2^m),Verilog实现如下:
// ceil(a/2**m)
assign b[n-m-1:0] = a[n-1:m] + |a[m-1:0];
4.3 四舍五入到最近的整数(round)截位
定义a为位宽为n bit的有符号数,a[n-1:0],截取m bit四舍五入,round(a/2^m),Verilog实现如下:
// round(a/2**m)
always @(*) begin
if(a[n-1] == 1'b0) begin // 正数
b[n-m-1:0] = a[n-1:m] + a[m-1];
end
else begin // 负数
b[n-m-1:0] = a[n-1:m] + (a[m-1] && (|a[m-2:0]));
end
end
需要注意的是,在这里我们没有考虑其截断结果的超限情况。
4.4 朝零方向取整(fix)截位
定义a为位宽为n bit的有符号数,a[n-1:0],截取m bit向0方向取整,fix(a/2^m),Verilog实现如下:
// fix(a/2**m)
always @(*) begin
if(a[n-1] == 1'b0) begin // 正数
b[n-m-1:0] = a[n-1:m];
end
else begin // 负数
b[n-m-1:0] = a[n-1:m] + |a[m-1:0];
end
end
其实就是正数放小,负数放大。
4.5 饱和(saturation)截位
定义a为位宽为n bit的有符号数,a[n-1:0],饱和到 m bit,Verilog实现如下:
// saturation to m bit
alwyas @(*) begin
if(a[n-1] == 1'b0) begin // 正数
if(|a[n-2:m] == 1'b1) begin
b[m-1:0] = {1'b0,{(m-1){1'b1}}};
end
else begin
b[m-1:0] = {1'b0,a[m-2:0]} ;
end
end
else begin
if(a[n-1] == 1'b1) begin // 负数
if(&a[n-2:m] == 1'b0) begin
b[m-1:0] = {1'b1,{(m-1){1'b0}}};
end
else begin
b[m-1:0] = {1'b1,a[m-2:0]} ;
end
end
end
5.0 补充
注意$signed()的使用,如果在前面的变量定义中没有把两个操作数定义为signed,那么后续如果需要进行有符号计算,可以使用此命令把操作数变成有符号数。
实际上,计算机上写入的有符号数都是补码形式,即:
logic signed [3:0] a = 4’b1011;
自己以现实中的数学思维写入的是-3,但是实际上当我们在计算机上定义了signed时,计算机认为我写入的值就是我们想写入数据的补码,所以上面我以为计算机会识别成-3,但是实际上计算机识别为-5。
而且在编写逻辑代码时,不用考虑太多,我们只需要按照需求进行+-进行正常使用即可。
参考资料
重要:
https://www.cnblogs.com/liujinggang/p/10549095.html
计算机为什么采用补码来进行运算_ImportNewXXT0101的博客-CSDN博客
一般:
matlab和Verilog之截位,四舍五入和饱和处理_verilog ceil_re_call的博客-CSDN博客
verilog实现 floor, round 四舍五入 和 saturation 操作 – FPGA论坛-资源最丰富FPGA/CPLD学习论坛 – 21ic电子技术开发论坛
FPGA设计中的float
今天的文章有符号数,无符号数概念以及各类截位方式的区别_数电逻辑运算符分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/81983.html