很多人用zynq平臺做視頻圖像開發(fā),但是對vdma了解比較少,上手起來稍微有些困難,我針對這一現(xiàn)象,做了一個基于vivado和modelsim的仿真和應(yīng)用測試工程,并寫篇文章做些介紹,希望能對大家有幫助。
一:xilinx vdma IP例化以及接口介紹
上面圖片就是在vivado2015.4中例化vdma的界面,首先對參數(shù)做些介紹:
Frame Buffers :選擇vdma緩存幾幀圖像,這里默認是寫通道和讀通道都設(shè)置相同的緩存幀數(shù),具體設(shè)置多少幀合適一般根據(jù)應(yīng)用來定,比如讀寫帶寬相同,想用ddr作為一個乒乓buffer,那就可以設(shè)置成2幀,寫第一個地址,讀第二個地址,寫第二個地址,讀第一個地址。這里面設(shè)置幾幀,就要在vdma寄存器配置的時候設(shè)置幾個幀起始地址。
Memory Map Data Width:代表數(shù)據(jù)到達AXI4總線上的位寬,比如這里設(shè)置成64,那就代表M_AXI_XX總線上的數(shù)據(jù)位寬是64bit,這時候如果stream上的數(shù)據(jù)是32bit,那vdma內(nèi)部會有一個帶寬轉(zhuǎn)換模塊,把數(shù)據(jù)拼成64bit。
Burst Size : AXI總線上突發(fā)傳輸?shù)拈L度,一般設(shè)置為16
Stream Data Width:vdma與pl邏輯部分通過axi stream協(xié)議交互數(shù)據(jù),這里代表stream數(shù)據(jù)位寬
Line Buffer Depth:vdma內(nèi)部會有一個行緩存fifo,stream數(shù)據(jù)會先寫入fifo,然后AXI總線邏輯會讀出到總線上,這個深度就代表fifo的深度。設(shè)置原則(個人理解):如果AXI總線數(shù)據(jù)帶寬是stream總線數(shù)據(jù)帶寬的1.5倍以上,這個fifo深度可以設(shè)置的小一點,如果AXI總線帶寬小于1.5倍的stream總線帶寬,那fifo的深度至少要是圖像一個有效行的一半。
Advanced : 這里面只說一下Fsync Options,這個信號是什么意思呢,就是告訴vdma什么時候開始運行,一般s2mm通道選擇tuser,就是說在tuser 拉高的時候開始傳輸。mm2s通道,可以選擇none,也可以選擇 mm2s_fsync,這里介紹一下這兩個的區(qū)別。
none : 就是沒有同步信號,但這并不是說沒有開始信號,而是只要mm2s_stream通道tready拉高,就開始傳輸,相當(dāng)于free模式
mm2s_fsync:當(dāng)這個信號發(fā)生一個下降沿的時候開始傳輸,如果沒有這個下降沿,即使mm2s_stream通道tready拉高也不會傳輸
下面是接口介紹:
M_AXI_XX : axi4總線接口,用來與ddr交互數(shù)據(jù)
M_AXIS_XX , S_AXIS_XX : axi stream接口,用來與pl交互數(shù)據(jù)
S_AXI_LITE :控制總線,接到ps的gp口或者寫一個axilite master總線去配置
其他接口不做介紹
二:下面開始一步步的詳解如何搭建一個vdma的仿真工程
FPGA的開發(fā),離不開仿真,很少有人能直接寫好代碼上板就成功的,仿真必不可少。但是有些應(yīng)用要用到vdma,vdma又要和ddr做數(shù)據(jù)交互,這樣做起來就很麻煩了,我這里就實現(xiàn)了一個簡單的方法,可以測試vdma,又不用去例化MIG搞什么ddr。下面開始!
系統(tǒng)框圖:
(1)因為是要仿真vdma,vdma顧名思義就是video dma,那肯定要先做一個視頻模塊,注意,我這里除了vdma和fifo用xilinx的ip。其他的都不用ip,這樣更通用性。
我這里就把這個視頻發(fā)生模塊叫做sensor,可以理解為xilin的tpg模塊,sensor模塊的接口如下:
module sensor
(
input rst,
input clk,
output reg vsync,
output reg hsync,
output reg de,
output reg vblank,
output reg[31:0]pix_out
);
parameter SENSOR_ACT_W = 640;
parameter SENSOR_ACT_H = 480;
parameter SENSOR_WIDTH = 800;
parameter SENSOR_HEIGHT = 600;
parameter H_START = 80;
parameter V_START = 60;
我這里構(gòu)建了一個圖像傳感器,總像素數(shù)是600*800,有效像素是 480*640,水平有效像素開始位置是80,垂直有效像素開始位置是60,這個模塊會讀取一個本地圖像數(shù)據(jù),rgb格式,這里為了測試方便,直接把像素輸出位寬設(shè)置為32bit。
(2)video轉(zhuǎn)axis模塊,相當(dāng)于 xilinx的vid in to stream模塊,接口如下:
module video2axis #
(
parameter DW = 32,
parameter WIDTH = 640,
parameter HEIGHT = 480
)
(
input axis_clk,
input axis_aresetn,
// axis
input reg_axis_s2mm_start,
output [DW-1:0] m_axis_tdata,
output [DW/8-1:0] m_axis_tkeep,
output reg m_axis_tvalid,
output m_axis_tlast,
output m_axis_tuser,
input m_axis_tready,
// video data
input video_clk,
input video_rst,
input video_hsync,
input video_vsync,
input video_hblank,
input video_vblank,
input video_de,
input [DW-1:0] video_data
);
這個模塊主要用到一個fifo來做數(shù)據(jù)緩存,只要注意一下stream協(xié)議的握手操作即可,由于stream協(xié)議比較簡單,這里就不多說了。至此,video數(shù)據(jù)就轉(zhuǎn)換到了stream數(shù)據(jù)。
(3)axis轉(zhuǎn)video模塊,接口如下:
module axis2video#
(
parameter DW = 32,
parameter WIDTH = 640,
parameter HEIGHT = 480
)
(
input axis_clk,
input axis_aresetn,
// axis
input reg_axis_mm2s_start,
input [DW-1:0] s_axis_tdata,
input s_axis_tvalid,
input s_axis_tlast,
input s_axis_tuser,
output reg s_axis_tready,
// video data
input video_clk,
input video_rst,
input video_hsync_i,
input video_vsync_i,
input video_hblank_i,
input video_vblank_i,
input video_de_i,
output video_hsync_o,
output video_vsync_o,
output video_hblank_o,
output video_vblank_o,
output video_de_o,
output reg[DW-1:0] video_data
);
這個模塊相當(dāng)于xilinx的vid out模塊,我這里是簡化版的,xilinx的ip寫的太復(fù)雜了,而且不容易用起來,其實也就是用一個fifo做數(shù)據(jù)緩存,然后根據(jù)外部video時序從fifo讀出到輸出。
(4)video timing gen模塊,接口如下:
module video_timing_gen #
(
parameter SENSOR_ACT_W = 640,
parameter SENSOR_ACT_H = 480,
parameter SENSOR_WIDTH = 800,
parameter SENSOR_HEIGHT = 600,
parameter SENSOR_HSYNC_START = 0,
parameter SENSOR_HSYNC_STOP = 40,
parameter SENSOR_VSYNC_START = 0,
parameter SENSOR_VSYNC_STOP = 4
)
(
input rst_n,
input video_clk,
input [12:0]reg_h_start,
input [12:0]reg_v_start,
output reg vsync,
output reg hsync,
output reg de,
output reg vblank,
output reg hblank
);
此模塊產(chǎn)生視頻時序,提供給 axis2video模塊,相當(dāng)于xilinx的vtc模塊。
(5) axi slave模塊,接口如下:
`define C_S_AXI_ADDR_WIDTH 32
module axi_slave #(
parameter integer C_S_AXI_ID_WIDTH = 6,
parameter integer C_S_AXI_DATA_WIDTH = 32
) (
input wire S_AXI_ACLK,
input wire S_AXI_ARESETN,
input wire [C_S_AXI_ID_WIDTH-1:0] S_AXI_AWID,
input wire [`C_S_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR,
input wire [7:0] S_AXI_AWLEN,
input wire [2:0] S_AXI_AWSIZE,
input wire [1:0] S_AXI_AWBURST,
input wire S_AXI_AWVALID,
output wire S_AXI_AWREADY,
input wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_WDATA,
input wire [C_S_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB,
input wire S_AXI_WLAST,
input wire S_AXI_WVALID,
output wire S_AXI_WREADY,
output wire [C_S_AXI_ID_WIDTH-1:0] S_AXI_BID,
output wire [1:0] S_AXI_BRESP,
output wire S_AXI_BVALID,
input wire S_AXI_BREADY,
input wire [C_S_AXI_ID_WIDTH-1:0] S_AXI_ARID,
input wire [`C_S_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR,
input wire [7:0] S_AXI_ARLEN,
input wire [2:0] S_AXI_ARSIZE,
input wire [1:0] S_AXI_ARBURST,
input wire S_AXI_ARVALID,
output wire S_AXI_ARREADY,
output wire [C_S_AXI_ID_WIDTH-1:0] S_AXI_RID,
output wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_RDATA,
output wire [1:0] S_AXI_RRESP,
output wire S_AXI_RLAST,
output wire S_AXI_RVALID,
input wire S_AXI_RREADY
);
這塊模塊是根據(jù)xilinx官方提供的參考設(shè)計基礎(chǔ)上修改而來的(xapp1168),協(xié)議部分完全沒有改動,這里拿他當(dāng)做ddr,具體修改是這樣的,
reg [31:0] mem [32’h01000000:0];
用寄存器組來模擬ddr
此模塊會根據(jù)axi master的時序來計算出要讀寫的地址
assign write_mem_address = axi_awv_awr_flag ? axi_awaddr: 0;
assign read_mem_address = axi_arv_arr_flag ? axi_araddr: 0;
寫操作:mem[write_mem_address>>2] <= #1 S_AXI_WDATA;
讀操作:mem_data_out <= mem[read_mem_address>>2];
做此修改以后,這個模塊就可以當(dāng)做ddr來用,為仿真提供了很大的方便
(6)vdma模塊,這個就用xilinx的vdma ip,注意,我這里不是在block design里面例化,所以端口需要自己在hdl里面做連接的。
這里還有一個模塊是 axi lite master模塊,作用是用來配置vdma的寄存器,這個模塊也是xilinx提供的,只需要做小量修改即可
always @(write_index)
begin
case (write_index)
// AXI VDMA 0 Set Up
1: awaddr <= 32'h43000030;
2: awaddr <= 32'h43c000ac;
3: awaddr <= 32'h43c000b0;
4: awaddr <= 32'h43c000a8;
5: awaddr <= 32'h43c000a4;
6: awaddr <= 32'h43c000a0;
7: awaddr <= 32'h43000000;
8: awaddr <= 32'h43c0005c;
9: awaddr <= 32'h43c00060;
10: awaddr <= 32'h43c00058;
11: awaddr <= 32'h43c00054;
12: awaddr <= 32'h43c00050;
default: awaddr <= 32'h00000000;
endcase
end
always @(write_index)
begin
case (write_index)
// AXI VDMA 0 Set Up
1: wdata <= 32'h00000003;
2: wdata <= 32'h00800000;
3: wdata <= 32'h00000000;
4: wdata <= 640*4;
5: wdata <= 640*4;
6: wdata <= 480;
7: wdata <= 32'h00000003;
8: wdata <= 32'h00000000;
9: wdata <= 32'h00800000;
10: wdata <= 640*4;
11: wdata <= 640*4;
12: wdata <= 480;
default: wdata <= 32'h00000000;
endcase
end
我這里是把ddr作為一個乒乓buffer,所以vdma緩存幀數(shù)選擇2幀,寄存器配置里面就配置兩個傳輸?shù)刂?。這樣就完成了對vdma的寄存器配置。
編寫test bench top文件,把這些模塊連接起來,至此,仿真工程就全部寫好了。
三:仿真
(1)vdma工作流程介紹
第一步,對vdma寄存器進行配置,并打開使能,這時候vdma處于待命狀態(tài),什么時候開始傳輸呢,下面詳細介紹
對于S2MM通道:之前在講vdma配置的時候有一個Advanced選項,里面有Fsync Options選項,可選none,s2mm_fsync,s2mm_tuer,三種同步模式。
none就是只要vdma就緒,就立馬準備接收數(shù)據(jù),不需要同步信號。
s2mm_fsync,當(dāng)選擇此模式時,vdma 模塊會有一個s2mm_fsync引腳,一般情況下是把視頻幀同步信號連到這上面,當(dāng)檢測到s2mm_fsync引腳有一個下降沿的時候,vdma正式進入傳輸狀態(tài)。
s2mm_tuer,這個信號和s2mm_fsync這個信號類似,但他是在stream協(xié)議里面的,vdma檢測到s2mm_tuer拉高以后(tuser只在一幀數(shù)據(jù)的第一個像素位置拉高),正式進入傳輸狀態(tài)
對于MM2S通道,同樣在vdma配置的Advanced選項里面有 none,mm2s_fsync兩種選擇模式。
none不需要同步信號,只要axis_mm2s通道的tready拉高,就開始從ddr讀取數(shù)據(jù)進行傳輸,選擇這種模式一般主要是把ddr里面的數(shù)據(jù)讀到pl里面進行處理,而不是轉(zhuǎn)成視頻
mm2s_fsync,選擇此同步模式,一般是把ddr的數(shù)據(jù)轉(zhuǎn)成視頻數(shù)據(jù),注意,這里重點講這個同步模式,當(dāng)vdma的讀通道選擇此同步模式的時候,vdma模塊會有一個mm2s_fsync信號,這個信號在讀操作中非常重要。當(dāng)vdma寄存器配置完成并開啟傳輸,mm2s通道進入等待過程,一直等到mm2s引腳信號出現(xiàn)一個下降沿,這時候vdma啟動讀操作,會從ddr預(yù)讀一些數(shù)據(jù)到內(nèi)部linebuffer,等到axis_mm2s通道的tready信號拉高,數(shù)據(jù)就開始傳輸,進入axis2video模塊的fifo,當(dāng)axis2video內(nèi)部fifo滿了以后,會拉低tready,這時候就會反饋到vdma,暫停讀操作,一直等到axis2video模塊的視頻時序輸入數(shù)據(jù)有效信號,這時候視頻開始輸出,axis2video內(nèi)部fifo數(shù)據(jù)減少,axis_mm2s通道開始恢復(fù)傳輸,繼續(xù)從vdma讀出數(shù)據(jù),vdma再通過axi總線從ddr讀取數(shù)據(jù),如此反復(fù),完成ddr數(shù)據(jù)到video數(shù)據(jù)的轉(zhuǎn)換
(2)仿真實踐
首先在vivado平臺例化一個vdma ip,然后添加進上述的各個模塊,代碼層級如下:
在tb_top里面對各個模塊做連接,這部分源碼如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// Engineer: EEPROM
//
//////////////////////////////////////////////////////////////////////////////////
module tb_top();
reg reset = 1'b1;
reg video_clk = 1'b0;
reg axis_clk = 1'b0;
reg axi_clk = 1'b0;
wire axi_lite_clk = video_clk;
wire sensor_vsync;
wire sensor_hsync;
wire sensor_de;
wire sensor_vblank;
wire [31:0]sensor_data;
wire [31:0]axis_s2mm_tdata;
wire axis_s2mm_tvalid;
wire axis_s2mm_tlast;
wire axis_s2mm_tuser;
wire axis_s2mm_tready;
wire [31:0]axis_mm2s_tdata;
wire axis_mm2s_tvalid;
wire axis_mm2s_tlast;
wire axis_mm2s_tuser;
wire axis_mm2s_tready;
//axi lite
wire [31:0]axi_lite_master_araddr;
wire [2:0]axi_lite_master_arprot;
wire axi_lite_master_arready;
wire axi_lite_master_arvalid;
wire [31:0]axi_lite_master_awaddr;
wire [2:0]axi_lite_master_awprot;
wire axi_lite_master_awready;
wire axi_lite_master_awvalid;
wire axi_lite_master_bready;
wire [1:0]axi_lite_master_bresp;
wire axi_lite_master_bvalid;
wire [31:0]axi_lite_master_rdata;
wire axi_lite_master_rready;
wire [1:0]axi_lite_master_rresp;
wire axi_lite_master_rvalid;
wire [31:0]axi_lite_master_wdata;
wire axi_lite_master_wready;
wire [3:0]axi_lite_master_wstrb;
wire axi_lite_master_wvalid;
// axi
wire [31:0] S_AXI_awaddr ;
wire [1:0] S_AXI_awburst;
wire [3:0] S_AXI_awcache;
wire [5:0] S_AXI_awid ;
wire [7:0] S_AXI_awlen ;
wire [1:0] S_AXI_awlock ;
wire [2:0] S_AXI_awprot ;
wire [3:0] S_AXI_awqos ;
wire S_AXI_awready;
wire [2:0] S_AXI_awsize ;
wire S_AXI_awvalid;
wire [5:0] S_AXI_bid ;
wire S_AXI_bready ;
wire [1:0] S_AXI_bresp ;
wire S_AXI_bvalid ;
wire [31:0] S_AXI_wdata ;
wire [5:0] S_AXI_wid ;
wire S_AXI_wlast ;
wire S_AXI_wready ;
wire [3:0] S_AXI_wstrb ;
wire S_AXI_wvalid ;
wire S_AXI_arready;
wire S_AXI_rlast;
wire S_AXI_rvalid;
wire [1:0] S_AXI_rresp;
wire [31:0] S_AXI_rdata;
wire [5:0] S_AXI_rid;
wire S_AXI_arvalid;
wire S_AXI_rready;
wire [1:0] S_AXI_arburst;
wire [1:0] S_AXI_arlock;
wire [2:0] S_AXI_arsize;
wire [2:0] S_AXI_arprot;
wire [31:0] S_AXI_araddr;
wire [3:0] S_AXI_arcache;
wire [7:0] S_AXI_arlen;
wire [3:0] S_AXI_arqos;
wire [5:0] S_AXI_arid;
initial begin
#100;
reset=0;
end
always # 10 video_clk = ~video_clk;
always # 5 axis_clk = ~axis_clk;
always # 2 axi_clk = ~axi_clk;
sensor u_sensor
(
.rst (reset),
.clk (video_clk),
.vsync (sensor_vsync),
.hsync (sensor_hsync),
.de (sensor_de),
.vblank (sensor_vblank),
.pix_out (sensor_data)
);
video2axis u_video2axis
(
.axis_clk (axis_clk),
.axis_aresetn (!reset),
.reg_axis_start (1'b1),
.m_axis_tdata (axis_s2mm_tdata ),
.m_axis_tkeep (axis_s2mm_tkeep ),
.m_axis_tvalid (axis_s2mm_tvalid),
.m_axis_tlast (axis_s2mm_tlast ),
.m_axis_tuser (axis_s2mm_tuser ),
.m_axis_tready (axis_s2mm_tready),
.video_clk (video_clk),
.video_rst (reset),
.video_hsync (sensor_hsync),
.video_vsync (sensor_vsync),
.video_hblank (1'b0),
.video_vblank (sensor_vblank),
.video_de (sensor_de),
.video_data (sensor_data)
);
wire vid_hsync_i;
wire vid_vsync_i;
wire vid_hblank_i;
wire vid_vblank_i;
wire vid_de_i;
wire vid_hsync_o;
wire vid_vsync_o;
wire vid_hblank_o;
wire vid_vblank_o;
wire vid_de_o;
wire [31:0] vid_video_o;
video_timing_gen
#(
.SENSOR_ACT_W (640),
.SENSOR_ACT_H (480),
.SENSOR_WIDTH (800),
.SENSOR_HEIGHT (600),
.SENSOR_HSYNC_START (0 ),
.SENSOR_HSYNC_STOP (40),
.SENSOR_VSYNC_START (0 ),
.SENSOR_VSYNC_STOP (4 )
)
u_video_timing_gen
(
.rst_n (!reset),
.video_clk (video_clk),
.reg_h_start (80),
.reg_v_start (60),
.vsync (vid_vsync_i),
.hsync (vid_hsync_i),
.de (vid_de_i),
.vblank (vid_vblank_i),
.hblank (vid_hblank_i)
);
axis2video u_axis2video
(
.axis_clk (axis_clk),
.axis_aresetn (!reset),
.reg_axis_mm2s_start (1'b1),
.s_axis_tdata (axis_mm2s_tdata),
.s_axis_tvalid (axis_mm2s_tvalid),
.s_axis_tlast (axis_mm2s_tlast),
.s_axis_tuser (axis_mm2s_tuser),
.s_axis_tready (axis_mm2s_tready),
.video_clk (video_clk),
.video_rst (reset),
.video_hsync_i (vid_hsync_i),
.video_vsync_i (vid_vsync_i),
.video_hblank_i (vid_hblank_i),
.video_vblank_i (vid_vblank_i),
.video_de_i (vid_de_i),
.video_hsync_o (vid_hsync_o),
.video_vsync_o (vid_vsync_o),
.video_hblank_o (vid_hblank_o),
.video_vblank_o (vid_vblank_o),
.video_de_o (vid_de_o),
.video_data (vid_video_o)
);
axi_lite_master u_axi_lite_master
(
.M_AXI_ACLK (axi_lite_clk ),
.M_AXI_ARESETN (!reset ),
.M_AXI_AWADDR (axi_lite_master_awaddr ), //[8:0]
.M_AXI_AWPROT ( ),
.M_AXI_AWVALID (axi_lite_master_awvalid ),
.M_AXI_AWREADY (axi_lite_master_awready ),
.M_AXI_WDATA (axi_lite_master_wdata ),
.M_AXI_WSTRB ( ),
.M_AXI_WVALID (axi_lite_master_wvalid ),
.M_AXI_WREADY (axi_lite_master_wready ),
.M_AXI_BRESP (axi_lite_master_bresp ),
.M_AXI_BVALID (axi_lite_master_bvalid ),
.M_AXI_BREADY (axi_lite_master_bready ),
.M_AXI_ARADDR (axi_lite_master_araddr ), //[8:0]
.M_AXI_ARPROT ( ),
.M_AXI_ARVALID (axi_lite_master_arvalid ),
.M_AXI_ARREADY (axi_lite_master_arready ),
.M_AXI_RDATA (axi_lite_master_rdata ),
.M_AXI_RRESP (axi_lite_master_rresp ),
.M_AXI_RVALID (axi_lite_master_rvalid ),
.M_AXI_RREADY (axi_lite_master_rready ),
.DDRX_PHY_INIT_DONE(1'b1),
.DONE_SUCCESS ( )
);
axi_vdma_test u_axi_vdma_test (
.s_axi_lite_aclk(axi_lite_clk),
.m_axi_mm2s_aclk(axi_clk),
.m_axis_mm2s_aclk(axis_clk),
.m_axi_s2mm_aclk(axi_clk),
.s_axis_s2mm_aclk(axis_clk),
.axi_resetn(!reset),
.s_axi_lite_awvalid(axi_lite_master_awvalid),
.s_axi_lite_awready(axi_lite_master_awready),
.s_axi_lite_awaddr (axi_lite_master_awaddr[8:0]),
.s_axi_lite_wvalid (axi_lite_master_wvalid),
.s_axi_lite_wready (axi_lite_master_wready),
.s_axi_lite_wdata (axi_lite_master_wdata),
.s_axi_lite_bresp (axi_lite_master_bresp),
.s_axi_lite_bvalid (axi_lite_master_bvalid),
.s_axi_lite_bready (axi_lite_master_bready),
.s_axi_lite_arvalid(axi_lite_master_arvalid),
.s_axi_lite_arready(axi_lite_master_arready),
.s_axi_lite_araddr (axi_lite_master_araddr[8:0]),
.s_axi_lite_rvalid (axi_lite_master_rvalid),
.s_axi_lite_rready (axi_lite_master_rready),
.s_axi_lite_rdata (axi_lite_master_rdata),
.s_axi_lite_rresp (axi_lite_master_rresp),
//.s2mm_frame_ptr_in(),
//.s2mm_frame_ptr_out(),
.m_axi_s2mm_awaddr (S_AXI_awaddr),
.m_axi_s2mm_awlen (S_AXI_awlen),
.m_axi_s2mm_awsize (S_AXI_awsize),
.m_axi_s2mm_awburst(S_AXI_awburst),
.m_axi_s2mm_awprot (S_AXI_awprot),
.m_axi_s2mm_awcache(S_AXI_awcache),
.m_axi_s2mm_awvalid(S_AXI_awvalid),
.m_axi_s2mm_awready(S_AXI_awready),
.m_axi_s2mm_wdata (S_AXI_wdata),
.m_axi_s2mm_wstrb (S_AXI_wstrb),
.m_axi_s2mm_wlast (S_AXI_wlast),
.m_axi_s2mm_wvalid (S_AXI_wvalid),
.m_axi_s2mm_wready (S_AXI_wready),
.m_axi_s2mm_bresp (S_AXI_bresp),
.m_axi_s2mm_bvalid (S_AXI_bvalid),
.m_axi_s2mm_bready (S_AXI_bready),
.m_axi_mm2s_araddr (S_AXI_araddr),
.m_axi_mm2s_arlen (S_AXI_arlen),
.m_axi_mm2s_arsize (S_AXI_arsize),
.m_axi_mm2s_arburst(S_AXI_arburst),
.m_axi_mm2s_arprot (S_AXI_arprot),
.m_axi_mm2s_arcache(S_AXI_arcache),
.m_axi_mm2s_arvalid(S_AXI_arvalid),
.m_axi_mm2s_arready(S_AXI_arready),
.m_axi_mm2s_rdata (S_AXI_rdata),
.m_axi_mm2s_rresp (S_AXI_rresp),
.m_axi_mm2s_rlast (S_AXI_rlast),
.m_axi_mm2s_rvalid (S_AXI_rvalid),
.m_axi_mm2s_rready (S_AXI_rready),
.s_axis_s2mm_tdata(axis_s2mm_tdata),
.s_axis_s2mm_tkeep(4'b1111),
.s_axis_s2mm_tuser(axis_s2mm_tuser),
.s_axis_s2mm_tvalid(axis_s2mm_tvalid),
.s_axis_s2mm_tready(axis_s2mm_tready),
.s_axis_s2mm_tlast(axis_s2mm_tlast),
.m_axis_mm2s_tdata(axis_mm2s_tdata),
.m_axis_mm2s_tkeep(),
.m_axis_mm2s_tuser(axis_mm2s_tuser),
.m_axis_mm2s_tvalid(axis_mm2s_tvalid),
.m_axis_mm2s_tready(axis_mm2s_tready),
.m_axis_mm2s_tlast(axis_mm2s_tlast),
.mm2s_fsync(!vid_vsync_i),
.s2mm_introut()
);
axi_slave u_axi_slave
(
.S_AXI_ACLK (axi_clk),
.S_AXI_ARESETN (!reset),
.S_AXI_AWID (S_AXI_awid ),
.S_AXI_AWADDR (S_AXI_awaddr ),
.S_AXI_AWLEN (S_AXI_awlen ),
.S_AXI_AWSIZE (S_AXI_awsize ),
.S_AXI_AWBURST (S_AXI_awburst),
.S_AXI_AWVALID (S_AXI_awvalid),
.S_AXI_AWREADY (S_AXI_awready),
.S_AXI_WDATA (S_AXI_wdata ),
.S_AXI_WSTRB (S_AXI_wstrb ),
.S_AXI_WLAST (S_AXI_wlast ),
.S_AXI_WVALID (S_AXI_wvalid ),
.S_AXI_WREADY (S_AXI_wready ),
.S_AXI_BID (S_AXI_bid ),
.S_AXI_BRESP (S_AXI_bresp ),
.S_AXI_BVALID (S_AXI_bvalid ),
.S_AXI_BREADY (S_AXI_bready ),
.S_AXI_ARID (S_AXI_arid ),
.S_AXI_ARADDR (S_AXI_araddr ),
.S_AXI_ARLEN ({4'b0,S_AXI_arlen} ),
.S_AXI_ARSIZE (S_AXI_arsize ),
.S_AXI_ARBURST (S_AXI_arburst),
.S_AXI_ARVALID (S_AXI_arvalid),
.S_AXI_ARREADY (S_AXI_arready),
.S_AXI_RID (S_AXI_rid ),
.S_AXI_RDATA (S_AXI_rdata ),
.S_AXI_RRESP (S_AXI_rresp ),
.S_AXI_RLAST (S_AXI_rlast ),
.S_AXI_RVALID (S_AXI_rvalid ),
.S_AXI_RREADY (S_AXI_rready )
);
integer file_fd;
reg vblank_o_r;
reg [1:0] frame_cnt=0;
always @(posedge video_clk) vblank_o_r <= vid_vblank_o;
always @(posedge video_clk)
begin
if((~vblank_o_r) & vid_vblank_o)
frame_cnt <= frame_cnt + 1'b1;
end
initial begin
file_fd = $fopen("output.rgb","wb");
end
always @(posedge video_clk)
begin
if(frame_cnt == 1 && vid_de_o)
begin
$fwrite(file_fd,"%c%c%c%c",vid_video_o[31:24],vid_video_o[23:16],vid_video_o[15:8],vid_video_o[7:0]);
end
else if(frame_cnt == 2)
begin
$fclose(file_fd);
$stop;
end
end
endmodule
接下來,就要準備仿真用數(shù)據(jù)了,我這里用matlab將一副圖片的圖像數(shù)據(jù)取出來,寫成rgb文件,當(dāng)做視頻數(shù)據(jù)源
image=imread('test.bmp');
image=imresize(image,[480,640]);
imshow(image);
w=640;
h=480;
fd=fopen('input.rgb','wb');
for i=1:h
for j=1:w
pix=[0,image(i,j,1),image(i,j,2),image(i,j,3)];
fwrite(fd,pix','uint8');
end
end
fclose(fd);
接下來在sensor模塊里面讀出這個圖像數(shù)據(jù),然后根據(jù)視頻時序發(fā)出
sensor.v代碼
`timescale 1ns / 1ps
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// Engineer: EEPROM
//
//////////////////////////////////////////////////////////////////////////////////
module sensor
(
input rst,
input clk,
output reg vsync,
output reg hsync,
output reg de,
output reg vblank,
output reg[31:0]pix_out
);
parameter SENSOR_ACT_W = 640;
parameter SENSOR_ACT_H = 480;
parameter SENSOR_WIDTH = 800;
parameter SENSOR_HEIGHT = 600;
parameter H_START = 80;
parameter V_START = 60;
parameter DATA_SIZE = SENSOR_WIDTH*SENSOR_HEIGHT;
reg [12:0] hcnt;
reg [12:0] vcnt;
reg [31:0] raw_array [DATA_SIZE-1:0];
integer i;
integer file_hdl;
initial
begin
file_hdl = $fopen("input.rgb", "rb");
for ( i=0; i= H_START && hcnt < H_START+SENSOR_ACT_W && vcnt >= V_START && vcnt < V_START+SENSOR_ACT_H )
de <= 1'b1;
else
de <= 1'b0;
end
always @(posedge clk)
begin
if(rst)
begin
vblank <= 1'b1;
end
else if(vcnt >= V_START-1 && vcnt <= V_START+SENSOR_ACT_H )
vblank <= 1'b0;
else
vblank <= 1'b1;
end
always @(posedge clk)
begin
if(rst)
begin
vsync <= 1'b0;
end
else if(vcnt <= 4)
vsync <= 1'b1;
else
vsync <= 1'b0;
end
always @(posedge clk)
pix_out <= (hcnt >= H_START &&hcnt < H_START+SENSOR_ACT_W && vcnt >= V_START && vcnt < V_START+SENSOR_ACT_H) ? raw_array[(vcnt-V_START)*SENSOR_ACT_W+hcnt-H_START]:0;
endmodule
視頻數(shù)據(jù)源有了,那么為了驗證vdma工作正常,即視頻數(shù)據(jù)讀寫ddr正常,就需要把讀回來的數(shù)據(jù)也存儲一下,代碼在tb_top最后有寫,至于為什么要等fram_cnt為1的時候開始寫,那是因為我做的是一個乒乓buffer,vdma讀出來的第一幀數(shù)據(jù)是無效數(shù)據(jù),第二幀開始才是有效數(shù)據(jù)。
代碼都準備完畢,開始仿真,我這里調(diào)用的modelsim,vivado如果使用modelsim仿真,這里不做介紹,網(wǎng)上搜一下資料還是比較多的
仿真關(guān)鍵信號時序:
這里面是做兩幀的仿真,牢騷一句,仿真真的太慢了
仿真結(jié)束以后,會把從DDR讀回來的視頻數(shù)據(jù)存儲到output.rbg文件里面,還是用matlab對這個數(shù)據(jù)做處理:
w=640;
h=480;
dst = zeros(h,w);
fd=fopen('output.rgb','r');
for i=1:h
for j=1:w
%pix=[0,image(i,j,1),image(i,j,2),image(i,j,3)];
pix=fread(fd,[1,4],'uint8');
dst(i,j)=pix(3);
dst(i,j,2)=pix(2);
dst(i,j,3)=pix(1);
end
end
fclose(fd);
figure
imshow(uint8(dst));
下面是輸入圖片和輸出圖片對比:
可見vdma工作正常
四:總結(jié)
通過對vdma的仿真,可以更深入的了解vdma的工作原理,工作流程,給實際應(yīng)用做好準備工作。同時,這么做也有更多的意義,對于一些需要DDR緩存才能完成的圖像算法,比如視頻3D降噪,運動物體檢測幀差法,HDR圖像合成等等,完全可以在此基礎(chǔ)上進行仿真,能更大程度的模擬FPGA實際工作狀況,提高算法移植效率。
評論
查看更多