基于FPGA的VGA/LCD显示控制器设计(中)

今天给大侠带来基于FPGA的VGA/LCD显示控制器设计,由于篇幅较长,分三篇。今天带来第二篇,中篇,VGA 显示原理以及VGA/LCD 显示控制器的基本框架,话不多说,上货。

之前也有图像处理以及VGA显示相关的文章,这里给个超链接,给各位大侠作个参考。

源码系列:基于FPGA的VGA驱动设计(附源工程)

基于FPGA的实时图像边缘检测系统设计(上)

基于FPGA的实时图像边缘检测系统设计(中)

基于FPGA的实时图像边缘检测系统设计(下)

 

 

 

 

 

导读 

 

 

 

VGA (Video Graphics Array) 即视频图形阵列,是IBM于1987年随PS/2机(PersonalSystem 2)一起推出的使用模拟信号的一种视频传输标准。这个标准对于现今的个人电脑市场已经十分过时。但在当时具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域取得了广泛的应用,是众多制造商所共同支持的一个低标准。

LCD ( Liquid Crystal Display 的简称)液晶显示器。LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过TFT上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。按照背光源的不同,LCD可以分为CCFL显示器和LED显示器两种。LCD已经替代CRT成为主流,价格也已经下降了很多,并已充分普及。

在之前的文章中介绍了如何获取、处理摄像头提供的视频信号,在实际应用中还需要将经过处理的信号显示在显示器上。这个过程与信号处理中的过程上是相反的,将数字信号按照电视信号的制式组成合乎时序、格式要求的信号,并加入用于控制的各种同步信号。本篇将通过 FPGA实现一个 VGA/LCD 显示控制器的实例,并详细介绍实现过程。

第二篇内容摘要:本篇会介绍VGA/LCD 显示控制器程序的实现,包括顶层程序、颜色查找表、颜色处理器、光标处理器、视频定时产生器以及输出 FIFO等相关内容。

 

三、VGA/LCD 显示控制器程序的实现

 

 

 

3.1 顶层程序

顶层程序需要连接并控制各个子模块,顶层vga_top模块代码如下:

module vga_enh_top (…);  //输入和输出  input wb_clk_i; //外部时钟输入  input wb_rst_i; // 同步高有效重启信号  input rst_i; // 异步重启信号  output wb_inta_o; // 中断请求信号  ….  //连接各个子模块  vga_wb_slave wbs (                    .clk_i ( wb_clk_i ),                    ….                   );                ….    //读输出 FIFO 内容时产生中断信号    always @(posedge clk_p_i)    luint_pclk <= #1 line_fifo_rreq & line_fifo_empty_rd;        always @(posedge wb_clk_i or negedge arst)    //ctrl_ven 是控制寄存器中的 VEN 位,是显示器工作的使能位    //显示器不工作时,清除中断        if (!ctrl_ven)            begin                sluint <= #1 1'b0;                luint <= #1 1'b0;            end        else            begin                sluint <= #1 luint_pclk;                luint <= #1 sluint;            endendmodule  

 

 

 

 

3.2 颜色查找表--Color Lookup Table

颜色查找表保存了 256 色分辨率下 R、G、B 所有可能颜色,因此它是一块 256×24 位的静态 RAM 区,每个像素由 R、G、B 每种颜色 8 位数据组成,程序包括两块这样的颜色查找表,一共 512×24 位。

颜色查找表的主要代码如下:

module vga_csm_pb (clk_i, req0_i, ack0_o, adr0_i, dat0_i, dat0_o, we0_i, req1_i, ack1_o, adr1_i, dat1_i, dat1_o, we1_i);    // 参数设置    //设置数据总线宽度    parameter DWIDTH = 32;        //地址总线宽度    parameter AWIDTH = 8;        //输入和输出    //时钟输入    input clk_i;        //连接到主机的接口    input [ AWIDTH -1:0] adr0_i; //地址输入信号    input [ DWIDTH -1:0] dat0_i; //数据输入信号    output [ DWIDTH -1:0] dat0_o; //数据输出信号    input we0_i; //写使能输入信号    input req0_i; //访问请求输入信号    output ack0_o; //访问应答输出信号    input [ AWIDTH -1:0] adr1_i; //地址输入信号    input [ DWIDTH -1:0] dat1_i; //数据输入信号    output [ DWIDTH -1:0] dat1_o; //数据输出信号    input we1_i; //写使能输入信号    input req1_i; //访问请求输入信号    output ack1_o; //访问应答输出信号        //变量声明    //多个选择信号    wire acc0, acc1;    reg dacc0, dacc1;    wire sel0, sel1;    reg ack0, ack1;        //存储器数据输出    wire [DWIDTH -1:0] mem_q;        //模块主体    //产生多路选择信号    assign acc0 = req0_i;    assign acc1 = req1_i && !sel0;        always@(posedge clk_i)        begin            dacc0 <= #1 acc0 & !ack0_o;            dacc1 <= #1 acc1 & !ack1_o;        end        assign sel0 = acc0 && !dacc0;    assign sel1 = acc1 && !dacc1;        always@(posedge clk_i)        begin            ack0 <= #1 sel0 && !ack0_o;            ack1 <= #1 sel1 && !ack1_o;        end        //选择输入地址信号、数据信号、控制信号等    wire [AWIDTH -1:0] mem_adr = sel0 ? adr0_i : adr1_i;    wire [DWIDTH -1:0] mem_d = sel0 ? dat0_i : dat1_i;    wire mem_we = sel0 ? req0_i && we0_i : req1_i && we1_i;        // 连接到事先定义好的单通道存储器    generic_spram #(AWIDTH, DWIDTH) clut_mem(                                              .clk(clk_i),                                              .rst(1'b0), //不重启                                              .ce(1'b1), //一直片选                                              .we(mem_we),                                              .oe(1'b1), //一直输出数据                                              .addr(mem_adr),                                              .di(mem_d),                                              .do(mem_q)                                             );        //指定数据输出    assign dat0_o = mem_q;    assign dat1_o = mem_q;        //产生应答输出    assign ack0_o = ( (sel0 && we0_i) || ack0 );    assign ack1_o = ( (sel1 && we1_i) || ack1 );    endmodule

 

代码中定义了一个通用的存储器,适用于 Altera、Xilinx 的 FPGA 产品。主要代码如下:

module generic_spram(    //通用 SRAM 接口    clk, rst, ce, we, oe, addr, di, do    )        //缺省地址和数据总线线宽    parameter aw = 6; //地址总线线宽    parameter dw = 8; //数据总线线宽        //通用 SRAM 接口    input clk; //时钟,上升沿有效    input rst; //复位信号,高有效    input ce; //片选信号,高有效    input we; //写使能信号,高有效    input oe; //输出使能信号,高有效    input [aw-1:0] addr; //地址总线输入    input [dw-1:0] di; //输入数据总线    output [dw-1:0] do; //输出数据总线        //模块主体    //如果是通用的 FPGA    `ifdef VENDOR_FPGA        reg [dw-1:0] mem [(1<        reg [aw-1:0] ra;            //读操作        always @(posedge clk)            if (ce)                ra <= #1 addr;                        assign #1 do = mem[ra];            //写操作        always @(posedge clk)            if (we && ce)                mem[addr] <= #1 di;    `else    //如果采用 XILINX 公司的 FPGA    `ifdef VENDOR_XILINX            wire [dw-1:0] q; //输出                //FPGA 存储器的实例化        // Virtex/Spartan2 BlockRAMs        xilinx_ram_sp xilinx_ram(                                .clk(clk),                                .rst(rst),                                .addr(addr),                                .di(di),                                .en(ce),                                .we(we),                                .do(do)                              );            defparam        xilinx_ram.dwidth = dw,        xilinx_ram.awidth = aw;    `else    //如果是 Altera 公司的 FPGA    `ifdef VENDOR_ALTERA        // 采用 Altera FLEX 系列的 EABs        altera_ram_sp altera_ram(                                  .inclock(clk),                                  .address(addr),                                  .data(di),                                  .we(we && ce),                                  .q(do)                              );                                      defparam            altera_ram.dwidth = dw,            altera_ram.awidth = aw;                `else    `else        //通用模式        reg [dw-1:0] mem [(1<        wire [dw-1:0] q; // RAM 输出        reg [aw-1:0] raddr; // RAM 读地址        //数据输出        assign do = (oe) ? q : {dw{1'bz}};                // RAM 读写        //读操作        always@(posedge clk)        if (ce) // && !we)            raddr <= #1 addr;                    assign #1 q = rst ? {dw{1'b0}} : mem[raddr];                //写操作        always@(posedge clk)            if (ce && we)                mem[addr] <= #1 di;                    `endif // !VENDOR_ALTERA    `endif // !VENDOR_XILINX    `endif // !VENDOR_FPGA        //采用 Altera 公司的 FPGA        `ifdef VENDOR_ALTERA        module altera_ram_sp (                                address,                                inclock,                                we,                                data,                                q                              );                        parameter awidth = 7;            parameter dwidth = 8;                        input [awidth -1:0] address;            input inclock;            input we;            input [dwidth -1:0] data;            output [dwidth -1:0] q;            syn_ram_irou #(                            "UNUSED",                            dwidth,                            awidth,                            1 << awidth                          );                        altera_spram_model (                                .Inclock(inclock),                                .Address(address),                                .Data(data),                                .WE(we),                                .Q(q)                            );                                    endmodule            `endif // VENDOR_ALTERA    //采用 XILINX 公司的 FPGA    `ifdef VENDOR_XILINX        module xilinx_ram_sp (            clk,            rst,            addr,            di,            en,            we,            do)                        parameter awidth = 7;            parameter dwidth = 8;                        input clk;            input rst;            input [awidth -1:0] addr;            input [dwidth -1:0] di;            input en;            input we;            output [dwidth -1:0] do;                        C_MEM_SP_BLOCK_V1_0 #(                awidth,                1,                "0",                1 << awidth,                1,                1,                1,                1,                1,                1,                1,                "",                16,                0,                0,                1,                1,                dwidth              );                            xilinx_spram_model (                                .CLK(clk),                                .RST(rst),                                .ADDR(addr),                                .DI(di),                                .EN(en),                                .WE(we),                                .DO(do)                               );        endmodule            `endif // VENDOR_XILINX

 

 

 

 

3.3 颜色处理器--Color Processor

颜色处理器负责每个像素的颜色的产生。这个功能由颜色处理器与输出 FIFO 共同完成,颜色处理器的内部结构如图 5 所示。

 

基于FPGA的VGA/LCD显示控制器设计(中)_第1张图片

图 5 颜色处理器的内部结构

 

颜色处理器包括地址产生器、数据缓冲和色彩化模块几部分:

• 地址产生器 在产生视频存储器的地址的同时,地址产生器操作存储器块的切换并记载要读取的像素数目。当所有像素读取完成后,切换存储器的块位置。

• 数据缓冲 暂时保存从视频存储器中读取的数据,对数据的访问可以按照连续地址进行。所有的数据按照连续的地址保存。8 位模式下,一个 32 位的字保存 4 个像素的数据;16位模式下,一个 32 位的字保存 2 个像素;24 位模式下,一个 32 位的字保存 1 1/3 个像素;32 位模式下,一个 32 位的字保存 1 个像素。

• 色彩化模块 将保存在数据缓冲区中的数据转换成颜色数据,并输出。

 

颜色处理器的主要代码如下:

module vga_colproc(clk, srst, vdat_buffer_di, ColorDepth, PseudoColor,    vdat_buffer_empty, vdat_buffer_rreq, rgb_fifo_full,    rgb_fifo_wreq, r, g, b,    clut_req, clut_ack, clut_offs, clut_q  );      //输入、输出    input clk; //输入时钟    input srst; //同步复位信号    input [31:0] vdat_buffer_di; //视频存储器数据输入    input [1:0] ColorDepth; //颜色深度(8 位、16 位、24 位模式)    input PseudoColor; //假彩色使能    input vdat_buffer_empty;    output vdat_buffer_rreq; //读取缓冲请求    reg vdat_buffer_rreq;    input rgb_fifo_full;    output rgb_fifo_wreq;    reg rgb_fifo_wreq;    output [7:0] r, g, b; //像素颜色信息    reg [7:0] r, g, b;    output clut_req; //颜色查找表请求    reg clut_req;    input clut_ack; //颜色查找表应答    output [ 7:0] clut_offs; //颜色查找表偏移量    reg [7:0] clut_offs;    input [23:0] clut_q; //颜色查找表数据输入        //变量申明    reg [31:0] DataBuffer;    reg [7:0] Ra, Ga, Ba;    reg [1:0] colcnt;    reg RGBbuf_wreq;        //模块内容    always @(posedge clk)        if (vdat_buffer_rreq)            DataBuffer <= #1 vdat_buffer_di;        //状态机    //把从数据缓冲读取的数据展开    parameter idle = 7'b000_0000,          fill_buf = 7'b000_0001,          bw_8bpp = 7'b000_0010,          col_8bpp = 7'b000_0100,          col_16bpp_a = 7'b000_1000,          col_16bpp_b = 7'b001_0000,          col_24bpp = 7'b010_0000,          col_32bpp = 7'b100_0000;                reg [6:0] c_state; // synopsys enum_state      reg [6:0] nxt_state; // synopsys enum_state          //状态机    always @(c_state or vdat_buffer_empty or ColorDepth or PseudoColor or rgb_fifo_full or colcnt or clut_ack)    begin : nxt_state_decoder        //初始化        nxt_state = c_state;        case (c_state)            //空闲状态            idle:                //如果数据缓冲区非空,数据 FIFO 不满的情况下,开始填充数据                if (!vdat_buffer_empty && !rgb_fifo_full)                    nxt_state = fill_buf;            // 把数据缓冲区中的数据填充到数据 FIFO 中            fill_buf:                //颜色模式判断                case (ColorDepth)                    2'b00:                        //伪彩色                        if (PseudoColor)                            nxt_state = col_8bpp;                        else                            nxt_state = bw_8bpp;                                                //16 位模式                    2'b01:                        nxt_state = col_16bpp_a;                                            //24 位模式                    2'b10:                        nxt_state = col_24bpp;                    //32 位模式                    2'b11:                        nxt_state = col_32bpp;                endcase                            //8 位黑白模式            bw_8bpp:            if (!rgb_fifo_full && !(|colcnt) )                if (!vdat_buffer_empty)                    nxt_state = fill_buf;                else                    nxt_state = idle;                        //8 位彩色模式            col_8bpp:                if (!(|colcnt))                    if (!vdat_buffer_empty && !rgb_fifo_full)                        nxt_state = fill_buf;                    else                        nxt_state = idle;                                    // 16 位彩色模式            col_16bpp_a:                if (!rgb_fifo_full)                    nxt_state = col_16bpp_b;            col_16bpp_b:                if (!rgb_fifo_full)                    if (!vdat_buffer_empty)                        nxt_state = fill_buf;                    else                        nxt_state = idle;                                    // 24 位彩色模式            col_24bpp:                if (!rgb_fifo_full)                    if (colcnt == 2'h1) // (colcnt == 1)                        nxt_state = col_24bpp; // 保持在当前状态                    else if (!vdat_buffer_empty)                        nxt_state = fill_buf;                    else                        nxt_state = idle;                                    // 32 位彩色模式            col_32bpp:            if (!rgb_fifo_full)                if (!vdat_buffer_empty)                    nxt_state = fill_buf;                else                    nxt_state = idle;        endcase    end        // 产生状态寄存器    always @(posedge clk)        if (srst)            c_state <= #1 idle;        else            c_state <= #1 nxt_state;                    reg iclut_req;        reg ivdat_buf_rreq;        reg [7:0] iR, iG, iB, iRa, iGa, iBa;        //输出解码    always @(c_state or vdat_buffer_empty or colcnt or DataBuffer or rgb_fifo_full or clut_ack or clut_q or Ba or Ga or Ra)    begin : output_decoder            //初始化数值        ivdat_buf_rreq = 1'b0;        RGBbuf_wreq = 1'b0;        iclut_req = 1'b0;                iR = 'h0;        iG = 'h0;        iB = 'h0;        iRa = 'h0;        iGa = 'h0;        iBa = '                    case (c_state)                //空闲状态                idle:                    begin                    //保存 RGB 数据的 FIFO 非空                        if (!rgb_fifo_full)                            if (!vdat_buffer_empty)                                ivdat_buf_rreq = 1'b1;                    // 进入到 8 位伪彩色模式                    RGBbuf_wreq = clut_ack;                    iR = clut_q[23:16];                    iG = clut_q[15: 8];                    iB = clut_q[ 7: 0];                end                            //填充数据到缓存中            fill_buf:                begin                    //进入 8 位伪彩色模式                    RGBbuf_wreq = clut_ack;                    iR = clut_q[23:16];                    iG = clut_q[15: 8];                    iB = clut_q[ 7: 0];                end                            // 8 位黑北模式            bw_8bpp:                begin                    if (!rgb_fifo_full)                        begin                            RGBbuf_wreq                                                         if ( (!vdat_buffer_empty) && !(|colcnt) )                                ivdat_buf_rreq = 1'b1;                        end                                            case (colcnt)                        2'b11:                            begin                                iR = DataBuffer[31:24];                                iG = DataBuffer[31:24];                                iB = DataBuffer[31:24];                            end                                                    2'b10:                            begin                                iR = DataBuffer[23:16];                                iG = DataBuffer[23:16];                                iB = DataBuffer[23:16];                            end                                                        2'b01:                                begin                                    iR = DataBuffer[15:8];                                    iG = DataBuffer[15:8];                                    iB = DataBuffer[15:8];                                end                                                            default:                                begin                                    iR = DataBuffer[7:0];                                    iG = DataBuffer[7:0];                                    iB = DataBuffer[7:0];                                end                        endcase                    end                                        //8 位模式                    col_8bpp:                    begin                        if (!(|colcnt))                            if (!vdat_buffer_empty && !rgb_fifo_full)                                ivdat_buf_rreq                                                         RGBbuf_wreq = clut_ack;                                                iR = clut_q[23:16];                        iG = clut_q[15: 8];                        iB = clut_q[ 7: 0];                        iclut_req = !rgb_fifo_full || (colcnt[1] ^ colcnt[0]);                    end                                        //16 位彩色模式                    col_16bpp_a:                        begin                        if (!rgb_fifo_full)                            RGBbuf_wreq = 1'b1;                                                    iR[7:3] = DataBuffer[31:27];                        iG[7:2] = DataBuffer[26:21];                        iB[7:3] = DataBuffer[20:16];                        end                                            col_16bpp_b:                        begin                            if (!rgb_fifo_full)                            begin                                RGBbuf_wreq = 1'b1;                                                                if (!vdat_buffer_empty)                                    ivdat_buf_rreq = 1'b1;                            end                                                            iR[7:3] = DataBuffer[15:11];                                iG[7:2] = DataBuffer[10: 5];                                iB[7:3] = DataBuffer[ 4: 0];                        end                                            // 24 位彩色模式                    col_24bpp:                    begin                        if (!rgb_fifo_full)                            begin                                RGBbuf_wreq                                                                 if ( (colcnt != 2'h1) && !vdat_buffer_empty)                                    ivdat_buf_rreq = 1'b1;                            end                                                    case (colcnt) // synopsis full_case parallel_case                            2'b11:                            begin                                iR = DataBuffer[31:24];                                iG = DataBuffer[23:16];                                iB = DataBuffer[15: 8];                                iRa = DataBuffer[ 7: 0];                            end                                                       2'b10:                            begin                                iR = Ra;                                iG = DataBuffer[31:24];                                iB = DataBuffer[23:16];                                iRa = DataBuffer[15: 8];                                iGa = DataBuffer[ 7: 0];                            end                            2'b01:                            begin                                iR = Ra;                                iG = Ga;                                iB = DataBuffer[31:24];                                iRa = DataBuffer[23:16];                                iGa = DataBuffer[15: 8];                                iBa = DataBuffer[ 7: 0];                            end                                                    default:                            begin                                iR = Ra;                                iG = Ga;                                iB = Ba;                            end                    endcase                end                                                                    // 32 位彩色模式                    col_32bpp:                    begin                        if (!rgb_fifo_full)                            begin                                RGBbuf_wreq = 1'b1;                                                                if (!vdat_buffer_empty)                                    ivdat_buf_rreq = 1'b1;                            end                                iR[7:0] = DataBuffer[23:16];                                iG[7:0] = DataBuffer[15:8];                                iB[7:0] = DataBuffer[7:0];                    end                endcase            end                        //产生输出寄存器            always @(posedge clk)                begin                    r <= #1 iR;                    g <= #1 iG;                    b <= #1 iB;                                        if (RGBbuf_wreq)                        begin                            Ra <= #1 iRa;                            Ba <= #1 iBa;                            Ga <= #1 iGa;                        end                                            if (srst)                        begin                            vdat_buffer_rreq <= #1 1'b0;                            rgb_fifo_wreq <= #1 1'b0;                            clut_req <= #1 1'b0;                        end                    else                        begin                            vdat_buffer_rreq <= #1 ivdat_buf_rreq;                            rgb_fifo_wreq <= #1 RGBbuf_wreq;                            clut_req <= #1 iclut_req;                        end                end                        //颜色查找表偏移量            always @(colcnt or DataBuffer)                case (colcnt) // synopsis full_case parallel_case                    2'b11: clut_offs = DataBuffer[31:24];                    2'b10: clut_offs = DataBuffer[23:16];                    2'b01: clut_offs = DataBuffer[15: 8];                    2'b00: clut_offs = DataBuffer[ 7: 0];                endcase                          //颜色计数器          always @(posedge clk)              if (srst)                  colcnt <= #1 2'b11;              else if (RGBbuf_wreq)                  colcnt <= #1 colcnt -2'h1;                  endmodule                                                           

  

 

 

 

3.4 光标处理器--Cursor Processor

VGA/LCD 显示控制器同时提供了硬件光标,可以为 GUI(图形用户界面,Graphics UserInterface)提供一个箭头一样的光标,如图 6 所示。

 

基于FPGA的VGA/LCD显示控制器设计(中)_第2张图片

图 6 光标处理器提供的光标

 

光标的形成由光标处理器(Cursor Processor)完成。程序为每个光标模板提供了 16kbit的空间,光标的分辨率可以选择,包括两种模板:

 

  • 32×32 像素模式 在这种模板中,每个像素数据保存在 16 位字节中。

  • 64×64 像素模式 在这种模板中,每个像素数据保存在 4 位字节中。

 

光标处理器的程序结构如图 7 所示。

 

基于FPGA的VGA/LCD显示控制器设计(中)_第3张图片

图 7 光标处理器的程序结构

 

当拷贝光标到光标数据缓冲区时,地址产生器产生进行写操作需要的存储器地址。光标数据缓冲器提供一块 512×32 位的 SRAM,用来保存光标的数据。光标处理器负责跟踪光标的位置,决定光标模板是否需要更新、光标是否需要显示等。

光标处理器的主要代码如下:

module vga_curproc (clk, rst_i, Thgate, Tvgate, idat, idat_wreq,    cursor_xy, cursor_en, cursor_res,    cursor_wadr, cursor_wdat, cursor_we,    cc_adr_o, cc_dat_i,    rgb_fifo_wreq, rgb);        //输入输出    input clk; //时钟输入    input rst_i; //同步高有效复位信号        //图像尺寸    input [15:0] Thgate, Tvgate; //水平和垂直尺寸        //图像数据    input [23:0] idat; //输入图像数据    input idat_wreq; // 图像数据写请求        //光标数据    input [31:0] cursor_xy; //光标坐标    input cursor_en; //光标有效标志    input cursor_res; //光标分辨率    input [ 8:0] cursor_wadr; // 光标缓冲区写地址    input [31:0] cursor_wdat; // 光标缓冲区写数据    input cursor_we; //光标缓冲区写有效        // 颜色寄存器接口    output [ 3:0] cc_adr_o; //光标颜色寄存器地址    reg [ 3:0] cc_adr_o;    input [15:0] cc_dat_i; // 光标颜色寄存器数据        //与 FIFO 的记录    output rgb_fifo_wreq; // RGB 数据输出请求    reg rgb_fifo_wreq;    output [23:0] rgb; // RGB 数据输出    reg [23:0] rgb;        //变量申明    reg dcursor_en, ddcursor_en, dddcursor_en;    reg [15:0] xcnt, ycnt;    wire xdone, ydone;    wire [15:0] cursor_x, cursor_y;    wire cursor_isalpha;    reg [15:0] cdat, dcdat;    wire [ 7:0] cursor_r, cursor_g, cursor_b, cursor_alpha;    reg inbox_x, inbox_y;    wire inbox;    reg dinbox, ddinbox, dddinbox;    reg [23:0] didat, ddidat, dddidat;    reg didat_wreq, ddidat_wreq;    wire [31:0] cbuf_q;    reg [11:0] cbuf_ra;    reg [ 2:0] dcbuf_ra;    wire [ 8:0] cbuf_a;    reg store1, store2;        // 程序主体    // 产生 x、y 的计数器    always@(posedge clk)        if(rst_i || xdone)            xcnt <= #1 16'h0;        else if (idat_wreq)            xcnt <= #1 xcnt + 16'h1;            assign xdone = (xcnt == Thgate) && idat_wreq;        always@(posedge clk)        if(rst_i || ydone)            ycnt <= #1 16'h0;        else if (xdone)            ycnt <= #1 ycnt + 16'h1;            assign ydone = (ycnt == Tvgate) && xdone;        // 解码光标位置,分解为两个坐标    assign cursor_x = cursor_xy[15: 0];    assign cursor_y = cursor_xy[31:16];        // 产生 inbox 信号    always@(posedge clk)        begin            inbox_x <= #1 (xcnt >= cursor_x) && (xcnt < (cursor_x + (cursor_res ? 16'h7f : 16'h1f) ));            inbox_y <= #1 (ycnt >= cursor_y) && (ycnt < (cursor_y + (cursor_res ? 16'h7f : 16'h1f) ));        end            assign inbox = inbox_x && inbox_y;        always@(posedge clk)        dinbox <= #1 inbox;       always@(posedge clk)        if (didat_wreq)            ddinbox <= #1 dinbox;        always@(posedge clk)        dddinbox <= #1 ddinbox;        // 产生光标缓冲区地址计数器    always@(posedge clk)        if (!cursor_en || ydone)            cbuf_ra <= #1 12'h0;        else if (inbox && idat_wreq)            cbuf_ra <= #1 cbuf_ra +12'h1;       always@(posedge clk)        dcbuf_ra <= #1 cbuf_ra[2:0];        assign cbuf_a = cursor_we ? cursor_wadr : cursor_res ? cbuf_ra[11:3] : cbuf_ra[9:1];       // 连接到光标存储器    generic_spram #(9, 32) cbuf(                                .clk(clk),                                .rst(1'b0), // 不复位                                .ce(1'b1), // 一直有效                                .we(cursor_we),                                .oe(1'b1), // 一直输出数据                                .addr(cbuf_a),                                .di(cursor_wdat),                                .do(cbuf_q)                            );                                 // 在 32×32 像素模式下解码光标数据    always@(posedge clk)        if (didat_wreq)            cdat <= #1 dcbuf_ra[0] ? cbuf_q[31:16] : cbuf_q[15:0];        always@(posedge clk)        dcdat <= #1 cdat;        //在 64×64 像素模式下解码光标数据    // 产生光标颜色地址    always@(posedge clk)        if (didat_wreq)            case (dcbuf_ra)                3'b000: cc_adr_o <= cbuf_q[ 3: 0];                3'b001: cc_adr_o <= cbuf_q[ 7: 4];                3'b010: cc_adr_o <= cbuf_q[11: 8];                3'b011: cc_adr_o <= cbuf_q[15:12];                3'b100: cc_adr_o <= cbuf_q[19:16];                3'b101: cc_adr_o <= cbuf_q[23:20];                3'b110: cc_adr_o <= cbuf_q[27:24];                3'b111: cc_adr_o <= cbuf_q[31:28];            endcase       // 产生光标颜色    assign cursor_isalpha = cursor_res ? cc_dat_i[15] : dcdat[15];    assign cursor_alpha = cursor_res ? cc_dat_i[7:0] : dcdat[7:0];    assign cursor_r = {cursor_res ? cc_dat_i[14:10] : dcdat[14:10], 3'h0};    assign cursor_g = {cursor_res ? cc_dat_i[ 9: 5] : dcdat[ 9: 5], 3'h0};    assign cursor_b = {cursor_res ? cc_dat_i[ 4: 0] : dcdat[ 4: 0], 3'h0};       // 延迟图像数据    always@(posedge clk)        didat <= #1 idat;        always@(posedge clk)        if (didat_wreq)            ddidat <= #1 didat;        always@(posedge clk)        dddidat <= #1 ddidat;       always@(posedge clk)        begin            didat_wreq <= #1 idat_wreq;            ddidat_wreq <= #1 didat_wreq;        end            //产生选择的单元    always@(posedge clk)        dcursor_en <= #1 cursor_en;            always@(posedge clk)        if (didat_wreq)            ddcursor_en <= #1 dcursor_en;                always@(posedge clk)        dddcursor_en <= #1 ddcursor_en;        always@(posedge clk)        if (ddidat_wreq)            if (!dddcursor_en || !dddinbox)                rgb <= #1 dddidat;            else if (cursor_isalpha)                `ifdef VGA_HWC_3D                    rgb <= #1 dddidat * cursor_alpha;                `else                    rgb <= #1 dddidat;                `endif            else                rgb <= #1 {cursor_r, cursor_g, cursor_b};                    // 产生写请求信号    always@(posedge clk)        if (rst_i)            begin                store1 <= #1 1'b0;                store2 <= #1 1'b0;            end        else            begin                store1 <= #1 didat_wreq | store1;                store2 <= #1 (didat_wreq & store1) | store2;            end                always@(posedge clk)        rgb_fifo_wreq <= #1 ddidat_wreq & store2;        endmodule

 

 

 

3.5 视频定时产生器--Video Timing Generator

视频定时产生器产生正确显示图像所必需的同步信号—水平同步信号、垂直同步信号。

1) 视频信号的水平同步信号

水平同步信号如图 8 所示。

基于FPGA的VGA/LCD显示控制器设计(中)_第4张图片

图 8 水平同步信号

 

Thsync 表示水平同步过程的时间,以像素节拍为单位进行测量。Thgdel 是水平门延迟时间,表示从同步结束到水平门信号开始之间的时间。Thgate 表示一条视频线可视区域内的时间。Thlen 表示整个水平同步的时间长度。

 

2) 视频信号的垂直同步信号

垂直同步信号如图 9 所示。

基于FPGA的VGA/LCD显示控制器设计(中)_第5张图片

图 9 垂直同步信号

 

Tvsync 表示垂直同步过程的时间,以行节拍为单位进行测量。Tvgdel 是垂直门延迟时间,表示从同步结束到垂直门信号开始之间的时间。Tvgate 表示一场视频信号可视区域内的时间。Tvlen 表示整个水平同步的时间长度。

视频定时产生器的主要代码如下:

module vga_tgen(    clk, clk_ena, rst,    Thsync, Thgdel, Thgate, Thlen, Tvsync, Tvgdel, Tvgate, Tvlen,    eol, eof, gate, hsync, vsync, csync, blank    )        //输入输出    input clk;    input clk_ena;    input rst;        //水平定时设置输入信号    input [ 7:0] Thsync; // 水平同步信号宽度    input [ 7:0] Thgdel; // 水平同步门延迟    input [15:0] Thgate; // 水平门(每行视频信号可视像素的数目)    input [15:0] Thlen; // 水平同步信号的长度 (每行视频信号的像素数目)        // 垂直定时设置输入信号    input [ 7:0] Tvsync; // 垂直同步信号宽度    input [ 7:0] Tvgdel; // 垂直同步门研制    input [15:0] Tvgate; // 垂直门(每场视频信号可视像素的数目)    input [15:0] Tvlen; //垂直同步信号的长度 (每场视频信号的像素行数)       //输出    output eol; // 一行信号的结尾    output eof; // 一场图像的结尾    output gate; // 垂直和水平门信号    output hsync; // 水平同步信号    output vsync; // 垂直同步信号    output csync; // 复合同步信号    output blank; // 空白信号       // 变量申明    wire Hgate, Vgate;    wire Hdone;        //程序主体    // 连接水平定时产生器    vga_vtim hor_gen(        .clk(clk),        .ena(clk_ena),        .rst(rst),        .Tsync(Thsync),        .Tgdel(Thgdel),        .Tgate(Thgate),        .Tlen(Thlen),        .Sync(hsync),        .Gate(Hgate),        .Done(Hdone)    );        // 连接垂直定时产生器    wire vclk_ena = Hdone & clk_ena;        vga_vtim ver_gen(        .clk(clk),        .ena(vclk_ena),        .rst(rst),        .Tsync(Tvsync),        .Tgdel(Tvgdel),        .Tgate(Tvgate),        .Tlen(Tvlen),        .Sync(vsync),        .Gate(Vgate),        .Done(eof)    );        // 指定输出    assign eol = Hdone;    assign gate = Hgate & Vgate;    assign csync = hsync | vsync;    assign blank = ~gate;    endmodule

 

行同步和场同步信号产生的主要代码如下:

module vga_vtim(clk, ena, rst, Tsync, Tgdel, Tgate, Tlen, Sync, Gate, Done);    // 输入输出    input clk; //时钟信号    input ena; // 计数使能    input rst; // 同步复位信号,高有效    input [ 7:0] Tsync; // 同步时间    input [ 7:0] Tgdel; // 门延迟    input [15:0] Tgate; // 门信号的时间    input [15:0] Tlen; // 行时间/场时间    output Sync; // 输出的同步信号    output Gate; // 门信号    output Done; // 行/场的结束标志    reg Sync;    reg Gate;    reg Done;        // 程序主体    //产生定时状态机    reg [15:0] cnt, cnt_len;    wire [16:0] cnt_nxt, cnt_len_nxt;    wire cnt_done, cnt_len_done;    assign cnt_nxt = {1'b0, cnt} -17'h1;    assign cnt_done = cnt_nxt[16];    assign cnt_len_nxt = {1'b0, cnt_len} -17'h1;    assign cnt_len_done = cnt_len_nxt[16];    reg [4:0] state;        parameter [4:0] idle_state = 5'b00001;    parameter [4:0] sync_state = 5'b00010;    parameter [4:0] gdel_state = 5'b00100;    parameter [4:0] gate_state = 5'b01000;    parameter [4:0] len_state = 5'b10000;        always @(posedge clk)        //复位        if (rst)            begin                state <= #1 idle_state;                cnt <= #1 16'h0;                cnt_len <= #1 16'b0;                Sync <= #1 1'b0;                Gate <= #1 1'b0;                Done <= #1 1'b0;            end        else if (ena)            begin                cnt <= #1 cnt_nxt[15:0];                cnt_len <= #1 cnt_len_nxt[15:0];                Done <= #1 1'b0;                case (state)                    //空闲状态                    idle_state:                        begin                            state <= #1 sync_state;                            cnt <= #1 Tsync;                            cnt_len <= #1 Tlen;                            Sync <= #1 1'b1;                        end                                            //同步                    sync_state:                        if (cnt_done)                            begin                                state <= #1 gdel_state;                                cnt <= #1 Tgdel;                                Sync <= #1 1'b0;                            end                                                //门延迟                    gdel_state:                        if (cnt_done)                            begin                                state <= #1 gate_state;                                cnt <= #1 Tgate;                                Gate <= #1 1'b1;                            end                                                //门                    gate_state:                        if (cnt_done)                            begin                                state <= #1 len_state;                                Gate <= #1 1'b0;                            end                                        //总长度                    len_state:                        if (cnt_len_done)                            begin                                state <= #1 sync_state;                                cnt <= #1 Tsync;                                cnt_len <= #1 Tlen;                                Sync <= #1 1'b1;                                Done <= #1 1'b1;                            end                endcase            endendmoduld

 

 

3.6 输出 FIFO

输出 FIFO 用于保证连续的数据流输出到 VGA 或者 LCD 显示器上。

输出 FIFO 的主要代码如下:

module vga_fifo_dc (rclk, wclk, aclr, wreq, d, rreq, q, rd_empty, rd_full, wr_empty, wr_full);        // 参数设置    parameter AWIDTH = 7; //128 个入口    parameter DWIDTH = 16; //16bit 的数据总线宽度        // 输入输出    input rclk; // 读时钟    input wclk; // 写时钟    input aclr; // 异步清除信号,低有效    input wreq; // 写请求信号    input [DWIDTH -1:0] d; // 数据输入    input rreq; // 读请求    output [DWIDTH -1:0] q; // 数据输出    output rd_empty; // FIFO 空的标志,与读时钟同步    reg rd_empty;    output rd_full; // FIFO 满的标志, 与读时钟同步    reg rd_full;    output wr_empty; // FIFO 空的标志,与写的时钟同步    reg wr_empty;    output wr_full; // FIFO 满的标志,与写的时钟同步    reg wr_full;        // 变量申明    reg [AWIDTH -1:0] rptr, wptr;    wire ifull, iempty;    reg rempty, rfull, wempty, wfull;        // 程序主体    // 读指针    always@(posedge rclk or negedge aclr)        if (~aclr)            rptr <= #1 0;        else if (rreq)            rptr <= #1 rptr + 1;        // 写指针    always@(posedge wclk or negedge aclr)        if (~aclr)            wptr <= #1 0;        else if (wreq)            wptr <= #1 wptr +1;        // 状态标志    wire [AWIDTH -1:0] tmp;    wire [AWIDTH -1:0] tmp2;        assign tmp = wptr - rptr;    assign iempty = (rptr == wptr) ? 1'b1 : 1'b0;    assign tmp2 = (1 << AWIDTH) -3;    assign ifull = ( tmp >= tmp2 ) ? 1'b1 : 1'b0;        //读时钟标志    always@(posedge rclk or negedge aclr)        if (~aclr)            begin                rempty <= #1 1'b1;                rfull <= #1 1'b0;                rd_empty <= #1 1'b1;                rd_full <= #1 1'b0;            end        else            begin                rempty <= #1 iempty;                rfull <= #1 ifull;                rd_empty <= #1 rempty;                rd_full <= #1 rfull;            end        // 写时钟标志    always@(posedge wclk or negedge aclr)        if (~aclr)            begin                wempty <= #1 1'b1;                wfull <= #1 1'b0;                wr_empty <= #1 1'b1;                wr_full <= #1 1'b0;            end        else            begin                wempty <= #1 iempty;                wfull <= #1 ifull;                wr_empty <= #1 wempty;                wr_full <= #1 wfull;            end                // 连接到双口存储器    generic_dpram #(AWIDTH, DWIDTH) fifo_dc_mem(          .rclk(rclk),          .rrst(1'b0),          .rce(1'b1),          .oe(1'b1),          .raddr(rptr),          .do(q),          .wclk(wclk),          .wrst(1'b0),          .wce(1'b1),          .we(wreq),          .waddr(wptr),          .di(d)      );      endmodule

 

 

 

本篇到此结束,下一篇带来基于FPGA的VGA/LCD显示控制器设计(下),程序的仿真与测试以及总结等相关内容。

 

 

END

 

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

 

 

 

 

 

精彩推荐

 

 

 

 

在FPGA中,同步信号、异步信号和亚稳态的理解

《Xilinx Zynq-7000 嵌入式系统设计与实现》电子版

Xilinx FPGA 开发流程及详细说明

Quartus prime 18.0标准版安装和破解过程说明

你可能感兴趣的:(FPGA项目开发经验分享,fpga,VGA,LCD)