这段时间一直在XUPV2P上做高清视频解码器,但是解码器总是要有能显示的东西,不然总是给人感觉不真实。但是从头开始编写display feeder工作量有些大,所以想偷偷懒。真幸运,给我找到了Slide Show using 256 MB DDR Memory 的plb_tft_cntlr_ref。
这个IP能显示640x480的BMP图片,但是离高清720p(1280x720)还是差距不小。
我想弄清其原理,优化应该不难。刚开始以为就是调几个参数就能搞定。结果这一搞就是一个多月,不过还是收获良多。
总结起来学到的东西与大家分享:plb_tft_cntlr_ref的原理与框图 ,VGA的显示原理,PLB总线的transfer的优化,BLOCK_RAM的使用,DCM的使用,chipsocpe的使用。
plb_tft_cntlr_ref原理:
Slide Show using 256 MB DDR Memory 这个例程的功能是这样:
首先,从CF卡里读取多个BMP图片的数据,去掉BMP的头信息之后,把RGB的数据放到内存中。
同时,每个BMP图片在内存中的地址都被记录。
最后,PPC通过DCR总线向把要显示的图片的地址写给plb_tft_cntlr_ref,plb_tft_cntlr_ref 就会根据这个地址从内存中读取数据进行显示。PPC通过传不同的地址给plb_tft_cntlr_ref,这样就能显示不同的图片,做到幻灯片的效果。
当然还有许多细节,下面遇到再说。
VGA的显示原理:
VGA的显示最先是用在CRT显示器上,虽然现在也可以用在LCD上,但是我们还是看看CRT的显示的原理图:
显示的时候电子枪是按下面的方法扫描:
简化点来说,显示的控制是这样的:
Controller要给出符合VGA显示时序的RGB,vsync,hsync,显示器才进行正确地显示。
怎样才能符合VGA的显示时序呢?
下面这张图给出答案。
从上图我们可以看到整个时序图可以分为4段:
1,显示阶段,也就是video_on使能的阶段。这个时候controlller要将RGB数据送给显示器,而且是每一个pixel_clk送出一个pixel的RGB数据。
2,折回(retrace),也就是电子枪扫完一行之后回到最初扫描的位置用的时间。
3,前沿(front porch),之所以称为前沿,是因为它在retrace的前面吧。对应于显示右边黑掉的部分。
4,后沿(back porch),对于与显示左边黑掉的部分。
显然,改成1280x720,最起码显示这些参数是要改动的,但是我不知道是多少。
在网上找了半天,结果还是在XUPV2P的user guide 上找到答案。真是佩服Xilinx,user guide做得可真全。
我想他们也不会介意我切一点上来:
这样就要找到plb_tft_cntlr_ref中,与这四个阶段有关的参数。
在h_sync.v与v_sync.v中我找到了这些参数,这四个阶段是用状态机来完成。不过修改起来还算简单。修改对应计数器的值就行了。
这个表格的参数也提示我pixel_clk要进行修改。而且与DCM有关。
DCM参数的修改
主要修改了两处,原来的IP是用DCM的分频功能从100Mhz分频得到50Mhz的pixel_clk.但是75Mhz是没法用分频得到,所以要用CLKFX。而且下面的参数要修改。这样就能从100Mhz的时钟得到75Mhz的时钟。
plb_tft_cntlr_ref 的框图:
一开始我并没有往1280x720去做,而是往800x600做,因为有一个问题会造成1280x720不成功,那就是Blockram的大小问题。
下面这张图可以说明这个IP从读数据到显示的过程。
由于PLB总线的时钟是100Mhz,而pixel_clk通常都不是100Mhz,那么这就是一个跨时钟域的问题,那么这就需要有一个buffer,FPGA中当然是用Blockram来完成。这个例程真的教会我很多东西。
先介绍一下数据的读取过程。
plb_tft_cntlr_ref从内存读取显示数据时并没有一次读一个图片的数据,因为数据量太大,FPGA上没有这么大的存储空间,它只是读取一行的显示数据到Blockram中,再由显示那边读去显示,一旦显示完成就会有一个getline的信号要求plb_tft_cntlr_ref从内存中读取另一行的显示数据。
所以现在只有1K,也就是1024,那么1280将超过这个数,那么就会出错。
我又想验证上面的修改是否正确,只能先做800x600。
这样把上面那些地方改成800x600之后,果然能显示800x600的图片。这都让我兴奋了一整晚。
BLOCK_RAM的修改
接下来就是修改Blockram了,我想还是先在800x600改改,从user guide上可以看到VGA的DAC是8位,这里只是用到6位,我想主要是为了节省BLOCKRAM,但是我想看看8位数据会不会能显示的效果好一点,而且为下面720p做好准备,所以决定用3个BLOCKRAM来代替一个BLOCKRAM,每一个BLOCKRAM存放一种颜色的8位数据。
修改代码如下:
PLB总线的transfer的优化
结果在意料之中,能正确地显示,但是没有看到比原来更好的效果,我想6位就够用了吧。
不过,既然Blockram都改大了,那就往720P做吧。
改完上面的时序参数之后,显示不出来正确的图像。我观察了一阵那些图像,好像前面几列是正确的,但是后面错了。想了很久都没有发现哪里错了,最后代码里的注释给了我启发,原IP用的是64-bit Master 8-word Line Read From a 64-bit Slave传输方式。这让我想到会不会是从内存读取的数据的速度太慢,显示已经用到后面的像素,所以没能显示。
因为这种传输方式每一次申请总线才读8个word。
我不过证实我的猜测是我学会用chipsocpe之后(对于如何使用chipscope来观察coreconnect的时序,我之前的博文中介绍过这里不再罗嗦。)
有兴趣的朋友可以看看:
使用chipscope来观察coreconnect总线时序之plb时序
使用chipscope来观察coreconnect总线时序之DCR时序
使用chipscope来观察coreconnect总线时序之OPB时序
看到了下面张图后,我才发现原来这种传输方式用在这个地方多么的浪费总线的带宽。
每一次等待16个clk之后能从DDR里读出数据,却只读了8个WORD,然后再去申请,再等16个clk。我想问题就出在这。为什么不多读几个呢,甚至一整行都读完。
我只有在PLB总线的datasheet里苦苦地寻找我要的那种transfer。终于,我发现Sequential Burst Read Transfer Terminated by Master十分的合适,一次能读完一整行的数据。所以就决定用它了。
对比64-bit Master 8-word Line Read From a 64-bit Slave
可以看到Sequential Burst Read Transfer Terminated by Master最大的区别在于多了一个Mn_rdBurst信号,这个信号一旦使能,就可以从DDR中连续不断地读到数据。
那Mn_rdBurst要使能多久呢?是不是640个时钟呢?(因为DDR是上下沿都能出数据,所以一个clk能出64个bit的数据)刚开始我也这样认为,但是用chipsocpe一看,才发现Mn_rdBurst使能的时候,DDR有时数据会有间断,所以不能准确地用640个时钟,应该多一点。看图就明白。
那怎样才知道传完一行数据呢。用PLB_MnRdDAck来使能计数器可以做到。
就这样,720P的显示被我征服了。呵呵!
虽然这只是万里长征的第一步,但是感觉这一步还是迈得挺踏实的,学会了不少东西。
生活总是这样,遇到问题,解决问题。在解决问题的过程中,我们不断地积累,不断地成长。
老师让我多做笔记,我想就用与非网这个博客来记录我的积累和成长。
无图不事实: