热度 16
2015-10-8 09:20
4884 次阅读|
3 个评论
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 。