前言
之前写过一个Verilog功能模块——符号位扩展,功能是将位宽小的有符号数赋值给位宽大的有符号数,保证数值不变。后来我发现根本不需要这么麻烦,只需要将原始数据设置成有符号数即可,Verilog编译器会自动补齐符号位。为此我详细研究了一下Verilog的位宽截断与补齐机制。
对于二进制或十六进制,位宽截断和补齐是很明显的,但对于十进制则需要先弄清楚,十进制在Verilog中如何转换为二进制,然后才能清楚如何对十进制表示的数进行位宽截断或补齐,为止,我又研究了一下Verilog中十进制数的二进制表示。
本文基于的仿真工具是Vivado 2021.2,基于的Verilog版本为Verilog-2001。本文未测试更多的编译器和Verilog版本,但可以预料到这些基础的机制应适用于所有编译器和Verilog版本
。
一. Testbench
module signExtension_tb();
timeunit 1ns;
timeprecision 1ps;
logic clk;
localparam CLKT = 2;
initial begin
clk = 0;
forever #(CLKT / 2) clk = ~clk;
end
localparam INPUT_WIDTH = 8;
localparam OUTPUT_WIDTH = 4;
logic [INPUT_WIDTH-1 : 0] din;
logic [OUTPUT_WIDTH-1 : 0] dout;
assign dout = din;
initial begin
#(CLKT) din = 8'h04;
#(CLKT) din = 8'h34;
#(CLKT) din = 8'hA4;
#(CLKT) din = 8'hF4;
#(CLKT) $stop;
end
endmodule
二. 位宽截断
localparam INPUT_WIDTH = 8;
localparam OUTPUT_WIDTH = 4;
// (不同类型的din与dout定义)
assign dout = din;
initial begin
#(CLKT) din = 8'h04;
#(CLKT) din = 8'h33;
#(CLKT) din = 8'hAA;
#(CLKT) din = 8'hFF;
#(CLKT) $stop;
end
2.1 无符号数 → 无符号数的位宽截断
logic [INPUT_WIDTH-1 : 0] din;
logic [OUTPUT_WIDTH-1 : 0] dout;
高位舍弃,低位保留。
2.2 无符号数 → 有符号数的位宽截断
logic [INPUT_WIDTH-1 : 0] din;
logic signed [OUTPUT_WIDTH-1 : 0] dout;
高位舍弃,低位保留。
2.3 有符号数 → 无符号数的位宽截断
logic signed [INPUT_WIDTH-1 : 0] din;
logic [OUTPUT_WIDTH-1 : 0] dout;
高位舍弃,低位保留。
2.4 有符号数 → 有符号数的位宽截断
logic signed [INPUT_WIDTH-1 : 0] din;
logic signed [OUTPUT_WIDTH-1 : 0] dout;
高位舍弃,低位保留。
2.3 位宽截断总结
高位舍弃,低位保留
。
三. 位宽补齐
localparam INPUT_WIDTH = 4;
localparam OUTPUT_WIDTH = 8;
// (不同类型的din与dout定义)
assign dout = din;
initial begin
#(CLKT) din = 4'h4;
#(CLKT) din = 4'h3;
#(CLKT) din = 4'hA;
#(CLKT) din = 4'hF;
#(CLKT) $stop;
end
3.1 无符号数 → 无符号数的位宽补齐
logic [INPUT_WIDTH-1 : 0] din;
logic [OUTPUT_WIDTH-1 : 0] dout;
低位保留,高位补0。
3.2 无符号数 → 有符号数的位宽补齐
logic [INPUT_WIDTH-1 : 0] din;
logic signed [OUTPUT_WIDTH-1 : 0] dout;
低位保留,高位补0。
3.3 有符号数 → 无符号数的位宽补齐
logic signed [INPUT_WIDTH-1 : 0] din;
logic [OUTPUT_WIDTH-1 : 0] dout;
低位保留,高位补符号位。
3.4 有符号数 → 有符号数的位宽补齐
logic signed [INPUT_WIDTH-1 : 0] din;
logic signed [OUTPUT_WIDTH-1 : 0] dout;
低位保留,高位补符号位。
3.5 位宽补齐总结
无符号数 → 无/有 :低位保留,高位补0
。
有符号数 → 无/有 :低位保留,高位补符号位
。
四. Verilog中十进制数的二进制表示
Verilog中十进制有两种表示方法:
4.1 不指定位宽的十进制数表示
initial begin
#(CLKT) din = 'd4; // 100
#(CLKT) din = -'d100; // 1001_1100
#(CLKT) din = 200; // 1100_1000
#(CLKT) din = 2147483648; //
#(CLKT) din = -2147483648; // 1000_0000_0000_0000_0000_0000_0000_0000
#(CLKT) $stop;
end
不指定位宽有两种写法:'d100
‘ 或 100
。两者是完全等效的。
在Verilog中,不指定位宽的十进制数默认为32位补码能表示的数的范围
:-2147483648 ~ -2147483647
。
我用的Linter工具是Vivado(xvlog),超出此范围,会有警告:[VRFC 10-2581] 2147483648 as 32-bit signed integer overflows, using -2147483648 insteadxvlog(VRFC 10-2581),如下图所示。
-2147483648也会报同样的警告,但这个是误报。
以下三种写法等效:
din = -'d100;
din = -100;
wire signed [31:0] a = -32'd100;
din = a;
无论din定义有无符号,都不影响结果,从二. 位宽截断
与 三.位宽补齐
我们知道,等号左边的变量如何定义不影响位宽截断或补齐,而等号右边的源数据有无符号也仅对补齐有影响
。
总结
:在Verilog中,不指定位宽的十进制数等价于wire signed [31:0] num 即32位二进制补码
。它对等号左边变量的赋值满足本文截断/补齐中总结的规律
。
4.2 指定位宽的十进制表示
在Verilog中,指定位宽的十进制等价于对其二进制补码进行截断或补齐
。
如:
-
‘6d64等价于补码8’b0100_0000进行6位截断,实际为6’b00_0000即全0,这有点反直觉,但仿真结果确实是这样;
-
-7’d66等价于补码8’b1011_1110进行7位截断,实际为7’b011_1110即62,这也很奇怪,
所以不要犯这种超出位宽表示范围的错误
,像这种写法:wire [6:0] c = -7'd66;
Linter工具并不会报错误和警告,犯这种错误排查非常困难且浪费时间,特别是在大工程中
。 -
’16d4 等价于补码4’b0100进行16位补齐,高位补符号位0;
-
-33’d2147483648等价于补码32’b1000_0000_0000_0000_0000_0000_0000_0000进行补齐,高位补符号位1;
五. 总结
-
不指定位宽的十进制等价于wire signed [31:0] num 即32位二进制补码; -
指定位宽的十进制等价于对其二进制补码进行截断或补齐; -
位宽截断:高位舍弃,低位保留; -
位宽补齐:无符号数 → 无/有 :低位保留,高位补0; -
位宽补齐:有符号数 → 无/有 :低位保留,高位补符号位;
如果本文对你有所帮助,欢迎点赞、转发、收藏、评论让更多人看到,赞赏支持就更好了。
如果对文章内容有疑问,请务必清楚描述问题,留言评论或私信告知我,我看到会回复。
徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。