「BUAA-CO」P1_Verilog中符号数的使用


算数右移符号>>>

我们都知道,逻辑左移符号是>>(即最高位补零), 算数右移符号是>>>(即最高位补符号位,但是以下例子是错误的)

wire [15:0] A, B, C;

assign C = (A >>> B);
assign C = (A >>> 3'b011);//右操作数无符号
assign C = (A >>> 3'sb011);//右操作数有符号

第一、二个例子中,尽管使用了>>>符号,但是A默认是无符号数,因此最终Verilog会默认在A的前面补上0。
第三个例子中,尽管3'sb011属于符号数,但是算术右移表达式最终结果的符号仅仅与左操作数有关,因此仍然是错误的。(实际上,所有的移位运算符的右操作数都被当作无符号数处理。)

系统函数 $signed( )

$signed()作为一个系统函数,其作用是执行内部的表达式,并将表达式的结果强制转换为有符号数类型(值与位宽完全相同吧),作为其返回值。该函数可以屏蔽外部表达式的符号性传递。需要注意的是,$signed()函数仅仅会将括号内表达式的结果强制进行转换,而不会影响表达式的执行。

wire [15:0] A, B, C;
wire signed [15:0] signA;

assign C = (signA >>> B);
assign C = ($signed(A) >>> B);
assign C = $signed($signed(A) >>> B);

assign C = $signed(A >>> B);

这样就可以解释,为什么前三个表达式是正确的的,而最后一个表示式是错误的(在A >>> B的执行中,A是作为无符号数进行移位)。

三目运算符? :

根据verilog的语言特性,如果一个表达式中既存在有符号数又存在无符号数,那么表达式的结果就是无符号数(即在表达式计算过程中,有符号数当作无符号数参与运算)。

如果表达式多层嵌套,那么每一个表达式结果的符号性由以下原则决定

  • 表达式的结果的符号性,由这个表达式的操作数以及外部影响共同决定
  • 表达式结果的符号性的决定,先于表达式结果的计算

也就是说,Verilog在计算一个表达式时,会先去计算这个表达式的符号性。而在得到表达式整体的符号性之后,整体的符号性会依次向内传递到每个操作数中。而对于任何一个表达式,如果其中有一个操作数的符号性为无符号,则整个表达式的符号性为无符号。(部分操作数不影响,如移位操作的右操作数;也有部分表达式的结果不受外界影响,如比较、位拼接、位截取表达式结果均为无符号数;但大部分表达式满足本规则。)
三目运算符也是一种表达式,因此遵从以上原则。下面我们结合以下例子进行进一步解释——

assign C = (1'b1)? $signed(A) >>> B : A + B;//错误
assign C = (1'b1)? $signed($signed(A) >>> B) : A + B;//正确
assign C = (1'b1)? $unsigned($signed(A) >>> B) : A + B;//正确

  • 首先分析第一个表达式。最外层的三目运算符中出现了无符号数A+B,因此这个表达式结果的符号性为无符号,并进一步将这个无符号性传给了$signed(A) >>> B。对于$signed(A) >>> B这个表达式,整体符号性已经被确定为无符号,因而又进一步传给操作数$signed(A),尽管$signed(A)的结果是有符号的,但是因为符号性的传递作用,$signed(A)在参与算术右移运算时仍然被当做无符号数,因此不符合预期。
  • 对于第二个表达式,同第一个表达式的分析,$signed($signed(A) >>> B)整体的符号性被确定是无符号,但是由于系统函数的作用,$signed(A) >>> B与外界分割开来,根据$signed函数的运算规则,$signed($signed(A) >>> B)先计算$signed(A) >>> B的值,然后将结果看作有符号数进行返回。
  • 需要注意是,符号性的传递并没有失效。$signed($signed(A) >>> B)有符号结果最终会被当作无符号数作为整个表达式(1'b1)? $signed($signed(A) >>> B) : A + B的返回值
    从以下实验可以看出
    wire flag_1, flag_2;
    wire A = 32'hf000;
    wire B = 32'h0001;
    assign flag_1 = ((1'b1)? $signed($signed(A) >>> B) : A + B) > 1;
    assign flag_2 = ((1'b1)? $unsigned($signed(A) >>> B) : A + B) > 1;
    //最终结果flag_1和flag_2都是1

位宽与符号

表达式的结果除了符号性以外,还有一个特质:位宽。表达式的符号性与等号左侧无关;但表达式的位宽一般来说是等号左右两侧所有表达式的位宽中最大的一个

wire [31:0]D;
wire [15:0]A,B;
assign D = (1'b1)?$signed($signed(A) >>> B):{A,B};

{A, B}的结果是一个32位的无符号数,这导致了右侧表达式的期望结果是一个32位无符号数(因为等号左侧最大位宽也是32位);而$signed($signed(A) >>> B)的结果是一个16位的有符号数。当这个有符号数被计算出后,它会先被转化成无符号数,再被扩展至32位。因此我们最终得到的变量D的高16位都会被填充上0。但是,如果D的位宽是16位,因此就不需要进行位扩展,问题也就没有暴露出来。

下面有一些正确的例子——

wire [31:0]D;
wire [15:0]A, B;
wire signed [15:0] signA, signB;
assign D = (1'b1) ? $signed(A) >>> B:$signed(A + B);
assign D = (1'b1) ? signA >>> signB : signA + signB;
assign D = (1'b1) ? $signed(A) >>> B:0;

变量和常量的符号性

如果一个变量没有显式的声明符号性,则会自动被当作无符号变量

wire [15:0] A; //无符号
wire unsigned [15:0] A; // 无符号
wire signed [15:0] A; //有符号

如果一个常量没有显式的声明进制,则被当成有符号的十进制数;否则会被当成无符号数。
assign C = (ALUOp==3'b101) ? $signed(A)>>>B : 0; //正确 (0 被看作有符号数)
assign C = (ALUOp==3'b101) ? $signed(A)>>>B : 32'b0; //错误 (显式声明了进制, 被当成无符号数)
assign C = (ALUOp==3'b101) ? $signed(A)>>>B : 32'sb0; //正确 (显式声明了进制,但是被显示声明了符号性)

其他有关符号的注意事项

  • 仅由操作数决定,不由左侧决定 (如 assign D = exp,exp 符号与 D 无关. 这一点区别于位宽, 位宽由左右两侧所有表达式的最大位宽决定)
  • 小数是有符号的, 显式声明了进制的数无符号, 除非用修饰符s声明了其有符号 (如 'd12 无符号,'sd12 有符号)
  • 位选/多位选择/位拼接的结果无符号 (如b[2], b[3:4], {b} 均无符号)
  • 比较表达式的结果无符号 (要么是 0, 要么是 1)
  • 由实数强转成整型的表达式有符号

文章作者: Hyggge
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hyggge !
  目录