数据的表达方式
数据有两种表达方式:浮点数和定点数。以单精度浮点数为例,表示为打头的一位符号位,紧接着8位指数位,尾巴是23位尾数。其表达方式就像二进制的科学计数法其中表示范围主要由指数位决定,精度取决于尾数。另一个就是本文的主角定点数,定点数的小数点由程序确定,对于2进制数定点数来说,点上小数点就相当于缩小了2^n(n由小数点的位置决定)。由于在FPGA中,以定点数运算为主,浮点数在此就抛掉了。
假定芯片是 16 位的,并且使用的定点数都是有符号的。最高位则代表符号位,则数值位占据了低 15 位。数值位又分为整数位和小数位,整数最低位和小数最高位之间为小数点的位置(小数点不占据实际位宽)。
1.定标
程序指定小数点的位置即为定标,有两种方法:Q表示方法和S表示方法。
(1) Q表示法:Qn表示低n位为小数,其余位为符号位和整数位。
(2) S表示法:Sn中低(15-n)为小数,其余位符号位和整数位。
即Q表示法中n代表着小数位的位数,S表示法中n代表着整数位的位数。
在使用XILINX VIVADO 中的IP核,都是以Q表示法表示。
2. 定点数的表示范围
(1)定标前
假设使用Q3格式来表示一个8位有符号定点数,那么他的表示范围是什么呢?硬件处理器中定点数以补码形式储存。而正数的补码是本身,关键在于复数的补码了。
先以原码形式表示此定点数:
负数部分: (1111_1111~1000_0000),
正数部分: (0000_0000~0111_1111),
装换成反码形式:
负数部分 : (1000_0000~1111_1111),
正数部分不变: (0000_0000~0111_1111),
装换成补码形式:
负数部分 (1000_0001~1000_0000),
正数部分不变: (0000_0000~0111_1111),
由于负数部分补码的最大值(也就是-0)溢出,遂表示为1000_0000,为保证“0”的唯一性,此数规定为十进制的-128。
所以负数部分的表示范围(-128,-1)
正数部分不变。表示范围(0,127)
综合正负两侧,即可得出(-128,127)。
(2)定标
定标的意义在于改变数据增长基数的,以Q3为例,未定标的增长单位为1,定标后增长基数为1/23,
所以表示范围为:(-128/23,127/23)。综合成公式即可有:
其中N为定点数的位数(排除符号位),n为定标的位数,其中n越大则数据越精确,但表示的范围也会越小。
在verilog中的类型声明默认为unsigned模式,若要声明有符号类型则:
output reg signed [15:0] dx,
output wire signed [15:0] dy
在声明时一定注意,有符号定点数的表示范围。还有一种使用方法是类型装换符:$signed.
加减运算
wire [7:0] b;
wire [7:0] c;
wire [8:0] a;
assign a = $signed( b - c );
这里要注意,加不加此转换符的结果都是一样的,两个正数相减,若出现负数会自动添符号, s i g n e d 和 signed和 signed和unsigned都是对结果作用。
wire signed [7:0] b2;
wire [7:0] c2;
wire [7:0] a2;
initial begin
b2 =-3;
c2 = 255;
end
assign a2 = b2 - c2
无符号数与有符号数进行计算,数据以无符号参与计算,此处可以看到b2由有符号数装换为无符号数,但其结果是一个有符号数。
移位操作
左移:左移时数位末尾填零,没多大问题。
右移:由于右移会在数据的最左侧填0,所以会覆盖原有的符号为,所有可以使用:
wire [7:0] a;
wire [7:0] b;
assign b = {a[7],a[7:1]};
或者可以在声明时使数位有一定空闲(对于正数来说就是有效数据前有很多的0,对于负数来说就是有很多1)。
乘除运算
乘法运算,运算结果的位数为运算元素位数和。在verilog中简单乘除法用移位来实现,而较为精确的乘除使用乘法器和除法器的IP核实现,所以不用担心。若有符号数使用不熟练,可以使用较大数位,可以避免错误。
Tips:溢出判断
module top_module (
input signed [7:0] a,
input signed[7:0] b,
output signed [7:0] s,
output overflow
);
assign s = a + b;
assign overflow = ( a[7] && b[7] && ~s[7] ) || (~a[7] && ~b[7] && s[7]);
endmodule