文章目录
- 1 实验任务
- 2 系统框图
- 3 硬件设计
- 3.1 IP核配置
- 3.2 注意事项
- 3.3 自定义IP核源码
- 4 软件设计
- 4.1 注意事项
- 4.2 工程源码
- 4.2.1 main.c文件
1 实验任务
基于14.1,相较于16.1,使用自定义IP核vid_gen_motion替换Xilinx TPG IP核。
2 系统框图
基于14.1,添加自定义IP核vid_gen_motion作为视频源,通过Video In to AXI4-Stream IP核转换后,连接到VDMA的写通道,如下图所示:
3 硬件设计
3.1 IP核配置
- 配置VDMA IP核
- (1)Basic页面
- 1)Frame Buffers:选择默认值3,即缓存3帧图像数据
- 2)Enable Write Channel:勾选,使能写通道
- 3)Write Burst Size:选择256,最大化传输效率
- 4)Line Buffer Depth:选择2048,图像分辨率为1920x1080,能够缓存一行像素数据
- (2)Advanced页面
- 1)保持默认值,采用动态同步锁相模式,写通道为主,读通道为从
- 1)保持默认值,采用动态同步锁相模式,写通道为主,读通道为从
- (1)Basic页面
- 配置Video In to AXI4-Stream IP核
- (1)Clock Mode:点选Independent
- (2)其他保持默认
- 配置VTC IP核
- (1)Include AXI4-Lite Interface:不勾选
- (2)Enable Generation:不勾选
- (3)Detection Options:保持默认
- 封装自定义IP核vid_gen_motion并添加,无需配置,默认输出1080p视频。
3.2 注意事项
- VTC IP核的Detection Options选择,官方文档pg016中有明确说明,如下图所示:
- (1)上图仅给出hsync和hblank信号的说明,vsync、vblank和data_valid信号亦如此
- 为各个接口自动连线:必须手动指定主从接口和互联模块的时钟
- (1)为VDMA的M_AXI_S2MM接口连线:从接口是PS的S_AXI_HP0接口
- (2)将Video In to AXI4-Stream和VDMA连接起来,为s_axis_s2mm_aclk和aclk时钟连线(选择PS输出的FCLK_CLK1时钟)
- (3)将Video In to AXI4-Stream和VTC连接起来,为VTC的clk时钟连线(选择Clocking Wizard输出的clk_out1时钟,即视频时钟)
- (4)将vid_gen_motion和Video In to AXI4-Stream连接起来,为clk和vid_io_in_clk时钟连线(选择Clocking Wizard输出的clk_out1时钟,即视频时钟)
- (1)为VDMA的M_AXI_S2MM接口连线:从接口是PS的S_AXI_HP0接口
3.3 自定义IP核源码
`timescale 1ns / 1psmodule vid_gen_motion (input wire clk,input wire clken,input wire rstn,output wire o_vid_vsync,output wire o_vid_vblank,output wire o_vid_hsync,output wire o_vid_hblank,output wire o_vid_active,output wire [23:0] o_vid_data
);
//***********************************************************************************************
// Constant Functions
//***********************************************************************************************//***********************************************************************************************
// Parameter Definitions
//***********************************************************************************************
// 1080p详细时序参数 (按照SMPTE 274M标准)
// 水平时序
parameter H_ACTIVE = 1920; // 有效视频像素数
parameter H_LEFT = 0; // 左侧黑边
parameter H_RIGHT = 0; // 右侧黑边
parameter H_FP = 88; // 行前沿(Front Porch)
parameter H_SYNC = 44; // 行同步脉冲
parameter H_BP = 148; // 行后沿(Back Porch)
parameter H_TOTAL = H_LEFT + H_ACTIVE + H_RIGHT + H_FP + H_SYNC + H_BP; // 2200// 垂直时序
parameter V_ACTIVE = 1080; // 有效视频行数
parameter V_TOP = 0; // 顶部黑边
parameter V_BOTTOM = 0; // 底部黑边
parameter V_FP = 4; // 场前沿(Front Porch)
parameter V_SYNC = 5; // 场同步脉冲
parameter V_BP = 36; // 场后沿(Back Porch)
parameter V_TOTAL = V_TOP + V_ACTIVE + V_BOTTOM + V_FP + V_SYNC + V_BP; // 1125// 方块参数
parameter SQUARE_SIZE = 20;
parameter SQUARE_SPEED = 1;// 关键边界定义 (保留完整边框参数)
// 水平方向 (严格按您要求的顺序: LEFT -> ACTIVE -> RIGHT -> FP -> SYNC -> BP)
localparam H_SYNC_START = H_LEFT + H_ACTIVE + H_RIGHT + H_FP;
localparam H_SYNC_END = H_SYNC_START + H_SYNC;
localparam H_ACTIVE_START = H_LEFT;
localparam H_ACTIVE_END = H_ACTIVE_START + H_ACTIVE;
localparam H_BLANK_START = H_LEFT + H_ACTIVE + H_RIGHT;
localparam H_BLANK_END = H_TOTAL;// 垂直方向 (严格按您要求的顺序: TOP -> ACTIVE -> BOTTOM -> FP -> SYNC -> BP)
localparam V_SYNC_START = V_TOP + V_ACTIVE + V_BOTTOM + V_FP;
localparam V_SYNC_END = V_SYNC_START + V_SYNC;
localparam V_ACTIVE_START = V_TOP;
localparam V_ACTIVE_END = V_ACTIVE_START + V_ACTIVE;
localparam V_BLANK_START = V_TOP + V_ACTIVE + V_BOTTOM;
localparam V_BLANK_END = V_TOTAL;
//***********************************************************************************************
// Signal Declarations
//***********************************************************************************************
reg [15:0] h_cnt;
reg [15:0] v_cnt;reg [15:0] square_x;
reg [15:0] square_y;reg x_dir; // 0=右移, 1=左移
reg y_dir; // 0=下移, 1=上移reg vid_vsync;
reg vid_vblank;
reg vid_hsync;
reg vid_hblank;
reg vid_active;
reg [23:0] vid_data;
//***********************************************************************************************
// Pipeline Inputs
//***********************************************************************************************//***********************************************************************************************
// Code
//***********************************************************************************************
// 水平计数器
always @(posedge clk or negedge rstn) beginif (!rstn) h_cnt <= 16'd0;else if (clken) begin if (h_cnt < H_TOTAL-1) h_cnt <= h_cnt + 1;else h_cnt <= 16'd0;end
end// 垂直计数器
always @(posedge clk or negedge rstn) beginif (!rstn) v_cnt <= 16'd0;else if (clken) begin if (h_cnt == H_TOTAL-1)if (v_cnt < V_TOTAL-1) v_cnt <= v_cnt + 1;else v_cnt <= 16'd0;elsev_cnt <= v_cnt; end
end// 方块移动逻辑(反弹模式)
always @(posedge clk or negedge rstn) beginif (!rstn) beginsquare_x <= 16'd0;square_y <= 16'd0;x_dir <= 0;y_dir <= 0;endelse if (clken) beginif (v_cnt == V_TOTAL-1 && h_cnt == H_TOTAL-1) begin// 水平移动if (x_dir == 0) begin // 向右移动if (square_x + SQUARE_SIZE + SQUARE_SPEED <= H_ACTIVE)square_x <= square_x + SQUARE_SPEED;else beginsquare_x <= H_ACTIVE - SQUARE_SIZE; // 贴住右边界x_dir <= 1; // 改为左移endendelse begin // 向左移动if (square_x >= SQUARE_SPEED)square_x <= square_x - SQUARE_SPEED;else beginsquare_x <= 0; // 贴住左边界x_dir <= 0; // 改为右移endend// 垂直移动if (y_dir == 0) begin // 向下移动if (square_y + SQUARE_SIZE + SQUARE_SPEED <= V_ACTIVE)square_y <= square_y + SQUARE_SPEED;else beginsquare_y <= V_ACTIVE - SQUARE_SIZE; // 贴住下边界y_dir <= 1; // 改为上移endendelse begin // 向上移动if (square_y >= SQUARE_SPEED)square_y <= square_y - SQUARE_SPEED;else beginsquare_y <= 0; // 贴住上边界y_dir <= 0; // 改为下移endendendend
end// 行同步信号生成
always @(posedge clk or negedge rstn) beginif (!rstn) vid_hsync <= 1'b0;else if (clken) begin if (h_cnt >= H_SYNC_START && h_cnt < H_SYNC_END)vid_hsync <= 1'b1;elsevid_hsync <= 1'b0;end
end// 行消隐信号生成
always @(posedge clk or negedge rstn) beginif (!rstn) vid_hblank <= 1'b0;else if (clken) begin if (h_cnt >= H_BLANK_START && h_cnt < H_BLANK_END)vid_hblank <= 1'b1;elsevid_hblank <= 1'b0;end
end// 场同步信号生成
always @(posedge clk or negedge rstn) beginif (!rstn) vid_vsync <= 1'b0;else if (clken) begin if (v_cnt >= V_SYNC_START && v_cnt < V_SYNC_END)vid_vsync <= 1'b1;elsevid_vsync <= 1'b0;end
end// 场消隐信号生成
always @(posedge clk or negedge rstn) beginif (!rstn) vid_vblank <= 1'b0;else if (clken) begin if (v_cnt >= V_BLANK_START && v_cnt < V_BLANK_END)vid_vblank <= 1'b1;elsevid_vblank <= 1'b0;end
end// 数据有效信号生成
always @(posedge clk or negedge rstn) beginif (!rstn) vid_active <= 1'b0;else if (clken) begin if (h_cnt >= H_ACTIVE_START && h_cnt < H_ACTIVE_END &&v_cnt >= V_ACTIVE_START && v_cnt < V_ACTIVE_END)vid_active <= 1'b1;elsevid_active <= 1'b0;end
end// 视频数据生成
always @(posedge clk or negedge rstn) beginif (!rstn) beginvid_data <= 24'hFFFFFF; // 默认白色背景endelse if (clken) begin if (h_cnt >= H_ACTIVE_START && h_cnt < H_ACTIVE_END &&v_cnt >= V_ACTIVE_START && v_cnt < V_ACTIVE_END)// 检查当前像素是否在方块区域内if (h_cnt >= square_x && h_cnt < square_x + SQUARE_SIZE &&v_cnt >= square_y && v_cnt < square_y + SQUARE_SIZE)vid_data <= 24'h000000; // 黑色方块elsevid_data <= 24'hFFFFFF; // 白色背景else beginvid_data <= 24'd0; // 消隐区输出0end end
end
//***********************************************************************************************
// Outputs
//***********************************************************************************************
assign o_vid_vsync = vid_vsync;
assign o_vid_vblank = vid_vblank;
assign o_vid_hsync = vid_hsync;
assign o_vid_hblank = vid_hblank;
assign o_vid_active = vid_active;
assign o_vid_data = vid_data;endmodule
4 软件设计
4.1 注意事项
- 自定义IP核vid_gen_motion生成一个背景为纯白,叠加一个黑色移动小方块的视频
- 通过VIO控制vid_gen_motion的clken信号
- (1)当clken拉低时
- 1)vid_gen_motion IP核暂停产生视频数据
- 2)显示器上小方块停止移动
- (2)当clken拉高时
- (1)vid_gen_motion IP核恢复产生视频数据
- (2)显示器上小方块继续从停止的位置开始移动
- (3)关于VTC输出的locked信号
- 1)控制Video In to AXI4-Stream的axis_stream信号,该信号"enable/disable writes into FIFO"
- 2)在clken信号先拉底后拉高的过程中会有一个失锁(locked=0)和重新锁定(locked=1)的过程
- 3)测试发现:在clken拉低后
- locked信号并未拉低(下图中axis_stream信号依然为高)
- Video In to AXI4-Stream的Video In接口,同步和消隐信号保持为低,active_video保持为高,该现象和clken停止的时机有关
- Video In to AXI4-Stream的AXI4-Stream接口,TREADY和TVALID信号一直为高,说明Video In to AXI4-Stream一直向VDMA写通道提供数据;但是,因为TUSER和TLAST信号的缺失(一直保持为低),VDMA写通道未将Video In to AXI4-Stream提供的数据写入PS侧DDR中(因为显示器上小方块停止移动)
- 4)测试发现:在clken拉高后
- locked信号会先拉低后拉高,表示VTC经过一个失锁然后再次锁定的过程,如下图所示
- locked信号会先拉低后拉高,表示VTC经过一个失锁然后再次锁定的过程,如下图所示
- 5)测试发现:locked信号有时也会在clken拉低后立即拉低,应该与clken拉低的时机和VTC的检测机制有关系
- 6)测试证明:VTC和VDMA的视频帧隔离和处理功能很强大
- (1)当clken拉低时
- 通过PS控制VDMA写通道启停
- (1)当VDMA写通道停止时
- 1)VDMA写通道暂停接收视频数据
- Video In to AXI4-Stream的AXI4-Stream接口的TREADY信号拉低,如下图所示
- Video In to AXI4-Stream的FIFO仍在接收vid_gen_motion提供的视频数据,导致FIFO溢出,overflow信号不停拉高(通过观察应该是在每一行开始后不久拉高一个时钟周期),如下图所示
- Video In to AXI4-Stream的AXI4-Stream接口的TREADY信号拉低,如下图所示
- 2)VDMA读通道依然在发送数据
- 显示器上小方块停止移动
- 说明VDMA读通道在重复发送同一帧视频数据,该帧视频数据是VDMA写通道在停止前写入的最后一帧
- 符合动态同步锁相模式的工作机制,即读通道(Dynamic Genlock Slave)会操作写通道(Dynamic Genlock Master)上一个周期操作的帧
- 1)VDMA写通道暂停接收视频数据
- (2)当VDMA写通道重启据时
- 1)Video In to AXI4-Stream的FIFO不再溢出
- 2)显示器上小方块继续开始移动,但起始位置已不再是之前停止的位置,而是vid_gen_motion产生的视频数据中小方块的当前位置
- (3)再次证明:VTC和VDMA的视频帧隔离和处理功能的强大
- (1)当VDMA写通道停止时
4.2 工程源码
4.2.1 main.c文件
/********************************************************************/#include "vdma/vdma_api.h"#include "xparameters.h"
#include "stdio.h"
#include "sleep.h"/********************************************************************/#define VDMA_DEVICE_ID XPAR_AXIVDMA_0_DEVICE_ID
#define MEMORY_BASEADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR#define IMAGE_WIDTH 1920
#define IMAGE_HEIGHT 1080/********************************************************************//********************************************************************/XAxiVdma VdmaInst;int FrameBufferAddr = (MEMORY_BASEADDR + 0x02000000);/********************************************************************/int main()
{//int Status;//printf("Vdma Video Forward Test.\n");// 启动VDMA读写操作Status = run_vdma_frame_buffer(&VdmaInst, VDMA_DEVICE_ID, IMAGE_WIDTH, IMAGE_HEIGHT, FrameBufferAddr, 0, 0, BOTH);if (Status == XST_FAILURE) {printf("Error : run vdma frame buffer failed.\n");return XST_FAILURE;}//while (1) {//sleep(10);printf("Stop vdma channel.\n");
// XAxiVdma_DmaStop(&VdmaInst, XAXIVDMA_READ);XAxiVdma_DmaStop(&VdmaInst, XAXIVDMA_WRITE);//sleep(15);printf("Start vdma channel.\n");
// XAxiVdma_DmaStart(&VdmaInst, XAXIVDMA_READ);XAxiVdma_DmaStart(&VdmaInst, XAXIVDMA_WRITE);}//return XST_SUCCESS;
}/*****************************************************************************//*****************************************************************************/