文章目錄
- 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;
}/*****************************************************************************//*****************************************************************************/