首页 理论教育 FPGA系统设计:函数实现全加器

FPGA系统设计:函数实现全加器

时间:2023-10-20 理论教育 版权反馈
【摘要】:图6.3用函数实现全加器的综合结果语句1调用了函数,函数有3个输入,a、b和cin,这3个输入是在模块里定义并在语句1进入函数并被其调用的。如果没有填[1:0],则是说这个函数的返回值默认是1位的二进制数。假设计数了6个1,是偶数,则要添加的偶校验位为0,模2计算的结果就为0。任务可以包含延迟和事件;函数不能有延迟和事件控制。例6.70:一个函数返回多个值。

FPGA系统设计:函数实现全加器

函数和任务很相识。使用函数的目的也是作为一段可重复使用的代码,可以在模块执行的过程中使用。我们先看一个例子了解一下函数的样子。

例6.66:用函数实现全加器

图6.3是综合以后的电路。关键的一点是,函数是可以综合的。要指出的是,Synplify综合图的有些连线的点是没有显示的。软件自身能识别哪些交叉点是连的。为了防止读者混淆,著者在Synplify综合图的有些地方添加了交叉点的黑点连接。

图6.3 用函数实现全加器的综合结果

语句1调用了函数,函数有3个输入,a、b和cin,这3个输入是在模块里定义并在语句1进入函数并被其调用的。

语句2是函数内部定义的输入。语句2定义输入的顺序和语句1的函数调用括号里的外部输入顺序一一对应。

语句3执行后的结果,赋值给func_add。而func_add就是函数名,可以看到,函数名其实就是起着函数返回值的作用。

我们把上面这段函数提取出来写到下面,来描述一个函数的基本结构。

首先,函数一定要有关键字function。

function后面要写上函数的名字。函数的名字一般就作为函数的返回值。函数的定义还可以有更加复杂的格式。比如我们改写语句4如下:

其中的automatic就是关键字,使用这个关键字的含义和任务的情形类似。automatic是根据具体需要可填可不填的。signed就是说这个函数的返回值是个有符号数。signed也是根据具体需要可填可不填的。函数的返回值类型可以使用别的关键字,比如real、integer和time等。[1:0]就是说该函数的返回值是个2位的二进制数。如果没有填[1:0],则是说这个函数的返回值默认是1位的二进制数。

然后就是语句5输入的定义。

再后面就是语句6函数的执行过程。

读者要思考一下,比如像语句6的右边这么一大堆复杂的计算,其结果赋值给左边。那么左边的,自然是函数的名字,相当于是个变量,它一定要有存数的功能。因此,对于语句4,当我们在声明一个函数func_add的时候,我们实际上是已经隐性的声明了一个同名的寄存器:

这样的话,函数执行到语句6,自然就把语句6右边的计算结果赋值到这个寄存器,然后返回给调用函数的地方,进行其他的计算。

从函数的这些架构可以看到,Verilog不是空中楼阁,而是一套实用的技术,它要涉及方方面面的细节。建立这套语言的时候,创立者就要考虑到语法结构的可实现性。

函数的特点是:

(1)函数只能有一个返回值。

(2)函数的代码里不能包含延迟。不能包含延迟的意思,指的是函数必须立刻执行。

(3)函数不能调用其他的任务。

(4)必须给函数至少一个输入。

(5)函数可以调用其他函数。

(6)函数不能有output定义,不能有inout定义。这是与任务不一样的地方。

(7)函数的调用不能单独作为一条语句。可以把函数(近似的)看作一个返回的输出变量,很明显,一个变量是不能单独的作为一条语句的。函数只能作为一个操作数出现在调用语句内。

(8)函数调用既可以出现在过程块里,也可以出现在连续赋值语句里。

看下面例句:

这个例句里,函数调用就出现在连续赋值语句里,getbyte函数返回值相当于是个寄存器变量,赋值给线网类型a1,相当于对线网类型变量进行驱动。

例6.67:

该函数是个for循环,从m=0开始,只要m小于4,每次执行完for语句,m自己加1。

例6.68:

有的时候发送端有一个8位的二进制数要传送出去,发送端要在这8位二进制数末尾添加一个校验位。假设事先约定好是偶检验。也就是说,若这八位二进制数里1的个数是偶数,则添加的这位偶校验位就该是0;若这八位二进制数里1的个数是奇数,则添加的这位偶校验位就该是1。由此确保添加了偶校验位以后,总的1的个数为偶数,这就是偶校验。

该函数取的函数名为even_parity_check_bit,顾名思义就是偶校验位。首先定义了输入的8位二进制数first_8_bit。定义了一个寄存器how_many_1_in_first_8_bit用来保存1的个数。定义了一个内部整数n用于for循环。然后进入for循环,在if条件判断里,挨个计算first_8_bit的每一位是否为1,为1则寄存器how_many_1_in_first_8_bit开始计数。最后用模2运算来计算偶校验位even_parity_check_bit。假设计数了6个1,是偶数,则要添加的偶校验位为0,模2计算的结果就为0。

读者可以试着改写该函数,计算奇校验位。

例6.69:阶乘的调用。

这个例子在综合的时候会提示错误信息,错误信息指出:For statement is only supported when the stop test condition is a comparison bet ween the loop variable and a constant.

综合不通过的原因在于,for循环语句里的oper应该使用常量。原因很简单,对于FPGA芯片来说,综合植入实际上就是把需要连接的与或非门在芯片上连起来,不需要连接的门就不连。这就意味着,综合通过后植入到芯片之后,芯片内部的连接就暂时定了。如果你这次调用阶乘是5阶的,下一次调用的阶乘是50阶的,很明显,这两个阶乘的电路肯定大不一样。不是常数阶,在综合时就没法确定电路形式,也就无法综合成功。

下面,我们对任务和函数的区别做一个比较。(www.xing528.com)

(1)任务的调用类似于模块里的实例调用,任务的调用不能用在持续赋值语句里;函数名实际上就是函数的返回值,函数可以在过程语句和持续赋值语句里调用。

(2)任务可以有任意个数的输入和输出;函数只能有一个输入,不能有输出和inout类型。

(3)任务可以包含延迟和事件;函数不能有延迟和事件控制。

(4)任务可以调用其他的任务和函数;函数可以调用其他函数,函数不能调用任务。

(5)任务本身就类似于实例调用,而实例调用的是通过调用的端口列表一一对应来沟通输入和输出的,所以任务没有返回值;函数以函数名作为返回值。

例6.70:一个函数返回多个值。

我们在前面介绍过函数没有输出,函数名就是函数的返回值,那么怎么通过函数调用实现多输出呢?上面这个例子介绍了一种方法。我们考虑到要采用连续赋值语句里使用函数返回值,于是将几个输出声明为线网类型。同时把几个输出进行拼接。

上面这个函数计算了两个输入in1和in2的与,结果作为第一个输出;两个输入in1和in2的或,结果作为第二个输出。两个输出的拼接赋值给函数名作为返回值。通过把函数名的返回值进行assign连续赋值来实现返回多个值。

图6.4是综合以后的结果。

图6.4 一个函数返回多个值的综合结果

例6.71:设计一个乘加器。

在很多的科学计算和工程应用的场合中,乘加操作是最基本的一种操作。所谓乘加操作指的是先计算两个数的乘法,然后将乘法运算的结果与另外一个操作数相加得到最终结果。在一些特殊的场合,比如数字信号处理器,乘加操作是最频繁的应用。数字信号处理领域,经常会涉及滤波器、FFT、卷积及各种矢量运算,这些运算的形式都类似于Σb(n)*x(n-k)。这类运算的乘法和加法总是一起出现。因此最好是能将乘法器和加法器相结合,在一个时钟周期就完成一次乘、加运算,同时累加乘法运算的结果。这样的运算单元称为乘法累加器。所以在尤其是数字信号处理这种场合,会自然而然的倾向于把乘加操做成硬件,以节省整个乘加操作的执行延迟。由此就出现了叫作乘加器的硬件。乘加器可以极大的减少乘加操作的耗费时间。

下面这个例子我们就来设计一个乘加器模块。

对于乘法器来说,硬件实现乘法器的方法就是挨个读取乘数的每一位。乘数的哪一位为1,则把被乘数左移相应的位数。左移一位就是乘以2。把每次左移的结果累加,就是乘法的结果。

图6.5是该程序的电路结构图。

图6.5 乘加器综合结果

例6.72:用function编写数字通信的3B4B码表

数字通信领域,存在着一种叫作mBnB的编码方式。待传送出去的信号有时候连着的“0”或者连着的“1”很多,接收端提取时钟时,又不希望接收的信号连“0”和连“1”太多,于是就出现了这种编码方式。mBnB码是把输入的二进制原始码流按照m个bit一组断开进行分组。把m个bit转换成事先约定好的n个bit一组的信号。这就是mBnB码。最通常用的有1B2B、3B4B、5B6B、8B9B、17B18B等等。其中1B2B也叫作曼彻斯特码。

表6.10 3B4B码表

表6.10就是3B4B码表。左边是3B码,有8种可能性。右边是4B码表,从16种可能性里选出了8种。比如说转换前的3B码是“111”,则转换为“0110”,再发送出去。

对于使用function来编写3B4B码表,我们可以考虑在function里使用case语句。

图6.6是仿真的波形。

图6.6 3B4B码表仿真波形

左边这一列是输入和输出端口。TX_FILE和TX_ERROR是ISE自带仿真器在做仿真时,系统自己生成的用于诊断的端口,我们不用管它们。

重点看in和out这两行。in这一行,我们依次输入了0-1-2-3-4-5-6-7,相应的函数调用运行后,out输出9-8-11-10-5-4-7-6。根据上面的码表,可知道我们编写的程序是正确的。上面这个仿真图显示的数字都是十进制。左边有个加号,点开加号以后,就可以看到二进制的情况了。如图6.7所示:

图6.7 3B4B码表仿真波形

我们在testbench新建fixture文件做上面这个模块的仿真时,可以把左边的加号点开然后用鼠标点击in信号的上下来输入高电平或低电平。也可以对in信号以十进制的形式手动输入激励。十进制输入会相对易于理解和容易输入。下面简单做一下如何以十进制的形式进行输入。如图6.8所示,先打开testbench里新建立的fixture仿真测试文件。等待用户输入激励信号的fixture仿真测试文件背景是白色的。在该文件的in信号的需要输入的地方右键单击,在弹出的菜单里选择“Set Value”,单击它。

图6.8 3B4B码表仿真调试

然后会弹出如图6.9所示的“Set Value”对话框。在对话框里输入想输入的十进制值,比如“1”,点击“OK”。

图6.9 3B4B码表仿真调试

fixture文件就会有变化,已经把输入“1”添加到in信号里了。如图6.10所示。

图6.10 3B4B码表仿真调试

保存fixture仿真测试文件。点击ISE集成开发环境左上角的该仿真测试文件,选中它(选中某个文件时,该文件会变蓝)。运行该文件。图6.11是仿真测试文件运行后的结果。可以看到给in信号的新输入“1”,已经导致输出信号out有了反应,out相应的输出了“8”。我们查看3B4B码表发现,3B输入“001”时,4B输出“1000”,“1000”确实是十进制的8。

图6.11 3B4B码表仿真调试

以上就是手动输入十进制数据的基本过程。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈