很多人用zynq平台做视频图像开发,但是对vdma了解比较少,上手起来稍微有些困难,我针对这一现象,做了一个基于vivado和modelsim的仿真和应用测试工程,并写篇文章做些介绍,希望能对大家有帮助。
一:xilinx vdma IP例化以及接口介绍
上面图片就是在vivado2015.4中例化vdma的界面,首先对参数做些介绍:
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拉高也不会传输
下面是接口介绍:
M_AXI_XX : axi4总线接口,用来与ddr交互数据
M_AXIS_XX , S_AXIS_XX : axi stream接口,用来与pl交互数据
S_AXI_LITE :控制总线,接到ps的gp口或者写一个axilite master总线去配置
其他接口不做介绍
二:下面开始一步步的详解如何搭建一个vdma的仿真工程
FPGA的开发,离不开仿真,很少有人能直接写好代码上板就成功的,仿真必不可少。但是有些应用要用到vdma,vdma又要和ddr做数据交互,这样做起来就很麻烦了,我这里就实现了一个简单的方法,可以测试vdma,又不用去例化MIG搞什么ddr。下面开始!
系统框图:
(1)因为是要仿真vdma,vdma顾名思义就是video dma,那肯定要先做一个视频模块,注意,我这里除了vdma和fifo用xilinx的ip。其他的都不用ip,这样更通用性。
我这里就把这个视频发生模块叫做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;
我这里构建了一个图像传感器,总像素数是600*800,有效像素是 480*640,水平有效像素开始位置是80,垂直有效像素开始位置是60,这个模块会读取一个本地图像数据,rgb格式,这里为了测试方便,直接把像素输出位宽设置为32bit。
(2)video转axis模块,相当于 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来做数据缓存,只要注意一下stream协议的握手操作即可,由于stream协议比较简单,这里就不多说了。至此,video数据就转换到了stream数据。
(3)axis转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
);
这个模块相当于xilinx的vid out模块,我这里是简化版的,xilinx的ip写的太复杂了,而且不容易用起来,其实也就是用一个fifo做数据缓存,然后根据外部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
);
此模块产生视频时序,提供给 axis2video模块,相当于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
);
这块模块是根据xilinx官方提供的参考设计基础上修改而来的(xapp1168),协议部分完全没有改动,这里拿他当做ddr,具体修改是这样的,
reg [31:0] mem [32’h01000000:0];
用寄存器组来模拟ddr
此模块会根据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];
做此修改以后,这个模块就可以当做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缓存帧数选择2帧,寄存器配置里面就配置两个传输地址。这样就完成了对vdma的寄存器配置。
编写test bench top文件,把这些模块连接起来,至此,仿真工程就全部写好了。
三:仿真
(1)vdma工作流程介绍
第一步,对vdma寄存器进行配置,并打开使能,这时候vdma处于待命状态,什么时候开始传输呢,下面详细介绍
对于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数据的转换
(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
接下来,就要准备仿真用数据了,我这里用matlab将一副图片的图像数据取出来,写成rgb文件,当做视频数据源
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模块里面读出这个图像数据,然后根据视频时序发出
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
视频数据源有了,那么为了验证vdma工作正常,即视频数据读写ddr正常,就需要把读回来的数据也存储一下,代码在tb_top最后有写,至于为什么要等fram_cnt为1的时候开始写,那是因为我做的是一个乒乓buffer,vdma读出来的第一帧数据是无效数据,第二帧开始才是有效数据。
代码都准备完毕,开始仿真,我这里调用的modelsim,vivado如果使用modelsim仿真,这里不做介绍,网上搜一下资料还是比较多的
仿真关键信号时序:
这里面是做两帧的仿真,牢骚一句,仿真真的太慢了
仿真结束以后,会把从DDR读回来的视频数据存储到output.rbg文件里面,还是用matlab对这个数据做处理:
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工作正常
四:总结
通过对vdma的仿真,可以更深入的了解vdma的工作原理,工作流程,给实际应用做好准备工作。同时,这么做也有更多的意义,对于一些需要DDR缓存才能完成的图像算法,比如视频3D降噪,运动物体检测帧差法,HDR图像合成等等,完全可以在此基础上进行仿真,能更大程度的模拟FPGA实际工作状况,提高算法移植效率。
评论
查看更多