【FPGA】VGA显示文字、彩条、图片——基于DE2-115

一、VGA概述

1.1 简述

  • **VGA(Video Graphics Array)**视频图形阵列是IBM于1987年提出的一个使用模拟信号的电脑显示标准。VGA接口即电脑采用VGA标准输出数据的专用接口。VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号)。
    在这里插入图片描述
  • 大多数计算机与外部显示设备之间都是通过模拟VGA接口连接,计算机内部以数字方式生成的显示图像信息,被显卡中的数字/模拟转换器转变为R、G、B三原色信号和行、场同步信号,信号通过电缆传输到显示设备中。对于模拟显示设备,如模拟CRT显示器,信号被直接送到相应的处理电路,驱动控制显像管生成图像。而对于LCD、DLP等数字显示设备,显示设备中需配置相应的A/D(模拟/数字)转换器,将模拟信号转变为数字信号。在经过D/A和A/D两次转换后,不可避免地造成了一些图像细节的损失。VGA接口应用于CRT显示器无可厚非,但用于连接液晶之类的显示设备,则转换过程的图像损失会使显示效果略微下降。
    而且可以从接口处来判断显卡是独显还是集成显卡,VGA接口竖置的说明是集成显卡,VGA接口横置说明是独立显卡(一般的台式主机都可以用此方法来查看)。

1.2 管脚定义

  • 管脚定义
管脚定义
1红基色 red
2绿基色 green
3蓝基色 blue
4地址码 ID Bit
5自测试 ( 各家定义不同 )
6红地
7绿地
8蓝地
9保留 ( 各家定义不同 )
10数字地
11地址码
12地址码
13行同步
14场同步
15地址码 ( 各家定义不同)

大家要是想了解更多的内容,比如原理等,可点击下方连接去百度百科查看:
百度百科-VGA

1.3 行、场时序及分辨率

  • VGA时序标准

在这里插入图片描述

  • 行同步时序:(行时序的各种信号,可对照下面的表格里的显示参数)

在这里插入图片描述

  • 场同步时序

在这里插入图片描述

  • VGA的不同分辨率显示参数

在这里插入图片描述

  • 将行同步时序图与场同步时序图结合起来就构成了 VGA 时序图:

在这里插入图片描述

红色区域表示在一个完整的行扫描周期中,Video 图像信息只在此区域有效,黄色区域表示在一个完整的场扫描周期中,Video 图像信息只在此区域有效,两者相交的橙色区域,就是 VGA 图像的最终显示区域。

其他可详见此博主的博客(点击跳转)

二、VGA显示文字

笔主整篇文章中使用的板子都是 EP4CE115F29C7,使用其他板子问题不大,但引脚绑定不太一样,可自行对应板子查找更改。

  • 引脚

在这里插入图片描述

2.1 点阵汉字生成

在这里插入图片描述

  • 在下面的字符里,使用中间那一栏,如图

在这里插入图片描述

  • 将那些字节复制粘贴到你的txt文件里(自己建一个就行)
  • 然后进行整理(笔主这里的学号名字是已经整理好的)

在这里插入图片描述

  • 用替换将原理点阵里的 , 0x 替换为(没错就是空白,不要输入空格,就是什么都不填),然后再把前面两列的0x删掉就可以了,最后一个字节那里打上 ; 就OK了。

在这里插入图片描述

2.2 工程建立

  • 笔主这里用的软件是Quartus 18.1
  • 自己新建一个工程(网上步骤很多,笔主之前的文章好像也写过),目录结构如下:
    在这里插入图片描述
名称作用
doc一些说明文档、仿真、上板结果等
ip知识产权
prj工程文件
rtl源文件(.v)
tb仿真文件
  • rtl 源文件里主要有:
    在这里插入图片描述
名称作用
vga_top顶层文件:主要是模块例化
vga_param参数定义:对各种不同的分辨率的显示参数定义
data_gen数据读取模块
vga_ctrlVGA驱动、控制模块
  • tb里还有一个仿真测试文件 vga_tb

2.3 引入ip核-实现特定时钟频率+不同分辨率显示

  • 为了正确在 640*480 的分辨率下显示,需要一个25MHz的时钟,我们需要引入 PLL ip核
  • 搜索PLL,如图:

在这里插入图片描述

  • 将文件存到 ip 里,笔主这里已经添加过了

在这里插入图片描述

  • 直接next

在这里插入图片描述

  • next
    在这里插入图片描述
  • 一路next到如下界面,设置为25MHz

在这里插入图片描述

  • 为了显示不同分辨率,笔主这里另一个分辨率为 800*600,需要一个40MHz的时钟,我们这里再加一个时钟 c1

在这里插入图片描述

  • 然后next到最后界面,选中生成例化模块,这里自己编写代码的时候运用比较方便

在这里插入图片描述

  • 改变分辨率步骤
    ①更改vga_param 里的 define 后面的名称,比如
    define vga_640_480 改为 define vga_800_600 (“ ` ”被省略了,博客里显示不出来)
    在这里插入图片描述
    ②vga_top里定义了时钟频率
    在这里插入图片描述
    ③改变一下1、2 处就行,比如 vga_25 改为 vga_40
    在这里插入图片描述

其他分辨率更改都是类似。

2.3 代码实现

  • vga_param
  • 笔主因为老师要求不同分辨率,所以只定义两种,其他可自行添加(根据前面的那个分辨率显示参数表格)
`define vga_640_480//这里在你使用不同分辨率的时候要改变

//笔主因为老师要求不同分辨率,所以定义两种,其他可自行添加(根据前面的那个分辨率显示参数表格)

`ifdef  vga_640_480
    //执行操作A
    `define H_Right_Border  8
    `define H_Front_Porch   8
    `define H_Sync_Time     96
    `define H_Back_Porch    40
    `define H_Left_Border   8
    `define H_Data_Time     640
    `define H_Total_Time    800

    
    `define V_Bottom_Border  8
    `define V_Front_Porch    2
    `define V_Sync_Time      2
    `define V_Back_Porch     25
    `define V_Top_Border     8
    `define V_Data_Time      480
    `define V_Total_Time     525

`elsif  vga_800_600
    //执行操作B
    `define H_Right_Border  0
    `define H_Front_Border  40
    `define H_Sync_Time     128
    `define H_Back_Porch    88
    `define H_Left_Border   0
    `define H_Data_Time     800
    `define H_Total_Time    1056

    
    `define V_Bottom_Border  0
    `define V_Front_Porch    1
    `define V_Sync_Time      4
    `define V_Back_Porch     23
    `define V_Top_Border     0
    `define V_Data_Time      600
    `define V_Total_Time     628
`endif 
  • vga_ctrl
`include "vga_param.v"

module vga_ctrl (
    input   wire              clk         , //vga clk 640*480 25.2MHz
    input   wire              rst_n       , //复位信号
    input   wire   [23:0]     data_disp   , //

    output  reg    [10:0]     h_addr      , //数据有效显示区域行地址
    output  reg    [10:0]     v_addr      , //数据有效显示区域场地址

    output  reg               vsync       , //
    output  reg               hsync       , //

    output  reg    [07:0]     vga_r       , //RGB三色
    output  reg    [07:0]     vga_g       , //
    output  reg    [07:0]     vga_b       , //
    output  reg               vga_blk     , //VGA 消隐信号
    output  wire              vga_clk       //

);
//参数定义
    parameter   H_SYNC_STA = 1 ,//行同步开始信号
                H_SYNC_STO = `H_Sync_Time ,//行同步停止
                H_Data_STA = `H_Sync_Time + `H_Back_Porch + `H_Left_Border,//行数据开始
                H_Data_STO = `H_Sync_Time + `H_Back_Porch + `H_Left_Border + `H_Data_Time ,//行数据开始

                V_SYNC_STA = 1 ,
                V_SYNC_STO = `V_Sync_Time ,
                V_Data_STA = `V_Sync_Time + `V_Back_Porch + `V_Top_Border,
                V_Data_STO = `V_Sync_Time + `V_Back_Porch + `V_Top_Border + `V_Data_Time;

    reg     [11:00]     cnt_h_addr  ; //行地址计数器
    wire                add_h_addr  ;
    wire                end_h_addr  ;

    reg     [11:00]     cnt_v_addr  ; //场地址计数器
    wire                add_v_addr  ;
    wire                end_v_addr  ;

//行地址计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_h_addr <= 12'd0;
        end
        else if (add_h_addr) begin
            if (end_h_addr) begin
                cnt_h_addr <= 12'd0;
            end
            else begin
                cnt_h_addr <= cnt_h_addr + 12'd1;
            end
        end
        else begin
            cnt_h_addr <= cnt_h_addr;
        end
    end

    assign   add_h_addr = 1'b1;
    assign   end_h_addr = add_h_addr && cnt_h_addr >= `H_Total_Time - 1;


//场地址计数器
   always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_v_addr <= 12'd0;
        end
        else if (add_v_addr) begin
            if (end_v_addr) begin
                cnt_v_addr <= 12'd0;
            end
            else begin
                cnt_v_addr <= cnt_v_addr + 12'd1;
            end
        end
        else begin
            cnt_v_addr <= cnt_v_addr;
        end
    end

    assign   add_v_addr =  end_h_addr;
    assign   end_v_addr = add_v_addr && cnt_v_addr >= `V_Total_Time - 1;


//行场同步信号
//行同步信号
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            hsync <= 1'b1;
        end
        else if (cnt_h_addr == H_SYNC_STA - 1) begin
            hsync <= 1'b0;
        end
        else if (cnt_h_addr == H_SYNC_STO - 1)begin
            hsync <= 1'b1;
        end
        else begin
            hsync <= hsync;
        end
    end

//场同步信号
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            vsync <= 1'b1;
        end
        else if (cnt_v_addr == V_SYNC_STA - 1) begin
            vsync <= 1'b0;
        end
        else if (cnt_v_addr == V_SYNC_STO - 1)begin
            vsync <= 1'b1;
        end
        else begin
            vsync <= vsync;
        end
    end

    assign   vga_clk  = ~clk;

//数据有效显示区域定义
//行
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            h_addr <= 11'd0;
        end
        else if ((cnt_h_addr >= H_Data_STA) && (cnt_h_addr <= H_Data_STO)) begin
            h_addr <= cnt_h_addr - H_Data_STA;
        end
        else begin
            h_addr <= 11'd0;
        end
    end

//场
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            v_addr <= 11'd0;
        end
        else if ((cnt_v_addr >= V_Data_STA) && (cnt_v_addr <= V_Data_STO)) begin
            v_addr <= cnt_v_addr - V_Data_STA;
        end
        else begin
            v_addr <= 11'd0;
        end
    end

//显示数据
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            vga_r <= 8'd0;
            vga_g <= 8'd0;
            vga_b <= 8'd0;
            vga_blk <= 1'b0;
        end
        else if ((cnt_h_addr >= H_Data_STA -1) && (cnt_h_addr <= H_Data_STO - 1)
                    && (cnt_v_addr >= V_Data_STA -1 ) && (cnt_v_addr <= V_Data_STO -1)) begin
            vga_r = data_disp[23:16];//data_dis[23-:8]
            vga_g = data_disp[15:08];//data_dis[15-:8]
            vga_b = data_disp[07:00];//data_dis[07-:8]
            vga_blk <= 1'b1;
        end
        else begin
            vga_r <= 8'd0;
            vga_g <= 8'd0;
            vga_b <= 8'd0;
            vga_blk <= 1'b0;
        end
    end
endmodule
  • data_gen
module data_gen (
    input   wire              clk         , //vga clk 640*480 25.2MHz
    input   wire              rst_n       , //复位信号

    input   wire   [10:0]     h_addr      , //数据有效显示区域行地址
    input   wire   [10:0]     v_addr      , //数据有效显示区域场地址

    output  reg    [23:0]     data_disp     //数据

);

    reg [ 223:0 ] char_line[ 15:0 ];//16*14个字符=224,224*16的字符存储区
//参数定义
    parameter
        BLACK   = 24'h000000,
        RED     = 24'hFF0000,
        GREEN   = 24'h00FF00,
        BLUE    = 24'h0000FF,
        YELLOW  = 24'hFFFF00,
        SKY_BULE= 24'h00FFFF,
        PURPLE  = 24'hFF00FF,
        GRAY    = 24'hC0C0C0,
        WHITE   = 24'hFFFFFF; 
    parameter
        H_VLD   = 640,
        // H_VLD   = 800,
        V_VLD   = 480,
        // V_VLD   = 600,
        
        PIC_W   = 224,
        PIC_H   = 16,

        X_START = (H_VLD - PIC_W >> 1)-1,//正中间
        Y_START = (V_VLD - PIC_H >> 1)- 1;

    reg   [10:00] pix_x,pix_y;//字符显示坐标

//显示区域
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            pix_x <= 11'd0;
            pix_y <= 11'd0;
        end
        else if ((h_addr >= X_START - 1 && h_addr < X_START + PIC_W)
                && (v_addr >= Y_START && v_addr < Y_START + PIC_H)) begin
            pix_x <= h_addr - X_START;
            pix_y <= v_addr - Y_START;
        end
        else begin
            pix_x = 11'h7FF;
            pix_y = 11'h7FF;
        end
    end
    
//显示颜色
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            data_disp <= WHITE;
        end
        else if (pix_x != 11'h7FF && pix_y != 11'h7FF) begin
            if (char_line[pix_y][223 - pix_x] == 1'b1) begin
                data_disp <= PURPLE ;
            end
            else begin
                data_disp <= WHITE   ;
            end
        end
        else 
            data_disp <= BLACK;
    end
    
//初始化显示文字
    always@( posedge clk or negedge rst_n ) begin
        if ( !rst_n ) begin//将前面得到的点阵放在这里~
          char_line[ 0 ]  = 224'h01001000000000000000000000000000000000000000000000000000;
          char_line[ 1 ]  = 224'h010008fe000000000000000000000000000000000000000000000000;
          char_line[ 2 ]  = 224'h02807f10000000000000000000000000000000000000000000000000;
          char_line[ 3 ]  = 224'h0440222018003c000800380018007e001800180018003c003c003c00;
          char_line[ 4 ]  = 224'h0820147c240042003800440024004200240024002400420042004200;
          char_line[ 5 ]  = 224'h10107f44400042000800420042000400420040004200420042004200;
          char_line[ 6 ]  = 224'h2fe84454400002000800420042000400420040004200420042004200;
          char_line[ 7 ]  = 224'hc10648545c000400080042004200080042005c004200020002002400;
          char_line[ 8 ]  = 224'h01005254620018000800460042000800420062004200040004001800;
          char_line[ 9 ]  = 224'h3ff844544200040008003a0042001000420042004200080008002400;
          char_line[ 10 ] = 224'h01004854420002000800020042001000420042004200100010004200;
          char_line[ 11 ] = 224'h11105154420042000800020042001000420042004200200020004200;
          char_line[ 12 ] = 224'h11084228220042000800240024001000240022002400420042004200;
          char_line[ 13 ] = 224'h210444241c003c003e0018001800100018001c0018007e007e003c00;
          char_line[ 14 ] = 224'h45048842000000000000000000000000000000000000000000000000;
          char_line[ 15 ] = 224'h02003082000000000000000000000000000000000000000000000000;
        end
    end
    
endmodule
  • vga_top
module vga_top (
    input   wire              clk   ,
    input   wire              rst_n ,

    output  wire              vsync       , //
    output  wire              hsync       , //
    output  wire    [07:00]   vga_r       , //RGB三色
    output  wire    [07:00]   vga_g       , //
    output  wire    [07:00]   vga_b       , //
    output  wire              vga_blk     , //  
    output  wire              vga_clk       //  

);
    wire [23:00] data_disp;
    wire            vga_25;
    wire            vga_40;//60MHz

    wire            locked;  

    wire [10:00] h_addr;
    wire [10:00] v_addr;

    wire            reset;

    assign reset = rst_n & locked;


    // pll	u_pll (
	// .areset ( !rst_n     ),
	// .inclk0 ( clk        ),
	// .c0     ( vga_25    )
	// );

    pll_25	pll_25_inst (
	.areset ( ~rst_n ),
	.inclk0 ( clk ),
	.c0 ( vga_25 ),
    .c1 ( vga_40 ),
	.locked (locked)
	);


    data_gen u_data_gen(
        .clk         (vga_25  ), //vga clk 640*480 25.2MHz
        .rst_n       (reset    ), //复位信号
        .h_addr      (h_addr   ), //数据有效显示区域行地址
        .v_addr      (v_addr   ), //数据有效显示区域场地址

        .data_disp   (data_disp)  //
    );
    vga_ctrl u_vga_ctrl(
        .clk         (vga_25 ), //vga clk 640*480 25.2MHz
        .rst_n       (reset    ), //复位信号
        .data_disp   (data_disp), //
        
        .h_addr      (h_addr   ), //数据有效显示区域行地址
        .v_addr      (v_addr   ), //数据有效显示区域场地址
        .vsync       (vsync    ), //
        .hsync       (hsync    ), //
        .vga_r       (vga_r    ), //RGB三色
        .vga_g       (vga_g    ), //
        .vga_b       (vga_b    ), //
        .vga_blk     (vga_blk  ), //
        .vga_clk     (vga_clk  )  //
    );
endmodule
  • vga_tb
module vga_top (
    input   wire              clk   ,
    input   wire              rst_n ,

    output  wire              vsync       , //
    output  wire              hsync       , //
    output  wire    [07:00]   vga_r       , //RGB三色
    output  wire    [07:00]   vga_g       , //
    output  wire    [07:00]   vga_b       , //
    output  wire              vga_blk     , //  
    output  wire              vga_clk       //  

);
    wire [23:00] data_disp;
    wire            vga_25;//25MHz
    wire            vga_40;//40MHz

    wire            locked;  

    wire [10:00] h_addr;
    wire [10:00] v_addr;

    wire            reset;
    
    assign reset = rst_n & locked;

    pll_25	pll_25_inst (
	.areset ( ~rst_n ),
	.inclk0 ( clk ),
	.c0 ( vga_25 ),
    .c1 ( vga_40 ),
	.locked (locked)
	);


    data_gen u_data_gen(
        .clk         (vga_25  ), //vga clk 640*480 25.2MHz
        .rst_n       (reset    ), //复位信号
        .h_addr      (h_addr   ), //数据有效显示区域行地址
        .v_addr      (v_addr   ), //数据有效显示区域场地址

        .data_disp   (data_disp)  //
    );
    vga_ctrl u_vga_ctrl(
        .clk         (vga_25 ), //vga clk 640*480 25.2MHz
        .rst_n       (reset    ), //复位信号
        .data_disp   (data_disp), //
        
        .h_addr      (h_addr   ), //数据有效显示区域行地址
        .v_addr      (v_addr   ), //数据有效显示区域场地址
        .vsync       (vsync    ), //
        .hsync       (hsync    ), //
        .vga_r       (vga_r    ), //RGB三色
        .vga_g       (vga_g    ), //
        .vga_b       (vga_b    ), //
        .vga_blk     (vga_blk  ), //
        .vga_clk     (vga_clk  )  //
    );
endmodule

2.4 上板验证

  • 640*480分辨率显示下:显示在正中间

请添加图片描述

  • 800*600分辨率显示下:可见因为分辨率的改变而数据读取模块的行场未改变,位置向左上偏移了一些,是正常的。

请添加图片描述

三、VGA显示彩条

这里彩条很简单,均匀显示,没有考虑每个彩条所显示的区域。

3.1 代码实现

  • data_gen
module data_gen (
    input   wire              clk         , //vga clk 640*480 25.2MHz
    input   wire              rst_n       , //复位信号

    input   wire   [10:0]     h_addr      , //数据有效显示区域行地址
    input   wire   [10:0]     v_addr      , //数据有效显示区域场地址

    output  reg    [23:0]     data_disp     //

);

//参数定义
    parameter
        BLACK   = 24'h000000,
        RED     = 24'hFF0000,
        GREEN   = 24'h00FF00,
        BLUE    = 24'h0000FF,
        YELLOW  = 24'hFFFF00,
        SKY_BULE= 24'h00FFFF,
        PURPLE  = 24'hFF00FF,
        GRAY    = 24'hC0C0C0,
        WHITE   = 24'hFFFFFF; 
        
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            data_disp <= WHITE;
        end
        else begin//彩条
            case (h_addr)
                0   : data_disp <= BLACK    ;
                80  : data_disp <= RED      ;
                160 : data_disp <= GREEN    ;
                240 : data_disp <= BLUE     ;
                320 : data_disp <= YELLOW   ;
                400 : data_disp <= SKY_BULE ;
                480 : data_disp <= PURPLE   ;
                560 : data_disp <= GRAY     ;
                default: data_disp <= data_disp ;
            endcase
        end
    end
endmodule
  • 其他 .v 文件与前文一致。

3.2 上板验证

  • 640*480分辨率显示下:显示在正中间

请添加图片描述

  • 800*600分辨率显示下:彩条变得不均匀,但符合预期。

请添加图片描述

四、VGA显示图片

4.1 24位位图

  • 在前面的学习中了解到图像的格式有多种,例如JPEG,BMP,PNG,JPG等,图像的位数也有单色、16色、256色、4096色、16位真彩色、24位真彩色、32位真彩色在这里插入图片描述这几种。
    VGA的驱动程序显示的格式为RGB565,我们先找到一张需要显示的彩色图片,经过处理,将该图片转化为ROM可以存储的格式,然后VGA驱动程序从ROM中读取数据,输出到VGA显示屏显示。尽量 选一张小的图片,因为ROM存储空间有限。
  • 可以用电脑自带的 画图 软件,将一张图片转为 bmp 格式

在这里插入图片描述

  • 使用BMP2Mif软件将 bmp 格式图片转换为 Mif 文件。

在这里插入图片描述

  • 转换后的.mif文件:

在这里插入图片描述

4.2 引入ROM ip核

  • 新建Quartus工程,产生ROM IP核,将生成的mif文件保存在ROM中
  • 搜索ROM,双击选择ROM:1-PORT
    在这里插入图片描述
  • 选择存储的位置,一般为了方便管理就存储在ip中

在这里插入图片描述

  • 更改设置,word大小设置要大于图片大小(52x52x24 < 65536),然后next
    在这里插入图片描述
  • 取消勾选q,然后next
    在这里插入图片描述
  • 如果是16位位图,就加载HEX文件,然后next

在这里插入图片描述

  • 这里因为我们转换的是24位位图,所以在ROM里要引入 .mif 文件。(16位位图不用管)

在这里插入图片描述

  • 勾选上输出 inst,方便例化,然后finish

在这里插入图片描述

  • 这里同时要调用前面的pll ip核生成一个25mHz的时钟。

4.3 代码实现

  • 在data_gen.v文件里,从ROM取出图片数据。
module data_gen (
    input   wire              clk         , //vga clk 640*480 25.2MHz
    input   wire              rst_n       , //复位信号

    input   wire   [10:0]     h_addr      , //数据有效显示区域行地址
    input   wire   [10:0]     v_addr      , //数据有效显示区域场地址

    output  reg    [23:0]     data_disp     //

);

    
    reg			[ 13:0 ]		rom_address				; // ROM地址
    wire		[ 23:0 ]		rom_data				; // 图片数据

    wire						flag_enable_out2			; // 图片有效区域
    wire						flag_clear_rom_address		; // 地址清零
    wire						flag_begin_h			    ; // 图片显示行
    wire						flag_begin_v			    ; // 图片显示列
    
    parameter	height = 52; // 图片高度
    parameter	width  = 52; // 图片宽度

    reg [ 223:0 ] char_line[ 15:0 ];//16*14个字符=224,224*16的字符存储区
//参数定义
    parameter
        BLACK   = 24'h000000,
        RED     = 24'hFF0000,
        GREEN   = 24'h00FF00,
        BLUE    = 24'h0000FF,
        YELLOW  = 24'hFFFF00,
        SKY_BULE= 24'h00FFFF,
        PURPLE  = 24'hFF00FF,
        GRAY    = 24'hC0C0C0,
        WHITE   = 24'hFFFFFF; 

    always @( posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        data_disp = BLACK;
    end
    else if ( flag_enable_out2 ) begin
        data_disp = rom_data;
    end
    else begin
        data_disp = BLACK;
    end
    end

    //ROM地址计数器
    always @( posedge clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            rom_address <= 0;
        end
        else if ( flag_clear_rom_address ) begin //计数满清零
            rom_address <= 0;
        end
            else if ( flag_enable_out2 ) begin  //在有效区域内+1
            rom_address <= rom_address + 1;
            end
        else begin  //无效区域保持
            rom_address <= rom_address;
        end
    end
    assign flag_clear_rom_address = rom_address == height * width - 1;
    assign flag_begin_h     = h_addr > ( ( 640 - width ) / 2 ) && h_addr < ( ( 640 - width ) / 2 ) + width + 1;
    assign flag_begin_v     = v_addr > ( ( 480 - height )/2 ) && v_addr <( ( 480 - height )/2 ) + height + 1;
    assign flag_enable_out2 = flag_begin_h && flag_begin_v;

    //实例化ROM
    rom	rom_inst (
    .address    ( rom_address   ),
    .clock      ( clk           ),
    .q          ( rom_data      )
    );
endmodule
  • 其他 .v 文件与前文一致。

4.4 上板验证

  • 640*480分辨率显示下:显示在正中间

请添加图片描述

  • 800*600分辨率显示下:图片向上偏移,符合预期。

请添加图片描述

tb文件

  • vga_tb
`timescale 1ns/1ps

`define clk_period 20 //时钟周期参数定义

module vga_tb ();

//激励信号
    reg              clk         ;
    reg              rst_n       ;
//观测信号
    wire              vsync       ;
    wire              hsync       ;
    wire   [07:0]     vga_r       ;
    wire   [07:0]     vga_g       ;
    wire   [07:0]     vga_b       ;
    wire              vga_blk     ;
    wire              vga_clk     ;

    //ASCII 显示颜色字符
    reg    [63:00]      CHAR_CLO    ;
    parameter
        BLACK   = 24'h000000,
        RED     = 24'hFF0000,
        GREEN   = 24'h00FF00,
        BLUE    = 24'h0000FF,
        YELLOW  = 24'hFFFF00,
        SKY_BULE= 24'h00FFFF,
        PURPLE  = 24'hFF00FF,
        GRAY    = 24'hC0C0C0,
        WHITE   = 24'hFFFFFF; 
    always @(*) begin
        case(u_vga_top.data_disp)
            BLACK    :  CHAR_CLO = "BLACK   ";
            RED      :  CHAR_CLO = "RED     ";
            GREEN    :  CHAR_CLO = "GREEN   ";
            BLUE     :  CHAR_CLO = "BLUE    ";
            YELLOW   :  CHAR_CLO = "YELLOW  ";
            SKY_BULE :  CHAR_CLO = "SKY_BULE";
            PURPLE   :  CHAR_CLO = "PURPLE  ";
            GRAY     :  CHAR_CLO = "GRAY    ";
            WHITE    :  CHAR_CLO = "WHITE   ";

            // default  :  CHAR_CLO = "WHITE   ";
            default  :  CHAR_CLO = CHAR_CLO;
            // default  :  WHITE = "WHITE   ";
        endcase
    end

//模块例化
    vga_top u_vga_top(
        .clk         (clk    ),
        .rst_n       (rst_n  ),

        .vsync       (vsync  ),   
        .hsync       (hsync  ),   
        .vga_r       (vga_r  ),       
        .vga_g       (vga_g  ),   
        .vga_b       (vga_b  ),   
        .vga_blk     (vga_blk),  
        .vga_clk     (vga_clk)   
    );

//系统初始化
    //时钟生成
    initial clk = 1'b0;
    always #(`clk_period / 2 ) clk = ~clk;

    //其他激励
    initial begin
        // clk = 1'b1;
        rst_n = 1'b0;
        #(`clk_period * 20 + 3)
        rst_n = 1'b1; //产生复位
        #(`clk_period * 20);
        
        repeat(2)begin
            @(negedge vsync);
        end

        #(`clk_period * 20000)
        $stop;

    end
endmodule

可自行验证

小小的总结

  • VGA显示,难点在于显示驱动模块,行场同步时序的代码编写,可以先写计数器和大模块,一些内部信号与标志信号可以逐步完善;其次就是数据显示中显示区域代码的编写。
  • VGA显示,显示彩条部分还可以改进,可以定义不同彩条的显示区域。显示文字主要就是显示位置的确定与汉字点阵的生成,汉字点阵还是费了一些功夫,最开始实在没找到一个比较合适的转换工具和方法,后来在朋友的帮助指点下终于可以了。显示图片一定要注意24位位图的话在ROM里面引入的是 .mif 文件,16位位图的话就引入hex文件。
  • 代码或哪一部分有问题欢迎留言指正。

参考文献

  1. 百度百科-VGA
  2. VGA显示原理、时序标准及相关参数
  3. 【FPGA实验】基于DE2-115平台的VGA显示
  • 17
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值