今天給大俠帶來基于FPGA的VGA/LCD顯示控制器設(shè)計,由于篇幅較長,分三篇。今天帶來第二篇,中篇,VGA 顯示原理以及VGA/LCD 顯示控制器的基本框架,話不多說,上貨。
導(dǎo)讀
VGA (Video Graphics Array) 即視頻圖形陣列,是IBM于1987年隨PS/2機(PersonalSystem 2)一起推出的使用模擬信號的一種視頻傳輸標(biāo)準(zhǔn)。這個標(biāo)準(zhǔn)對于現(xiàn)今的個人電腦市場已經(jīng)十分過時。但在當(dāng)時具有分辨率高、顯示速率快、顏色豐富等優(yōu)點,在彩色顯示器領(lǐng)域取得了廣泛的應(yīng)用,是眾多制造商所共同支持的一個低標(biāo)準(zhǔn)。
LCD ( Liquid Crystal Display 的簡稱)液晶顯示器。LCD 的構(gòu)造是在兩片平行的玻璃基板當(dāng)中放置液晶盒,下基板玻璃上設(shè)置TFT(薄膜晶體管),上基板玻璃上設(shè)置彩色濾光片,通過TFT上的信號與電壓改變來控制液晶分子的轉(zhuǎn)動方向,從而達(dá)到控制每個像素點偏振光出射與否而達(dá)到顯示目的。按照背光源的不同,LCD可以分為CCFL顯示器和LED顯示器兩種。LCD已經(jīng)替代CRT成為主流,價格也已經(jīng)下降了很多,并已充分普及。
在之前的文章中介紹了如何獲取、處理攝像頭提供的視頻信號,在實際應(yīng)用中還需要將經(jīng)過處理的信號顯示在顯示器上。這個過程與信號處理中的過程上是相反的,將數(shù)字信號按照電視信號的制式組成合乎時序、格式要求的信號,并加入用于控制的各種同步信號。本篇將通過 FPGA實現(xiàn)一個 VGA/LCD 顯示控制器的實例,并詳細(xì)介紹實現(xiàn)過程。
第二篇內(nèi)容摘要:本篇會介紹VGA/LCD 顯示控制器程序的實現(xiàn),包括頂層程序、顏色查找表、顏色處理器、光標(biāo)處理器、視頻定時產(chǎn)生器以及輸出 FIFO等相關(guān)內(nèi)容。
三、VGA/LCD 顯示控制器程序的實現(xiàn)
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 內(nèi)容時產(chǎn)生中斷信號
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;
end
endmodule
3.2 顏色查找表--Color Lookup Table
顏色查找表保存了 256 色分辨率下 R、G、B 所有可能顏色,因此它是一塊 256×24 位的靜態(tài) RAM 區(qū),每個像素由 R、G、B 每種顏色 8 位數(shù)據(jù)組成,程序包括兩塊這樣的顏色查找表,一共 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);
// 參數(shù)設(shè)置
//設(shè)置數(shù)據(jù)總線寬度
parameter DWIDTH = 32;
//地址總線寬度
parameter AWIDTH = 8;
//輸入和輸出
//時鐘輸入
input clk_i;
//連接到主機的接口
input [ AWIDTH -1:0] adr0_i; //地址輸入信號
input [ DWIDTH -1:0] dat0_i; //數(shù)據(jù)輸入信號
output [ DWIDTH -1:0] dat0_o; //數(shù)據(jù)輸出信號
input we0_i; //寫使能輸入信號
input req0_i; //訪問請求輸入信號
output ack0_o; //訪問應(yīng)答輸出信號
input [ AWIDTH -1:0] adr1_i; //地址輸入信號
input [ DWIDTH -1:0] dat1_i; //數(shù)據(jù)輸入信號
output [ DWIDTH -1:0] dat1_o; //數(shù)據(jù)輸出信號
input we1_i; //寫使能輸入信號
input req1_i; //訪問請求輸入信號
output ack1_o; //訪問應(yīng)答輸出信號
//變量聲明
//多個選擇信號
wire acc0, acc1;
reg dacc0, dacc1;
wire sel0, sel1;
reg ack0, ack1;
//存儲器數(shù)據(jù)輸出
wire [DWIDTH -1:0] mem_q;
//模塊主體
//產(chǎn)生多路選擇信號
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
//選擇輸入地址信號、數(shù)據(jù)信號、控制信號等
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), //一直輸出數(shù)據(jù)
.addr(mem_adr),
.di(mem_d),
.do(mem_q)
);
//指定數(shù)據(jù)輸出
assign dat0_o = mem_q;
assign dat1_o = mem_q;
//產(chǎn)生應(yīng)答輸出
assign ack0_o = ( (sel0 && we0_i) || ack0 );
assign ack1_o = ( (sel1 && we1_i) || ack1 );
endmodule
代碼中定義了一個通用的存儲器,適用于 Altera、Xilinx 的 FPGA 產(chǎn)品。主要代碼如下:
module generic_spram(
//通用 SRAM 接口
clk, rst, ce, we, oe, addr, di, do
)
//缺省地址和數(shù)據(jù)總線線寬
parameter aw = 6; //地址總線線寬
parameter dw = 8; //數(shù)據(jù)總線線寬
//通用 SRAM 接口
input clk; //時鐘,上升沿有效
input rst; //復(fù)位信號,高有效
input ce; //片選信號,高有效
input we; //寫使能信號,高有效
input oe; //輸出使能信號,高有效
input [aw-1:0] addr; //地址總線輸入
input [dw-1:0] di; //輸入數(shù)據(jù)總線
output [dw-1:0] do; //輸出數(shù)據(jù)總線
//模塊主體
//如果是通用的 FPGA
`ifdef VENDOR_FPGA
reg [dw-1:0] mem [(1<<aw) -1:0]
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<<aw)-1:0]; // RAM 內(nèi)容
wire [dw-1:0] q; // RAM 輸出
reg [aw-1:0] raddr; // RAM 讀地址
//數(shù)據(jù)輸出
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
顏色處理器負(fù)責(zé)每個像素的顏色的產(chǎn)生。這個功能由顏色處理器與輸出 FIFO 共同完成,顏色處理器的內(nèi)部結(jié)構(gòu)如圖 5 所示。
圖 5 顏色處理器的內(nèi)部結(jié)構(gòu)
顏色處理器包括地址產(chǎn)生器、數(shù)據(jù)緩沖和色彩化模塊幾部分:
- 地址產(chǎn)生器 在產(chǎn)生視頻存儲器的地址的同時,地址產(chǎn)生器操作存儲器塊的切換并記載要讀取的像素數(shù)目。當(dāng)所有像素讀取完成后,切換存儲器的塊位置。
- 數(shù)據(jù)緩沖 暫時保存從視頻存儲器中讀取的數(shù)據(jù),對數(shù)據(jù)的訪問可以按照連續(xù)地址進行。所有的數(shù)據(jù)按照連續(xù)的地址保存。8 位模式下,一個 32 位的字保存 4 個像素的數(shù)據(jù);16位模式下,一個 32 位的字保存 2 個像素;24 位模式下,一個 32 位的字保存 1 1/3 個像素;32 位模式下,一個 32 位的字保存 1 個像素。
- 色彩化模塊 將保存在數(shù)據(jù)緩沖區(qū)中的數(shù)據(jù)轉(zhuǎn)換成顏色數(shù)據(jù),并輸出。
顏色處理器的主要代碼如下:
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; //同步復(fù)位信號
input [31:0] vdat_buffer_di; //視頻存儲器數(shù)據(jù)輸入
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; //顏色查找表應(yīng)答
output [ 7:0] clut_offs; //顏色查找表偏移量
reg [7:0] clut_offs;
input [23:0] clut_q; //顏色查找表數(shù)據(jù)輸入
//變量申明
reg [31:0] DataBuffer;
reg [7:0] Ra, Ga, Ba;
reg [1:0] colcnt;
reg RGBbuf_wreq;
//模塊內(nèi)容
always @(posedge clk)
if (vdat_buffer_rreq)
DataBuffer <= #1 vdat_buffer_di;
//狀態(tài)機
//把從數(shù)據(jù)緩沖讀取的數(shù)據(jù)展開
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
//狀態(tài)機
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)
//空閑狀態(tài)
idle:
//如果數(shù)據(jù)緩沖區(qū)非空,數(shù)據(jù) FIFO 不滿的情況下,開始填充數(shù)據(jù)
if (!vdat_buffer_empty && !rgb_fifo_full)
nxt_state = fill_buf;
// 把數(shù)據(jù)緩沖區(qū)中的數(shù)據(jù)填充到數(shù)據(jù) 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; // 保持在當(dāng)前狀態(tài)
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
// 產(chǎn)生狀態(tài)寄存器
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
//初始化數(shù)值
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)
//空閑狀態(tài)
idle:
begin
//保存 RGB 數(shù)據(jù)的 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
//填充數(shù)據(jù)到緩存中
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
//產(chǎn)生輸出寄存器
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
//顏色計數(shù)器
always @(posedge clk)
if (srst)
colcnt <= #1 2'b11;
else if (RGBbuf_wreq)
colcnt <= #1 colcnt -2'h1;
endmodule
3.4 光標(biāo)處理器--Cursor Processor
VGA/LCD 顯示控制器同時提供了硬件光標(biāo),可以為 GUI(圖形用戶界面,Graphics UserInterface)提供一個箭頭一樣的光標(biāo),如圖 6 所示。
圖 6 光標(biāo)處理器提供的光標(biāo)
光標(biāo)的形成由光標(biāo)處理器(Cursor Processor)完成。程序為每個光標(biāo)模板提供了 16kbit的空間,光標(biāo)的分辨率可以選擇,包括兩種模板:
- 32×32 像素模式 在這種模板中,每個像素數(shù)據(jù)保存在 16 位字節(jié)中。
- 64×64 像素模式 在這種模板中,每個像素數(shù)據(jù)保存在 4 位字節(jié)中。
光標(biāo)處理器的程序結(jié)構(gòu)如圖 7 所示。
圖 7 光標(biāo)處理器的程序結(jié)構(gòu)
當(dāng)拷貝光標(biāo)到光標(biāo)數(shù)據(jù)緩沖區(qū)時,地址產(chǎn)生器產(chǎn)生進行寫操作需要的存儲器地址。光標(biāo)數(shù)據(jù)緩沖器提供一塊 512×32 位的 SRAM,用來保存光標(biāo)的數(shù)據(jù)。光標(biāo)處理器負(fù)責(zé)跟蹤光標(biāo)的位置,決定光標(biāo)模板是否需要更新、光標(biāo)是否需要顯示等。
光標(biāo)處理器的主要代碼如下:
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; //同步高有效復(fù)位信號
//圖像尺寸
input [15:0] Thgate, Tvgate; //水平和垂直尺寸
//圖像數(shù)據(jù)
input [23:0] idat; //輸入圖像數(shù)據(jù)
input idat_wreq; // 圖像數(shù)據(jù)寫請求
//光標(biāo)數(shù)據(jù)
input [31:0] cursor_xy; //光標(biāo)坐標(biāo)
input cursor_en; //光標(biāo)有效標(biāo)志
input cursor_res; //光標(biāo)分辨率
input [ 8:0] cursor_wadr; // 光標(biāo)緩沖區(qū)寫地址
input [31:0] cursor_wdat; // 光標(biāo)緩沖區(qū)寫數(shù)據(jù)
input cursor_we; //光標(biāo)緩沖區(qū)寫有效
// 顏色寄存器接口
output [ 3:0] cc_adr_o; //光標(biāo)顏色寄存器地址
reg [ 3:0] cc_adr_o;
input [15:0] cc_dat_i; // 光標(biāo)顏色寄存器數(shù)據(jù)
//與 FIFO 的記錄
output rgb_fifo_wreq; // RGB 數(shù)據(jù)輸出請求
reg rgb_fifo_wreq;
output [23:0] rgb; // RGB 數(shù)據(jù)輸出
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;
// 程序主體
// 產(chǎn)生 x、y 的計數(shù)器
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;
// 解碼光標(biāo)位置,分解為兩個坐標(biāo)
assign cursor_x = cursor_xy[15: 0];
assign cursor_y = cursor_xy[31:16];
// 產(chǎn)生 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;
// 產(chǎn)生光標(biāo)緩沖區(qū)地址計數(shù)器
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];
// 連接到光標(biāo)存儲器
generic_spram #(9, 32) cbuf(
.clk(clk),
.rst(1'b0), // 不復(fù)位
.ce(1'b1), // 一直有效
.we(cursor_we),
.oe(1'b1), // 一直輸出數(shù)據(jù)
.addr(cbuf_a),
.di(cursor_wdat),
.do(cbuf_q)
);
// 在 32×32 像素模式下解碼光標(biāo)數(shù)據(jù)
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 像素模式下解碼光標(biāo)數(shù)據(jù)
// 產(chǎn)生光標(biāo)顏色地址
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
// 產(chǎn)生光標(biāo)顏色
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};
// 延遲圖像數(shù)據(jù)
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
//產(chǎn)生選擇的單元
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};
// 產(chǎn)生寫請求信號
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 視頻定時產(chǎn)生器--Video Timing Generator
視頻定時產(chǎn)生器產(chǎn)生正確顯示圖像所必需的同步信號—水平同步信號、垂直同步信號。
1) 視頻信號的水平同步信號
水平同步信號如圖 8 所示。
圖 8 水平同步信號
Thsync 表示水平同步過程的時間,以像素節(jié)拍為單位進行測量。Thgdel 是水平門延遲時間,表示從同步結(jié)束到水平門信號開始之間的時間。Thgate 表示一條視頻線可視區(qū)域內(nèi)的時間。Thlen 表示整個水平同步的時間長度。
2) 視頻信號的垂直同步信號
垂直同步信號如圖 9 所示。
圖 9 垂直同步信號
Tvsync 表示垂直同步過程的時間,以行節(jié)拍為單位進行測量。Tvgdel 是垂直門延遲時間,表示從同步結(jié)束到垂直門信號開始之間的時間。Tvgate 表示一場視頻信號可視區(qū)域內(nèi)的時間。Tvlen 表示整個水平同步的時間長度。
視頻定時產(chǎn)生器的主要代碼如下:
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;
//水平定時設(shè)置輸入信號
input [ 7:0] Thsync; // 水平同步信號寬度
input [ 7:0] Thgdel; // 水平同步門延遲
input [15:0] Thgate; // 水平門(每行視頻信號可視像素的數(shù)目)
input [15:0] Thlen; // 水平同步信號的長度 (每行視頻信號的像素數(shù)目)
// 垂直定時設(shè)置輸入信號
input [ 7:0] Tvsync; // 垂直同步信號寬度
input [ 7:0] Tvgdel; // 垂直同步門研制
input [15:0] Tvgate; // 垂直門(每場視頻信號可視像素的數(shù)目)
input [15:0] Tvlen; //垂直同步信號的長度 (每場視頻信號的像素行數(shù))
//輸出
output eol; // 一行信號的結(jié)尾
output eof; // 一場圖像的結(jié)尾
output gate; // 垂直和水平門信號
output hsync; // 水平同步信號
output vsync; // 垂直同步信號
output csync; // 復(fù)合同步信號
output blank; // 空白信號
// 變量申明
wire Hgate, Vgate;
wire Hdone;
//程序主體
// 連接水平定時產(chǎn)生器
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)
);
// 連接垂直定時產(chǎn)生器
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
行同步和場同步信號產(chǎn)生的主要代碼如下:
module vga_vtim(clk, ena, rst, Tsync, Tgdel, Tgate, Tlen, Sync, Gate, Done);
// 輸入輸出
input clk; //時鐘信號
input ena; // 計數(shù)使能
input rst; // 同步復(fù)位信號,高有效
input [ 7:0] Tsync; // 同步時間
input [ 7:0] Tgdel; // 門延遲
input [15:0] Tgate; // 門信號的時間
input [15:0] Tlen; // 行時間/場時間
output Sync; // 輸出的同步信號
output Gate; // 門信號
output Done; // 行/場的結(jié)束標(biāo)志
reg Sync;
reg Gate;
reg Done;
// 程序主體
//產(chǎn)生定時狀態(tài)機
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)
//復(fù)位
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)
//空閑狀態(tài)
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
end
endmoduld
3.6 輸出 FIFO
輸出 FIFO 用于保證連續(xù)的數(shù)據(jù)流輸出到 VGA 或者 LCD 顯示器上。
輸出 FIFO 的主要代碼如下:
module vga_fifo_dc (rclk, wclk, aclr, wreq, d, rreq, q, rd_empty, rd_full, wr_empty, wr_full);
// 參數(shù)設(shè)置
parameter AWIDTH = 7; //128 個入口
parameter DWIDTH = 16; //16bit 的數(shù)據(jù)總線寬度
// 輸入輸出
input rclk; // 讀時鐘
input wclk; // 寫時鐘
input aclr; // 異步清除信號,低有效
input wreq; // 寫請求信號
input [DWIDTH -1:0] d; // 數(shù)據(jù)輸入
input rreq; // 讀請求
output [DWIDTH -1:0] q; // 數(shù)據(jù)輸出
output rd_empty; // FIFO 空的標(biāo)志,與讀時鐘同步
reg rd_empty;
output rd_full; // FIFO 滿的標(biāo)志, 與讀時鐘同步
reg rd_full;
output wr_empty; // FIFO 空的標(biāo)志,與寫的時鐘同步
reg wr_empty;
output wr_full; // FIFO 滿的標(biāo)志,與寫的時鐘同步
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;
// 狀態(tài)標(biāo)志
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;
//讀時鐘標(biāo)志
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
// 寫時鐘標(biāo)志
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
本篇到此結(jié)束,下一篇帶來基于FPGA的VGA/LCD顯示控制器設(shè)計(下),程序的仿真與測試以及總結(jié)等相關(guān)內(nèi)容。