米联客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:
写位宽选择32位,你可能会疑问,我的输入视频是24位RGB的数据,这里为啥要搞个32位的呢?
读位宽直接对接AXI4,进而对接DDR3是吧?AXI4的数据位宽只有那么几个选择啊,最高256位,这里选个128位不过分吧?
既然读位宽定了128,如果你的写位宽保持24位,那么128/24=5.333333,这样没法儿搞了,读写肯定是错的,如果写位宽搞个32位,那么128/32=4,这下好了。。。
R0_FIFO的配置反过来,这里就不说了,截个图吧:
再来说说图像缓存的过程:
写过程:
首先检测输入图像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截图如下:
配置如下:
其中的R_Addr_Start和R_Addr_Start的设置很重要,比如:
整个工程以动态彩条为视频源,1080p,最后一HDMI输出,很完美,BD工程如下:
输出效果:
打码了,所以有点花。。。
整个动态彩条工程是以K7410T的FPGA作为平台的,重在解决视频三帧缓存的问题,兄弟们拿过去可以修改后用到自己的工程,我也可以提供有限的技术交流,因为我的水平很low
需要工程的兄弟可以加我WX(staticJKN)