ZYNQ Linux VCU使用
- 版本:2023.1
VCU概括
- VCU是单独的硬核,没有直接挂在PS上,需要PL用相关的IP核进行连接,PS也需要对应的驱动加载
- 内部有单独的MCU,需要PS加载bootloader和应用程序
- MCU通过AXI接口读取(执行)位于DDR上应用程序
- 编解码单元通过单独的HP读取相关数据
- 编解码常用输入输出图像格式YUV420SP,不支持RGB

PL
核心IP:VCU
- 这里配置最大支持的编解码格式

BLOCK DESIGN

PS
设备树(自动生成)
c
vcu_0: vcu@a0000000 {
#address-cells = <2>;
#clock-cells = <1>;
#size-cells = <2>;
clock-names = "pll_ref", "aclk", "vcu_core_enc", "vcu_mcu_enc", "vcu_core_dec", "vcu_mcu_dec";
clocks = <&misc_clk_0>, <&zynqmp_clk 71>, <&vcu_0 0>, <&vcu_0 1>, <&vcu_0 2>, <&vcu_0 3>;
compatible = "xlnx,vcu-1.2", "xlnx,vcu";
interrupt-names = "vcu_host_interrupt";
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
ranges ;
reg = <0x0 0xa0040000 0x0 0x1000>, <0x0 0xa0041000 0x0 0x1000>;
reg-names = "vcu_slcr", "logicore";
encoder: al5e@a0000000 {
compatible = "al,al5e-1.2", "al,al5e";
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
reg = <0x0 0xa0000000 0x0 0x10000>;
};
decoder: al5d@a0020000 {
compatible = "al,al5d-1.2", "al,al5d";
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
reg = <0x0 0xa0020000 0x0 0x10000>;
};
};
相关文件
使用petalinux编译的文件系统中已有这些文件。
| 文件 | 说明 |
|---|---|
| xilinx_vcu.ko | VCU IP配置模块,内核源码中 |
| dmaproxy.ko allegro.ko al5e.ko al5d.ko | 编码器和解码器驱动,没在内核源码中,可以在github上找对应版本的vcu-modules |
| al5e_b.fw al5e.fw | 编码器的bootloader和应用程序,类似的还有解码器的,位于/lib/firmware,用户不能修改 |
内核驱动 vcu-modules
dmaproxy
- 为用户空间提供DMA调用的接口,使用内部的DMA控制器
- 注册字符设备 /dev/dmaproxy
- 内核:申请DMA通道 → \to →映射缓冲区 → \to →传输 → \to →清理资源
- 用户空间打开字符设备,调用ioctl
- 内核空间配置DMA传输,挂在等待队列上等待唤醒
- DMA传输完成后回调函数,唤醒对应事件
- 返回用户空间,结束阻塞
allegro
- VCU的MCU固件加载和启动接口
- 主控通信协议(Mailbox 与 MCU 交互)
- 队列与多用户管理(并发处理)
- 零拷贝内存管理(DMA-BUF 核心)
邮箱通信实现:基于共享内存实现的环形缓冲区(Ring Buffer)+ 中断,维护一个读指针和写指针,每条消息分为消息头和消息体,将整条消息存入缓冲区,然后触发中断通知对方读取。
al5d
- 注册字符设备 /dev/allegroDecodeIP,核心是和MCU进行通信控制
al5e
- 注册字符设备 /dev/allegroIP
用户接口 vcu-ctrl-sw
- https://github.com/Xilinx/vcu-ctrl-sw
- 编解码库以及命令行工具,操作/dev/allegroDecodeIP和/dev/allegroIP进行编解码
解码工具 ctrlsw_decoder
- 输出是NV12,即YUV420SP。
- 解码如果写入文件的话,帧率比较低。
bash
ctrlsw_decoder -in input_file.h264 -avc -out output_file.yuv
ctrlsw_decoder -in 1080P.h264 -avc -noyuv
bash
Allegro DVT2 - |AVC|HEVC| Decoder Reference Software v1.0.73 - Copyright (C) 2008-2023
Confidential material
Resolution: 1920x1088
FourCC: NV12
Profile: 100
Level: 42
Bitdepth: 8
Crop top: 0
Crop bottom: 8
Crop left: 0
Crop right: 0
Display resolution: 1920x1080
Sequence picture: progressive
Buffers needed: 8 of size 3342336
Displayed picture #433 - Complete
Decoded time = 2.4220 s; Decoding FrameRate ~ 179.1908 Fps; Frame(s) conceal = 0
编码工具 ctrlsw_encoder
- 使用vcu-ctrl-sw里的文件进行测试,配置文件指定参数。
bash
ctrlsw_encoder -cfg test/config/encode_example.cfg
bash
Allegro DVT2 - |AVC|HEVC| Encoder Reference Software v1.0.73 - Copyright (C) 2008-2023
Confidential material
Encoding picture #9 - Flushing...
Achieved bitrate = 703.2600 Kbps
10 pictures encoded. Average FrameRate = 1428.5714 Fps
标准化中间件 vcu-omx-il
- https://github.com/Xilinx/vcu-omx-il
- 基于vcu-ctrl-sw实现的一个标准的API,编译出来是一个库文件
- OpenMAX IL 集成层,OpenMAX Integration Layer (简称OpenMAX IL 或 OMX IL)
- 一个由 Khronos 集团(也是 OpenGL 标准的制定者)维护的开放、免版税的标准 API。
- 它的核心使命是提供一套标准化的接口,让上层应用(或多媒体框架)能够以一种统一的方式,去加载、控制、连接和卸载硬件或软件的多媒体编解码组件(如 H.264 编码器、JPEG 解码器),从而实现"一次编写,到处运行"的跨平台可移植性。
GStreamer插件 gst-plugins-vcu
- https://github.com/Xilinx/gst-plugins-vcu
- 命令行通过插件调用底层硬件编解码
软件模拟源测试
- 编码
- UDP推流
bash
gst-launch-1.0 -v videotestsrc is-live=true ! \
video/x-raw,format=NV12,width=1920,height=1080,framerate=60/1 ! \
omxh265enc periodicity-idr=60 ! \
video/x-h265,alignment=nal ! \
h265parse config-interval=1 ! \
mpegtsmux alignment=7 ! \
udpsink host=192.168.3.88 port=5004 sync=false

问题记录
-
Mcu didn't start (no ping after startup, is mcu enabled ?)
al5e和al5d驱动加载失败,重新编译FSBL后解决。原因是添加VCU IP后会自动使能PS的几个HP接口,需要重新编译FSBL(Bootloader)。如果未开启会导致MCU的AXI通信异常。以下是M_AXI_MCU接口异常和正常情况。
异常MCU AXI通信,读取数据大部分都是一样的,和DDR不一致。

正常MCU AXI通信。

-
Mcu didn't start (no ping after startup, is mcu enabled ?)
在AXI通信正常的情况下,依旧报这个错。判断MCU是否启动的原理是通过中断读取MCU消息,中断错误也会出现这个问题。多个PL到PS中断的话,自动生成VCU设备树的中断号和实际不符,比如仍为第一个89。
其他
CPU Cache取指缺失时,从DDR中取数据到Cache行中。这里取数据的过程用的是AXI4协议,传输类型是WRAP类型,只会取一个cache行的数据,如果长度超过后会从头开始。
比如一行是32B,第一个地址是0xD8,第二个是0xDC,再往后超过该行范围,第三个从0xC0开始开始。
