赞
踩
在这篇博客里笔者为大家简单梳理下,在FPGA工程当中经常用到几个经典的设计思想,通过了解学习这些基本设计思想并灵活应用,对后期的理解代码、实际编程、模块划分、定位错误、分析问题将会起到比较大的帮助。
跨时钟域的数据同步是FPGA设计当中的经常遇到的问题,也是一个难点和重点,很多工程当中实际测试存在不稳定性,更多时候是因为跨时钟域的数据接口部分没有处理好,对于一个FPGA项目工程来说,优秀的时序逻辑是通过设计出来的,而不是迭代仿真反复修改出来的,更不是人为生改硬凑上的。
在实际项目当中,有些FPGA工程师喜欢手工加BUFT调整数据延时,还有喜欢迭代仿真后,根据仿真结果手动再把一些信号打了几拍后使用,尤其在本模块和上游模块使用异步时钟的时候,想要保证两个模块数据之间足够的建立时间和保持时间,其实这些做法都是非常不可取的,一般对于数据的延迟不规律或者不可测以及跨时钟域数据交互的情况,不同模块之间就需要建立一些同步机制,常用的包括:1.使用数据同步使能指示信号;2.数据通过片内RAM和FIFO存储读取;3.使用片外的sdram/ddr3存储器颗粒实现数据交互同步,下面我们围绕这三种方法逐一为大家展开说明。RAM和FIFO是FPGA设计当中非常重要的两个IP核,在后面的章节里我们会和大家一起在Xilinx的Vivado开发环境下编写测试文件代入Modelsim里仿真实际去观察RAM和FIFO两个IP核的行为,也把实际项目工程中的使用经验分享给大家。大家可以先把RAM简单理解成C语言当中的数组,而把FIFO理解成C语言当中的队列,虽然这样说肯定是不太严谨的,但是RAM和FIFO在FPGA当中实际功能和C语言中的数组、队列有异曲同工的作用。
这种方法用起来非常简单高效,如下图1所示,是实际项目工程上应用很多的MCU和FPGA通过SPI总线通信,两个SOC芯片通过SPI总线连接基本可以满足绝大多数的数据传输需求,其原理就是MCU作为SPI总线的主机,FPGA作为SPI总线的从机,由MCU产生SPI_CLK作为总线的时钟,在数据交互的时候拉低片选SPI_CS,通过SPI_MOSI、SPI_MISO和FPGA交互数据,当然因为SPI总线是全双工的,FPGA既可以通过SPI_MOSI总线向MCU读取数据,也可以通过SPI_MISO总线向MCU写入数据。
图1 MCU和FPGA通过SPI总线读写数据
对于FPGA端,FPGA作为从机一直捕获SPI_CLK,通常SPI总线的极性CPOL和相位CPHA均会被设置成1,有关FPGA做主机和从机的SPI总线设计在开发板的24例程中,我们会详细展开,所以这里就不做赘述了。FPGA通过快时钟边沿采样SPI_CLK,得到SPI_CLK的上升沿作为读取总线上数据的读使能mcu_read_en,然后依次读取SPI_MOSI总线上的数据,同样的得到SPI_CLK的下降沿作为写入总线上数据的写使能mcu_write_en,然后依次写入SPI_MISO总线上的数据。但是这种方法在处理跨时钟域的数据同步的时候,只能用在FPGA主时钟快于外部时钟的时候,原因很简单因为只有当FPGA主时钟比外部时钟快的时候,才可能通过边沿采样原理得到外部时钟的上升沿或下降沿,然后去做后续的时序逻辑设计。比如,在这里通过SPI总线通信,由MCU做主机由FPGA做从机,MCU的时钟相对于FPGA来说就属于低频域。
这种方法适用于大多数情况下的跨时钟域的数据同步,FPGA内部的片内RAM 和FIFO都可以通过IP核配置成异步时钟读写,同时读操作和写操作也都是相互独立的。下图2是异步时钟FIFO和双口RAM的IP核配置,FIFO即First In First Out的缩写,顾名思义就是类似队列一样,先进先出、顺序读写,使用异步时钟FIFO实现跨时钟域数据的交互更方便,不同于前面一种方法,这种方法可以实现低频域到高频域、高频域到低频域之间的数据交互,使用异步时钟FIFO作为模块与模块之间的数据同步是FPGA设计当中经常会使用到的方法。
相比于异步时钟FIFO而言,异步时钟双口RAM,使用起来逻辑设计上相对复杂些,但是双口RAM可以实现对不同地址的读写操作,并且读操作和写操作也都是相互独立,而不是FIFO只能先进先出、顺序读写,一般来说,异步时钟FIFO已经可以实现大多数模块与模块之间的数据同步,但是有些应用场景上,使用双口RAM最佳,不方便使用FIFO作为模块间的数据同步缓存。
图2 异步时钟FIFO和双口RAM的IP核
因为FPGA的片内存储器资源是有限的,但当有大量跨时钟域的数据需要同步的时候,就需要借助片外的sdram或者ddr3内存颗粒。图3是经典的利用外部存储器乒乓操作读写大量跨时钟域的数据的示意图,同一时刻外部数据通过时钟域A写入片外存储器ddr3/sdram 1,同时通过时钟域B读取片外存储器ddr3/sdram 2,下一时刻外部数据通过时钟域A写入片外存储器ddr3/sdram 2,同时通过时钟域B读取片外存储器ddr3/sdram 1,读写完成后切换一次乒乓操作。
图3 使用片外的sdram/ddr3存储器实现数据交互同步
FPGA设计当中有个非常经典的思想即以面积换取速度的思想,FPGA的流水线设计也来源于这个思想。所谓流水线(Pipeline)设计,我们用工业生产组装中的“流水生产线”来举例说明。如果一台设备在生产的时候,分为ABCDE五个相互独立的步骤才能生产组装完毕,每个步骤需要1小时,那么全都由一个工人来负责的话,则生产一台设备需要5小时,如果采用现实生活当中的流水线操作,在生产线上安排了5个工人分别去负责ABCDE五个步骤,那么每个小时就能生产完毕一台设备,虽然总的来看是工人用的多了,但是生产效率的却提高了5倍。
所以通过这个现实生活当中的例子,大家可以看出来,如果一个任务可以人为的拆分成很多个相互独立的步骤,那么具体分工下来确实可以很大程度上提高工作效率,流水线设计在FPGA当中还是得到非常广泛的应用,比如UDP包文的校验和计算就可以通过流水线设计实现,对于很多图像处理卷积运算、图像压缩处理方面都可以使用流水线设计。
之前笔者就一直反复在向大家传递模块划分的理念,模块划分在项目开发当中非常重要,模块和模块之间如何建立起连接也非常重要,在实际工作当中,笔者也遇见过一个.v模块,写了5000多行的Verilog程序,中间没有太多的注释,状态机连着状态机,很多信号还打了几拍拿来使用,整体看起来就会很头疼,很难去理解当时写程序人的设计思路,而且也很难去在原有代码的基础上添加新的功能。建议大家在设计之前,多想明白、理清楚整体需求,划分好模块,再动手去写代码,前期都设计好了再去写代码其实只是一件体力活,请不要盲目性地上来胡子眉毛一把抓,想到哪写到哪,然后不停地仿真测试修改、来回返工。模块划分在实际项目工程当中,按照功能去划分模块最为常见,一个模块去实现某个或者某些需求功能,自顶向下的设层层细分。
串并转换,或者通俗地说,数据组包和数据拆包在FPGA当中是非常常见的设计。串并转换原理很简单,就是使用移位寄存器去实现数据转换的一个过程。其中并转串就是通过移位寄存器把并行的数据一位一位发送出去,而串转并就是通过移位寄存器把串行收到的数据再转换成所需的并行格式,其中最为简单的就是串口上的应用,发送端实现并转串的功能,接收端则实现串转并的功能,常用的串并转换的方式有采用移位寄存器收发,通过计数器赋值中间信号量等等方法,这些在后面的例程中会围绕具体设计展开叙述,这里也举个例子帮助大家来理解,下面是FPGA做主机收发SPI总线上的数据示意图,如图4所示 FPGA作为SPI总线的主机通过mosi总线发送数据,这里就使用了并转串的思想,把tx_spi_data数据送到移位寄存器mosi_shift上,通过外接SPI的MOSI总线把数据一位一位的发送出去,如图5所示FPGA作为SPI总线的主机通过miso总线接收数据,这里则使用了串转并的思想,通过外接SPI的MISO总线把数据一位一位的接收过来再打包成rx_spi_data把数据送至上游模块处理。
图4 FPGA作为SPI总线的主机通过mosi总线发送数据
图5 FPGA作为SPI总线的主机通过miso总线接收数据
为了实现整个FPGA项目工程当中良好的实时性,乒乓操作在实际应用当中非常普遍,广泛应用在FPGA视频加速处理和复杂的数字信号处理当中。
关于乒乓操作,有很多FPGA相关书籍都多多少少做了一些介绍,但是一方面可能因为书籍作者本身对项目实战中的乒乓操作理解并不太深刻,另一方面可能单纯文字化的表达很难让读者体会到其中的要点,笔者在最初学习FPGA的时候也阅读了不少相关介绍乒乓操作的书籍,但是说真的几乎连描述性文字都大同小异,从头到尾来回读上很多遍也没能体会到乒乓操作存在的意义和具体地实现方式,只能体会到书籍作者想要表达对于ram空间或者ddr3/sdram颗粒不同内存地址需要来回切换读写。在这里,笔者用“OV7725摄像头实时采集送VGA彩色显示”这个例程来为大家介绍乒乓操作的意义和实施。
大家不妨先去思考几个问题,这也是实际项目工程实施落地中所必须面对的:1.为什么要做乒乓操作;2. 什么时候做乒乓操作;3. 怎么去做乒乓操作;4.什么是局部乒乓和全局乒乓;5.在FPGA视频研发当中,乒乓操作和多帧缓存有何异同。笔者想如果大家搞明白这些问题以后,那么在工作中的项目实战开发也就会清晰很多。
我们就结合“OV7725摄像头实时采集送显LCD”这个例子来为大家逐一解答上面的这些问题,但是OV7725摄像头配置、采集时序以及VGA显示时序等等相关知识我们在此就不做详细介绍了,将留在对应的例程中展开叙述。大家可以先简单地把整个视频图像采集系统理解成:输入的OV7725实时采集的视频源是640*480*30帧数据流,其时钟频率是12Mhz,而输出的VGA实时显示的视频源是640*480*60帧数据流,其时钟频率是25Mhz,这个过程就涉及到上面提到的跨时钟域的数据同步,为了方便理解我们再去进一步简化整个模型,假设采集图像和显示图像两者同时进行,因为VGA读取外部内存的速度近乎快于OV7725摄像头写入外部内存的速度的两倍,那么站在VGA显示的角度来看整个模型,这里假设采集和显示完完全全时钟同步,如图5所示,T0时刻结束时,因为只有1/2个图像帧数据存储写入外部内存中,VGA去读取外部内存就显示了空白和1/2帧数据图像;在T1时刻结束,VGA刚好显示完全第一帧数据图像;在T2时刻结束,VGA显示了1/2第一帧图像和1/2第二帧图像;在T3时刻结束,VGA刚好显示完全第二帧数据图像,但是在实际工程中,首先不可能保证采集和显示完完全全时钟同步;其次因为项目当中使用的摄像头种类也五花八门,未必就是恰好每秒30帧图像;再者两者写入和读取外部内存的时序逻辑是不同的。这也就意味着如果我们不做任何处理,单纯使用外部内存进行单帧存储图像的话,各个时刻内VGA显示的图像,图像之间帧与帧几乎必然会交错在一起。
图5 ddr3/sdram进行单帧存储示意图
通过上面的模型分析,这也就回答了第一个和第二个问题,如果我们不做乒乓操作的话,因为采集图像和显示图像之间显然存在各种不同步性,也必然将导致图像显示会时时刻刻出现帧与帧之间的交错现象,那么对于一个采集处理显示系统来说没有任何实时性可言。当一个系统需要良好的实时性时候,比如在视频加速处理实时显示的时候,在数模转换芯片dac实时计算并输出波形的时候,我们通常就会考虑流水线设计、乒乓操作、多帧缓存等技术,让用户完全感觉不到FPGA中间在做并行运算处理,使得整个系统实时可靠。如果只使用了外部内存单帧存储图像的方法,可以明显看到在切换图片的时候,两幅图片必然会有交错的地方。
然后我们再来结合这个例程回答第三个问题,即怎么去做乒乓操作,如图4-16很好地说明了怎么使用片外ddr3/sdram存储器去进行乒乓读写操作,豌豆开发板上配备有一颗镁光原装的128Mb存储大小的ddr3颗粒,同时Xilinx官方也提供了MIG IP核去方便地读写ddr3不同地址上的数据,这些细节的知识点我们都会在对应的例程中做详细说明,大家可以先通过图6直观地去理解整个乒乓操作的过程。因为ddr3颗粒的读写数据速度要远远高于12Mhz和25Mhz,所以完全可以作为乒乓操作片外读写数据的中间桥梁。
摄像头OV7725实时采集图像数据,再通过图像输入数据选择模块先写入ddr3颗粒Bank0地址空间,而图像输出数据选择模块在这段时间会一直读取ddr3颗粒Bank1地址空间的数据,而后VGA视频流显示模块再按照VGA显示的时序逻辑显示Bank1中所存储的图像数据,当一帧640*480像素的数据写入Bank0地址空间完毕后,切换读写内存的地址空间,即新的一帧640*480像素的数据会去写入Bank1地址空间,而图像输出数据选择模块在这段时间会一直读取ddr3颗粒Bank0地址空间的数据,VGA这段时间又在显示Bank0中所存储的图像数据,来回交替重复依次循环。
图6 片外ddr3/sdram乒乓读写示意图
接着我们再来回答第四个问题,这也是非常重要的概念,即什么是局部乒乓和全局乒乓,不少FPGA书籍对此隐隐约约提及到,但是不作为重点也没有展开描述,其实真正搞清楚这个概念,对于整体设计层面上的理解会有很大帮助。
“局部乒乓”就是说只站在数据写入端去看整个设计,当写完一帧完整的图像数据到数据缓存区Bank0就直接切换地址去写入另外一块Bank1;而“全局乒乓”则是说站在数据写入端和数据读取端去看整个设计,当写完一帧完整的图像数据到数据缓存区Bank0后,并且数据读取端也从Bank1读完了一帧完整的图像数据后,再去切换读写地址,实际项目工程中通常会去使用“全局乒乓”,虽然可能代码层面上设计比较麻烦些,但是通过“全局乒乓”的操作,可以很大程度上保证画面显示的完整性,不会因为像“局部乒乓”一样,因为一帧图像写入完全而另一帧图像没有读取完全的时候,强行进行乒乓切换,导致画面会有交错的可能性。
最后就是第五个问题了,即在FPGA视频研发当中,乒乓操作和多帧缓存有何异同,相信这是很多朋友们非常关心的问题,同时这也是笔者在学习实践FPGA过程中被困扰了很久的问题,笔者是通过后期不断动手实践后才慢慢领悟到的。乒乓操作这个思想真的非常好,但是大家再仔细想想看它真正地从根本上去解决了图像输入端和图像输出端之间不匹配或者说不同步的问题了吗,其实只能说是缓解了这个问题,通过全局乒乓的操作,很好地避免了因为对外部存储器地址来回交替读写而导致图像帧与帧之间的交错的现象,可以让用户看到640*480像素的“实时图像”,但是这又不可避免地会因为读写完成之间的等待,使得整个视频尤其是当分辨率较高或者对图像前处理较多的时候,图像流畅度方面就会大打折扣,因为图像分辨率高了,图像前处理多了,跨时钟域读写数据也就变多了,那么整体设计上读写等待的时间就不可避免的变长了,两帧缓存的乒乓操作在项目实施落地过程中就会显得力不从心,因为用户会明显感到处理后的视频非常卡顿,为了能更好地“欺骗”人眼,使得FPGA视频加速处理的项目更好实施落地,所以就引入了多帧缓存的技术,大家可以看成是两帧缓存乒乓操作的升级版,多帧缓存主要就是为了解决视频流畅度的问题,笔者曾遇见过五帧缓存的视频加速处理项目,其流畅性真的非常棒,肉眼完全看不出任何卡顿,但是多帧缓存对外部存储资源的要求也很高,为了保证对外部存储器的读写速度,动则就需要4颗ddr3外部存储颗粒的硬件支持,因为多颗ddr3颗粒每个时钟内读写的位宽上也增大了,所以对显示质量不是很高的情况下,真的不推荐这样做,笔者自己也去尝试过三帧缓存的处理方案,使用的Artix7开发板外挂2颗ddr3外部存储颗粒,做了些图像前处理在分辨率1024*768的情况下,实际测试视频显示的效果也很流畅,同时6层PCB板2颗ddr3的硬件配置也比较节约,所以实际转产项目中很多时候就是在寻找成本和性能之间的平衡点。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。