前言
这是三月的第二篇博客,不得不说的是,有点儿怀念在实验室学习写博客的感觉(当然,并不怀念项目缠身,整日被催进度的噩梦日子),两个大屏幕,一台换了固态硬盘,因此还算流畅的台式电脑,简直不要太爽。平日里最讨厌用笔记本电脑,是因为觉得笔记本的键盘和鼠标很不人性化,这下好了,关键时期,笔记本成为了最依赖的东西,用笔记本打字也顺畅了,都是惯得。哈。当然最重要的还是学习氛围,在家是容易懒惰的,我宁愿打打游戏度日,也不愿坐在桌子前去思考一些复杂的问题,这是大脑惰性的选择。
好啦,这些日子也只能想想啦,也许这样的日子,哪怕一天也不会再有啦,我总是顾虑的,考虑到最坏的情况,疫情结束恐怕还是遥遥无期,毕竟学校不是一个随便开玩笑的地方,人口大量聚集,一起上课,一起吃饭,集体宿舍,这都给传染病提供了最好的传染途径,因此,疫情不结束,恐怕难以开学,更何况开学之后还有很多事情要处理,要么关乎毕业,要么。。。
进入正题,上篇博客是关于时域优化的内容, 回忆一下吧。
时域优化也就是对时序上的问题进行优化,例如我想更快的时钟去处理事件,而更快的时钟要求更精进的设计以及硬件条件要跟得上,假设硬件条件(FPGA)能跟得上,那么剩下的问题就是设计的问题了。
设计的问题,包括冗余的问题,这就需要对逻辑表达式进行化简,简洁之道,但是鉴于FPGA内部结构的特殊性,例如使用LUT进行组合逻辑的实现,因此,过于化简也未必就是好事,也许出现反常现象,这个具体看上篇博客吧。
其次进行时域优化的方法是结构调整,通过结构调整,可以使得某些设计或者某部分设计的关键路径变得更加的优化(更短),这就有利于时序。
再者就是分布调整,这个就很好理解,例如过长的组合逻辑(过于复杂的组合逻辑),分开实现等等,可以解决组合逻辑耗时过长的问题,有利于时序的优化。
就这样吧,更多的可以参考上篇博客。
本篇博客讨论的依然是时空变换问题,着重于空域优化,摘自《FPGA之道》,作者对于这方便的描述可谓是独到,让我们一起站在巨人的肩膀上,学习下作者对于此部分内容的见解。
时空变换之空域优化
本小节我们主要关注空域内部的优化方法。注意,在做空域优化时,往往是不以牺牲时域余量为前提的,因为它的目的主要是去除空域内部的一些冗余。接下来,将为大家介绍几类比较常用的空域优化方法:
逻辑化简
正如我们在【时空变换之时域优化->逻辑化简】小节中所讨论的,采用逻辑化简的方法不仅对提高时域方面的性能有好处,对提高空域性能方面也具有很好的效果,不过受制于FPGA芯片所基于的LUT结构以及编译器的优化功能,我们并不需要在此花费过多精力,仅需在我们的HDL代码中稍作注意,剔除明显且过分的组合逻辑冗余即可。具体的逻辑化简方法,可以参考【共同语言篇->数字逻辑电路基础知识->数字逻辑的化简】章节。
资源合并
资源合并是提高空域余量的另一个好方法,它的原理有点类似于数据压缩,即当FPGA设计中需要同时用到两个以上功能一样的模块时,想办法让其功能变为单个模块的N倍,但是资源的膨胀却小于单个模块的N倍,这样一来,便给空域腾出了可观的余量。注意,资源合并的方法并不是对任何情况都适用的,而且它还必须以资源本身有余为前提,即在当前条件下,资源并没有被充分使用。下面,我们就以FPGA内部的BRAM资源为例,来介绍一下具体的资源合并情况。
BRAM是FPGA内部一个比较灵活的资源,假设,某一型号FPGA芯片内部每一块BRAM的容量为8kbits,可以被配置成为1bit x 8k、2bits x 4k、4bits x 2k、8bits x 1k、16bits x 512 以及32bits x 256这几种可供选用的RAM形式。
如果我们需要缓存1路连续的数据,数据位宽均为8bits,缓存深度要求为512个采样,那么此时,我们可以选取8bits x 1k或16bits x 512这两种BRAM配置模式,来得到可以满足当前缓存要求的RAM。注意,此时无论选用上述两种中哪一种配置模式,BRAM资源都没有被充分使用——对于8bits x 1k模式来说,有一半的存储空间被闲置了;而对于16bits x 512模式来说,有一半的数据位宽被浪费了。
现在假设我们需要同时缓存两路这样的连续数据,那么大脑中的第一反应估计就是为2路数据都创建一个独立的缓存,而这样一来,就需要2块BRAM资源。每块BRAM资源的存储容量为8kbits,可是每路路数据仅需要缓存容量为4kbits,这也就是说,如果仅仅看存储容量的话,那么其实1块BRAM就足够应付这2路数据的缓存了。不过虽然FPGA内部的BRAM一般都是双口RAM,有两套独立的读、写及控制总线,但是高吞吐的数据缓存一般都是采用A写B读的操作模式(详见【本篇->编程思路->数据的存储->双口RAM的HDL描述与用法】小节),因此两套端口都被占用,所以即便是缓存第1路数据的BRAM中有多余的空间,由于它已经没有多余的读、写及控制端口供第2路数据使用,因此也不能贸然合并,这便是资源合并方法所受到的限制。不过如果这2路数据在此仅仅是需要一个缓存操作,并且对于缓存的读、写并没有特别不同于彼此的要求,那么我们其实可以将它们看做1路位宽为16bits的连续数据,这样便可以将BRAM配置成为16bits x 512的模式,来同时对它们进行缓存。这样一来,整个缓存电路的功能变为原来的两倍,但BRAM资源却仍然只需要1个,因此,总的资源膨胀率将会远小于2倍,为空域争取了客观的余量。
模块复用之分时复用
模块复用可分为两类:第一类是分时复用,它也是针对处理当FPGA设计中需要用到两个以上功能一样的模块时的空域优化需求,但分时复用与资源合并的方法有着本质不同,那就是分时复用针对的是不同时使用的多个功能相同模块的空域优化,而资源合并针对的是需要同时使用的多个功能相同模块的空域优化,这点是需要明确清楚地。模块复用的第二个类别是提速复用,这是以牺牲时域余量为前提的,因此将在后续的【时空变换之时间换空间】小节中予以介绍,而本小节则主要关注模块分时复用的方法。
模块分时复用的思路很简单,当一个模块的功能需要在多处使用时,如果这些使用不会同时发生,那么就可以考虑按照时间来分配该模块的使用权,从而达到该模块只需要被例化一次,但却可以为多个逻辑块提供服务。这就好比在家里看电视一样,晚上67点,动画片时间,小孩子最爱看;晚上78点,新闻时间,一路随着新中国走来的老一辈最爱看;晚上8~10点,各类肥皂剧以及综艺节目,工作压力较大的中青年人士最爱看。这样一来,虽然电视只有一台,但是各个年龄段人的需求都得到了满足,而分时复用也就是如此而已。注意,分时复用的方法同样也无法对任何情况都适用,而且它也必须以资源本身有余为前提,即在当前条件下,资源并没有被充分使用。下面我们仍以BRAM为例,来介绍一下具体的时分复用情况。
假设FPGA系统中有两个不会同时执行的任务——任务1和任务2,它们分别需要缓存第1路、第2路的数据,数据的宽度为8bits,缓存深度要求仍为512。与【资源合并】小节中的例子类似,在最开始的时候,我们通常会为每一个任务分配一个BRAM作为缓存,可以看出,此时资源仍然没有被充分利用,因为任务1执行的时候任务2所使用的BRAM处于闲置状态,反之,任务2执行的时候任务1所使用的BRAM也处于闲置状态。但与【资源合并】小节的例子又不同,本例中,任务1和任务2是不会同时执行的,因此我们在缓存第1路数据的时候无法同时也去缓存第2路数据,故难以应用资源合并的方法来优化空域。不过,如果任务1及任务2每次的执行都完成了本次所有数据的写入和读取操作,那么我们就可以让任务1和任务2来分时复用1个BRAM,从而在仅使用1个BRAM的条件下就完成了任务1和任务2的数据缓存需求,大大提高了空域的余量。但是,若任务1及任务2并不是每次执行都完成了本次所有数据的写入和读取操作,那么分时复用的方法就难以派上用场,这便是分时复用方法的制约。例如,现在共有四个任务,任务1、任务2分别负责将接收到的第1路、第2路数据写入缓存中,而任务3、任务4分别负责将缓存中的第1路、第2路数据读出。此时,即使这四个任务中,没有哪两个会同时执行,我们仍然不能使用分时复用的方式来优化空域,因为如果仍然想仅使用1个BRAM作为缓存的话,会给系统带来操作错误。例如,如果任务1执行完成之后,下一个触发的任务不是任务3,那么就会带来错误——如果下一个任务是任务2,那么BRAM中保存的第1路数据将会被覆盖,这样下次任务3执行的时候将无法读出正确的数据;如果下一个任务是任务4,那么由于BRAM中保存的是第1路数据,所以任务4读出的数据也是错误的。
类似的例子还有累加器的使用,如果串行发生的每个任务都能在它的执行过程结束后得到需要的累加结果,那么累加器就可以被多个任务复用;如果某个任务需要被多次调用后才能得到最终的累加结果,那么就必须为它单独分配一个累加器。
静态重构
FPGA芯片为什么需要配置?因为一旦掉电,FPGA芯片便变成一张白纸,不再具有任何有意义的逻辑功能,所以我们需要在每次系统上电的时候,采用在【知己知彼篇->FPGA芯片的配置方法】章节中介绍的那些FPGA芯片的配置方法,来对FPGA芯片进行配置,从而使得FPGA芯片具有我们预期的逻辑功能。
恰恰是FPGA芯片这种使用前需要配置的特征,可以被利用来进行空域优化,要想弄明白其中原因,我们需要先搞清楚一个问题,那就是对于一个被配置好的FPGA芯片,它将保持这配置好的功能到什么时候呢?答案有两个:一、系统掉电时,二、另外一次配置。没错,正是这利用这第二个答案,使得更加宏大的空域优化成为可能,这便是FPGA芯片的静态重构技术。
静态重构其实是更加宏观一些的分时复用,只不过之前介绍的分时复用指的是FPGA设计内部某一个模块或者某一个资源的分时复用,而静态重构,指的是整个FPGA芯片的分时复用。
静态重构技术的具体操作方式,其实就是提前针对多种不同的需求编译出多个不同的FPGA配置文件,然后在系统运行时,根据每个时段的不同需求,利用了FPGA芯片的被动配置模式或者JTAG配置模式,选择性的将具有对应功能的配置文件下载到FPGA芯片当中并开始工作。由于静态重构技术其实就是分时复用的宏观扩展,因此它的应用也受到相同的限制,不过对于那些适合应用静态重构技术的情况,其对空域方面的优化那是非常非常可观的!下面,我们就通过一个例子来对静态重构技术进行更为形象的了解:
假设你现在需要在FPGA上实现一个通用的视频编码器,即能够根据需求将输入的原始图像序列压缩为当前主流的mpeg2、mpeg4或h264等视频流并发送出去。在视频压缩领域中,往往编码要远比解码复杂,因此,我们先假设实现一个基本的mpeg2编码器需要相当于400万门的等效逻辑资源、实现一个基本的mpeg4编码器需要相当于700万门的等效逻辑资源、实现一个基本的h264编码器需要相当于600万门的等效逻辑资源。如果你现在是在做一个产品,那么你需要考虑成本,因此你做了市场调研,查到了如下两个型号的FPGA芯片的价格:
FPGA_CHIP_A —— 1000万门、40美元;
FPGA_CHIP_B —— 2000万门、100美元;
请问你该选择哪款芯片来开发你的产品呢?
如果你的方案是在FPGA内部同时实现mpeg2、mpeg4及h264这三个编码器,然后根据实际情况每次使能其中一个作为当前工作的视频压缩引擎,那么你需要选择FPGA_CHIP_B,这样每一个产品上的FPGA相关成本为100美元。
如果你在设计方案的时候参考了静态重构技术,那么你的方案可能是这样的:针对mpeg2、mpeg4及h264各建立一个单独的FPGA工程,并分别编译出三个单独的配置文件分别对应mpeg2、mpeg4及h264编码功能,然后根据实际情况,每次载入一个配置文件到当前的FPGA芯片中以实现相应的视频压缩功能。此时,你就可以选择FPGA_CHIP_A,如果存储多个配置文件需要花费10美元购买相应的存储芯片或微处理器,那么现在每一个产品上的FPGA相关成本仅为50美元,比上一方案节省了50美元,由此可见静态重构技术对空域方面的贡献以及其伟大之处。
动态重构
还有什么技术比静态重构更激动人心的吗?有!那就是动态重构!
动态重构仍然是分时复用技术的延伸,不过相比于静态重构,它在某些方面有着不可比拟的优越性:
首先,静态重构是整个FPGA芯片的重构,哪怕两个设计之间仅仅有一点不同,也必须对FPGA芯片进行大换血;而动态重构则可以针对FPGA设计中的某一个小部分进行重新配置,同时保持其他部分的状态不变。
其次,静态重构的过程中,整个FPGA芯片处于配置状态,此时所有I/O端口均处于输出或者高阻状态,因此不能进行工作;而动态重构仅影响FPGA芯片中的一部分逻辑,因此其他逻辑部分在动态重构过程中仍可以进行正常工作。
第三,静态重构一般耗时较长,这一点,容量越大的FPGA芯片感觉就越明显,有时候配置一次恐怕要用到几秒或十几秒;而动态重构仅针对性的修改某一小部分逻辑,并且可以选择更加快捷的下载机制,因此配置过程耗时短。
最后,并不是所有的FPGA芯片都支持静态重构的,例如基于反熔丝的FPGA芯片,但是我们有办法让所有的FPGA芯片都支持动态重构。
列举了这么多的优点,那到底什么是动态重构技术呢?其实动态重构就是一种在FPGA运行的过程中,通过某种方法动态的改变其某一部分电路所对应逻辑功能的技术。如果你对本书开始的内容还有印象,可以发现这好像与【共同语言篇->硬件描述语言->软件编程思路与FPGA编程思路的区别->执行方式对比->资源占用与释放】小节中的介绍的内容相冲突,因为在那一小节中,我们明确的阐述了FPGA编程中不存在对资源动态的申请与释放,那么动态重构又是怎么在FPGA芯片中实现的呢?
其实在【本篇->编程思路->状态机,FPGA的灵魂->状态机的实现方式->基于RAM的实现方式浅析】小节中,我们已经引出了动态重构的一些概念和应用,大家可以回顾一下。还记得组合逻辑是如何在FPGA芯片中实现的吧?没错,这是查找表(LUT)的功劳。LUT就像是一张空白的真值表,你填成什么样子,它就具有什么样的功能。那么LUT是什么?LUT其实就是数据存储啊!这也是为什么我们在【本篇->编程思路->数据的存储】章节中将一个看似不起眼的数据存储功能说的如此神乎其神,好像离了它FPGA芯片就不运行一样,因为事实真的就是如此。FPGA的配置过程,其实就是将配置文件的内容写入FPGA芯片的过程,这实际上就是一个数据存储的过程。但是,显然在FPGA运行的时候,我们无法重复这一数据存储的过程,因为除了静态重构方法以外,我们几乎没有方法去修改FPGA芯片所基于的那个特殊的SRAM中的数据内容(不过随着技术的进步,相信FPGA的集成开发环境会逐渐支持对该SRAM的非配置修改的)。不过在FPGA运行的时候,倒是给我们提供了不少可供使用的数据存储资源,例如触发器、查找表、BRAM甚至片外存储芯片等。因此,与LUT可呈现出多种不同功能一样,任何数据存储都可以被看做一张真值表,只要能够动态的修改数据存储中的内容,就相当于动态的修改了它的逻辑功能。数据存储中的内容可以被动态修改么?当然,因为FPGA中的数据存储载体都可以以RAM的形式出现。综上所述,动态重构技术就是利用RAM作为逻辑功能的载体,通过运行时修改RAM中的内容,来到达逻辑功能的动态改变,因此对空域性能有着很好的提升。下面我们仍以BRAM为例,来介绍一下具体的动态重构技术的使用:
仍以一个8kbits的BRAM为例,若我们将其配置为8bits x 1k的模式,那么其地址总线addr应该为10bits,数据总线data应该为8bits。
如果我们需要实现一个乘法功能Y = AxB,若A、B均为4bits位宽,那么其结果用一个8bits的数便可以完全表示。此时,我们将该BRAM的地址总线连接到““00” + A + B”(此处+号为连接符),然后动态的修改BRAM中地址0~255的内容如下即可:
0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、
0、1、2、3、4、5、6、7、8、9、10、11、12、13、14、15、
0、2、4、6、8、10、12、14、16、18、……
0、3、6、9、12、……
……
0、15、30、45、60、……225。
如果接下来又要实现一个除法的功能Y = A / B,若A为8bits、B为2bits,那么其结果用一个8bits的数便可以完全表示。此时,我们将该BRAM的地址总线连接到“A + B”(此处+号为连接符),然后动态的修改BRAM中地址0~1023的内容如下即可:
*、0、0、0、(*号表示不关心,因为被除数不应该为0)
*、1、0、0、(商的整数结果采用去尾法得到)
*、2、1、0、
*、3、1、1、
*、4、2、1、
*、5、2、1、
……
*、255、127、85。
如果接下来又想实现一个波形发生器,产生一个方波,那么则可以使用一个10bits计数器的计数值连接到BRAM的地址总线,然后动态的修改BRAM中地址0~1023的内容类似如下即可:
0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、
80、80、80、80、80、80、80、80、80、80、80、80、80、80、80、80、
0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、0、
80、80、80、80、80、80、80、80、80、80、80、80、80、80、80、80、
……
如此这般,只要修改一次BRAM中的存储内容,就完成了一次动态重构操作,只要不断的修改,就可以仅仅利用一块BRAM实现出成千上万种行为迥异的逻辑,这是一项多么令人激动的技术啊!
思路转换
思路转换同样也是提高空域性能的一个好方法,关于它的具体讨论,可以参照【时空变换之时域优化->思路转换】小节。
来源:CSDN
作者:Reborn Lee
链接:https://blog.csdn.net/Reborn_Lee/article/details/104629419