《FPGA编程三大范例.docx》由会员分享,可在线阅读,更多相关《FPGA编程三大范例.docx(11页珍藏版)》请在第一文库网上搜索。
1、FPGA编程三大范例虽然FPGA可使用Yeri1og或VHD1等低层次硬件描述语言(HD1)来缄程,但现在己有多种高层次综合(H1S)工具可以采用以C/C+之类的更高层次的语言编写的算法描述,并将其转换为Veri1og或VHD1等低层次的硬件描述语言。随后,下游工具即可对转换后的语言进行处理,以便对FPGA器件进行编程。此类流程的主要优势在于,您可使用诸如C/C+等编程语言来编写高效代码,而后将代码转换为硬件,但这类编程语言的优势仍能得以完整保留。此外,写好代码乃是软件设计师的专长,比学习新的硬件描述语言更简单。以C/C+编写的程序本质上是专为冯诺依曼样式的架构编写的,此类架构中用户程序内的每
2、条指令都是按顺序执行的。为了实现高性能,H1S工具必须推断顺序代码中的并行性,并利用它来实现更高的性能。要解决这个问题可并不简单。此外,优秀的软件遢遗按明确定义的规则和实践来编写程序,例如,RTTK递归和动态存储器分配。其中诸多技巧在硬件中都无法找到直接等效的对象,故而给H1S工具带来了诸多挑战。这也意味着任意现成软件都无法高效转换为硬件。最低限度,需检验此类软件中是否存在不可综合的构造,并需要重构代码,使其可综合。现如今,即使软件程序可自动转换(或综合)为硬件,但要实现可接受的结果质量(QoR),仍需要额外工作(例如,重写软件)以帮助H1S工具实现期望的性能目标。为此,您需要了解正确编写软件
3、的最佳实践,以确保在FPGA器件上正常执行软件。在接下来的几个章节内,将着重探讨如何首先识别部分宏观级别架构最优化以明确程序结构,然后聚焦更细化的微观级别架构最优化来实现性能目标。生产者使用者范例请考虑软件设计师编写多线程程序的方式,通常有一个主线程用于执行某些初始化步骤,随后分叉为多个子线程用于执行某些并行计算,当所有并行计算都完成后,主线程会整理结果并写入输出。程序员必须理清哪些部分可以分叉以供并行计算,哪些部分需要按顺序执行。这种分叉/连接类型的并行化操作不仅适用于CPU,也适用于FPGA,但FPGA上的吞吐量的关键模式之一是生产者使用者范例。您需要将生产者使用者范例应用于顺序程序,并将
4、其转换为可并行执行的抽取功能以便提升性能。您可借助一条简单的问题语句的帮助来更好地理解这个分解进程。假定您有一份数据手册,可供我们将其中的项导入列表。随后,您将对列表中的每个项进行处理。处理完每个项需耗时约2秒。处理完后,您将把结果写入另一份数据手册,此操作将耗时约每项各1秒。因此,如果输入Exce1工作表中有总计100个项,那么将耗时总计300秒来生成输出。这样做的目的是对此问题进行分解,以便您识别能够并行执行的任务,从而提升系统吞吐量。图1.程序工作流程处理数据X25607-030622第一步是了解程序工作流程,识别独立的任务或函数。整个工作流程分4个步骤,类似上图所示的程序工作流程(无重
5、叠)。在此示例中,“写入输出”(步骤3)任务独立于“处理数据”(步骤2)处理任务。虽然步骤3取决于步骤2的输出,但当步骤2中的任意项完成处理后,您即可立即将其写入输出文件。您无需等待所有数据都完成处理后再开始将数据写入输出文件。此类型的交织/重叠任务执行方式是非常常见的原则。如上图所示(例如:含重叠的程序工作流程)。如图所示,含重叠的工作流程比不含重叠时更快完成。现在,您可将步骤2视作为生产者,将步骤3视作为使用者。生产者使用者模式对于CPU性能的影响有限。您可交织执行每个线程的步骤,但这需要谨慎分析,以便充分利用底层多线程和11高速缓存架构,因此较为耗时。但在FPGA上,由于可采用定制架构,
6、生产者和使用者线程可以同时执行,开销极低甚至没有,因此能够显著提升吞吐量。首先考量的最简单案例是单一生产者和单一使用者通过大小有限的缓冲器来进行通信。如果缓冲器已满,那么生产者可以选择阻塞/停滞或者丢弃数据。当使用者从缓冲器中移除某个项后,它会通知生产者,生产者随后开始再次填充缓冲器。如果使用者发现缓冲器己空,则可以同样方式停滞。当生产者将数据置入缓冲器后,它会唤醒休眠中的使用者。该解决方案可通过进程间通信(通常使用监控器或信号量)来实现。不充分的解决方案可能导致死锁,即两个进程都停滞并等待唤醒。但在单一生产者和使用者的情况下,通信模式与先入先出(FIFO)或乒乓缓冲器(出PO)实现之间存在强
7、映射关系。这种类型的通道无需依赖信号量、互斥体或监控器来进行数据传输,即可提供高效的数据通信。使用此类锁定原语对于性能可能开销较大,并且难以使用和调试。PIPO和FIFO是常用的选择,因为可以避免端到端的原子同步需求。在此类宏观级别架构最优化中,可通过缓冲器封装通信,使程序员可免于担心存储器模型和其它非确定性行为(如争用条件等)。在此类设计中可达成的网组类型为纯粹的“数据流网络”,可在输入侧接受串流数据,对此数据串流执行一些基本处理,然后将其作为数据串流发出。并行程序的复杂性完全被抽离。请注意,“导入数据(步骤1)和“导出数据(步骤4)同样在最大程度提升可用的并行性方面扮演着相应的角色。为了使
8、计算能够与I/O成功重叠,重要的是第一步对输入的读取结果进行封装,最后一步则是写入输出。这样即可实现I/O与计算的最大程度重叠。在计算步骤中间读取或写入输入/输出皿将会限制设计的可用并发性。这同样是在对设计工作流程进行设计时需要牢记的。最后,此类“数据流网络”的性能依赖于设计师能够持续向网络馈送数据,使数据能够在系统中保持串流。数据流中出现中断可能导致性能下降。视频串流应用就是一个很好的例子,比如在线游戏中,实时高清(HD)视频持续流经系统,帧处理率受到持续监控,以确保满足期望的结果质量。游戏玩家可以在屏幕上立即观察到帧处理率下降。假想一下,这能为一大群游戏玩家提供持续性帧率支持,同时功耗相比
9、传统CPU或GPU架构显著降低-这就是硬件加速的魅力。使数据在生产者与使用者之间持续保持流动至关重要。下一步,您将深入了解本节中介绍的这种串流范例。串流数据范例串流是一种重要的抽象:它表示无限制的连续更新数据集,其中“无限制”表示“大小未知或者大小无限”。串流可以是一连串数据(标量或缓冲器)在源(生产者)进程与目标(使用者)进程之间单向流动。串流范例会强制您根据数据访问模式(或序列)来思考。在软件中,随机存储器对数据的访问几乎是免费的(忽略高速缓存成本),但在硬件中,执行顺序访问实际上是很有利的,此类访问可转换为串流。将算法分解为生产者使用者关系并通过网络串流数据来进行通信具有如下所述几大优势
10、。它允许程序员以顺序方式定义算法,并通过其它方式来提取并行度(例如,通过编译器)。诸如任务间同步等复杂性会被抽离。它允许生产者和使用者任务同时处理数据,这是提升吞吐量的关键。另一个优势是代码更清洁且更简单。如前文所述,对于生产者和使用者范例,数据传输模式与FIFO或PIPO缓冲器实现之间存在强映射关系。FIFO缓冲器只是预定义大小/深度的队列,其中插入队列的首个元素也会成为可从队列跳出的首个元素。使用FIFO缓冲器的主要优势在于,只要生产者将数据插入缓冲器,使用者进程就可以立即在FIFO缓冲器内部开始访问数据。使用FIFO缓冲器的唯一问题在于,由于生产者与使用者之间的生产/使用速率不同,可能导
11、致FIFO缓冲器大小错误,从而导致死锁。在具有多个生产者和使用者的设计中,这种情况较为常见。乒乓缓冲器属于用于加速进程的双缓冲器,可将I/O操作与数据处理操作重叠。其中一个缓冲器用于保存数据块,以便使用者进程能够看到完整(旧)版本的数据,而另一个缓冲器中,生产者进程则正在创建新(部分)版本的数据。当新的数据块完成并有效时,使用者和生产者进程将交换对两个缓冲器的访问。由此导致,使用乒乓缓冲器会增加器件的整体吞吐量,并帮助防止出现最终瓶颈。PIPO的主要优势在于,工具能够将生产速率与使用速率自动匹配,并创建高性能且无死锁的通信通道。此处值得注意的是,无论使用的是FIFO还是PIPO,关键特性是相同
12、的:生产者将数据块发送或者串流至使用者。数据块可以是单个值,也可以是一组N个值。块越大,所需存储器资源越多。以下是简单的求和应用,用于展示经典的串流网络/数据流网络。在此例中,该应用的目标是成对添加随机数值串流,然后打印这些数值。前两个任务(任务1和2)提供了随机数值串流以供添加。这些数值通过FIFO通道发送到求和任务(任务3),任务3会从FIFO通道读取值。随后,求和任务将输出发送到打印任务(任务4),以发布结果。FIFO通道可在这些独立的执行线程之间提供异步缓冲。图2.串流网络/数据流网络X2S608(BO622连接每项“任务”的串流通常是作为FIFO队列来实现的。FIFO能够抽离程序员的
13、并行行为,使其专注于推理任务活动(调度)的“快照”时间。FIFO能够使并行化更易于实现。这主要得益于它减少了程序员实现并行化框架或容错解决方案时,必须应付的可变空间。独立内核之间的FIFO展现出经典的排队行为。对于纯串流系统,可使用排队或网络流模型来对此行为进行建模。这种数据流类型网络和串流最优化的另一个主要优势在于它可按不同粒度级别来应用。程序员可以在每项任务内部设计此类网络,也可以为任务或内核系统设计此类网络。实际上,您可以通过串流网络来以分层方式例化并连接多个串流网络或任务。支持更细粒度的并行化的另一项最优化措施是流水打拍。流水线范例流水线是您日常生活中常用的概念。造车厂生产线就是一个典
14、型的例子,其中每一项具体任务通常都是由一个独立且唯一的工作站来完成的,如安装引擎、安装车门和安装车轮。各工作站各自对一辆不同的车并行执行自己的任务。当某一辆车执行完某一项任务后,它就会移至下一个工作站。完成各项任务的时间差可通过“缓冲”(将一辆或多辆车暂存在各工作站之间的空间内)和/或“停滞”(暂时中止上游工作站的操作)来加以调整,直至下一个工作站变为可用为止。假设组装一辆车需要执行3项任务A、B和C,这3项任务分别需要20、10和30分钟。那么,如果全部3项任务均由单个工作站来执行,工厂每60分钟才能输出一辆车。通过使用3个工作站组成的流水线,该工厂60分钟即可输出第一辆车,随后每30分钟再
15、输出一辆新车。正如此示例所示,流水线并不会降低时延,即单个项穿越整个系统的总时间。但它会增加系统吞吐量,即,第一个项完成后处理新的项的速率。由于流水线的吞吐量不可能优于其最慢的元素,因此程序员应尝试在各阶段间拆分工作和资源,以使各阶段耗用相同时间来完成自己的任务。在上述车辆组装线示例中,如果3项任务A、B和C各自耗时20分钟,而不是分别耗时20、10和30分钟,那么时延将仍为60分钟,但每隔20分钟(而不是30分钟)即可完成一辆新车。下图显示了承担制造3辆车任务的假想生产线示例。假定任务A、B和C各耗时20分钟,那么顺序生产线将需要180分钟才能生产3辆车。而流水线式生产线只需100分钟即可生
16、产3辆车。生产第一辆车所耗费的时间为60分钟,称为流水线的迭代时延。生产完第一辆车后,后两辆车各自只需20分钟,这称为流水线的启动时间间隔(H)。生产三辆车所耗费的总时间为100分钟,称为流水线的总时延,即,总时延二迭代时延+II*(项数-1)。因此,改善II即可改善总时延,但不影响迭代时延。从程序员视角来看,流水线范例可适用于设计中的函数和循环。确定初始建立时间成本后,要实现理想吞吐量目标,应为1,即初始建立时间延迟过后,在流水线的每个周期都将有输出可用。在以上示例中,初始建立时间延迟60分钟过后,每隔20分钟就有一辆车可用。图3.流水打拍Sequentia11atency=BeforePipe1iningAfterPipe1ining流水打拍是经典的微观级别架构最优化,可应用于多个抽象层。在前文中,我们通过生产者使用者范例解释了任务级别流水打拍。这一-概念同样适用于指令级别。这实际上是使生产者使用者流水线(和串流)保持填满并繁