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*4K
到512*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
需要注意的:
enb
是VGA驱动模块
发送来的,了解VGA驱动模块
的都知道,它会有一个数据读使能信号- 这里因为VGA显示时钟频率65M,比图像数据的60M大,所以刚开始边存边读时会存在读空值的情况,但对于显示来说没啥影响。因为只有图像数据还没存储完成时会有此情况,一旦存储完成,之后就不会再读空了,所以最多只影响前几帧,而肉眼是看不到的。