FPGA VGA图片显示(vga_top)
一、VGA简介
关于VGA
的一些基础知识可以参考这篇文章:VGA介绍。
二、程序设计
FMC_apt
模块存储着图像数据vga_top
模块发送rom_rd_en
信号请求图像数据vga_data
,并且例化vga_driver
模块输出vga
显示所需的时序。
顶层模块的代码如下:
module vga_top(pclk,
rst_n,
vga_data,
rom_rd_en,
hsync,
vsync,
vga_r,
vga_g,
vga_b);
input pclk;
input rst_n;
input [15:0] vga_data;//存储图像数据的地方输入的16bit的图像数据
output rom_rd_en;//向存储图像数据的地方发出的读使能信号,
//表示请求vga_data输入
output hsync;//vga的接口
output vsync;
output [4:0] vga_r;
output [5:0] vga_g;
output [4:0] vga_b;
reg rom_valid;
wire [10:0] pixel_xpos;
wire [10:0] pixel_ypos;
wire [15:0] pixel_data ;
wire [15:0] vga_rgb;
localparam POS_X = 10'd100; //图片区域起始点横坐标
localparam POS_Y = 10'd100; //图片区域起始点纵坐标
localparam WIDTH = 10'd512; //图片区域宽度
localparam HEIGHT = 10'd512; //图片区域高度
localparam TOTAL = 19'd262144; //图案区域总像素数
localparam WHITE = 16'b1111_1111_1111_1111; //屏幕背景
assign vga_r = vga_rgb[15:11];
assign vga_g = vga_rgb[15:10];
assign vga_b = vga_rgb[15:11];
assign rom_rd_en = (pixel_xpos >= POS_X) && (pixel_xpos < POS_X + WIDTH)
&& (pixel_ypos >= POS_Y) && (pixel_ypos < POS_Y + HEIGHT)
? 1'b1 : 1'b0;
always @(posedge pclk )
begin
if (!rst_n)
rom_valid <= 1'b0;
else
rom_valid <= rom_rd_en;
end
assign pixel_data = rom_valid ? vga_data : WHITE;
vga_driver u_vga_driver(
.vga_clk (pclk),
.sys_rst_n (rst_n),
.vga_hs (hsync),
.vga_vs (vsync),
.vga_rgb (vga_rgb),
.pixel_data (pixel_data),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos)
);
endmodule
VGA驱动模块的代码如下所示:
module vga_driver(
input vga_clk, //VGA驱动时钟
input sys_rst_n, //复位信号
//VGA接口
output vga_hs, //行同步信号
output vga_vs, //场同步信号
output [15:0] vga_rgb, //红绿蓝三原色输出
input [15:0] pixel_data, //像素点数据
output [ 9:0] pixel_xpos, //像素点横坐标
output [ 9:0] pixel_ypos, //像素点纵坐标
);
//parameter define
parameter H_SYNC = 10'd96; //行同步
parameter H_BACK = 10'd48; //行显示后沿
parameter H_DISP = 10'd640; //行有效数据
parameter H_FRONT = 10'd16; //行显示前沿
parameter H_TOTAL = 10'd800; //行扫描周期
parameter V_SYNC = 10'd2; //场同步
parameter V_BACK = 10'd33; //场显示后沿
parameter V_DISP = 10'd480; //场有效数据
parameter V_FRONT = 10'd10; //场显示前沿
parameter V_TOTAL = 10'd525; //场扫描周期
//reg define
reg [9:0] cnt_h;
reg [9:0] cnt_v;
//wire define
wire vga_en;
wire data_req;
//*****************************************************
//** main code
//*****************************************************
//VGA行场同步信号
assign vga_hs = (cnt_h <= H_SYNC - 1'b1) ? 1'b0 : 1'b1;
assign vga_vs = (cnt_v <= V_SYNC - 1'b1) ? 1'b0 : 1'b1;
//使能RGB565数据输出
assign vga_en = (((cnt_h >= H_SYNC+H_BACK) && (cnt_h < H_SYNC+H_BACK+H_DISP))
&&((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//RGB565数据输出
assign vga_rgb = vga_en ? pixel_data : 16'd0;
//提前一个周期请求像素点颜色数据输入
assign data_req = (((cnt_h >= H_SYNC+H_BACK-1'b1) && (cnt_h < H_SYNC+H_BACK+H_DISP-1'b1))
&& ((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//像素点坐标
assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;
assign pixel_ypos = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;
//行计数器对像素时钟计数
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_h <= 10'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 10'd0;
end
end
//场计数器对行计数
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 10'd0;
end
end
endmodule
程序中第14至25行通过变量声明定义了分辨率为640*480、刷新速率为60hz时VGA时序中的各个参数。
程序第59至69行通过行计数器cnt_h对像素时钟计数,计满一个行扫描周期后清零并重新开始计数。程序第71至81行通过场计数器cnt_v对行进行计数,即扫描完一行后cnt_v加1,计满一个场扫描周期后清零并重新开始计数。
将行场计数器的值与VGA时序中的参数作比较,我们就可以判断行场同步信号何时处于低电平同步状态,以及何时输出RGB565格式的图像数据(38~48行)。程序50至57行输出当前像素点的纵横坐标值,由于坐标输出后下一个时钟周期才能接收到像素点的颜色数据,因此数据请求信号data_req比数据输出使能信号vga_en提前一个时钟周期。