FPGA中的二进制数可以分为定点数和浮点数两种格式,虽然浮点数的加/减法运算相对于定点数而言,在运算步骤和实现难度上都要复杂得多,但基本的运算仍然是通过将浮点数分解为定点数运算,以及移位等运算步骤来实现的。因此本节只针对定点数的运算进行分析讲解。
进行FPGA实现的设计输入语言主要有Verilog HDL和VHDL两种。本书使用Verilog HDL进行设计,这里只介绍Verilog HDL中对定点数的运算及处理方法。
Verilog HDL中最常用的数据类型是单比特wire和reg,以及它们的向量形式。当进行数据运算时,Verilog HDL是如何判断二进制数的小数位、有符号数表示形式等信息的呢?在Verilog HDL程序中,所有二进制数均当成整数来处理,也就是说,小数点均在最低位的右边。如果要在程序中表示带小数的二进制运算,那么该如何处理呢?其实,进行Verilog HDL程序设计时,定点数的小数点位可由程序设计者隐性标定。例如,对于两个二进制数00101和00110,当进行加法运算时,Verilog HDL的编译器按二进制规则逐位相加,结果为01011。如果设计者将数据均看成无符号整数,则表示5+6=11;如果设计者将小数点位均看成在最高位与次高位之间,即0 Δ 0101、0 Δ 0110、0 Δ 1011,则表示0.3125+0.375=0.6875。
需要注意的是,与十进制数运算规则相同,即进行加/减法运算时,参加运算的两个数的小数点位置必须对齐,且结果的小数点位置相同。仍然以上面的两个二进制数00101和00110为例,进行加法运算时,如果两个数的小数点位置不同,如分别为0 Δ 0101、00 Δ 110,则代表的十进制数分别为0.3125和0.75。两个数不经过处理,仍然直接相加,Verilog HDL的编译器按二进制规则逐位相加,结果为01011。小数点位置与第一个数相同,则表示0.6875,小数点位置与第二个数相同,则表示1.375,显然结果不正确。为进行正确的运算,需要在第二个数末位补0,为00 Δ 1100,两个数再直接相加,得到01 Δ 1001,转换成十进制数为1.0625,得到正确的结果。
显然,如果设计者将数据均看成无符号整数,则不需要进行小数位扩展,因为Verilog HDL编译器会自动将参加运算的数据以最低位对齐进行运算。
Verilog HDL如何表示负数呢?如二进制数1111,在程序中是表示15还是-1?方法十分简单。在声明端口或信号时,默认状态均表示无符号数;如果需要指定某个数为有符号数,则只需要在声明时增加signed关键字即可。如“wire signed[7:0]number;”则表示将number声明为8比特字长的有符号数,在对其进行运算时自动采用有符号数运算。无符号整数是指所有二进制数均是正整数,对于 B 比特的二进制数:
将其转换成十进制数,为
有符号数则指所有二进制数均是补码形式的整数,对于 B 比特的二进制数,转换成十进制数为
有读者可能要问了:如果在设计文件中要同时使用有符号数和无符号数操作,那么该怎么处理呢?为了更好地说明程序中对二进制数表示形式的判断方法,我们来看一个具体的实例。
例3-1:在Verilog HDL中同时使用有符号数及无符号数的应用实例。
在Vivado中编写一个Verilog HDL文件,同时使用有符号数及无符号数进行运算,并进行仿真。
由于该程序文件十分简单,这里直接给出文件源代码。
图3-4所示为有符号数加法及无符号数加法RTL原理图,从图中可以看出,signed_out、unsigned_out均为d1、d2进行相加后的输出,且加法器并没有标明是否为有符号运算。
图3-4 有符号数加法及无符号数加法RTL原理图
图3-5所示为有符号数加法及无符号数加法的仿真波形图,从图中可以看出,signed_out及unsigned_out的输出结果完全相同,这是什么原因呢?相同的输入数据,进行无符号数运算和有符号数运算的结果竟然没有任何区别!既然如此,何必在程序中区分有符号数及无符号数呢?原因其实十分简单,对于加法、减法,无论是否为有符号数运算,其结果均完全相同,因为二进制数的运算规则完全相同。如果将二进制数转换成十进制数,我们就可以看出两者的差别了。下面以列表的形式来分析具体的运算结果,如表3-3所示。
图3-5 有符号数加法及无符号数加法的仿真波形图
表3-3 有符号数及无符号数加法运算结果表
分析表3-3中的数据,结合二进制数的运算规则可以得出以下几点结论。
对于 B 比特的二进制数,若当成无符号整数,则表示的范围为0~2 B -1;若当成有符号整数,则表示的范围为2 B -1 ~2 B -1 -1。
如果二进制数的表示范围没有溢出,将运算数据均当成无符号数或有符号数,则运算结果正确。
两个 B 比特的二进制数进行加/减法运算,若要确保运算结果不溢出,则需要 B +1比特数据存放运算结果。
两个二进制数进行加/减法运算,只要输入数据相同,则不论当成有符号数还是无符号数,其运算结果的二进制形式完全相同。
虽然在二进制数的加/减法运算中,两个二进制数运算结果的二进制形式完全相同,但在实际进行Verilog HDL程序设计时,仍然十分有必要根据设计需要采用signed关键字进行有符号声明。例如,进行比较运算时,对于无符号数据,1000大于0100,对于有符号数据,1000小于0100。
加法及减法运算在数字电路中的实现相对较为简单,在用综合工具综合设计时,RTL电路图中加、减操作会被直接综合成加法器或减法器组件。乘法运算在其他软件编程语言中实现也十分简单,但用门电路、加法器、触发器等基本数字电路元件实现乘法功能却不是一件容易的事。在采用AMD公司器件进行FPGA工程设计时,如果选用的目标器件内部集成了专用的硬件乘法器IP核,则Verilog HDL语言的乘法运算符在综合成电路时将直接综合成硬件乘法器,否则综合成由LUT等基本元件组成的乘法电路。与加/减法运算相比,乘法运算需要占用成倍的硬件逻辑资源。当然,实际FPGA工程设计中,在需要用到乘法运算的情况下,可以尽量使用目标器件提供的硬件乘法器IP核,这种方法不仅不需要占用普通逻辑资源,并且可以达到很高的运算速度。
FPGA器件中的硬件乘法器IP核资源是十分有限的,而乘法运算本身比较复杂,用基本逻辑单元按照乘法运算规则实现乘法运算时占用的资源比较多。设计中遇到的乘法运算可分为信号与信号之间的运算,以及常数与信号之间的运算。对于信号(数据)与信号之间的运算通常只能使用硬件乘法器IP核实现,而对于常数与信号之间的运算则可以通过移位及加/减法实现。信号 A 与常数相乘运算操作的分解例子如下:
A ×16= A 左移4位
A ×20= A ×16+ A ×4= A 左移4位+ A 左移2位
A ×27= A ×32- A ×4- A = A 左移5位- A 左移2位- A
需要注意的是,由于乘法运算结果的数据位数比乘数的数据位数多,因此在用移位及加法操作实现乘法运算前,需要将数据位数进行扩展,以免出现数据溢出现象。
在AMD公司集成开发环境下的Verilog HDL语言编译环境中,除法、指数、求模、求余等操作均无法在Verilog HDL程序中直接进行。实际上,用基本逻辑元件构建这4种运算本身是十分复杂的工作,如果要用Verilog HDL实现这些运算,一种方法是使用开发环境提供的IP核或使用商业IP核;另一种方法只能是将运算分解成加法、减法、移位等操作步骤来逐步实现。
AMD公司的FPGA器件一般都提供硬件除法器IP核。对于信号与信号之间的除法运算,最好的方法是采用Vivado提供的现成IP核,而对于除数是常量的除法运算,则可以采取加法、减法、移位运算来完成除法功能。下面是一些信号 A 与常数相除运算操作的分解例子:
A ÷2≈ A 右移1位
A ÷3 ≈A ×(0.25+0.0625+0.0156) ≈A 右移2位+ A 右移4位+ A 右移6位
A ÷4 ≈A 右移2位
A ÷5 ≈A ×(0.125+0.0625+0.0156) ≈A 右移3位+ A 右移4位+ A 右移6位
需要说明的是,与普通乘法运算不同,常数乘法运算可以通过左移运算得到完全准确的结果,而常数除法运算却不可避免地存在运算误差。显然,采用分解方法的除法运算只能得到近似正确的结果,且分解运算的项数越多,精度越高。这是由FPGA等数字信号处理硬件平台不可避免地有限字长效应引起的。
1.有效数据位的概念
众所周知,在FPGA数字运算中,每个数据位都需要相应的寄存器来存储,参与运算处理的数据位越多,所占用的硬件资源也越多。为确保运算结果的正确性,或者为尽量获取较高的运算精度,通常又不得不增加相应的运算字长。因此,为确保硬件资源的有效利用,需要在工程设计时,准确地掌握运算中的有效数据位长度,尽量减少无效数据位参与运算,从而避免浪费宝贵的硬件资源。
有效数据位是指表示有用信息的数据位。例如,整数型的有符号二进制数据001,显然只需要用2比特数据即可正确表示01,最高位的符号位其实没有代表任何信息。
2.加法运算中的有效数据位
先考虑两个二进制数之间的加法运算(对于补码数据来说,加/减法运算规则相同,因此只讨论加法运算情况)。假设数据位数较大的位数为 N ,则加法运算结果需要用 N +1位才能保证运算结果不溢出,也就是说,两个长度为 N (另一个数据位长度也可以小于 N )的二进制数进行加法运算,运算结果的有效数据位长度为 N +1。如果运算结果只能采用 N 位数据表示,那么该如何对结果进行截取呢?截取后的结果如何能保证运算的正确性呢?下面我们还是以具体的例子来进行分析。
例如,两个长度为4的二进制数 d 1 、 d 2 进行相加运算。我们来考查 d 1 、 d 2 取不同值时的运算结果及截位后的结果。为便于分析比较,列出了表3-4。
表3-4 有效数据位截位与加法运算结果的关系
分析表3-4的运算结果可知,对两个长度为 N 的二进制数进行加法运算时,需要采用 N +1位数据才能获得完全准确的结果。如果需要采用 N 位数据存放结果,那么取低 N 位时就会产生溢出,得出错误结果,取高 N 位时则不会出现溢出,运算结果相当于减小了一半。
前面的分析实际上是将数据均当成整数,也就是说,小数点位置均位于最低位的右边。在数字信号处理中,定点数通常限制在-1~1,即把小数点固定在最高位和次高位之间。同样是表3-4中的例子,考虑小数运算时,运算结果的小数点位置又该如何确定呢?对比表3-4中的数据,可以很容易地看出,如果采用 N +1位数据表示运算结果,则小数点位置位于次高位的右边,而不再是最高位的右边;如果采用 N 位数据表示运算结果,则小数点位置位于最高位的右边。也就是说,运算前后小数点右边的数据位数(也是小数的位数)是固定不变的。实际上,在Verilog HDL语言环境中,如果对两个长度为 N 的数据进行加法运算,为了得到 N +1位的准确结果,那么必须先对参加运算的数进行一位符号位扩展。
3.乘法运算中的有效数据位
与加法运算一样,我们同样考查乘数均采用补码表示形式(有符号数)的情况,这也是FPGA进行数字信号处理时最常用的数据表示形式,在讨论清楚补码的相关情况后,读者很容易得出无符号数的运算规律。
从表3-5中可以得出以下几条运算规律。
对字长分别为 M 、 N 的数据进行乘法运算时,需要采用 M + N 位字长的数据才能得到准确的结果。
对于乘法运算,不需要通过扩展位数来对齐乘数的小数点位置。
当乘数为小数时,乘法结果的小数的位数等于两个乘数的小数的位数之和。
当需要对乘法运算结果截取时,为了保证得到正确的结果,只能取高位,而舍去低位数据,这样相当于降低了运算结果的精度。
只有当两个乘数均为所能表示的最小负数(最高位为1,其余位均为0)时,才有可能出现最高位与次高位不同的情况,也就是说,只有在这种情况下,才需要 M + N 位字长的数据来存放准确的最终结果。其他情况下,实际上均有两位相同的符号位,只需要 M + N -1位字长即可存放准确的运算结果。
表3-5 有效数据位截位与乘法运算结果的关系
续表
Vivado开发环境提供的乘法器IP核在选择输出数据位数时,如果选择全精度运算,则会自动生成 M + N 位字长的运算结果。在实际FPGA工程设计中,如果预先知道某位乘数不可能出现最小负值的情况,或者通过一些控制手段去除出现最小负值的情况,则完全可以只用 M + N -1位字长存放运算结果,从而节约一位寄存器资源。如果乘法运算只是系统的中间环节,则后续的每个运算步骤均可节约一位寄存器资源。
4.乘加运算中的有效数据位
前面讨论运算结果的有效数据位时,都是指参加运算的信号均是变量的情况。在数字信号处理中,通常会遇到乘加运算的情况,一个典型的例子是有限脉冲响应(Finite Impulse Response,FIR)滤波器的设计。当乘法系数是常量时,最终运算结果的有效数据位需要根据常量的大小来重新计算。
例如,需要设计一个FIR滤波器:
假设滤波器系数为13、-38、74、99、99、74、-38、13,如果输入数据为 N 比特的二进制数,则滤波器输出最少需要采用多少位来准确表示呢?显然,要保证运算结果不溢出,我们需要计算滤波器输出的最大值,并以此推算输出的有效数据位数。方法其实十分简单,只需要计算所有滤波器系数绝对值之和,再计算表示该绝对值之和所需的最小无符号二进制数位数 n ,则滤波器输出的有效数据位数为 N + n 。对于上面的实例,可知滤波器绝对值之和为448,至少需要9比特二进制数表示,因此 n =9。