VGA原理
视频图形阵列(Video Graphics Array,VGA),是IBM在1987年推出的视频传输标准。
VGA从屏幕左上角第一个像素点开始,自左向右进行扫描,每一行扫描完成后,回到下一行最左位置的像素点,继续上述过程,最后一行像素点扫描完成后,回到屏幕左上角,继续下一轮扫描。
VGA接口重点需要明确每一行的扫描时间,称为行时序,以及每一次屏幕扫描的时间,称为场时序。
行时序由四个部分组成,行同步(Hor Sync)、行消隐(Hor Back Porch)、行视频有效(Hor Active Video)、行前肩(Hor Front Porch)。先后顺序为,
Hor Sync -> Hor Back Porch -> Hor Active Video -> Hor Front Porch
其中,行同步信号需要是负脉冲,且行时序中的每个部分持续的时间单位为像素,即时钟周期。
场时序由四个部分组成,场同步(Ver Sync)、场消隐(Ver Back Porch)、场视频有效(Ver Active Video)、场前肩(Ver Front Porch)。先后顺序为,
Ver Sync -> Ver Back Porch -> Ver Active Video -> Ver Front Porch
其中,场同步信号需要时负脉冲,且场时序中每个部分持续的时间单位为行。
VGA在不同的分辨率和刷新频率下,对于行时序和场时序各个部分的时间要求也不相同,VGA时序参数给出常用显示模式与对应的行时序和场时序的参数。
VGA实现
本文实现的在分辨率为640*480,刷新频率为60Hz的VGA驱动。
- 时钟:25.175 MHz
- 行时序参数(单位:像素)
- 行同步(Hor Sync):96
- 行消隐(Hor Back Porch):48
- 行视频有效(Hor Active Video):640
- 行前肩(Hor Front Porch):16
- 场时序参数(单位:行)
- 场同步(Ver Sync):2
- 场消隐(Ver Back Porch):33
- 场视频有效(Ver Active Video):480
- 场前肩(Ver Front Porch) :10
VGA同步信号
module vga_dirver(
input wire clk_i,
input wire rst_i,
output reg hor_sync_o,
output reg ver_sync_o,
output wire display_en_o
);
localparam HOR_SYNC_CNT = 96;
localparam HOR_BACK_PORCH_CNT = 48;
localparam HOR_ACTIVE_VIDEO_CNT = 640;
localparam HOR_FRONT_PORCH_CNT = 16;
localparam HOR_PERIOD = 800;
localparam VER_SYNC_CNT = 2;
localparam VER_BACK_PORCH_CNT = 33;
localparam VER_ACTIVE_VIDEO_CNT = 480;
localparam VER_FRONT_PORCH_CNT = 10;
localparam VER_PERIOD = 525;
reg [31:0] hor_count;
reg [31:0] ver_count;
reg hor_sync;
reg ver_sync;
// hor_sync
always @ (*) begin
if(rst_i == 1'b1) begin
hor_sync = 1'b1;
end else begin
hor_sync = (hor_count < HOR_SYNC_CNT) ? 1'b0 : 1'b1;
end
end
// 访问显存,hor_sync_o 打一拍输出
always @ (posedge clk_i) begin
if(rst_i == 1'b1) begin
hor_sync_o <= 1'b1;
end else begin
hor_sync_o <= hor_sync;
end
end
// ver_sync
always @ (*) begin
if(rst_i == 1'b1) begin
ver_sync = 1'b1;
end else begin
ver_sync = (ver_count < VER_SYNC_CNT) ? 1'b0 : 1'b1;
end
end
// 访问显存,ver_sync_o 打一拍输出
always @ (posedge clk_i) begin
if(rst_i == 1'b1) begin
ver_sync_o <= 1'b1;
end else begin
ver_sync_o <= ver_sync;
end
end
// display_en_o
assign display_en_o = (hor_count >= HOR_SYNC_CNT + HOR_BACK_PORCH_CNT) &&
(hor_count < HOR_SYNC_CNT + HOR_BACK_PORCH_CNT + HOR_ACTIVE_VIDEO_CNT) &&
(ver_count >= VER_SYNC_CNT + VER_BACK_PORCH_CNT) &&
(ver_count < VER_SYNC_CNT + VER_BACK_PORCH_CNT + VER_ACTIVE_VIDEO_CNT);
// hor_count
always @ (posedge clk_i) begin
if(rst_i == 1'b1) begin
hor_count <= 0;
end else begin
if(hor_count == HOR_PERIOD - 1) begin
hor_count <= 0;
end else begin
hor_count <= hor_count + 1;
end
end
end
// ver_count
always @ (posedge clk_i) begin
if(rst_i == 1'b1) begin
ver_count <= 0;
end else begin
if(ver_count == VER_PERIOD - 1) begin
if(hor_count == HOR_PERIOD - 1) begin
ver_count <= 0;
end else begin
ver_count <= ver_count;
end
end else begin
if(hor_count == HOR_PERIOD - 1) begin
ver_count <= ver_count + 1;
end else begin
ver_count <= ver_count;
end
end
end
end
endmodule
VGA显示存储
module vga_rom(
input wire clk_i,
input wire wr_en_i,
input wire [9:0] wr_addr_i,
input wire [11:0] wr_data_i,
input wire [9:0] rd_addr_i,
output reg [11:0] rd_data_o
);
reg [11:0] vga_rom [0:1023];
always @ (posedge clk_i) begin
if(wr_en_i == 1'b1) begin
vga_rom[wr_addr_i] <= wr_data_i;
end else begin
end
end
always @ (posedge clk_i) begin
rd_data_o <= vga_rom[rd_addr_i];
end
endmodule
VGA顶层文件
module vga_top(
input wire clk_i,
input wire rst_i,
input wire [11:0] display_rd_data_i,
output reg [9:0] display_rd_addr_o,
output reg [3:0] red_o,
output reg [3:0] grn_o,
output reg [3:0] blu_o,
output wire hor_sync_o,
output wire ver_sync_o
);
wire display_en;
reg [11:0] display_rd_data;
// red_o grn_o blu_o
always @ (*) begin
if(rst_i == 1'b1) begin
red_o = 0;
grn_o = 0;
blu_o = 0;
end else begin
red_o = display_rd_data[3:0];
grn_o = display_rd_data[7:4];
blu_o = display_rd_data[11:8];
end
end
// display_rd_data
always @ (*) begin
if(rst_i == 1'b1) begin
display_rd_data = 0;
end else begin
if(display_en == 1'b1) begin
display_rd_data = display_rd_data_i;
end else begin
display_rd_data = 0;
end
end
end
// display_rd_addr_o
always @ (posedge clk_i) begin
if(rst_i == 1'b1) begin
display_rd_addr_o <= 0;
end else begin
if(display_en == 1'b1) begin
display_rd_addr_o <= display_rd_addr_o + 1;
end else begin
display_rd_addr_o <= display_rd_addr_o;
end
end
end
vga_dirver vga_dirver_inst(
.clk_i(clk_i),
.rst_i(rst_i),
.hor_sync_o(hor_sync_o),
.ver_sync_o(ver_sync_o),
.display_en_o(display_en)
);
endmodule
测试文件
在显存中随机写入数据,比较显示时序和行同步与场同步时序。
module testbench();
reg clk;
reg rst;
reg vga_rom_wr_en;
reg [11:0] vga_rom_wr_data;
reg [9:0] vga_rom_wr_addr;
wire [11:0] vga_rom_rd_data;
wire [9:0] vga_rom_rd_addr;
wire [3:0] red;
wire [3:0] grn;
wire [3:0] blu;
wire hor_sync;
wire ver_sync;
initial begin
clk = 1;
rst = 1;
vga_rom_wr_en = 1;
#4000
rst = 0;
vga_rom_wr_en = 0;
end
// 20 MHz时钟
always begin
#(40 / 2) clk = ~clk;
end
// 初始化显存
initial begin
vga_rom_wr_addr = 0;
vga_rom_wr_data = $random;
repeat(50) begin
# 40
if(vga_rom_wr_en == 1'b1) begin
vga_rom_wr_addr <= vga_rom_wr_addr + 1;
vga_rom_wr_data <= $random;
end
end
end
vga_top vga_top_inst(
.clk_i(clk),
.rst_i(rst),
.display_rd_data_i(vga_rom_rd_data),
.display_rd_addr_o(vga_rom_rd_addr),
.red_o(red),
.grn_o(grn),
.blu_o(blu),
.hor_sync_o(hor_sync),
.ver_sync_o(ver_sync)
);
vga_rom vga_rom_inst(
.clk_i(clk),
.wr_en_i(vga_rom_wr_en),
.wr_addr_i(vga_rom_wr_addr),
.wr_data_i(vga_rom_wr_data),
.rd_addr_i(vga_rom_rd_addr),
.rd_data_o(vga_rom_rd_data)
);
endmodule