米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳

米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳
对于做图像处理的兄弟来说,图像缓存是基本操作,一般是图像三帧缓存于DDR3,然后再读出显示,DDR3操作很复杂,所以Xilinx官方出了个MIG 的IP核供开发者使用,但对于像我这样的little_white来说,操作MIG 的用户接口还是不方便,所以又有了挂载AXI4总线的AXI4_MIG,这下不就简单了,直接操作AXI不就完了吗?不再需要关心底层怎么搞了。
基于此,米联客搞了一个叫做FDMA的东西,实质就是一个AXI4_FULL的主机总线,即使有了FDMA还不行,还得有控制图像读写发热控制器,米联客都给出了源码,也给了文档,但无奈,米联客的代码虽然写得精简漂亮,但文档写得确实一般,加之代码的有些变量命名也不太恰当,使得像我这样的little_white有些云雾缭绕
所以我对FDMA及其控制器的源码进行了优化,现在逐行解读:
FDMA部分:


	module uiFDMA#
	(
		parameter  integer        C_M_AXI_BURST_LEN	 = 64 , //AXI的一次读写突发长度
		parameter  integer        C_M_AXI_ID_WIDTH	 = 1  ,
		parameter  integer        C_M_AXI_ID		 = 0  ,
		parameter  integer        C_M_AXI_ADDR_WIDTH = 32 , 
		parameter  integer        C_M_AXI_DATA_WIDTH = 32  //AXI的数据位宽					
	)
	(

		//user logic 
		input   wire                               pkg_wr_areq         ,	//FDMS包写请求,一个时钟脉冲		
		output  wire                               pkg_wr_last		   ,	//FDMS包写数据结尾
		input   wire [C_M_AXI_DATA_WIDTH-1 :0]     pkg_wr_data		   ,	//FDMS包写数据包
		output	wire                               pkg_wr_en		   ,	//指示FDMS正在写数据,高有效
		input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_wr_addr		   ,	//FDMS包写数据包地址
		input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_wr_size         ,	//FDMS包写数据包长度,应为
																			//C_M_AXI_BURST_LEN的整数倍 

        input   wire                               pkg_rd_areq         ,     
		output  wire                               pkg_rd_last			,
		output  wire [C_M_AXI_DATA_WIDTH-1 :0]     pkg_rd_data			,	
		output  wire                               pkg_rd_en           ,  	
		input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_rd_addr			, 	
		input   wire [C_M_AXI_ADDR_WIDTH-1 :0]     pkg_rd_size         , 			
		//input  	wire  							INIT_AXI_TXN		,
		
		input 	wire  								M_AXI_ACLK			,
		input 	wire  								M_AXI_ARESETN		,
		output 	wire [C_M_AXI_ID_WIDTH-1 : 0]		M_AXI_AWID			,	 
		output 	wire [C_M_AXI_ADDR_WIDTH-1 : 0] 	M_AXI_AWADDR		,    
		output 	wire [7 : 0]						M_AXI_AWLEN			,    
		output 	wire [2 : 0] 						M_AXI_AWSIZE		,    
		output 	wire [1 : 0] 						M_AXI_AWBURST		,    
		output 	wire  								M_AXI_AWLOCK		,    
		output 	wire [3 : 0] 						M_AXI_AWCACHE		,    
		output 	wire [2 : 0] 						M_AXI_AWPROT		,    
		output 	wire [3 : 0] 						M_AXI_AWQOS			,     
		output 	wire  								M_AXI_AWVALID		,    
		input	wire  								M_AXI_AWREADY		,    
		output  wire [C_M_AXI_DATA_WIDTH-1 : 0] 	M_AXI_WDATA			,	 
		output  wire [C_M_AXI_DATA_WIDTH/8-1 : 0] 	M_AXI_WSTRB			,	 
		output  wire  								M_AXI_WLAST			,	 			
		output  wire  								M_AXI_WVALID		,	 
		input   wire  								M_AXI_WREADY		,	 
		input   wire [C_M_AXI_ID_WIDTH-1 : 0] 		M_AXI_BID			,		
		input   wire [1 : 0] 						M_AXI_BRESP			,		
		input   wire  								M_AXI_BVALID		,	
		output  wire  								M_AXI_BREADY		,	 
		                                                                     
		output  wire [C_M_AXI_ID_WIDTH-1 : 0] 		M_AXI_ARID			,	 
		output  wire [C_M_AXI_ADDR_WIDTH-1 : 0] 	M_AXI_ARADDR		,	 	
		output  wire [7 : 0] 						M_AXI_ARLEN			,	 
		output  wire [2 : 0] 						M_AXI_ARSIZE		,	 
		output  wire [1 : 0] 						M_AXI_ARBURST		,	 
		output  wire  								M_AXI_ARLOCK		,	 
		output  wire [3 : 0] 						M_AXI_ARCACHE		,	 
		output  wire [2 : 0] 						M_AXI_ARPROT		,	 
		output  wire [3 : 0] 						M_AXI_ARQOS			,	 	   
		output  wire  								M_AXI_ARVALID		,	 
		input   wire  								M_AXI_ARREADY		,	 
		input   wire [C_M_AXI_ID_WIDTH-1 : 0] 		M_AXI_RID			,	 
		input   wire [C_M_AXI_DATA_WIDTH-1 : 0] 	M_AXI_RDATA			,	 
		input   wire [1 : 0] 						M_AXI_RRESP			,	 
		input   wire  								M_AXI_RLAST			,	 
		input   wire  								M_AXI_RVALID		,    
		output  wire  								M_AXI_RREADY			 
	);
         
	  function integer clogb2 (input integer bit_depth);              
	  begin                                                           
	    for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                   
	      bit_depth = bit_depth >> 1;                                 
	    end                                                           
	  endfunction                                                     

	 localparam integer C_TRANSACTIONS_NUM = clogb2(C_M_AXI_BURST_LEN-1);	
	 localparam integer BURST_SIZE = C_M_AXI_BURST_LEN * C_M_AXI_DATA_WIDTH/8;	//一包FDMA数据包所占的字节数(或理解为一包数据的总地址)
	// AXI4LITE signals
	//AXI4 internal temp signals
//write
	reg 	[C_M_AXI_ADDR_WIDTH-1 : 0] 	axi_awaddr	;	//写地址
	reg  						 		axi_awvalid	;	//写地址有效
	wire 	[C_M_AXI_DATA_WIDTH-1 : 0] 	axi_wdata	;	//写数据
	reg  								axi_wlast	;	//写数据结尾
	reg  								axi_wvalid	;	//写数据有效
//read	
	reg 	[C_M_AXI_ADDR_WIDTH-1 : 0] 	axi_araddr	;	//读地址
	reg  								axi_arvalid	;	//读地址有效
	reg  								axi_rready	;	//读数据准备
	
//	wire [C_TRANSACTIONS_NUM+2 : 0] 	burst_size_bytes;	
	assign M_AXI_AWID		= C_M_AXI_ID;
	assign M_AXI_AWADDR		= axi_awaddr;
	assign M_AXI_AWLEN		= C_M_AXI_BURST_LEN - 1;
	assign M_AXI_AWSIZE		= clogb2((C_M_AXI_DATA_WIDTH/8)-1);
	assign M_AXI_AWBURST	= 2'b01;
	assign M_AXI_AWLOCK		= 1'b0;
	assign M_AXI_AWCACHE	= 4'b0010;
	assign M_AXI_AWPROT		= 3'h0;
	assign M_AXI_AWQOS		= 4'h0;
	assign M_AXI_AWVALID	= axi_awvalid;
	assign M_AXI_WDATA		= axi_wdata;
	assign M_AXI_WSTRB		= {
     (C_M_AXI_DATA_WIDTH/8){
     1'b1}};	//每个字节都选通,都有效
	assign M_AXI_WLAST		= axi_wlast;
	assign M_AXI_WVALID		= axi_wvalid;
	assign M_AXI_BREADY		= axi_bready;
		
	assign M_AXI_ARID		= C_M_AXI_ID;
	assign M_AXI_ARADDR		= axi_araddr;
	assign M_AXI_ARLEN		= C_M_AXI_BURST_LEN - 1;
	assign M_AXI_ARSIZE		= clogb2((C_M_AXI_DATA_WIDTH/8)-1);
	assign M_AXI_ARBURST	= 2'b01;
	assign M_AXI_ARLOCK		= 1'b0;
	assign M_AXI_ARCACHE	= 4'b0010;
	assign M_AXI_ARPROT		= 3'h0;
	assign M_AXI_ARQOS		= 4'h0;
	assign M_AXI_ARVALID	= axi_arvalid;
	assign M_AXI_RREADY		= axi_rready;
	
   	reg [7 :0 ]                      w_axi4_cnt   ; 
    reg [C_M_AXI_ADDR_WIDTH-1 : 0]   WR_BASE_ADDR  ;
    reg [C_M_AXI_ADDR_WIDTH-1 : 0]   RD_BASE_ADDR  ;
	reg [C_M_AXI_ADDR_WIDTH-1 : 0]   w_fdma_cnt   ;
	reg [C_M_AXI_ADDR_WIDTH-1 : 0]   r_fdma_cnt   ;  
    reg                              w_axi4_flag  ;
    reg                              r_axi4_addr_flag ;
    reg                              r_axi4_data_flag;
    
	wire w_next = (axi_wvalid && M_AXI_WREADY);
	wire r_next = (M_AXI_RVALID && axi_rready);
    
	assign pkg_wr_en      =   w_next;  								 //写一包FDMAMA数据标志 
	assign pkg_wr_last    =  (w_next && w_fdma_cnt==pkg_wr_size-1); //写一包FDMAMA数据结尾	
	assign axi_wdata      =  pkg_wr_data;
	
	assign pkg_rd_en      =  r_next;								 //读一包FDMAMA数据标志 	
	assign pkg_rd_last    =  (r_next && r_fdma_cnt==pkg_rd_size-1); //读一包FDMAMA数据结尾	
	assign pkg_rd_data    =  M_AXI_RDATA;

//----------------------------------------------------------------------------	
//AXI4 FULL Write
//AXI4 data is ready for axi master write to slave	

reg w_fdma_flag;	//这里原来不叫这个名字,由于命名不直观,我改了
//标志fdma传输过程
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) w_fdma_flag <= 1'b0;
	else if(w_fdma_flag==1'b0 && pkg_wr_areq== 1'b1) w_fdma_flag <= 1'b1; 		                            
	else if(pkg_wr_last == 1'b1) w_fdma_flag <= 1'b0;	
end		

//AXI4 write burst lenth busrt addr --------------------------------------------
always @(posedge M_AXI_ACLK) begin
    if(M_AXI_ARESETN == 1'b0) axi_awaddr <= 'd0;
    else if(w_fdma_flag==1'b0 && pkg_wr_areq) axi_awaddr <= pkg_wr_addr;	//FDMA给axi_awaddr赋初值
    else if(axi_awvalid == 1'b1 && M_AXI_AWREADY == 1'b1) axi_awaddr <= axi_awaddr + BURST_SIZE ;  //AXI4地址总线准备好后,再增加一包FDMA地址	
end               	

//AXI4 write cycle flag---AXI4写周期标志
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) w_axi4_flag <= 1'b0;
	else if(w_axi4_flag == 1'b0 && w_fdma_flag) w_axi4_flag <= 1'b1; 		//与w_fdma_flag同步拉高                            
	else if(w_axi4_flag == 1'b1 && axi_wlast == 1'b1) w_axi4_flag <= 1'b0;	//拉的条件是axi_wlast
end

//AXI4 write addr valid---------------------------------------------
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) axi_awvalid<= 1'b0;
	else if(w_axi4_flag == 1'b1 && M_AXI_AWREADY==1'b1) axi_awvalid <= 1'b0;
	else if(w_axi4_flag  == 1'b0 && w_fdma_flag == 1'b1) axi_awvalid <= 1'b1;     
end
		
//AXI4 write data---------------------------------------------------		
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN  == 1'b0) axi_wvalid <= 1'b0;
	else if(w_axi4_flag  == 1'b0 && w_fdma_flag == 1'b1) axi_wvalid <= 1'b1;	//FDMA请求拉,axi_wvalid就拉,提前于M_AXI_WREADY
	else if(w_axi4_flag == 1'b1 && axi_wlast == 1'b1) axi_wvalid <= 1'b0;
end

//AXI4 写数据计数器,在M_AXI_ACLK下,axi4数据总线每次写128bit数据
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) w_axi4_cnt <= 'd0;
	else if(w_axi4_cnt==C_M_AXI_BURST_LEN) w_axi4_cnt <= 'd0;	//写完256个128bit的数据时
	else if(w_next) w_axi4_cnt <= w_axi4_cnt + 1'b1;            
	else w_axi4_cnt <= w_axi4_cnt ;
end

//FDMA 写数据计数器,在M_AXI_ACLK下,axi4数据总线每次写128bit数据,FDMA的pkg_wr_size是C_M_AXI_BURST_LEN的整数倍:256 256x2。。。
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) w_fdma_cnt <= 'd0;
	else if(w_next && w_fdma_cnt== pkg_wr_size-1) w_fdma_cnt <= 'd0;
	else if(w_next) w_fdma_cnt <= w_fdma_cnt + 1'b1;	
	else w_fdma_cnt <= w_fdma_cnt;
end
			
//AXI4 write data last data----------------------------------------- 
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) axi_wlast <= 1'b0;
	else if(w_axi4_cnt == M_AXI_AWLEN-1) axi_wlast <= 1'b1;
	else axi_wlast <= 1'b0;
end
assign  axi_bready = 1'b1;     		
		
//----------------------------------------------------------------------------	
//AXI4 FULL Read----------------------------------------- 	
reg r_fdma_flag;
    
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) r_fdma_flag <= 1'b0;
	else if(r_fdma_flag==1'b0 && pkg_rd_areq==1'b1) r_fdma_flag <= 1'b1; 		                            
	else if(pkg_rd_last == 1'b1) r_fdma_flag <= 1'b0;
end
		
//AXI4 read addr read addr burst-------------------------
always @(posedge M_AXI_ACLK) begin
    if(M_AXI_ARESETN == 1'b0) axi_araddr <='d0;
    else if(r_fdma_flag==1'b0 && pkg_rd_areq==1'b1) axi_araddr <= pkg_rd_addr;						//axi_araddr赋初值
    else if(axi_arvalid == 1'b1 && M_AXI_ARREADY == 1'b1) axi_araddr <= axi_araddr + BURST_SIZE;	//axi_araddr+FDMA_addr	
end
									        
//AXI4 r_axi4_addr_flag----瞬时拉信号 	
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 0) r_axi4_addr_flag <= 1'b0;
	else if(r_fdma_flag &&  M_AXI_ARREADY && axi_arvalid ) r_axi4_addr_flag <= 1'b0;	//此时地址总线跳变,表明下一包数据即将开始
	else if(r_fdma_flag && r_axi4_data_flag == 1'b0 ) r_axi4_addr_flag <= 1'b1;			
end

//AXI4 read addr valid----瞬时拉信号
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) axi_arvalid <= 1'b0;
	else if(r_axi4_addr_flag && axi_arvalid && M_AXI_ARREADY ) axi_arvalid <= 1'b0;
	else if(r_axi4_addr_flag && axi_arvalid == 1'b0) axi_arvalid <= 1'b1;
end
			
//AXI4 r_axi4_data_flag ready for read data----------------
always @(posedge M_AXI_ACLK) begin
		if(M_AXI_ARESETN == 1'b0) begin
			r_axi4_data_flag <= 1'b0;
			axi_rready     <= 1'b0;		//这行原本是缺少的,我加上了
		end
		else if(r_axi4_addr_flag && axi_arvalid && M_AXI_ARREADY)
		begin
			r_axi4_data_flag <= 1'b1;
			axi_rready     <= 1'b1;
	    end
		else if(r_axi4_data_flag && r_next && M_AXI_RLAST)
		begin
			r_axi4_data_flag <= 1'b0;
			axi_rready     <= 1'b0;
	    end
end
	
//AXI4 data user counter for frame space lenth-----------	
always @(posedge M_AXI_ACLK) begin
	if(M_AXI_ARESETN == 1'b0) r_fdma_cnt <= 'd0;
	else if(r_next && r_fdma_cnt== pkg_rd_size-1) r_fdma_cnt <= 'd0;
	else if(r_next) r_fdma_cnt <= r_fdma_cnt + 1'b1;	
	else r_fdma_cnt <= r_fdma_cnt;	
end	
	              			   
endmodule

再来FDMA控制器部分,这是重点:
在此之前,先来说说三帧缓存的架构,图就不画了,可以自行脑补:
一帧图像的像素时钟是不定的,分辨率不同则像素时钟不同,DDR3的IP的用户时钟也是不同的,所以输入图像进入DDR3之前肯定是要做跨时钟域处理的,实质就是把输入图像的像素时钟转换到DDR3的操作时钟下来,这样DDR3才能吞吐图像嘛。。。那肯定就用FIFO咯。。。
把图像写入DDR3的FIFO叫做写FIFO,记作W0_FIFO,W0_FIFO自身带有读写端口
W0_FIFO的写端口对接输入图像,W0_FIFO的读端口对接FDMA的写端口,即对接DDR3
把图像从DDR3读出的FIFO叫做读FIFO,记作R0_FIFO,R0_FIFO自身带有读写端口
R0_FIFO的写端口对接FDMA的读端口,即对接DDR3,R0_FIFO的读端口对接显示接口
这个框图你能想象出来吗?实在想不通可以画个图。。。
这个读写FIFO的配置有讲究,请看:
W0_FIFO:
米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳_第1张图片
写位宽选择32位,你可能会疑问,我的输入视频是24位RGB的数据,这里为啥要搞个32位的呢?
读位宽直接对接AXI4,进而对接DDR3是吧?AXI4的数据位宽只有那么几个选择啊,最高256位,这里选个128位不过分吧?
既然读位宽定了128,如果你的写位宽保持24位,那么128/24=5.333333,这样没法儿搞了,读写肯定是错的,如果写位宽搞个32位,那么128/32=4,这下好了。。。
R0_FIFO的配置反过来,这里就不说了,截个图吧:
米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳_第2张图片
再来说说图像缓存的过程:
写过程:
首先检测输入图像VS的上升沿,这个信号很重要:他标志一帧图像的到来,同时也标志一帧图像的结束或者下一帧图像的到来,当检测到VS的上升沿时,需要做两件事,因为他是一帧图像的到来的标志,所以要进入写数据状态,又因为他是一帧图像的结束或者下一帧图像到来的标志,所以要更新AXI4的写地址或者读地址。
在写数据时,W0_FIFO的写入端:
以输入视频的de信号作为写有效,这样可以保证写入的数据时有效的像素数据;
在写数据时,W0_FIFO的读出端:
以FDMA的pkg_wr_en作为读有效,这样,这样又可以保证写入的数据时有效的像素数据;
这里有个关键的问题,那就是W0_FIFO读写时钟的问题:
以1080p图像为例:
W0_FIFO的写时钟=148.5M;
W0_FIFO的读时钟=DDR3的用户时钟=100M;
有兄弟就会疑惑了:写比读块,FIFO不卡?
当然不会,请看下面:
虽然写时钟是148.5M,但写入端的数据位宽只有32位;
虽然读时钟是100M,但读出端的数据位宽是128位;
128/32=4;
也就是说,写入端写了4个数据,读出端才读出1个数据;
也就是说,读出端相当于148.5X4被的速度,咋可能卡呢。。。。。

读过程和谐过程反向,就不解释了

下面你看FDMA控制器源码,我做了小幅改动:

module fdma_controller#
(
	parameter  integer  ADDR_OFFSET     = 0   ,
	parameter  integer  BUF_SIZE        = 3   ,
	parameter  integer  H_CNT           = 1920, 
	parameter  integer  V_CNT           = 1080,
	parameter  integer  W_ADDR_START    = 0   ,
	parameter  integer  R_ADDR_START    = 0   ,
	parameter  integer  FDMA_DATA_WIDTH = 128 ,
	parameter  integer  FDMA_BURST_LEN  = 256 ,
	parameter  integer  M_AXI_ID_WIDTH  = 1   ,
	parameter  integer  M_AXI_ADDR_WIDTH= 32  
)
(
    input           ui_clk,
    input           ui_rstn,
//sensor input -W0_FIFO--------------
    input           i_video_vs,
    input           i_video_clk,
    input           i_video_de,
    input  [23:0]   i_video_rgb, 
//hdmi output -R0_FIFO--------------- 
    input           o_video_vs,
    input           o_video_clk,
    input           o_video_de,
    output [23:0]   o_video_rgb,
//AXI4_FULL_MASTER-------
	input  							 M_AXI_ACLK		,
	input  							 M_AXI_ARESETN	,
	output [M_AXI_ID_WIDTH-1 : 0]	 M_AXI_AWID		,	
	output [M_AXI_ADDR_WIDTH-1 : 0]  M_AXI_AWADDR	,   
	output [7 : 0]					 M_AXI_AWLEN	,   
	output [2 : 0] 					 M_AXI_AWSIZE	,   
	output [1 : 0] 					 M_AXI_AWBURST	,   
	output  						 M_AXI_AWLOCK	,   
	output [3 : 0] 					 M_AXI_AWCACHE	,   
	output [2 : 0] 					 M_AXI_AWPROT	,   
	output [3 : 0] 					 M_AXI_AWQOS	,   
	output  						 M_AXI_AWVALID	,   
	input 							 M_AXI_AWREADY	,   
	output [FDMA_DATA_WIDTH-1 : 0] 	 M_AXI_WDATA	,	
	output [FDMA_DATA_WIDTH/8-1 : 0] M_AXI_WSTRB	,	
	output  						 M_AXI_WLAST	,	
	output  						 M_AXI_WVALID	,	
	input   						 M_AXI_WREADY	,	
	input  [M_AXI_ID_WIDTH-1 : 0] 	 M_AXI_BID		,	
	input  [1 : 0] 					 M_AXI_BRESP	,	
	input   						 M_AXI_BVALID	,	
	output  						 M_AXI_BREADY	,	
	                                                             
	output [M_AXI_ID_WIDTH-1 : 0] 	 M_AXI_ARID		,	
	output [M_AXI_ADDR_WIDTH-1 : 0]  M_AXI_ARADDR	,	
	output [7 : 0] 					 M_AXI_ARLEN	,	
	output [2 : 0] 					 M_AXI_ARSIZE	,	
	output [1 : 0] 					 M_AXI_ARBURST	,	
	output  						 M_AXI_ARLOCK	,	
	output [3 : 0] 					 M_AXI_ARCACHE	,	
	output [2 : 0] 					 M_AXI_ARPROT	,	
	output [3 : 0] 					 M_AXI_ARQOS	,	
	output  						 M_AXI_ARVALID	,	
	input   						 M_AXI_ARREADY	,	
	input  [M_AXI_ID_WIDTH-1 : 0] 	 M_AXI_RID		,	
	input  [FDMA_DATA_WIDTH-1 : 0] 	 M_AXI_RDATA	,	
	input  [1 : 0] 					 M_AXI_RRESP	,	
	input   						 M_AXI_RLAST	,	
	input   						 M_AXI_RVALID	,   
	output  						 M_AXI_RREADY		

    );
    
//parameter FBUF_SIZE = BUF_SIZE -1'b1;
parameter FBUF_2 = BUF_SIZE -1'b1;
//parameter BURST_SIZE  = 1024*4;	// one time 4KB ,一次突发读写的地址
//								//FDMA设置的是128*256,所以BURST_SIZE=128x256/8=4096=4KB的存储空间
parameter BURST_UP_ADDR  = FDMA_DATA_WIDTH*FDMA_BURST_LEN/8;	// one time 4KB ,一次突发读写的地址
								//FDMA设置的是128*256,所以BURST_SIZE=128x256/8=4096=4KB的存储空间
//parameter BURST_TIMES = H_CNT*V_CNT/1024;// one frame burst times,突发一次的数据是256*128=32768bit/(4*8)=1024
parameter FRAME_BURST_OK = (H_CNT*V_CNT)/(FDMA_DATA_WIDTH*FDMA_BURST_LEN/8/(3+1));//one frame burst times,突发一次的数据是256*128=32768bit/(4*8)=1024
																		       //本来一个像素是3个字节,这里+1变为4个字节是因为:以32位作为AXI4的传输,128位宽的总线除不尽24
																			   //这里也是一帧图像传输完成的标志
parameter PKG_SIZE    = 256;
assign pkg_wr_size = PKG_SIZE;
assign pkg_rd_size = PKG_SIZE;

//------------vs 滤波---------------
reg  W0_FIFO_Rst; 
reg  R0_FIFO_Rst;
 
wire W0_FS;
wire R0_FS;  

reg [6:0] W0_Fbuf;	//写帧缓存buf,从0开始写,写完到2后再回到0循环写 :0-->1-->2-->0
reg [6:0] R0_Fbuf;	//读帧缓存buf,从2开始读,读完到0后再回到2循环读 :2-->1-->0-->2

//----------fdma signals write------- 
reg           pkg_wr_areq;      
wire          pkg_wr_en	 ;
wire          pkg_wr_last;
wire  [31:0]  pkg_wr_addr;
wire  [127:0] pkg_wr_data;
wire  [31:0]  pkg_wr_size;
//----------fdma signals read---------
reg           pkg_rd_areq; 
wire          pkg_rd_en  ;   
wire          pkg_rd_last;      
wire  [31:0]  pkg_rd_addr;
wire  [127:0] pkg_rd_data;
wire  [31:0]  pkg_rd_size;   

wire [31:0] i_video_rgb_r;
assign i_video_rgb_r = {
     8'h11,i_video_rgb};
wire [31:0] o_video_rgb_r;
assign o_video_rgb = o_video_rgb_r[23:0];

fs_cap fs_cap_W0(
  .clk_i(ui_clk),
  .rstn_i(ui_rstn),
  .vs_i(i_video_vs),
  .fs_cap_o(W0_FS)
);

fs_cap fs_cap_R0(
  .clk_i(ui_clk),
  .rstn_i(ui_rstn),
  .vs_i(o_video_vs),
  .fs_cap_o(R0_FS)
);

parameter S_IDLE  =  2'd0;  
parameter S_RST   =  2'd1;  
parameter S_DATA1 =  2'd2;   
parameter S_DATA2 =  2'd3; 

reg [1  :0]  W_MS;		 //写状态机
reg [22:0]   W0_addr;	 //写数据地址
reg [31 :0]  W0_fs_cnt;	 //输入图像的vs信号计数器
reg [10  :0] W0_pkg_cnt; //写fdma包计数器
wire [10:0]  W0_rcnt;	 //写FIFO中可读的数据量,128bit的数据
reg W0_REQ;
reg [1 :0]  R_MS;		//读状态机
reg [22 :0] R0_addr;    //读数据地址
reg [31 :0] R0_fs_cnt;  //输入图像的vs信号计数器
reg [10 :0] R0_pkg_cnt; //写fdma包计数器
wire [10:0] R0_wcnt;	//读FIFO中已写的数据量,128bit的数据
reg R0_REQ;

assign pkg_wr_addr = {
     W0_Fbuf,W0_addr}+ ADDR_OFFSET;
assign pkg_rd_addr = {
     R0_Fbuf,R0_addr}+ ADDR_OFFSET;
//assign pkg_wr_data = W0_fs_cnt;
//--------一副图像写入DDR------------
 always @(posedge ui_clk) begin
    if(!ui_rstn)begin
        W_MS <= S_IDLE;
        W0_addr <= 21'd0;
        pkg_wr_areq <= 1'd0;
        W0_FIFO_Rst <= 1'b1;
        W0_fs_cnt <= 0;
        W0_pkg_cnt <= 0;
        W0_Fbuf <= W_ADDR_START;	//从0buf处开始写
    end
    else begin
      case(W_MS)	//空闲状态:检测输入图像的vs,然后进入写FIFO的复位 	
       S_IDLE:begin
          W0_addr <= 21'd0;
          W0_fs_cnt <= 0;
          W0_pkg_cnt <= 11'd0;
          if(W0_FS) W_MS <= S_RST;
       end
       S_RST:begin	//复位状态:对输入图像的vs进行计数,一者用vs复位写FIFO,二者,如果vs突然中断,则不会写图像进DDR
          if(W0_fs_cnt > 8'd20 ) W_MS <= S_DATA1;
          W0_FIFO_Rst <= (W0_fs_cnt < 8'd10); 
          W0_fs_cnt <= W0_fs_cnt +1'd1;
        end          
        S_DATA1:begin 
            if(W0_pkg_cnt == FRAME_BURST_OK) begin	//一帧写完
                if(W0_Fbuf == FBUF_2) W0_Fbuf <= W_ADDR_START;	//如果写帧缓存buf到了2,则回到0,以此循环写                     
                else W0_Fbuf <= W0_Fbuf + 1'b1;				//如果写帧缓存buf没到2,则从0开始向上加
                W_MS <= S_IDLE;								//同时,一帧写完了,回到S_IDLE状态,写下一帧
            end
            else if(W0_REQ) begin
                W0_fs_cnt <=0;		 //此处对W0_fs_cnt置零,为下一帧到来前做准备
                pkg_wr_areq <= 1'b1;
                W_MS <= S_DATA2;  
            end           
         end
         S_DATA2:begin
            pkg_wr_areq <= 1'b0; 
            if(pkg_wr_last)begin	//一包fdma数据写完!=一帧图像写完
                W_MS <= S_DATA1; 	//一包fdma数据写完后,进入S_DATA1状态,检测一帧图像是否写完
                W0_pkg_cnt <= W0_pkg_cnt + 1'd1;    //一包fdma数据写完后,W0_pkg_cnt+1,当W0_pkg_cnt==FRAME_BURST_OK时,一帧图像写完
                W0_addr <= W0_addr + BURST_UP_ADDR; //一包fdma数据写完后,AXI4地址+BURST_UP_ADDR
            end
         end
       endcase
    end
 end 

//--------一副图像读出DDR------------
 always @(posedge ui_clk) begin
   if(!ui_rstn)begin
       R_MS <= S_IDLE;
       R0_addr <= 21'd0;
       pkg_rd_areq <= 1'd0;
       R0_fs_cnt <=0;
       R0_pkg_cnt <=0;
       R0_FIFO_Rst <= 1'b1;
       R0_Fbuf <= R_ADDR_START;       
   end
   else begin
     case(R_MS)
       S_IDLE:begin
         R0_addr <= 21'd0;
         R0_fs_cnt <=0;
         R0_pkg_cnt <=0;
         if(R0_FS) R_MS <= S_RST;
       end 
       S_RST:begin
         if(R0_fs_cnt > 8'd20 ) R_MS <= S_DATA1;
         R0_FIFO_Rst <= (R0_fs_cnt < 8'd10); 
         R0_fs_cnt <= R0_fs_cnt + 1'd1;
       end  
       S_DATA1:begin 
           if(R0_pkg_cnt == FRAME_BURST_OK ) begin	//读完一帧图像
               R_MS <= S_IDLE;
               if(W0_Fbuf == W_ADDR_START) R0_Fbuf <= FBUF_2; //如果此时正在写FBUF_0,则从FBUF_2开始读,反之亦然,形成交错读写
               else R0_Fbuf <= W0_Fbuf - 1'b1;				  //如果此时未写FBUF_0,  
           end
           else if(R0_REQ) begin
               pkg_rd_areq <= 1'b1;
               R_MS <= S_DATA2;  
           end           
        end
        S_DATA2:begin
           pkg_rd_areq <= 1'b0;   
           if(pkg_rd_last)begin	//一包fdma数据读完!=一帧图像读完
               R_MS <= S_DATA1; //一包fdma数据读完后,进入S_DATA1状态,检测一帧图像是否读完
               R0_pkg_cnt <= R0_pkg_cnt + 1'd1;		//一包fdma数据读完后,R0_pkg_cnt+1,当R0_pkg_cnt==FRAME_BURST_OK时,一帧图像读完 
               R0_addr <= R0_addr + BURST_UP_ADDR;  //一包fdma数据读完后,AXI4地址+BURST_UP_ADDR
           end
        end
      endcase
   end
end 

 always@(posedge ui_clk)
 begin     
     W0_REQ    <= (W0_rcnt    > PKG_SIZE -5); //当写FIFO中有PKG_SIZE -5个128bit的可读数据时,发起写请求,将数据写入DDR
     R0_REQ    <= (R0_wcnt    < PKG_SIZE -5); //当读FIFO中存在已写数据时(128bit),发起读请求,将数据从DDR读出 
 end
 
W0_FIFO W0_FIFO_0 (
  .rst(W0_FIFO_Rst),  // input wire rst
  .wr_clk(i_video_clk),  // input wire wr_clk
  .din(i_video_rgb_r),        // input wire [31 : 0] din
  .wr_en(i_video_de),    // input wire wr_en
  .rd_clk(ui_clk),  // input wire rd_clk 
  .rd_en(pkg_wr_en),    // input wire rd_en
  .dout(pkg_wr_data),      // output wire [63 : 0] dout
  .rd_data_count(W0_rcnt)  // output wire [10 : 0] wr_data_count
);

 R0_FIFO R0_FIFO_0 (
  .rst(R0_FIFO_Rst),  // input wire rst
  .wr_clk(ui_clk),  // input wire wr_clk
  .din(pkg_rd_data),        // input wire [63 : 0] din
  .wr_en(pkg_rd_en),    // input wire wr_en
  .wr_data_count(R0_wcnt),  // output wire [6 : 0] rd_data_count
  .rd_clk(o_video_clk),  // input wire rd_clk 
  .rd_en(o_video_de),    // input wire rd_en
  .dout(o_video_rgb_r)      // output wire [31 : 0] dout
);

最后,我将FDMA及其控制器进行了封装,IP截图如下:
米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳_第3张图片
配置如下:
米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳_第4张图片
其中的R_Addr_Start和R_Addr_Start的设置很重要,比如:
米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳_第5张图片
整个工程以动态彩条为视频源,1080p,最后一HDMI输出,很完美,BD工程如下:
米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳_第6张图片

输出效果:
米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳_第7张图片
打码了,所以有点花。。。
整个动态彩条工程是以K7410T的FPGA作为平台的,重在解决视频三帧缓存的问题,兄弟们拿过去可以修改后用到自己的工程,我也可以提供有限的技术交流,因为我的水平很low
需要工程的兄弟可以加我WX(staticJKN)

你可能感兴趣的:(arm)