该例程主要分为 3 个模块, 用于实现 VGA 的驱动显示功能。 其中顶层模块例化了 vga 时钟模块,VGA 驱动模块以及 VGA 显示测试模块。
vga 时钟模块是通过调用 PLL IP 核产生 VGA 的驱动时钟。 需要注意的是选择不同的分辨率要生成不同的驱动时钟,这个可以在VESA官网上找到每种分辨率对应的驱动时钟,以及行场信号信息,这里直接贴出“VESA和计算机显示监视器计时(DMT)行业标准和指南”:
之前也用示波器抓过行场信号,这里也贴出来,便于理解(图一为场信号【红色】、行信号【黄色】以及使能信号【蓝色】的总和;图二为行信号和使能信号,图二可以看出显示前沿、有效数据即蓝色使能信号、显示后沿,行同步信号):
现在分析代码:输入接口为:系统时钟clk_24m、外部按键复位rst_n;输出接口为:像素时钟vga_clk(用于驱动显示频)、行同步信号vga_hs、场同步信号vga_hs、输出数据使能vga_de、红色数据通道vga_r、绿色数据通道vga_g、蓝色数据通道vga_b。
首先是PLL模块,PLL输入时钟为系统时钟24MHz,产生一个108MHz时钟用于驱动显示器,通过查找上述的“VESA和计算机显示监视器计时(DMT)行业标准和指南”可以知道该频率用于驱动1280x1024@60Hz分辨率。
接下来是显示器驱动模块(Driver.v),该模块用于产生行场信号。1、模块定义hcnt对行同步信号进行计数,该分辨率计数1688次后清零hcnt重新计数;2、hcnt计数H_SYNC(112)次时产生行同步信号,拉高lcd_hs;3、定义vcnt对场同步信号进行计数,计数1066次后清零vcnt重新计数,vcnt自加时还须满足行信号计数完1688次;4、vcnt计数V_SYNC(3)次时产生场同步信号,拉高lcd_vs;5、当hcnt满足行有效数据区间(hcnt >= H_SYNC + H_BACK - H_AHEAD && hcnt < H_SYNC + H_BACK + H_DISP - H_AHEAD),并且vcnt满足场有效数据区间(vcnt >= V_SYNC + V_BACK && vcnt < V_SYNC + V_BACK + V_DISP)拉高数据使能信号lcd_en,此时向显示器发送像素数据。
`timescale 1ns/1ns
/* VGA参数配置表
************ clk H_SYNC H_BACK H_DISP H_FRONT H_TOTAL V_SYNC V_BACK V_DISP V_FRONT V_TOTAL *
640x480@60Hz 25.2MHz 96 48 640 16 800 2 33 480 10 525 *
800x600@60Hz 40MHz 128 88 800 40 1056 4 23 600 1 628 *
1024x768@60Hz 65MHz 136 160 1024 24 1344 6 29 768 3 806 *
1280x720@60Hz 74.25MHz 40 220 1280 110 1650 5 20 720 5 750 *
1280x1024@60Hz 108MHz 112 248 1280 48 1688 3 38 1024 1 1066 *
1920x1080@60Hz 148.5MHz 44 148 1920 88 2200 5 36 1080 4 1125 *
*/
module Driver
#(
parameter H_SYNC = 112 , // 行同步信号时间
parameter H_BACK = 248 , // 行消隐后肩时间
parameter H_DISP = 1280 , // 行数据有效时间
parameter H_FRONT = 48 , // 行消隐前肩时间
parameter H_TOTAL = 1688, // 行扫描总时间
parameter V_SYNC = 3 , // 列同步信号时间
parameter V_BACK = 38 , // 列消隐后肩时间
parameter V_DISP = 1024 , // 列数据有效时间
parameter V_FRONT = 1 , // 列消隐前肩时间
parameter V_TOTAL = 1066 // 列扫描总时间
)
(
input wire clk, //VGA clock
input wire rst_n, //sync reset
input wire [23:0] lcd_data, //lcd data
//lcd interface
output wire lcd_dclk, //lcd pixel clock
output wire lcd_hs, //lcd horizontal sync
output wire lcd_vs, //lcd vertical sync
output wire lcd_en, //lcd display enable
output wire [23:0] lcd_rgb, //lcd display data
//user interface
output wire [11:0] lcd_xpos, //lcd horizontal coordinate
output wire [11:0] lcd_ypos //lcd vertical coordinate
);
localparam H_AHEAD = 12'd1;
reg [11:0] hcnt;
reg [11:0] vcnt;
wire lcd_request;
/*******************************************
SYNC--BACK--DISP--FRONT
*******************************************/
//h_sync counter & generator
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
hcnt <= 12'd0;
else
begin
if(hcnt < H_TOTAL - 1'b1) //line over
hcnt <= hcnt + 1'b1;
else
hcnt <= 12'd0;
end
end
assign lcd_hs = (hcnt <= H_SYNC - 1'b1) ? 1'b0 : 1'b1; // line over flag
//v_sync counter & generator
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
vcnt <= 12'b0;
else if(hcnt == H_TOTAL - 1'b1) //line over
begin
if(vcnt == V_TOTAL - 1'b1) //frame over
vcnt <= 12'd0;
else
vcnt <= vcnt + 1'b1;
end
end
assign lcd_vs = (vcnt <= V_SYNC - 1'b1) ? 1'b0 : 1'b1; // frame over flag
// LED clock
assign lcd_dclk = ~clk;
// Control Display
assign lcd_en = (hcnt >= H_SYNC + H_BACK && hcnt < H_SYNC + H_BACK + H_DISP) &&
(vcnt >= V_SYNC + V_BACK && vcnt < V_SYNC + V_BACK + V_DISP)
? 1'b1 : 1'b0; // Display Enable Signal
assign lcd_rgb = lcd_en ? lcd_data : 24'h000000;
//ahead x clock
assign lcd_request = (hcnt >= H_SYNC + H_BACK - H_AHEAD && hcnt < H_SYNC + H_BACK + H_DISP - H_AHEAD) &&
(vcnt >= V_SYNC + V_BACK && vcnt < V_SYNC + V_BACK + V_DISP)
? 1'b1 : 1'b0;
//lcd xpos & ypos
assign lcd_xpos = lcd_request ? (hcnt - (H_SYNC + H_BACK - H_AHEAD)) : 12'd0;
assign lcd_ypos = lcd_request ? (vcnt - (V_SYNC + V_BACK)) : 12'd0;
endmodule
最后是显示模块(Display.v):该模块在数据有效期间将像素数据按坐标规律输出给显示器,这里一共定义四种播放模式。
`timescale 1ns/1ns
// Define colors RGB--8|8|8
`define RED 24'hFF0000
`define GREEN 24'h00FF00
`define BLUE 24'h0000FF
`define WHITE 24'hFFFFFF
`define BLACK 24'h000000
`define YELLOW 24'hFFFF00
`define CYAN 24'hFF00FF
`define ROYAL 24'h00FFFF
// Define Display Mode
// `define VGA_HORIZONTAL_COLOR // 八种颜色横彩条
// `define VGA_VERTICAL_COLOR // 八种颜色竖彩条
// `define VGA_GRAY_GRAPH // 红色彩条2x5
`define VGA_GRAFTAL_GRAPH // lcd_data <= lcd_xpos * lcd_ypos;
module Display
#(
parameter H_DISP = 1280,
parameter V_DISP = 1024
)
(
input wire clk,
input wire rst_n,
input wire [11:0] lcd_xpos, //lcd horizontal coordinate
input wire [11:0] lcd_ypos, //lcd vertical coordinate
output reg [23:0] lcd_data //lcd data
);
`ifdef VGA_HORIZONTAL_COLOR
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
lcd_data <= 24'h0;
else
begin
if (lcd_ypos >= 0 && lcd_ypos < (V_DISP/8)*1)
lcd_data <= `RED;
else if(lcd_ypos >= (V_DISP/8)*1 && lcd_ypos < (V_DISP/8)*2)
lcd_data <= `GREEN;
else if(lcd_ypos >= (V_DISP/8)*2 && lcd_ypos < (V_DISP/8)*3)
lcd_data <= `BLUE;
else if(lcd_ypos >= (V_DISP/8)*3 && lcd_ypos < (V_DISP/8)*4)
lcd_data <= `WHITE;
else if(lcd_ypos >= (V_DISP/8)*4 && lcd_ypos < (V_DISP/8)*5)
lcd_data <= `BLACK;
else if(lcd_ypos >= (V_DISP/8)*5 && lcd_ypos < (V_DISP/8)*6)
lcd_data <= `YELLOW;
else if(lcd_ypos >= (V_DISP/8)*6 && lcd_ypos < (V_DISP/8)*7)
lcd_data <= `CYAN;
else
lcd_data <= `ROYAL;
end
end
`endif
`ifdef VGA_VERTICAL_COLOR
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
lcd_data <= 24'h0;
else
begin
if (lcd_xpos >= 0 && lcd_xpos < (H_DISP/8)*1)
lcd_data <= `RED;
else if(lcd_xpos >= (H_DISP/8)*1 && lcd_xpos < (H_DISP/8)*2)
lcd_data <= `GREEN;
else if(lcd_xpos >= (H_DISP/8)*2 && lcd_xpos < (H_DISP/8)*3)
lcd_data <= `BLUE;
else if(lcd_xpos >= (H_DISP/8)*3 && lcd_xpos < (H_DISP/8)*4)
lcd_data <= `WHITE;
else if(lcd_xpos >= (H_DISP/8)*4 && lcd_xpos < (H_DISP/8)*5)
lcd_data <= `BLACK;
else if(lcd_xpos >= (H_DISP/8)*5 && lcd_xpos < (H_DISP/8)*6)
lcd_data <= `YELLOW;
else if(lcd_xpos >= (H_DISP/8)*6 && lcd_xpos < (H_DISP/8)*7)
lcd_data <= `CYAN;
else
lcd_data <= `ROYAL;
end
end
`endif
`ifdef VGA_GRAFTAL_GRAPH
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
lcd_data <= 24'h0;
else
lcd_data <= lcd_xpos * lcd_ypos;
end
`endif
`ifdef VGA_GRAY_GRAPH
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
lcd_data <= 24'h0;
else
begin
if(lcd_ypos < V_DISP/2)
lcd_data <= {lcd_ypos[7:0], lcd_ypos[7:0], lcd_ypos[7:0]};
else
lcd_data <= {lcd_xpos[7:0], lcd_xpos[7:0], lcd_xpos[7:0]};
end
end
`endif
endmodule
以下为四种显示效果,后续在发个VGA显示的【实战篇】,通过按键控制VGA输出显示的模式(下图四种),另一个按键控制VGA输出的分辨率:
本帖最后由 1nnocent 于 2022-7-30 09:19 编辑引用: Jacktang 发表于 2022-8-4 07:35 四种显示效果可以,期待后续的VGA显示测试
安路这款FPGA的PLL IP核产生的频率不是很准,目前只能做两种分辨率的切换,本来想做个四五种分辨率按键控制切换的,产生的五个时钟,只有两个符合要求
引用: 1nnocent 发表于 2022-8-4 08:55 安路这款FPGA的PLL IP核产生的频率不是很准,目前只能做两种分辨率的切换,本来想做个四五种分辨率按键控 ...
PLL IP不能生成任意频率,有些频率会存在误差,具体可以从IP配置界面里看到。