本人写代码基本从来不用signed,一般碰到有符号数都是在代码里面显式的去处理,如果认为某个数是有符号数,就按照有符号数处理,否则就按照无符号数处理。
但是最近看别人的代码,发现很多的代码,还是用了Signed语法,那么大家在用之前,有没有想过Signed声明到底做了什么?今天这篇文章我将和大家一起探讨这个话题。其实掌握了Signed用法,可以简化代码逻辑,因此该用的地方还是要用的,不要因为不清楚怕出错就完全不用。
1、Signed的本质
在我看来,Signed的本质其实就只有一个,影响自动扩展补的是0还是符号位。除此之外,仿真器在查看相应的值的时候,会按照有符号数和无符号数显示不同的值,但这一点我认为不是本质,只是显示格式的区别而已。
我们直接看例子:
wire signed [1:0] a_signed =-1; wire [1:0] a_unsigned=-1; wire [2:0] b_signed=-a_signed; wire [2:0] b_unsigned=-a_unsigned;
大家可以先猜一下,如果用二进制标志,这四个值分别是多少?
大家有没有猜到?我们来分析一下,首先是a_signed和a_unsigned。这两个没有什么好说的,你赋值给的是-1,其实在仿真器看来就是给的32'b1111....11111,一股脑的全部赋值,然后截取低两比特,因此这两个值是一样的。
接下来重点来了,b_signed为什么是001?由于a_signed在给b_signed赋值的时候,位宽不够用,因此需要先在高位补数,又因为a_signed是有符号数,实际上补的都是符号位,也就是1。因此实际上是3'b111取反,而取反按位取反再加1。因此就得到了001。
我们再看一下b_unsigned为什么是101,由于a_unsigned是无符号数,因此补的其实是0。所以其实际上是011取反,也就是101。
大家可以做更多的实验,会发现其本质就只是影响自动扩展补的是0还是符号位(针对的是右移往高比特扩展的数,不是左移往低比特扩展的数,左移低比特补的肯定是0)。
大家可能会想,逻辑右移和算术右移有区别吗?我们再看一个例子
wire [1:0] a_unsigned_shift = a_unsigned >>1; wire [1:0] a_signed_shift = a_signed >>1; wire [1:0] a_unsigned_arith_shift = a_unsigned >>>1; wire [1:0] a_signed_arith_shift = a_signed >>>1;
大家接着猜一下这些值分别是多少?
首先对于逻辑右移,不管你是有符号数还是无符号数。补的都是0,即11舍去高比特,补上0,因此是0。
我们再看算术右移,算术右移补的是符号位,对于a_unsigned,是无符号数,也可以认为是正数,符号位为0,因此补的自然也是0,所以是11舍去高比特补上0,为01。
只有最后一种情况,符号位是1,右移补的也是1,自然就是11。
给大家留个思考题,下面这些分别是多少?
wire [2:0] a_unsigned_shift = a_unsigned >>1; wire [2:0] a_signed_shift = a_signed >>1; wire [2:0] a_unsigned_arith_shift = a_unsigned >>>1; wire [2:0] a_signed_arith_shift = a_signed >>>1;
2、Signed的用法
除了上面的自动扩展符号位以外,Signed还可以用于两个有符号的数相互比较大小。如果没有声明Signed的话,那么就得分四种情况去讨论。比如两个数a[2:0],b[2:0],你得写成下面的情况。
if(a[2]&!b[2]) else if(a[2] & b[2]) else if(!a[2] &b[2]) else if(!a[2] & !b[2])
非常的麻烦啊,如果声明了Signed的话,可以直接比较,不用考虑这么多的情况。
此外对于加法减法乘法,我们都应该用有符号数和有符号数做运算,无符号数和无符号数做运算。不允许有符号数和无符号数做运算,直接算非常容易出错!但如果真的碰到两个数一个是有符号数一个是无符号数的情况应该怎么办呢?
对于常数,比如a+6'd3,其中a是有符号数,应该写成a+signed'(6'd3)。
再比如a<-3'd4,实际上仿真器会认为是a<100,仿真器会认为011,010这些数小于100,也就是3小于-4,这是非常滑稽可笑的。你如果真的要比较,一定要带上signed'(-3'd4),或者你就直接写-4,你直接写-4会认为这就是有符号数,按照有符号数来比较就没问题了。常数你但凡指定了位宽,都认为是无符号数,会出各种各样的错误。为了稳妥起见,我建议大家直接都加上signed。
对于变量,如a+b,其中a是有符号数,b是无符号数。不允许直接转换类型,应该扩展一比特再转,不然别人本来高比特的1代表正数,你一转就成了负数的。应该要写成a+signed'({1'b0,b}),这个时候会认为这个0是符号位,还是个正数,这样就不会出错了。
此外对于有符号数中,四则运算要和其它语句分开使用。比如assign d = a?b+w:c,其中b和w是有符号数,别的都是无符号数,这样写是不行的。这样会默认所有的数都是无符号数,会出错的。你可以用assign x=b+w,再用assign d=a?x:c。这样是可以的。
可以看到我如果直接这么写,这个d居然算出来了100,amazing!直接把11和01当无符号数加起来了。实际上一个是-1,一个是1,我们希望加出来应该是0。
wire signed [1:0] b,w; wire [2:0] d; wire [2:0] c; wire a; assign a=1; assign b=-1; assign w=1; assign d=a?b+w:c;
按照我说的方式重写
wire signed [1:0] b,w; wire signed [2:0] x; wire [2:0] d; wire [2:0] c; wire a; assign a=1; assign b=-1; assign w=1; assign x=b+w; assign d=a?x:c;
非常的NICE,这样才符合预期。
此外对于有符号数向右移位的时候,用>>>,不要用>>和直接截位。后两种默认补的是0,而不是符号位。
掌握了我说的注意事项,大家使用SIgned的时候应该就不会有什么问题了,可以放心大胆的用。如果还是用不惯,那就不用吧,其实我也不喜欢用(bushi