Verilog 图像数据的采样和缓冲(FMC_apt)

一、问题提出

我们假设上一级传输过来了如下的图像数据:

在这里插入图片描述

  • 时钟为60MHZ
  • HBLANK为512CLK,
  • HSIZE为4096CLK,
  • VSIZE为(4096+512)*4096+512=18874880CLK,
  • VBLANK为14458453CLK
  • DATA_OUT为16bit,一次传输4096个数据。

如果想通过VGA将图像显示出来,VGA显示用的时钟是65MHZ,那么就有两个问题需要我们解决:

  • 图像分辨率是4K * 4K,数据量太大,我们需要将其压缩为比如512 * 512
  • 图像数据的时钟和VGA显示用的时钟频率不一样,我们需要用双口RAM缓冲一下,其实你也可以用FIFO;不过需要注意存储器的大小应该设置为数据的个数,这样才能在读完一副图之后,恰好再从头开始读,循环往复

二、问题解决

1、创建Vivado的伪双口RAM
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其中的RAM深度:262144 = 512 * 512!!!

2、代码设计

4K*4K512*512,图像的长宽都变为原来的1/8,所以:

  • 对于每行数据,我们可以每8个数据采样一次
  • 对于每场数据,我们每8行数据采样一行
module Img_apt(
    input rst_n,
    input clk,
    input vsync,
    input hsync,
    input [15:0] din,
    input        vga_clk,
    input		 enb,	   
    output[15:0] dout
);
reg vsync_reg;
reg hsync_reg;
reg [15:0] din_reg;

always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			vsync_reg <= 'b0;
			hsync_reg <= 'b0;
			din_reg <= 'b0;
		end
	else
		begin
			vsync_reg <= vsync;
			hsync_reg <= hsync;
			din_reg <= din;
		end
end
reg [2:0] cnt_row;
reg [2:0] cnt_8;
wire	  ena;
reg [17:0]	addr_wr;
//这里是对原始图像数据做了采样,也即没有保存所有的数据,毕竟4k*4k太大了

//下面由于使用的是8倍采样,并且cnt_row和cnt_8都是3位,刚好到7后自动归零
//所以如果是其他倍数采样,那么就需要自己手动再添加一个归零条件,让cnt_row和cnt_8等于倍数-1时归零
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		cnt_row <= 'b0;
	//每一帧结束归零一下
	else if (!vsync_reg)
		cnt_row <= 'b0;
	//检测到行同步信号的下降沿,此时行计数+1;由于只有3位,所以每计数8行也会归零一次
	else if(!hsync_reg & hsync_reg2)
		cnt_row <= cnt_row + 1;
	else
		cnt_row <= cnt_row;
end
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		cnt_8 <= 'b0;
	//每一帧或者每一行无效时都要归零一下
	//也即不止每一帧,每一行也从第一个数据开始算
	//因此如果每一行的数据个数不是8的整数倍,那么最后的数据会被忽略一次
	else if (!vsync_reg | !hsync_reg)
		cnt_8 <= 'b0;
	//当处于每8行中的第一行时,列计数+1;由于只有3位,所以每计数8列也会归零一次
	else if (cnt_row==3'b001 && hsync_reg)
		cnt_8 <= cnt_8 + 1;
	else
		cnt_8 <= cnt_8;
end	

assign	wr_en = (cnt_8 == 3'b111)? 1:0;

always@(posedge	clk or negedge rst_n)
begin
		if(!rst_n)
			addr_wr <='b0;
		else if (!vsync_reg)
			addr_wr <= 'b0;
        //地址等于最大地址262143之后就不写了,之后RAM就充当ROM供vga驱动模块读就行了
		else if (addr_wr == 18'b11_1111_1111_1111_1111)
			addr_wr <= addr_wr;
		else if (ena)
			addr_wr <= addr_wr + 1;
		else 
			addr_wr <= addr_wr;
end		
reg	[17:0]		addr_rd;

always @(posedge vga_clk or negedge rst_n)							
 begin         
    if (!rst_n) begin
        addr_rd   <= 18'd0;
    end
    else if(enb) begin
        if(addr_rd < 18'b11_1111_1111_1111_1111)
            addr_rd <= addr_rd + 1'b1;    //每次读RAM操作后,读地址加1
        else
            addr_rd <= 1'b0;              //读到RAM末地址后,从首地址重新开始读操作
    end
    else
        addr_rd <= addr_rd;
end

dual_port_ram		u_dual_port_ram(
	.clka				(clk),
    .ena				(1'b1),
    .wea				(ena),
    .addra				(addr_wr),
    .dina				(din_reg),
	
    .clkb				(vga_clk),
    .enb				(enb),
    .addrb				(addr_rd),
    .dout				(dout)
	);
endmodule

需要注意的:

  • enbVGA驱动模块发送来的,了解VGA驱动模块的都知道,它会有一个数据读使能信号
  • 这里因为VGA显示时钟频率65M,比图像数据的60M大,所以刚开始边存边读时会存在读空值的情况,但对于显示来说没啥影响。因为只有图像数据还没存储完成时会有此情况,一旦存储完成,之后就不会再读空了,所以最多只影响前几帧,而肉眼是看不到的。
posted @ 2021-06-12 16:22  耐心的小黑  阅读(195)  评论(0编辑  收藏  举报