目录
本文的目的是阐述RTL中的整数表示
1. 整数表示
如果将最高位作为符号位,就是有符号整数,否则就是无符号整数
1.1. 无符号数 unsigned
verilog中unsigned
或者不加,就为无符号数
即对于Nbit的无符号数,二进制 N ′ b 000...00 → N ′ b 111...11 N'b000...00→N'b111...11 N′b000...00→N′b111...11,表示十进制 0 → ( 2 N ) − 1 0→(2^{N})-1 0→(2N)−1
1.2. 有符号数 signed
verilog中signed
,就为有符号数。有符号数用补码表示
● 二进制正数_补:符号位(0为正)+原码。
● 二进制负数_补:符号位(1为负)+补码。
正负数转换规则为:符号位取反,其余位按位取反,末位加1。
即对于Nbit的有符号数,二进制 N ′ b 100...00 → N ′ b 011...11 N'b100...00→N'b011...11 N′b100...00→N′b011...11,表示十进制 − ( 2 N − 1 ) → ( 2 N − 1 ) − 1 -(2^{N-1})→(2^{N-1})-1 −(2N−1)→(2N−1)−1
例如3表示4’b0011,则-3表示为4’b1101,-5为4’b1011。
1.3. signed
与 unsigned
关键词,用于标注变量是有符号还是无符号。特性如下
● 所有常量、变量均为signed,将按照有符号数扩位、运算
● 变量:unsigned或signed截位、signed扩位等均看作unsigned。使用$signed()
转化为signed
● 常量:3'd2
为unsigned、$signed(3'd2)
和2
为signed
注意
signed
和unsigned
相互赋值不会改变数值
举例如下
`timescale 1ns/1ps
module arith_tb();
logic unsigned [3:0] u_a1;
logic unsigned [2:0] u_a2;
logic signed [3:0] s_a1;
logic signed [2:0] s_a2;
initial begin
u_a1 = 4'b0110;
u_a2 = 3'b110;
s_a1 = 4'b1001;
s_a2 = 3'b100;
//--------------------------------------------------------------------------------------------------------
// compare
//--------------------------------------------------------------------------------------------------------
$display("s_a1>4'd5 = %b",s_a1>4'd5); // 4'd9 > 4'd5 = 1
$display("s_a1>$signed(4'd5) = %b",s_a1>$signed(4'd5)); // -4'd7 > 4'd5 = 0
$display("s_a1<-4'd5 = %b",s_a1<-4'd5); // 4'd9 < 4'd11 = 1
$display("s_a1<$signed(-4'd5) = %b",s_a1<$signed(-4'd5)); // -4'd7 < -4'd5 = 1
$display("s_a1<s_a2 = %b",s_a1<s_a1); // -4'd7 < -4'd4 = 1
//--------------------------------------------------------------------------------------------------------
// add
//--------------------------------------------------------------------------------------------------------
s_a1 = s_a2 + u_a2; //1010 = 0100 + 0110 - automatic extract 0
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + s_a2; //1000 = 1100 + 1100 - automatic extract sign bits
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + 3'd2; //0110 = 0100 + 0010 - automatic extract sign bits
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + $signed(3'd2); //1110 = 1100 + 0010 - automatic extract sign bits
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + s_a2[2:1]; //0110 = 0100 + 0010 - automatic extract 0
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + $signed(s_a2[2:1]); //1010 = 1100 + 1110 - automatic extract sign bits
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + {1'b0,s_a2}; //1000 = 0100 + 0100 - automatic extract 0
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + {$signed(1'b0),s_a2}; //1000 = 0100 + 0100 - automatic extract 0
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2 + $signed({1'b0,s_a2}); //0000 = 1100 + 0100 - automatic extract sign
#1;
$display("s_a1 = %b",s_a1);
s_a1 = -s_a2 ; //0100 - automatic inverse
#1;
$display("s_a1 = %b",s_a1);
//--------------------------------------------------------------------------------------------------------
// bit calculation
//--------------------------------------------------------------------------------------------------------
s_a1 = u_a2; //0110 - automatic extract 0
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a2; //1100 - automatic extract sign bits
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a1 >>> 1; //1110 - arithmetic right shift
#1;
$display("s_a1 = %b",s_a1);
s_a1 = s_a1 >> 1; //0111 - logic right shift
#1;
$display("s_a1 = %b",s_a1);
end
endmodule
1.4. 位宽确定
那么RTL中的位宽如何确定呢?
对于某个整数,按照如下方法判定位宽,防止溢出。
整数位宽python:math.ceil[math.(max abs)] (+1)
小数位宽python:math.floor[math.log2(精度)]
例如某个无符号数c范围为0-168.346,那么其整数部分为0~168位宽就为 c e i l [ l o g 2 ( 168 ) ] = 8 ceil[log2(168)] = 8 ceil[log2(168)]=8,小数部分为0.001~0.999位宽就为 f l o o r [ l o g 2 ( 0.001 ) ] = − 10 floor[log2(0.001)] = -10 floor[log2(0.001)]=−10,因此c位宽为10 + 8 = 18bit
有符号数c范围为-345345.457456~178345.56754,整数位宽为 c e i l [ l o g 2 ( 345345 ) ] + 1 = 20 ceil[log2(345345)] +1= 20 ceil[log2(345345)]+1=20,小数部分为20,c位宽就为20 + 20 = 40
整数位宽的原理是,多少位宽能覆盖所有整数。
小数位宽的原理是,多少位宽可所有小数都可转化为整数。所以最小小数,即精度,对应的位宽才可覆盖所有小数。
2. 整数 2 n 2^n 2n倍乘除运算
2.1. 扩位
将一个小位宽的有符号整数增加位宽 ,可用于 2 n 2^n 2n倍乘除。无论是设计者手动扩位还是因为位宽不够自动扩位,都遵循如下规则
unsigned RTL:左扩n位{n'd0,a};
,右扩n位{a,n'd0};
unsigned python:左扩n位a
,右扩n位a * math.pow(2,n)
signed RTL:左扩n位{{n{a[15]}},a};
,右扩n位{a,n'd0};
signed python:左扩n位a
,右扩n位a * math.pow(2,n)
例如
logic unsigned [2:0] u_a;
logic signed [2:0] s_b;
logic signed [3:0] s_c;
initial begin
u_a = 3'b010;
s_b = 3'b110;
#1;
s_c = u_a; //s_c为4'b0010
s_c = s_b; //s_c为4'b1110
end
2.2. 算术移位
注意有符号数逻辑移位会连同符号位一起移位!!!这样的操作无意义!!!
对于有符号数来说,保持符号位不变的算术移位才有意义!!!
unsigned/signed 算术移位 RTL:右移n位a >>> n;
,左移n位(溢出风险)a <<< n
unsigned/signed 算术移位 python:右移n位math.floor(a / math.pow(2,n))
,左移n位a * math.pow(2,n)
例如-3为4’b1101,左移为4’b1010为-6,右移4’b1110为-2。例如4’b1010为-6,右移1位为4’b1101为-3,左移1位为4’b0100为+4溢出。
2.3. 截位、四舍五入
尾部砍位和四舍五入可用于 2 n 2^n 2n倍除。如果直接抛弃小数部分,即截位。如果根据小数作近似,则为四舍五入。
unsigned 直接尾截n位,python:math.floor(a / math.pow(2,n))
unsigned直接尾截n位,RTL:a[15:n]
unsigned 四舍五入尾截n位,python:round(a / math.pow(2,n))
unsigned 四舍五入尾截n位,RTL:a[15:n] + a[n-1]
如果是signed正数,直接截位,四舍五入就为a[15:n] + a[n-1]
。
如果是负数,就要看a[n-1:0]
是否是'b100...00100...
的格式,因为只有这样的格式取反加1后a[n-1] == 1'd0
,即a
的模四舍五入后无需进位,其他格式都需要进位(例如'b1
、'b100...00
、'b0..010..0
等)。无需进位的话,补码四舍五入后a[15:n]
就需要加1。
同理,负数直接截位一定不进位,其a[15:n]
就需要加1。
也就是说,
[{1'b1,Z,Q}]_补
先求模[{Z,Q}]_原
,四舍五入进位则得到[Z+1]_原
,其补码[Z+1]_补 = [Z]_反
Verilog对数据进行四舍五入(round)与饱和(saturation)截位
signed 直接尾截n位,python:math.floor(a / math.pow(2,n))
signed直接尾截n位,RTL:a[15:n] + a[15]
signed 四舍五入尾截n位,python:round(a / math.pow(2,n))
signed 四舍五入尾截n位,RTL:a[15:n] + (a[15]? (a[n-1] && (|a[n-2:0])) : a[n-1])
signed 四舍五入尾截1位,RTL:a[15:1] + (a[15]? (1'd0 : a[0])
例如有符号数+15 = 6’b001111,+15/4 = 3.75,截位就是3 = 4’b0011,四舍五入就是+4 = 4’b0100。
有符号数-15 = 6’b110001,-15/4 = -3.75,截位就是-3 = 6’b1101,四舍五入是 -4 = 6’b1100
有符号数-17 = 6’b101111,-17/4 = -4.25,截位就是-4 = 6’b1100,四舍五入是 -4 = 6’b1100
有符号数-16 = 6’b110000,-16/4 = -4,截位就是-3 = 6’b1101,四舍五入是 -4 = 6’b1100
再如-15/2 = -7.5,截位就是-7 = 5’b11001,四舍五入是-8 = 5’b11000
注意截位的溢出风险,如果+15 = 5’b01111,那么+15/4四舍五入就是5’b000