前言
在进行数字信号处理的时候,计算是必不可少的,通常情况下,能够不用乘法器和除法器就不用乘除法器,可以采用移位和加减法的方式来完成计算。但在一些特殊情况下,希望采用乘除法,这时候在FPGA当中就需要专用的IP了。乘除法在FPGA当中实现起来是比较困难的一件事情。若直接在verilog 代码中使用了乘法或者除法,其实最终对应到电路中,要么是采用大量的block ram来实现,要么是占用DSP资源。这种情况下,对资源的占用是拿捏不准确。因此需要使用专用的乘除法器来实现乘除法。
1. 数据的表示
1.1原码,反码,补码
在进行数字信号处理的时候,这三个码首先得搞清楚了。例如
数据 | 源码 | 反码 | 补码 |
---|---|---|---|
65 | 01000001 | 01000001 | 01000001 |
-65 | 11000001 | 10111110 | 10111111 |
正数的源码补码,反码都是它本身。但是负数就需要注意了,关于如何来计算源码反码补码,其实也比较简单。
- 首先最高位为符号位,余下的都是数据位。
- 求得数据的绝对值,然后根据数据的正负,在最高位填0或1。正数填0,负数填1,就得到了数据的源码。
- 若数据是负数
- 那么保持最高位不变,其余位按位取反,就得到了数据的反码。
- 保持高位不变,在反码的基础上加1就成了数据的补码。
举几个简单例子。就用自带的计算器这个软件,调到程序员模式就开始计算。
首先是正数:例如100,那么它的二进制表示就是8’b0110_0100。
如果是负数:例如-100,那么他的二进制补码的表示就是 8’b1001_1100
在FPGA当中,数据都是以补码的形式存在的,知道了这个,就能够很好对数据进行运算了。
1.2 对数据求绝对值
由于在FPGA当中,数据都是以补码的形式存在的,因此对数据进行求绝对值的时候,就比较好操作了。如果一个数是正数,那么这个数据绝对值就是它本身。如果一个数是负数,那么直接按位取反再加一就可以了。
数据类型 | 求绝对值方法 |
---|---|
正数 | 绝对值就是数据本身 |
负数 | 按位取反再加一 |
还是上面的例子,-100的补码是8’1001_1100
它的绝对值是100。也就是8’b0110_0100。也就是由负数的补码,按位取反(包含符号位)再加一得到。
1.3 数据位宽的扩展与缩小
在进行数据位宽的扩展的时候,对于有符号数的处理,得格外的小心,最高位一定不能给搞忘了。
举个简单的例子:
用8bit表示一个正数100
现在用16bit来表示这个数,现在多了8bit,因此多出的高位的8bit,需要用原始数据的最高位来填充,也就是最高位全部填充0。
若用8bit数据表示一个负数 -100;
现在用16bit来表示这个数,现在多了8bit,因此多出的高位的8bit,需要用原始数据的最高位来填充,也就是最高位全部填充1。
1.4 定点数
前面写的都是整数,在FPGA当中如何来表示小数呢。这里就涉及到一个量化的问题,就是用一个整数来表示小数。在FPGA中常用的就是定点数来表示小数。所谓定点就是指小数点的位置是固定的。比如一个数用8位表示,符号位1位,整数位1位,小数位4位。那么如何使用定点数来表示一个这样的数呢。
比如4.5,按照上面的方式进行定点化。
4.5
×
2
4
=
72
4.5\times 2^{4}= 72
4.5×24=72
可以看到定点化之后的结果是72。其中符号位1位,整数位3位,小数位4位。
负数的定点化和正数类似。比如:
−
4.5
×
2
4
=
−
72
-4.5\times 2^{4}= -72
−4.5×24=−72
个人在识别负数的时候,有个个人的小喜好。那就是符号位为1表示这个数是负数,那么最大的负数表示范围就是-2N-1(N是数据的位宽),那么用补码表示这个数据的时候,就是最大的负数表示范围加上后面的数。就比如这个 -72。最大的负数表示范围是-128,再加上后面的符号位后面的数就是结果。(歪门邪道,不可信)
其实可以看作:
−
72
=
−
2
7
+
2
5
+
2
4
+
2
3
-72 = -2^{7} + 2^{5}+2^{4}+2^{3}
−72=−27+25+24+23
1.4.1 量化误差
在使用定点数的时候,不可避免地会引入量化误差。这个量化误差的精度是由需要量化的小数的位宽决定的。比如一个8bit数,最高位符号位,3位整数位,4位小数位。那么量化位宽就是4位。因此量化的最小精度是1/24,也就是0.0625。
在这里举一个简单的例子。
对于1.0625在不同情况下,进行定点数的转化。
case(1) : 8bit数,最高位为符号位,3位整数位,4位小数位
case(2): 8bit数,最高位为符号位,4位位整数, 3位小数位
对于第一种情况:量化的结果为:
1.0625
×
2
4
=
17
1.0625\times 2^{4} = 17
1.0625×24=17
能够完整地表达出原始的数据,1.0625
对于第二种情况:量化的结果为:
1.0625
×
2
3
=
8.5
≅
8
1.0625\times 2^{3} = 8.5\cong 8
1.0625×23=8.5≅8
此时由于量化,导致量化后的数据相较于原始数据有了误差,能表达的数据是1。
对比上面两种情况可以看出,当小数位为3位的时候,对1.0625进行量化的时候,就出现了量化的误差。这个就是由量化的位宽(量化精度)引起的。
1.4.2 负数的定点化
负数的定点化其实和前面正数的定点化是类似的。只是最终需要取补码。
例如: 最高位为符号位,3位整数位,4位小数位
−
1.0625
×
2
4
=
−
17
-1.0625\times 2^{4} = -17
−1.0625×24=−17
其实和前面一样的,最高位符号位为1,最大负数表示范围:-8。因此
−
8
+
2
2
+
2
1
+
2
−
1
+
2
−
2
+
2
−
3
+
2
−
4
=
−
1.0625
-8+2^{2}+2^{1}+2^{-1}+2^{-2}+2^{-3}+2^{-4}= -1.0625
−8+22+21+2−1+2−2+2−3+2−4=−1.0625(歪门邪道,且不可信233333333)
2. xilinx除法器IP使用
IP的配置界面如下啦:
- 设置除法器的类型,基二还是还是查找表。
- 然后是被除数和除数的位宽。
- 然后数余数类型。
- 可以选择小数和余数。
- 小数的部分的位宽
在左边可以看到输入输出信号的在总线上所占据的bit。
简简单单仿个真
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2020 All rights reserved
// -----------------------------------------------------------------------------
// Author : WCC 1530604142@qq.com
// File : tb_divider_sim
// Create : 2020-12-24
// Revise : 2020-
// Editor : Vscode, tab size (4)
// Functions : divider test
//
// -----------------------------------------------------------------------------
`timescale 1ns / 1ps
module tb_divider_sim();
reg clk ;
reg signed [15:0] divisor ;
reg signed [15:0] dividend ;
reg data_in_tvalid ;
wire div_valid ;
wire signed [31:0] div_data ;
div_gen_0 inst_divider (
.aclk(clk), // input wire aclk
.s_axis_divisor_tvalid(data_in_tvalid), // input wire s_axis_divisor_tvalid
.s_axis_divisor_tdata(divisor), // input wire [15 : 0] s_axis_divisor_tdata
.s_axis_dividend_tvalid(data_in_tvalid),// input wire s_axis_dividend_tvalid
.s_axis_dividend_tdata(dividend), // input wire [15 : 0] s_axis_dividend_tdata
.m_axis_dout_tvalid(div_valid), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(div_data) // output wire [31 : 0] m_axis_dout_tdata
);
initial begin
clk = 0;
forever #(10) clk = ~clk;
end
initial begin
data_in_tvalid = 0;
dividend = 0;
divisor = 0;
repeat(100)@(posedge clk);
data_in_tvalid = 1;
divisor = 16'd5;
dividend = 16'd25;
repeat(30)@(posedge clk);
divisor = 16'd5;
dividend = -16'd25;
repeat(30)@(posedge clk);
divisor = 16'd5;
dividend = 16'd12;
repeat(30)@(posedge clk);
divisor = 16'd5;
dividend = -16'd12;
repeat(30)@(posedge clk);
divisor = 16'd5;
dividend = -16'd4;
repeat(30)@(posedge clk);
divisor = 16'd5;
dividend = -16'd4;
end
endmodule
2.3 仿真结果
从输入的激励来看,前两个是能够被整除的,后面几个是不能被整除的。
这里看一下不能被整除的这几个。
- 12/5
这个得到的结果该是2.4,可以看到整数部分是2,小数部分是0.4。
在前面设置IP的时候,可以看到,指示小数部分的数据位宽16bit,其中符号位1位,所以量化精度就是1/215。
于是可以计算一下,用15位去量化这个小数的时候,计算出来的结果
0.4 × 2 15 = 13107.2 ≅ 13107 0.4\times 2^{15}= 13107.2\cong 13107 0.4×215=13107.2≅13107
量化后的结果是13107,16进制就是16’h3333。而这也正好是上面仿真的到的结果。
- -12/5
这个结果得到的是-2.4,整数部分是-2,小数部分是-0.4。
小数部分量化得到的结果是:
换成16进制表示:
与仿真得到的结果一致,简直是nice。
2.4 除法器IP的总结
可以看到除法器IP的使用,需要指定除法器的基于类型,除数和被除数的位宽,得到结果的余数类型需要指定。然后还需要注意的是,除法器使用的接口是axis接口的,只有在valid有效的时候,才会开始计算。
同时也还要注意到,除法器得到结果是需要很长的latency的。这个latency可以设置。合适的才是最好的