Verilog中的浮点数的表示以及浮点数除法IP核的用法
初学FPGA,最近用到除法,刚开始想要自己写一段除法的代码来解决,和好多菜鸟一样先去网上输入“verilog除法器”去搜索,搜到了一些代码,但是这些代码都是:分子除以分母得到的结果是商和余数。例如10除以5商2余0,这个还好,因为可以整除。如果是11除以5商2余1,可是我想得到的结果是2.2。继续在网上找,还是没有找到。后来听从大神指点试试Verilog自带的除法IP核,于是就去找IP核。马上就要切入正题了,在此先介绍一下本人的背景,主要是增强大家学习FPGA的信心。
本人本科学的是物理学,硕士学的是光学,大学期间对电脑的认识就是计算机二级的水平,本科毕业论文用了一点MATLAB,可以说对于编程完全空白,对于数电中的二极管,三极管,触发器都只停留在知道名字上。对于二进制,十六进制,浮点,整型都没有概念。硕士也就用过LabVIEW。就是这样的基础来学FPGA的,介绍这么一堆,是因为以前看帖子时经常会看到发帖的人会默认读者懂数电,懂模电,懂进制,懂单片机,懂各种协议,懂一切工科的基础知识。但是问题来了,对于我这种理科生,啥也不懂,怎么办,回答问题的人当然会说:不懂就自己去看呗,谁能把所有的东西都告诉你。事实确实是这样的,什么问题都要自己去解决,但是我想说如果解答者能站在提问者的角度去回答,那么双方都会获益匪浅的,能知道提问者问的是什么并站在提问者的角度去回答对于解答者是一个很高的要求。当然提问者也要懂得独立思考,不要什么都去问,先自己查做好准备再提问,这样才能够提出准确,高效的问题。解答者也乐意解答。
下面我们切入正题:
首先我们把要处理的数据分成两类:整型和浮点型,通俗的说就是整数和小数。我们用这两种数做除法得到的结果无非两种,整除和不能整除,对应的商就是整数和小数。在此我对除法的编程代码原理不做介绍,只介绍如何调用IP核,对于除法结果为商和余数的代码在网上可以找到,大家可以自己看。至于结果为小数(也就是没有余数)的我还没有找到。大家可以找找。我们在此调用quartus自带的除法IP核,首先调用除法IP核的方法是:
打开quartus菜单中的tools选择MegaWizard Plug-In Manager,就会跳出窗口:
{C}{C}
点击next。
{C}{C}
在这个页面中先看左边的窗口,点开第一个文件夹(Arithmetic)图标,其中有两个除法IP核,第一个就是图中所示的ALTFP_DIV,这个IP核得到的结果只有商,而且是浮点数的。另外一个需要往下拖才能看到是LPM_DIVIDE,这个我们稍后再说,先看ALTFP_DIV。右上角可以选定使用的芯片系列,稍微往下选择输出的编程语言,我用的是Verilog,再往下就是输入创建的文件名,我取的是division。剩下的不用管,点击next就可以了。
{C}{C}
到了这个页面先提醒大家一下,如果大家想详细了解这个IP核的信息可以点击右上角的Documentation,打开浏览器观看。页面的信息大家可以自己看看,根据自己的要求选择,在此选择单精度,至于中间靠右下的地方在说明数据的组成,这个放到下面说,至于有个14的参数,我还不是很理解,目前理解是计算这个除法需要消耗14个时钟,知道的帮我更正一下,点击next。
这个页面也不做解释,内容大家自己看,点击next。
这个页面根据需要选择,点击next。
直接点击next。
这是最后一个页面,可以根据自己需要选择要生成的文件。我一般选择把所有的复选框对勾都去掉,因为division.v(第一个文件)已经默认生成了,之所以做这种选择是因为我是个菜鸟,不知道其他文件有什么用途,所以大家可以给我海量的补充啊!
下面我们要做的就是仿真了,但是在仿真之前我们需要对输入和输出的数据做一个了解,这儿要花费很大的篇幅,大家要做好心理准备。
输入的dataa是分子,datab是分母,result是商。三者都是单精度32位的数据。输入的时钟不在讨论之内。因为这个IP核处理的是浮点除法,所以输入输出都是浮点型。下面就来解释整数和小数是如何表示成浮点型的二进制,因为计算机不认识小数点。
根据IEEE-745标准规定:单精度32位的数据组成是这样的,最高位是符号位(【32】),表示正负,往下数八位(【31】-【24】)是阶位,剩下的23位是尾数位。如下图所示:
符号位为0表示正数,1表示负数。用字母S表示
阶位用字母P(十进制)表示,变化范围是0-256。
尾数用M表示。
这样的一个32位表示的十进制数为((-1)^S)*(2^(P-127))*1.M,
那么如何将十进制的整数和小数表示成这样的一个32位浮点数呢?
举个例子吧:
{C}1、 整数
比如21,21的二进制是10101,表示成小数形式就是10101.0,将其变为科学计数法形式可以写成1.01010*(2^4),括号中2的指数(4)用来确定阶位,方法是P-127=4,现在我们就能写出21的32位二进制表示结果了,首先符号位是0,阶位P = 127+4,也就是131,表示为8位的二进制是10000011,这就是阶位,尾数如何表示呢?我们从刚刚得到的1.01010*(2^4)中找答案:尾数取1.01010的小数部分,即01010,将01010按照顺序从尾数的高位排起,剩余的补零,得到的结果就是01010000000000000000000。然后将得到符号位,阶位,尾数位连接起来,为了看清楚,用空格分开,结果就是:
0 10000011 01010000000000000000000。这就是21的32位二进制浮点数表示。
{C}2、 纯小数(不带整数部分的小数)
以0.25为例,先做这样的操作,0.25*2=0.5,0.5*2=1。所以0.25=1*(2^-2)。
同样可以写成1.0*(2^-2)。此时就可以写了,符号位是0,阶位P=(-2)+127,即125,表示成8位的二进制是01111101,尾数是1.0的小数部分,即0,所以尾数是23个0。所以0.25的32位二进制浮点数是:
0 01111101 00000000000000000000000。
至此纯小数的讨论还没有结束,因为0.25只代表乘2之后,无论乘多少次都能得到整数的小数。还有一部分是无论乘多少次2都得不到的整数的小数。比如0.3,0.6,0.7等,其实只要小数的最后一位不是5就属于这种小数。我们以0.3为例来说明。
方法是这样的:首先符号位是0,要得到阶位和尾数要进行以下操作,
0.3*2=0.6,我们取0.6的整数部分0记在尾数的最高位(【23】位),
0.6*2=1.2,我们取1.2的整数部分1记在尾数的次高位(【22】),取完后1.2就变为0.2了。下次运算就要用0.2了。
0.2*2=0.4,我们取0.4的整数部分0记在尾数的次次高位(【21】)。
依次推下去直到填满尾数的23个位。
在此可以告诉大家一个规律,其实只要稍微观察就可以发现:
0.3*2=0.6 取0
0.6*2=1.2 取1
0.2*2=0.4 取0
0.4*2=0.8 取0
0.8*2=1.6 取1
0.6*2=1.2 取1
0.2*2=0.4 取0
0.4*2=0.8 取0
0.8*2=1.6 取1
第一位是0,从第二位开始会以1001的形式重复,这是因为被乘数在以0.6,0.2,0.4,0.8为周期重复。所以可以很轻易写出23位(此处我们写26位,因为稍后小数点需要右移两位,还要近似)的结果:01001100110011001100110011。得到尾数后就要讨论阶数了,因为尾数是小数部分所以要写成0.0100110011001100110011001,将其写成二进制的科学计数形式:
0.0100110011001100110011001= 1.00110011001100110011001*(2^-2),所以阶数就是
P=(-2)+127 ,即125,将其写成8位二进制是01111101,至此,0.3的符号,阶数,尾数都有了,0.3的二进制32位单精度浮点数就是:
0 01111101 00110011001100110011001
但是仿真结果却是:0 01111101 00110011001100110011010
{C}{C}
与我们结果不同的是尾数的后两位,我们是01,仿真结果是10,这是因为尾数的第24位是1,根据四舍五入的原则,24位会向上进一位,所以01+1变为10。即0.3真正的结果是:0 01111101 00110011001100110011010。
{C}3、 带整数的小数
为了简单,我就偷个懒,以5.3为例,说明方法就好,学会了纯小数的表示,这个就很简单了。首先将整数和小数部分分开写成5和0.3,分别出其二进制表达,5的二进制是101,0.3的二进制就是我们上面导出的01001100110011001100110011将101作为整数位,01001100110011001100110011作为小数位,写成
101. 01001100110011001100110011,将其写成二进制的科学计数法形式,
101. 01001100110011001100110011 = 1. 0101001100110011001100110011*(2^2),则阶位是P = 2+127 = 129,写成八位二进制是10000001,
尾数是1. 0101001100110011001100110011的小数部分,遗憾的是尾数只有23位,所以只能取小数部分的前23位,即01010011001100110011010,则5.3的二进制32位单精度浮点数就是:
0 10000001 01010011001100110011010。
长篇大论说了这么多,相信大家都看烦了,但是还没有结束,为什么呢?因为我们现在只是把我们熟悉的十进制的整数和小数表示成了二进制32位单精度浮点数,那么这个二进制32位单精度浮点数是不是能够准确的表示十进制数呢?在此我们还要说明二进制32位单精度浮点数的精度问题,大家再受累看会吧!我们就采用前面得到的四个浮点数来举例做一个逆过程,同时说明一下这个公式((-1)^S)*(2^(P-127))*1.M的用法:
21 其二进制单精度浮点数为0 10000011 01010000000000000000000
0.25其二进制单精度浮点数为0 01111101 00000000000000000000000
0.30其二进制单精度浮点数为0 01111101 00110011001100110011010
5.3 其二进制单精度浮点数为0 10000001 01010011001100110011010
先验证21,用0 10000011 01010000000000000000000来得到21,第一位是0说明是正数,接下来8位是阶位转为十进制是131,即P = 131,1.M = 1.0101,注意1.0101是二进制的,要将其转成十进制的小数。转换方法是:
整数位乘2^(0),每一个尾数位分别乘以对应的阶,对应的阶已在上图标出,然后相加。1.0101可以表示为:1.0101 = 1*2^(0) + 0*2^(-1) + 1*2^(-2) + 0*2^(-3) + 1*2^(-4) = 1.3125。
利用公式((-1)^S)*(2^(P-127))*1.M得到((-1)^0)*(2^(131-127))*1.3125 = 21。可见21被准确的表示了。
0.25大家可以自己去验证,因为0.25也可以被准确的表示。最终的公式是:
((-1)^0)*(2^(125-127))*1.0 = 0.25。
接下来验证0.3,用0 01111101 00110011001100110011010表示,符号位是0,阶位转成十进制是125,即P = 125,1.M = 1. 00110011001100110011010,将其转换成十进制的小数,位数比较多,只将1乘的阶位写出,1. 00110011001100110011010 =1*2^(0) + 1*2^(-3) + 1*2^(-4) + 1*2^(-7) + 1*2^(-8) + 1*2^(-11) + 1*2^(-12) + 1*2^(-15) + 1*2^(-16) + 1*2^(-19) + 1*2^(-20) + 1*2^(-22) = 1.20000004768371582,利用公式((-1)^S)*(2^(P-127))*1.M得到2^(125-127)* 1.20000004768371582 = 0.300000011920928955,由此可见精度还是相当高的。之所以大于0.3是因为当时我们在处理尾数最后一位时选择从后一位进位了,不知大家还记得否?建议取前八位验证,这样工作量会小好多,如果我取到第六位1*2^(0) + 1*2^(-3) + 1*2^(-4) + 1*2^(-7) + 1*2^(-8) = 1.19921875,1.19921875*0.25 = 0.2998046875这个精度还凑合吧!所以说二进制32位单精度浮点数在大多数时候并不能完全准确的表示十进制的小数。
我想说了这么多,大家应该都明白了吧,剩下的5.3大家就自己验证吧,我们还是回到除法吧。一开始我们就利用浮点除法的IP核创建了浮点数除法的代码文件division.v。接下来写个简单的测试程序。
这个例子是用1除以5,结果应该是0.2。仿真结果是:
从图中看出经过14个时钟周期得到了结果,这个好像是对应了前面创建文件时设置的参数。结果为:0 01111100 10011001100110011001101这个结果唯一有疑问的地方就是尾数的最后一位,我们先写出0.2的浮点数表示:符号0,写出二进制小数是
0.2*2=0.4 取0
0.4*2=0.8 取0
0.8*2=1.6 取1
0.6*2=1.2 取1
0.2*2=0.4 取0
0.4*2=0.8 取0
0.8*2=1.6 取1
0.6*2=1.2 取1
所以小数部分是以0011循环的,即0.0011001100110011001100110011,科学计数表示:0.0011001100110011001100110011=1.1001100110011001100110011*2^(-3),保留小数点后的23位10011001100110011001100,可是仿真结果最后一位是1,这个在之前举例0.3中已经提到过,最后一位要从下一位进位,小数点后的第二十四位是1,所以应该向前进1,所以尾数的最后一位取1。0.2的表示为:0 01111100 10011001100110011001101。
至此,浮点的除法就介绍完了,很啰嗦,可能跟我的表达能力有关吧,我以后尽量简洁一些。
至于第二个除法模块LPM_DIVIDE我建议大家自己去看一下,它的结果是商和余数。网上也有很多代码能实现,大家可以自己去查看。
第一次写,不足的地方请指出,互相学习,邮箱:koukuanxyz@163.com。
用户3820093 2016-6-23 16:24
用户1835172 2015-10-8 09:22
用户1696769 2015-9-24 16:16