一、AXI Stream协议及视频流格式
AXI Stream关键的只有两根信号线,及tvalid核tready。tvalid是主设备驱动的信号,表示Stream上的数据是有效的,tready由从设备驱动,表示从设备下一个时钟到来时能够接收数据。AXI Stream的特点是这两个信号不存在互相等待的关系,及数据传输只发生再两者均有效的时候,从而效率很高,可以认为是连续传输,避免了死锁的情况。
AXI Stream还有一些附加信号,通常是伴随再数据中传输,主要包括tuser,tlast,tkeep等。tlast在标准流协议中表示一个数据包结束,伴随最后一个数据传出,tuser可以很多位,是用户定义信号,用来表达用户自己需要传输的数据。tkeep是字节修饰符,位数位数据宽度/8,当总线数据某字节有效时,tkeep对应位就为高。
Xilinx 所有与视频有关的IP均遵循一套AXI Stram视频流协议,其中对tlast与tuser赋予了特殊的含义。视频流中除了数据以外,包好start of frame和end of line信号,其中eol用tlast表示,sof用tuser表示。这两个信号均伴随像素数据传输,只保持一次有效传输的时间,sof和一帧第一个像素一起传出,eol和一行最后一个像素一起传输。视频流中有时会加入tkeep信号,无特殊情况时需全部拉高。
二、VDMA的详细讲解和使用
1、 vdma IP核配置界面
上面图片就是在vivado2015.4中例化vdma的界面,首先对参数做些介绍:
基本配置 |
高级配置 |
地址线宽度 |
是否使能异步模式(自动) |
帧存数量 |
写通道帧同步 |
是否使能读写通道 |
写通道GenLock模式选择 |
数据线宽度 |
写通道是否允许非对齐传输 |
触发长度 |
读通道帧同步 |
AXI-Stream流数据位宽 |
读通道GenLock模式选择 |
Line Buffer深度 |
读通道是否允许非对齐传输 |
Frame Buffers :选择vdma缓存几帧图像,这里默认是写通道和读通道都设置相同的缓存帧数,具体设置多少帧合适一般根据应用来定,比如读写带宽相同,想用ddr作为一个乒乓buffer,那就可以设置成2帧,写第一个地址,读第二个地址,写第二个地址,读第一个地址。这里面设置几帧,就要在vdma寄存器配置的时候设置几个帧起始地址。
Memory Map Data Width:代表数据到达AXI4总线上的位宽,比如这里设置成64,那就代表M_AXI_XX总线上的数据位宽是64bit,这时候如果stream上的数据是32bit,那vdma内部会有一个带宽转换模块,把数据拼成64bit。
Burst Size : AXI总线上突发传输的长度,一般设置为16
Stream Data Width:vdma与pl逻辑部分通过axi stream协议交互数据,这里代表stream数据位宽
Line Buffer Depth:vdma内部会有一个行缓存fifo,stream数据会先写入fifo,然后AXI总线逻辑会读出到总线上,这个深度就代表fifo的深度。设置原则(个人理解):如果AXI总线数据带宽是stream总线数据带宽的1.5倍以上,这个fifo深度可以设置的小一点,如果AXI总线带宽小于1.5倍的stream总线带宽,那fifo的深度至少要是图像一个有效行的一半。
Advanced : 这里面只说一下Fsync Options,这个信号是什么意思呢,就是告诉vdma什么时候开始运行,一般s2mm通道选择tuser,就是说在tuser 拉高的时候开始传输。mm2s通道,可以选择none,也可以选择 mm2s_fsync,这里介绍一下这两个的区别。
none : 就是没有同步信号,但这并不是说没有开始信号,而是只要mm2s_stream通道tready拉高,就开始传输,相当于free模式
mm2s_fsync:当这个信号发生一个下降沿的时候开始传输,如果没有这个下降沿,即使mm2s_stream通道tready拉高也不会传输
详细说明
对于S2MM通道:之前在讲vdma配置的时候有一个Advanced选项,里面有Fsync Options选项,可选none,s2mm_fsync,s2mm_tuer,三种同步模式。
none就是只要vdma就绪,就立马准备接收数据,不需要同步信号。
s2mm_fsync,当选择此。模式时,vdma 模块会有一个s2mm_fsync引脚,一般情况下是把视频帧同步信号连到这上面,当检测到s2mm_fsync引脚有一个下降沿的时候,vdma正式进入传输状态。
s2mm_tuer,这个信号和s2mm_fsync这个信号类似,但他是在stream协议里面的,vdma检测到s2mm_tuer拉高以后(tuser只在一帧数据的第一个像素位置拉高),正式进入传输状态
对于MM2S通道,同样在vdma配置的Advanced选项里面有 none,mm2s_fsync两种选择模式。
none不需要同步信号,只要axis_mm2s通道的tready拉高,就开始从ddr读取数据进行传输,选择这种模式一般主要是把ddr里面的数据读到pl里面进行处理,而不是转成视频
mm2s_fsync,选择此同步模式,一般是把ddr的数据转成视频数据,注意,这里重点讲这个同步模式,当vdma的读通道选择此同步模式的时候,vdma模块会有一个mm2s_fsync信号,这个信号在读操作中非常重要。当vdma寄存器配置完成并开启传输,mm2s通道进入等待过程,一直等到mm2s引脚信号出现一个下降沿,这时候vdma启动读操作,会从ddr预读一些数据到内部linebuffer,等到axis_mm2s通道的tready信号拉高,数据就开始传输,进入axis2video模块的fifo,当axis2video内部fifo满了以后,会拉低tready,这时候就会反馈到vdma,暂停读操作,一直等到axis2video模块的视频时序输入数据有效信号,这时候视频开始输出,axis2video内部fifo数据减少,axis_mm2s通道开始恢复传输,继续从vdma读出数据,vdma再通过axi总线从ddr读取数据,如此反复,完成ddr数据到video数据的转换
下面是接口介绍:
M_AXI_XX : axi4总线接口,用来与ddr交互数据
M_AXIS_XX , S_AXIS_XX : axi stream接口,用来与pl交互数据
S_AXI_LITE :控制总线,接到ps的gp口或者写一个axilite master总线去配置
其他接口不做介绍
2、为什么要使用VDMA
在讲解VDMA之前,先来探讨一下为什么要学习和使用VDMA,以明确学习目的。由于使用VDMA可以方便地实现双缓冲和多缓冲机制,所以本小节引入了帧缓存和缓冲机制的概念。另外,VDMA可以很好地契合Zynq内部架构,缩短开发周期。再加上VDMA本身能够高效地实现数据存取,所以在基于Zynq(也包括其他Xilinx FPGA)图像、视频处理系统中,VDMA可谓是必不可少的。
3、帧缓存
缓冲存储器(Frame Buffer):简称帧缓存或显存,它是屏幕所显示画面的一个直接映象,又称为位映射图(Bit Map)或光栅。帧缓存的每一存储单元对应屏幕上的一个像素,整个帧缓存对应一帧图像。
在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块画布,系统在画布上绘制好画面之后,就可以通知显示设备读取Frame Buffer进行显示了。
注意,笔者这里所说的Frame Buffer和Linux的Frame Buffer不是同一个概念,这里仅指显示缓存(画布)本身,并不是Linux下的一个设备。
4、双缓冲机制
最早解释多缓冲区如何工作的方式,是通过一个现实生活中的实例来解释的。在一个阳光明媚的日子,你想将水池里的水打满,而又找不到水管的时候,就只能用手边的木桶来灌满水池。水桶满了之后,关掉水龙头,将水提到水池旁边,倒进去,然后走回到水龙头。重复上述工作,如此往复直到将水池灌满。这就类似单缓冲工作过程,当你想将木桶里的水倒出的时候,你必须关掉水龙头。
现在假设你用两个木桶来做上面的工作。你会注满第一个木桶然后将第二个木桶换到水龙头下面,这样,在第二个水桶注满的时间内,你就可以将第一个木桶里面的水倒进水池里面,当你回来的时候,你只需要再将第一个木桶换下第二个注满水木桶,当第一个木桶开始注水的时候你就将第二个木桶里面的水倒进水池里面。重复这个过程直到水池被注满。很容易看得到用这种技术注满水池将会更快,同时也节省了很多等待木桶被注满的时间,而这段时间里你什么也做不了,而水龙头也就不用等待从木桶被注满到你回来的这段时间了。
当你雇佣另外一个人来搬运一个被注满的木桶时,这就有点类似于三个缓冲区的工作原理。如果将搬运木桶的的时间很长,你可以用更多的木桶,雇佣更多的人,这样水龙头就会一直开着注满木桶了。
在计算机图形学中,双缓冲是一种画图技术,使用这种技术可以使得画图没有(至少是减少)闪烁、撕裂等不良效果,并减少等待时间。
双缓冲机制的原理大概是:所有画图操作将它们画图的结果保存在一块系统内存区域中,这块区域通常被称作“后缓冲区(back buffer)”,当所有的绘图操作结束之后,将整块区域复制到显示内存中,这个复制操作通常要跟显示器的光栈束同步,以避免撕裂。双缓冲机制必须要求有比单缓冲更多的显示内存和CPU消耗时间,因为“后缓冲区”需要显示内存,而复制操作和等待同步需要CPU时间。
基于双缓冲机制可以实现页交换,页交换初始状态如下图所示:
如上图所示,此时由于处于初始状态,画图操作的结果都在后缓冲区中,而屏幕上显示的则是前缓冲区中的内容。此时画图操作尚未完成,画图操作完成之后,页转换操作开始执行,示意图如下图所示:
如上图所示,画图操作结束,下一个画图操作的结果保存对象指向前缓冲区,屏幕的显示对象指向后缓冲区,此时前缓冲区变成实际意义上的后缓冲区,后缓冲区变成实际意义上的前缓冲去,即实现“页交换”操作。
有时候也在页交换链中设置多个“后缓冲区”,这是就需要多缓冲区机制的支持。
5、Zynq硬件架构
在Zynq芯片内部,PS和PL是共享DDR控制器的。PS访问DDR十分简单,只要操作DDR映射的虚拟地址即可。对于PL而言,要接入DDR,必须通过AXI_HP端口。
Zynq共有四个AXI_HP通道,通道数据宽度可以配置为32位或64位,这些接口通过FIFO控制器连接PL到存储接口上,其中有两条连接到DDR存储控制器上,还有一条是连接到双端口的OCM上的,下图是AXI_HP访问DDR和OCM的连接图。
由上图可以看出,AXI_HP接口也是遵循AXI协议的,因此利用VDMA可以直接连接HP端口。除了使用VDMA,当然也可以自己开发出符合AXI协议的IP,但是综合考虑设计成本,没太有必要自己实现。此外,自己实现的IP功能也不见得比VDMA强大。
6、VDMA的作用
VDMA数据接口可以分为读、写通道,用户可以通过写通道将AXI-Stream类型的数据流写入DDR3,通过读通道可以从DDR3读取数据,并以AXI-Stream类型的格式输出。由此可知,VDMA本质上是一个数据搬运IP,为数据进、出DDR3提供了一种便捷的方案。
将数据存入DDR之后,CPU就可以进行一些处理(缩放、裁剪等),然后再送至显示设备,达到期望的应用目的。当然,也可能是简单地对捕获的视频进行解析,将数据存入帧缓存,以供显示。
VDMA可以控制多达32个帧存,并可以自由地进行帧存切换,所以就能够轻松地实现双缓冲和多缓冲操作。这也是一个很重要的特性,在后续进行系统设计的时候,通常是采用多缓冲的方式实现显示。
7、VDMA接口介绍
7.1 时钟和复位
各种总线都有自己的时钟信号,不用特别说明,需要指出的是,这些时钟是异步的,并不需要用同一个时钟。但在设计过程中,如无特别需求,可以使用相同的时钟,以降低设计难度。
同步复位信号axi_resetn,同步时钟为s_axi_lite_aclk,低电平有效(至少要保持16个时钟周期的低电平,才能够生效),有效时复位整个IP核。
7.2 AXI总线相关信号
l AXI4-Lite接口(S_AXI_LITE)
l AXI4读接口(M_AXI_MM2S)
l AXI4写接口(M_AXI_S2MM)
l AXI4-Stream主接口(M_AXI_MM2S)
l AXI4-Stream从接口(S_AXI_S2MM)
前缀S_、M_分别表示Slave和Master;后缀MM2S、S2MM说明数据流向是从memory map到stream还是从stream到memory map。具体每个接口所包含的信号,在基础篇第20章已有介绍,此处不再介绍。
7.3 视频同步接口信号
信号名称 |
方向 |
详细描述 |
mm2s_fsync |
Frame Sync |
MM2S帧同步输入。使能该信号后,VDMA操作开始于fsync每个下降沿。该信号至少要持续一个m_axis_mm2s_aclk时钟周期 |
s2mm_fsync |
Frame Sync |
S2MM帧同步输入。使能该信号后,VDMA操作开始于fsync每个下降沿。该信号至少要持续一个s_axis_s2mm_aclk时钟周期 |
7.4 GenLock相关信号
在下一节将详细介绍这些信号的作用和应用场合。
信号名称 |
方向 |
详细描述 |
mm2s_frame_ptr_in(5:0) |
输入 |
输入的帧编号 |
mm2s_frame_ptr_out(5:0) |
输出 |
输出当前帧的编号 |
s2mm_frame_ptr_in(5:0) |
输入 |
输入的帧编号 |
s2mm_frame_ptr_out(5:0) |
输出 |
输出当前帧的编号 |
8、读写通道工作时序
清晰地理解VDMA读写通道的工作时序,对以后的设计有很大的帮助,很多设计都是根据本小节所示的样例时序设计出来的。在下一章,读者就能够有所体会。
8.1 读通道(MM2S)时序
下图描述了读通道的时序,5行,每行16字节,跨度为32字节。
从图中可以看出:在收到mm2s_fsync信号后,VDMA在m_axi_mm2s_araddr的起始地址处发出m_axi_mm2s_arvalid信号。M_axi_mm2s_arvalid总共有效5次,分别获取一帧的5行数据。从MM读取的数据存储在行缓存里,当收到来自axi-stream端的m_axis_mm2s_tvalid信号后,将数据发送到axi-stream端。每一行的结束,axi-stream端会使m_axis_mm2s_tlast有效。
8.2 写通道(S2MM)时序
下图描述了写通道的时序,5行,每行16字节,跨度为32字节。
从图中可以看出:在收到s2mm_fsync信号后,VDMA发出s2mm_fsync_out和s_axis_s2mm_tready表明已经准备好接收来自axi-stream端的数据。读取到的数据存储在行缓存里,m_axi_s2mm_awvalid有效后,紧接着有效m_axi_s2mm_wvalid信号,同时将数据放至m_axi_s2mm_wdata。
9、寄存器
VDMA的寄存器如下表所示。所有寄存器都被映射到非缓存内存空间。该内存空间必须按照AXI字(32位)进行对齐,换句话说,寄存器偏移地址至少间隔4个字节。
寄存器名称 |
偏移地址 |
详细描述 |
MM2S_VDMACR |
00h |
MM2S VDMA控制寄存器 |
MM2S_VDMASR |
04h |
MM2S VDMA状态寄存器 |
保留 |
08h~10h |
N/A |
MM2S_REG_INDEX |
14h |
MM2S寄存器索引 |
保留 |
18h~24h |
N/A |
PARK_PRT_REG |
28h |
MM2S和S2MM Park指针寄存器 |
VDMA_VERSION |
2Ch |
VDMA版本寄存器 |
S2MM_VDMACR |
30h |
S2MM VDMA控制寄存器 |
S2MM_VDMASR |
34h |
S2MM VDMA状态寄存器 |
保留 |
38h |
N/A |
S2MM_VDMA_IRQ_MASK |
3Ch |
S2MM错误中断掩码寄存器 |
保留 |
40h |
N/A |
S2MM_REG_INDEX |
44h |
S2MM寄存器索引 |
保留 |
48h~4Ch |
N/A |
MM2S_VSIZE |
50h |
MM2S垂直方向显示大小寄存器 |
MM2S_HSIZE |
54h |
MM2S水平方向显示大小寄存器 |
MM2S_FRMDLY_STRIDE |
58h |
MM2S帧延迟和跨度寄存器 |
MM2S_START_ADDRESS(1~16) |
5Ch~98h |
MM2S帧存起始地址(1~16) |
保留 |
9Ch |
N/A |
S2MM_VSIZE |
A0h |
S2MM垂直方向显示大小寄存器 |
S2MM_HSIZE |
A4h |
S2MM水平方向显示大小寄存器 |
S2MM_FRMDLY_STRIDE |
A8h |
S2MM帧延迟和跨度寄存器 |
S2MM_START_ADDRESS(1~16) |
ACh~E8h |
S2MM帧存起始地址(1~16) |
所有寄存器字节序都是小端格式,如下图所示。
各个寄存器的名称和大致作用从上表就可以看出,接下来,笔者会详细介绍重要寄存器的具体bit的作用。明白了每个bit的作用之后,自然就知道写入什么值能够达到自己的控制目的。
从上表可以看出,寄存器可以分为两组,分别对应MM2S通道和S2MM通道,两组寄存器的功能是相似的,区别仅在于偏移地址和所服务的对象。因此,在学习完MM2S通道的所有寄存器之后,只要大致浏览一下S2MM通道对应的寄存器的关键位即可(个别位不相同),在使用高级功能时,再仔细查阅VDMA用户手册。
9.1 MM2S VDMA 控制寄存器(00h)
顾名思义,该寄存器用于控制VDMA,具体可以实现复位、使能锁相同步、设定帧存切换模式、启动VDMA读写通道等操作。每一位作用如下图所示,低4位是最重要的,接下来会详细介绍。
位 |
名称 |
默认值 |
接入类型 |
描述 |
31~4 |
非常用位,请参考VDMA使用手册自学 |
|||
3 |
GenlockEn |
0h |
可读可写 |
使能锁相同步或者动态锁相同步模式。 0:关闭Genlock或动态Genlock同步 1:开启Genlock或动态Genlock同步 注:该位仅在通道被配置成锁相同步从接口或者动态锁相主、从接口时才起作用。配置成锁相同步主接口时,该位为保留位,值恒为0。 |
2 |
Reset |
0h |
可读可写 |
0:正常操作;1:复位MM2S通道 |
1 |
Circular_Park |
1h |
可读可写 |
指定帧存为循环模式还是停留模式 0:停留模式-显示用缓存页将停留在PARK_PTR_REG.RdFrmPntrRef指定的帧存; 1:循环模式-循环切换显示用缓存页 |
0 |
RS |
0h |
可读可写 |
运行/停止,控制VDMA通道的运行和停止。 开始任何VDMA操作前,该位必须置1. 0:停止;1:运行。 |
9.2 MM2S VDMA 状态寄存器(04h)
该寄存器用于获取VDMA工作状态。每一位作用如下图所示,低4位是最重要的,接下来会详细介绍。
位 |
名称 |
默认值 |
接入类型 |
描述 |
31~1 |
非常用位,请参考VDMA使用手册自学 |
|||
0 |
Halted |
1h |
只读 |
指示VDMA运行是否停止。 0:运行;1:停止。 |
9.3 PARK_PTR_REG停留指针寄存器(28h)
该寄存器用于管理读、写通道的数据传输。
位 |
名称 |
默认值 |
接入类型 |
描述 |
31~29 |
保留 |
0h |
只读 |
|
28~24 |
WrFrmStore |
0h |
只读 |
用于存储写通道正在操作的帧的编号。指示S2MM通道正在操作的帧。 |
23~21 |
保留 |
0h |
只读 |
|
20~16 |
RdFrmStore |
0h |
只读 |
用于存储读通道正在操作的帧的编号。指示MM2S通道正在操作的帧。 |
15~13 |
保留 |
0h |
只读 |
|
12~8 |
WrFrmPtrRef |
0h |
可读可写 |
通过帧编号指定写通道操作的帧。当工作在停留模式,S2MM通道操作对象停留在WrFrmPtrRef指定的帧。 |
7~5 |
保留 |
0h |
只读 |
|
4~0 |
RdFrmPtrRef |
0h |
可读可写 |
通过帧编号指定读通道操作的帧。当工作在停留模式,MM2S通道操作对象停留在RdFrmPtrRef指定的帧。 |
学习了这个寄存器之后,就可以发现:当VDMA工作在Parked模式下,通过操作该寄存器,就能够实现帧缓存的切换,建立自己想要的缓存切换机制。
9.4 MM2S 帧存起始地址(0x5C~0x98)
有最多32个寄存器用于存放帧存起始地址,其分别存在于两个寄存器bank上:bank0和bank1,每个bank上有16个寄存器。这两个bank上有相同的起始偏移地址(0x5C),选择这两个bank可以通过MM2S_REG_INDEX的值进行选择。假如想访问第1个寄存器,则给MM2S_REG_INDEX赋值为0,并设定偏移地址为0x5C;如果想访问第17个寄存器,需要将MM2S_REG_INDEX设为1,并设定初始偏移地址为0x5C。
9.5 MM2S_FRMDLY_STRIDE MM2S帧延迟和跨度(58h)
该寄存器有两个作用,第一是bit24~bit28指定帧延迟,仅用于Genlock从模式,指定从接口比主接口至少要延迟多少个帧;第二是低16位指定水平方向的跨度,同样以字节为单位。所谓跨度是指每两行第一个像素之间间隔的数据个数,具体请参考22.3.2小节,VDMA帧存格式。
9.6 MM2S_HSIZE MM2S水平方向尺寸(54h)
该寄存器的低16位用于指定每一行有多少字节的数据需要传输。例如显示分辨率为640*480,每个像素4个字节(RGB+Alpha),该值应该设定为640*4。
9.7 MM2S_VSIZE MM2S垂直方向尺寸(50h)
该寄存器有两个作用,第一是用低13位指定总共有多少行;第二是启动MM2S的传输。当MM2S_VDMACR.RS=1,对该寄存器的写操作会将所有设定参数传递给VDMA内部寄存器模块,用于VDMA控制。对某个通道进行配置时,必须在最后一步设置该寄存器。
9.8 S2MM VDMA 控制寄存器(30h)
顾名思义,该寄存器用于控制VDMA S2MM通道,具体可以实现复位、使能锁相同步、设定帧存切换模式、启动VDMA读写通道等操作。每一位作用如下图所示,低4位是最重要的,接下来会详细介绍。
位 |
名称 |
默认值 |
接入类型 |
描述 |
31~4 |
非常用位,请参考VDMA使用手册自学 |
|||
3 |
GenlockEn |
0h |
可读可写 |
使能锁相同步或者动态锁相同步模式。 0:关闭Genlock或动态Genlock同步 1:开启Genlock或动态Genlock同步 注:该位仅在通道被配置成锁相同步从接口或者动态锁相主、从接口时才起作用。配置成锁相同步主接口时,该位为保留位,值恒为0。 |
2 |
Reset |
0h |
可读可写 |
0:正常操作;1:复位S2MM通道 |
1 |
Circular_Park |
1h |
可读可写 |
指定帧存为循环模式还是停留模式 0:停留模式-显示用缓存页将停留在PARK_PTR_REG.RdFrmPntrRef指定的帧存; 1:循环模式-循环切换显示用缓存页 |
0 |
RS |
0h |
可读可写 |
运行/停止,控制VDMA通道的运行和停止。 开始任何VDMA操作前,该位必须置1. 0:停止;1:运行。 |
9.9 S2MM VDMA 状态寄存器(34h)
该寄存器用于获取S2MM工作状态。每一位作用如下图所示,低4位是最重要的,接下来会详细介绍。
位 |
名称 |
默认值 |
接入类型 |
描述 |
31~1 |
非常用位,请参考VDMA使用手册自学 |
|||
0 |
Halted |
1h |
只读 |
指示VDMA运行是否停止。 0:运行;1:停止。 |
9.10 S2MM 帧存起始地址(0xAC~0xE8)
有最多32个寄存器用于存放帧存起始地址,其分别存在于两个寄存器bank上:bank0和bank1,每个bank上有16个寄存器。这两个bank上有相同的起始偏移地址(0x5C),选择这两个bank可以通过S2MM_REG_INDEX的值进行选择。假如想访问第1个寄存器,则给S2MM_REG_INDEX赋值为0,并设定偏移地址为0x5C;如果想访问第17个寄存器,需要将MM2S_REG_INDEX设为1,并设定初始偏移地址为0x5C。
9.11 S2MM_FRMDLY_STRIDE S2MM帧延迟和跨度(A8h)
该寄存器有两个作用,第一是bit24~bit28指定帧延迟,仅用于Genlock从模式,指定从接口比主接口至少要延迟多少个帧;第二是低16位指定水平方向的跨度,同样以字节为单位。所谓跨度是指每两行第一个像素之间间隔的数据个数,具体请参考22.3.2小节,VDMA帧存格式。
9.12 S2MM_HSIZE S2MM水平方向尺寸(A4h)
该寄存器的低16位用于指定每一行有多少字节的数据需要传输。例如显示分辨率为640*480,每个像素4个字节(RGB+Alpha),该值应该设定为640*4。
9.13 S2MM_VSIZE S2MM垂直方向尺寸(A0h)
该寄存器有两个作用,第一是用低13位指定总共有多少行;第二是启动S2MM的传输。当S2MM_VDMACR.RS=1,对该寄存器的写操作会将所有设定参数传递给VDMA内部寄存器模块,用于VDMA控制。对某个通道进行配置时,必须在最后一步设置该寄存器。
10 Genlock同步机制
10.1 什么是Genlock?
Genlock,同步锁相,可以使一套或多套系统与同一同步源实现同步。能够使视频的刷新和外部视频源保持一致。当提供了一个适当的信号后,系统就会把它的显示刷新率和这个信号进行锁定 。
在许多视频应用中,输入端产生数据的速率往往不同于输出端数据速率,为了避免由速率不一致导致的潜在错误,帧缓冲的使用是很有必要的。帧缓冲机制开辟多个缓冲页,用于保存数据,输入和输出端分别操作不同的帧存,从而避免了冲突。
VDMA的锁相同步特性正是用于阻止读、写通道同时操作同一个帧存。VDMA的每个通道都可以选择自己的操作类型(同步锁相主/从或者动态同步锁相主/从),利用该特性,禁止主从接口同时访问同一缓存,从而保持同步。
VDMA支持四种模式的锁相同步,分别为:
Ø Genlock Master(锁相同步主端)
Ø Genlock Slave(锁相同步从端)
Ø Dynamic Genlock Master(动态锁相同步主端)
Ø Dynamic Genlock Slave(动态锁相同步从端)
10.2 Genlock Master
读通道(MM2S):当配置为Genlock Master时,该通道不会跳过或者重复任一帧数据,并把当前帧的编号输出在mm2s_frame_ptr_out端口。通道不会检测mm2s_frame_ptr_in端口提供的帧编号。Genlock Slave通道应跟随Genlock Master通道变化,但有一定的延迟。延迟大小预定义在寄存器中(*frmdly_stride[28:24])。
写通道(S2MM):当配置为Genlock Master时,该通道不会跳过或者重复任一帧数据,并把当前帧的编号输出到s2mm_frame_ptr_out端口。通道不会检测s2mm_frame_ptr_in端口提供的帧编号。Genlock Slave通道应跟随Genlock Master通道变化,但有一定的延迟。延迟大小预定义在寄存器中(*frmdly_stride[28:24])。
10.3 Genlock Slave
读通道(MM2S):当配置为Genlock Slave时,该通道会通过跳过或者重复一些帧的方式,尝试与Genlock Master同步。通道会对mm2s_frame_ptr_in端口进行采样,获取Genlock Master的帧编号。为了实现状态反馈,通道会把当前帧的编号输出到mm2s_frame_ptr_out端口。
指定通道工作在Genlock Slave模式,必须进行如下操作。
Ø 将GenlockEn置1(MM2S_VDMACR[3]=1),使能主、从通道之间的Genlock同步。
Ø 将GenlockSrc置1(MM2S_VDMACR[7]=1),使能内部Genlock模式。如果在Vivado IDE中同时使能读、写通道,该位默认置位。当GenlockSRC=1时,VDMA默认支持内部同步锁相总线。这样一来就没有必要在外部对帧指针端口(*frame_ptr_out和*_frame_ptr_in)进行连接了。
Ø 根据主从通道的帧率,使用mm2s_frmdly_stride[28:24]设定合适的延迟时间。
写通道(S2MM):当配置为Genlock Slave时,该通道会通过跳过或者重复一些帧的方式,尝试与Genlock Master同步。通道会对s2mm_frame_ptr_in端口进行采样,获取Genlock Master的帧编号。为了实现状态反馈,通道会把当前帧的编号输出到s2mm_frame_ptr_out端口。
指定通道工作在Genlock Slave模式,必须进行如下操作。
Ø 将GenlockEn置1(S2MM_VDMACR[3]=1),使能主、从通道之间的Genlock同步。
Ø 将GenlockSrc置1(S2MM_VDMACR[7]=1),使能内部Genlock模式。如果在Vivado IDE中同时使能读、写通道,该位默认置位。当GenlockSRC=1时,VDMA默认支持内部同步锁相总线。这样一来就没有必要在外部对帧指针端口(*frame_ptr_out和*_frame_ptr_in)进行连接了。
Ø 根据主从通道的帧率,使用mm2s_frmdly_stride[28:24]设定合适的延迟时间。
10.4 Dynamic Genlock Master
动态Genlock Master与Genlock Master的区别在于,主通道会跳过从通道正在操作的帧。举例而言,对于三帧存而言,动态Genlock Master会按照0,1,2,0,1,2的顺序循环使用帧存,一旦检测到Master即将操作Slave正在操作的帧,就会跳过该帧继续循环。因此,如果Slave通道一直在操作帧存1,那么Master通道就会在帧0和帧2之间来回切换。
10.5 Dynamic Genlock Slave
Dynamic Genlock Slave通道会操作Dynamic Genlock Master通道上一周期操作的帧。
下图描述了一种简单的Genlock操作时序。在这个示例中,S2MM通道是Genlock Master,MM2S通道是Genlock Slave,并且写通道帧率高于读通道帧率。
由于读通道帧率慢于写通道,所以读通道仅处理帧2和帧0,跳过帧1不做处理。
11、软件控制流程
以下步骤是最简单的VDMA控制初始化操作。
Ø 写VDMACR寄存器,将VDMACR.RS设为1,启动VDMA通道。
Ø 设定有效的帧缓存起始地址。
Ø 设定帧延迟(仅针对Genlock从模式)以及跨度到FRMDLY_STRIDE寄存器。
Ø 设定水平方向字节数到HSIZE寄存器。
Ø 设定竖直方向行数到VSIZE寄存器。启动通道的数据传输。
在VDMA运行过程中,可以动态的进行显示参数配置,但是需要注意的是,想要使参数生效,必须在设置的最后一步,对VSIZE寄存器进行写操作。
最后,给出一段通过VDMA对DDR读写传输的进行初始化的示例代码:
//VDMA configurateAXI VDMA0
/*****************从DDR读数据设置**********************/
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x0, 0x4); //reset
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x0, 0x8); //gen-lock
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x5C, 0x08000000);
// AXI4 Data Width为32位,是4个字节数
// 0x0A000000 0x0015F900
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x5C+4, 0x0A000000);
// 0x09000000 0x002BF200
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x5C+8, 0x09000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x54, 640);// 640
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x58, 0x01000280);
// 第0位: 运行 – 启动VDMA操作,在运行VDMA时,其状态寄存器中的停止位赋值为0 第一位:循环模式 -通过连续循环帧缓冲
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x0, 0x03);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x50, 480);//480
/*********** 写入DDR设置*************************/
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x30, 0x4); //reset
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x30, 0x8); //genlock
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xAC, 0x08000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xAC+4, 0x0A000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xAC+8, 0x09000000);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xA4, 640);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xA8, 0x01000280);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0x30, 0x03);
XAxiVdma_WriteReg(XPAR_AXIVDMA_0_BASEADDR, 0xA0, 480);
启动VDMA的库函数版本程序
1、 main函数
本课程提供了二种方式启动VDMA,第一种是通过库函数版本,第二种是通过寄存器版本。寄存器版本主要是验证我们对VDMA的寄存器掌握情况。库函数具备更强的功能,和可维护性。
表6-6-1
#include "sys_intr.h" #include "xaxivdma.h" #include "xaxivdma_i.h" #define VTC_BASEADDR XPAR_MIZ702_VTG_VGA_0_BASEADDR #define DDR_BASEADDR 0x00000000 //#define UART_BASEADDR 0xe0001000 #define VDMA_BASEADDR XPAR_AXI_VDMA_0_BASEADDR #define H_STRIDE 640 #define H_ACTIVE 640 #define V_ACTIVE 480 #define pi 3.14159265358 #define COUNTS_PER_SECOND (XPAR_CPU_CORTEXA9_CORE_CLOCK_FREQ_HZ)/64 #define VIDEO_LENGTH (H_STRIDE*V_ACTIVE) #define VIDEO_BASEADDR0 DDR_BASEADDR + 0x2000000 #define VIDEO_BASEADDR1 DDR_BASEADDR + 0x3000000 #define VIDEO_BASEADDR2 DDR_BASEADDR + 0x4000000 u32 *BufferPtr[3]; unsigned int srcBuffer = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000); int run_triple_frame_buffer(XAxiVdma* InstancePtr, int DeviceId, int hsize, int vsize, int buf_base_addr, int number_frame_count, int enable_frm_cnt_intr); int main(void) { u32 Status; Miz702_EMIO_init(); ov7725_init_rgb(); XAxiVdma InstancePtr; xil_printf("Starting the first VDMA \n\r"); Status = run_triple_frame_buffer(&InstancePtr, 0, 640, 480, srcBuffer, 2, 0); if (Status != XST_SUCCESS) { xil_printf("Transfer of frames failed with error = %d\r\n",Status); return XST_FAILURE; } else { xil_printf("Transfer of frames started \r\n"); } print("TEST PASS\r\n"); //VDMA configurateAXI VDMA0 /****************往DDR写数据设置**********************/ /*Xil_Out32((VDMA_BASEADDR + 0x030), 0x00000003);// enable circular mode Xil_Out32((VDMA_BASEADDR + 0x0AC), VIDEO_BASEADDR0); // start address Xil_Out32((VDMA_BASEADDR + 0x0B0), VIDEO_BASEADDR1); // start address Xil_Out32((VDMA_BASEADDR + 0x0B4), VIDEO_BASEADDR2); // start address Xil_Out32((VDMA_BASEADDR + 0x0A8), (H_STRIDE*4)); // h offset (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x0A4), (H_ACTIVE*4)); // h size (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x0A0), V_ACTIVE);*/ // v size (480) /*****************从DDR读数据设置**********************/ /*Xil_Out32((VDMA_BASEADDR + 0x000), 0x00000003); // enable circular mode Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0); // start address Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR1); // start address Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR2); // start address Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*4)); // h offset (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*4)); // h size (640 * 4) bytes Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE); // v size (480) */ while (1) ; return XST_SUCCESS; } |
2、 vdma_api.c函数
XAxiVdma_LookupConfig函数是XILINX 库函数的标准调用方式,可以获取到硬件的默认配置参数。默认的配置参数保存在 参数表XAxiVdma_ConfigTable 中。
表6-6-2-1 XAxiVdma_LookupConfig
/*****************************************************************************/ /** * Look up the hardware configuration for a device instance * * @param DeviceId is the unique device ID of the device to lookup for * * @return * The configuration structure for the device. If the device ID is not found, * a NULL pointer is returned. * ******************************************************************************/ XAxiVdma_Config *XAxiVdma_LookupConfig(u16 DeviceId) { extern XAxiVdma_Config XAxiVdma_ConfigTable[]; XAxiVdma_Config *CfgPtr = NULL; int i; for (i = 0; i < XPAR_XAXIVDMA_NUM_INSTANCES; i++) { if (XAxiVdma_ConfigTable[i].DeviceId == DeviceId) { CfgPtr = &XAxiVdma_ConfigTable[i]; break; } } return CfgPtr; } |
表 XAxiVdma_ConfigTable参数表
XAxiVdma_Config XAxiVdma_ConfigTable[] = { { XPAR_AXI_VDMA_0_DEVICE_ID, XPAR_AXI_VDMA_0_BASEADDR, XPAR_AXI_VDMA_0_NUM_FSTORES, XPAR_AXI_VDMA_0_INCLUDE_MM2S, XPAR_AXI_VDMA_0_INCLUDE_MM2S_DRE, XPAR_AXI_VDMA_0_M_AXI_MM2S_DATA_WIDTH, XPAR_AXI_VDMA_0_INCLUDE_S2MM, XPAR_AXI_VDMA_0_INCLUDE_S2MM_DRE, XPAR_AXI_VDMA_0_M_AXI_S2MM_DATA_WIDTH, XPAR_AXI_VDMA_0_INCLUDE_SG, XPAR_AXI_VDMA_0_ENABLE_VIDPRMTR_READS, XPAR_AXI_VDMA_0_USE_FSYNC, XPAR_AXI_VDMA_0_FLUSH_ON_FSYNC, XPAR_AXI_VDMA_0_MM2S_LINEBUFFER_DEPTH, XPAR_AXI_VDMA_0_S2MM_LINEBUFFER_DEPTH, XPAR_AXI_VDMA_0_MM2S_GENLOCK_MODE, XPAR_AXI_VDMA_0_S2MM_GENLOCK_MODE, XPAR_AXI_VDMA_0_INCLUDE_INTERNAL_GENLOCK, XPAR_AXI_VDMA_0_S2MM_SOF_ENABLE, XPAR_AXI_VDMA_0_M_AXIS_MM2S_TDATA_WIDTH, XPAR_AXI_VDMA_0_S_AXIS_S2MM_TDATA_WIDTH, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_1, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_5, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_6, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_7, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_9, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_13, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_14, XPAR_AXI_VDMA_0_ENABLE_DEBUG_INFO_15, XPAR_AXI_VDMA_0_ENABLE_DEBUG_ALL, XPAR_AXI_VDMA_0_ADDR_WIDTH } }; |
WriteSetup VDMA写通道设置,主要设置分辨率,延迟参数,开启CircularBuf 模式,使能Gen-Lock。更底层的分析读者可以顺藤摸瓜下去。
表6-6-2-3 WriteSetup
/*****************************************************************************/ /** * * This function sets up the write channel * * @param dma_context is the context pointer to the VDMA engine.. * * @return XST_SUCCESS if the setup is successful, XST_FAILURE otherwise. * * @note None. * ******************************************************************************/ static int WriteSetup(vdma_handle *vdma_context) { int Index; u32 Addr; int Status; vdma_context->WriteCfg.VertSizeInput = vdma_context->vsize; vdma_context->WriteCfg.HoriSizeInput = vdma_context->hsize; vdma_context->WriteCfg.Stride = vdma_context->hsize; vdma_context->WriteCfg.FrameDelay = 0; /* This example does not test frame delay */ vdma_context->WriteCfg.EnableCircularBuf = 1; vdma_context->WriteCfg.EnableSync = 1; /* Gen-Lock */ vdma_context->WriteCfg.PointNum = 0; vdma_context->WriteCfg.EnableFrameCounter = 0; /* Endless transfers */ vdma_context->WriteCfg.FixedFrameStoreAddr = 0; /* We are not doing parking */ /* Configure the VDMA is per fixed configuration, This configuration * is being used by majority of customers. Expert users can play around * with this if they have different configurations */ Status = XAxiVdma_DmaConfig(vdma_context->InstancePtr, XAXIVDMA_WRITE, &vdma_context->WriteCfg); if (Status != XST_SUCCESS) { xil_printf( "Write channel config failed %d\r\n", Status); return Status; } /* Initialize buffer addresses * * Use physical addresses */ Addr = vdma_context->buffer_address; /* If Debug mode is enabled write frame is shifted 3 Frames * store ahead to compare read and write frames */ #if DEBUG_MODE Addr = Addr + vdma_context->InstancePtr->MaxNumFrames * \ (vdma_context->WriteCfg.Stride * vdma_context->vsize); #endif for(Index = 0; Index < vdma_context->InstancePtr->MaxNumFrames; Index++) { vdma_context->WriteCfg.FrameStoreStartAddr[Index] = Addr; #if DEBUG_MODE xil_printf("Write Buffer %d address: 0x%x \r\n",Index,Addr); #endif Addr += (vdma_context->hsize * vdma_context->vsize); } /* Set the buffer addresses for transfer in the DMA engine */ Status = XAxiVdma_DmaSetBufferAddr(vdma_context->InstancePtr, XAXIVDMA_WRITE, vdma_context->WriteCfg.FrameStoreStartAddr); if (Status != XST_SUCCESS) { xil_printf("Write channel set buffer address failed %d\r\n", Status); return XST_FAILURE; } /* Clear data buffer */ #if DEBUG_MODE memset((void *)vdma_context->buffer_address, 0, vdma_context->ReadCfg.Stride * vdma_context->ReadCfg.VertSizeInput * vdma_context->InstancePtr->MaxNumFrames); #endif return XST_SUCCESS; } |
ReadSetup VDMA读通道设置,主要设置分辨率,这里的延迟参数1,否则图像会有卡顿,开启CircularBuf 模式,使能Gen-Lock。更底层的分析读者可以顺藤摸瓜下去。
表6-6-2-4 ReadSetup
/*****************************************************************************/ /** * * This function sets up the read channel * * @param vdma_context is the context pointer to the VDMA engine. * * @return XST_SUCCESS if the setup is successful, XST_FAILURE otherwise. * * @note None. * ******************************************************************************/ static int ReadSetup(vdma_handle *vdma_context) { int Index; u32 Addr; int Status; vdma_context->ReadCfg.VertSizeInput = vdma_context->vsize; vdma_context->ReadCfg.HoriSizeInput = vdma_context->hsize; vdma_context->ReadCfg.Stride = vdma_context->hsize; vdma_context->ReadCfg.FrameDelay = 0; /* This example does not test frame delay */ vdma_context->ReadCfg.EnableCircularBuf = 1; vdma_context->ReadCfg.EnableSync = 1; /* Gen-Lock */ vdma_context->ReadCfg.PointNum = 0; vdma_context->ReadCfg.EnableFrameCounter = 0; /* Endless transfers */ vdma_context->ReadCfg.FixedFrameStoreAddr = 0; /* We are not doing parking */ /* Configure the VDMA is per fixed configuration, This configuration is being used by majority * of customer. Expert users can play around with this if they have different configurations */ Status = XAxiVdma_DmaConfig(vdma_context->InstancePtr, XAXIVDMA_READ, &vdma_context->ReadCfg); if (Status != XST_SUCCESS) { xil_printf("Read channel config failed %d\r\n", Status); return XST_FAILURE; } /* Initialize buffer addresses * * These addresses are physical addresses */ Addr = vdma_context->buffer_address; for(Index = 0; Index < vdma_context->InstancePtr->MaxNumFrames; Index++) { vdma_context->ReadCfg.FrameStoreStartAddr[Index] = Addr; /* Initializing the buffer in case of Debug mode */ #if DEBUG_MODE { u32 i; u8 *src; u32 total_pixel = vdma_context->ReadCfg.Stride * vdma_context->vsize; src = (unsigned char *)Addr; xil_printf("Read Buffer %d address: 0x%x \r\n",Index,Addr); for(i=0;i<total_pixel;i++) { src[i] = i & 0xFF; } } #endif Addr += vdma_context->hsize * vdma_context->vsize; } /* Set the buffer addresses for transfer in the DMA engine * The buffer addresses are physical addresses */ Status = XAxiVdma_DmaSetBufferAddr(vdma_context->InstancePtr, XAXIVDMA_READ, vdma_context->ReadCfg.FrameStoreStartAddr); if (Status != XST_SUCCESS) { xil_printf( "Read channel set buffer address failed %d\r\n", Status); return XST_FAILURE; } return XST_SUCCESS; } |
StartTransfer 启动VDMA读写通道
表6-6-2-5 StartTransfer
/*****************************************************************************/ /** * * This function starts the DMA transfers. Since the DMA engine is operating * in circular buffer mode, video frames will be transferred continuously. * * @param InstancePtr points to the DMA engine instance * * @return * – XST_SUCCESS if both read and write start successfully * – XST_FAILURE if one or both directions cannot be started * * @note None. * ******************************************************************************/ static int StartTransfer(XAxiVdma *InstancePtr) { int Status; /* Start the write channel of VDMA */ Status = XAxiVdma_DmaStart(InstancePtr, XAXIVDMA_WRITE); if (Status != XST_SUCCESS) { xil_printf("Start Write transfer failed %d\r\n", Status); return XST_FAILURE; } /* Start the Read channel of VDMA */ Status = XAxiVdma_DmaStart(InstancePtr, XAXIVDMA_READ); if (Status != XST_SUCCESS) { xil_printf("Start read transfer failed %d\r\n", Status); return XST_FAILURE; } return XST_SUCCESS; } |