Verilog实现VGA字符显示
Verilog实现VGA字符显示
实现目标
在显示器中以640*480的分辨率显示0-9、A-Z、‘:’、‘*’的任意字符,字符大小为7*8的像素规模。
实现原理
1、基本的VGA显示
所有VGA显示差不多都是基于下面这一段Verilog代码的,时钟分频、行同步信号hs、场同步信号vs、rgb颜色值,这些都是必不可少的,还要注意的是对于640*480的分辨率,显示器真正可显示的区域为行计数介于31~510、列计数介于144~783之间(实际上对不同的显示器可能会有几个像素的差异),在这个范围之外都必须把rgb值设置为全0,否则会对显示造成干扰。
下面代码效果为在显示器屏幕中央显示一个绿色矩形方块。
// vga_display.v `timescale 1ns / 1ps module vga_display( input clk, input rst, output reg [2:0] r, output reg [2:0] g, output reg [1:0] b, output hs, output vs ); // 显示器可显示区域 parameter UP_BOUND = 31; parameter DOWN_BOUND = 510; parameter LEFT_BOUND = 144; parameter RIGHT_BOUND = 783; // 屏幕中央的矩形方块 parameter up_pos = 211; parameter down_pos = 330; parameter left_pos = 384; parameter right_pos = 543; wire pclk; reg [1:0] count; reg [9:0] hcount, vcount; // 获得像素时钟25MHz assign pclk = count[1]; always @ (posedge clk or posedge rst) begin if (rst) count <= 0; else count <= count+1; end // 列计数与行同步 assign hs = (hcount < 96) ? 0 : 1; always @ (posedge pclk or posedge rst) begin if (rst) hcount <= 0; else if (hcount == 799) hcount <= 0; else hcount <= hcount+1; end // 行计数与场同步 assign vs = (vcount < 2) ? 0 : 1; always @ (posedge pclk or posedge rst) begin if (rst) vcount <= 0; else if (hcount == 799) begin if (vcount == 520) vcount <= 0; else vcount <= vcount+1; end else vcount <= vcount; end // 设置显示信号值 always @ (posedge pclk or posedge rst) begin if (rst) begin r <= 0; g <= 0; b <= 0; end else if (vcount>=UP_BOUND && vcount<=DOWN_BOUND && hcount>=LEFT_BOUND && hcount<=RIGHT_BOUND) begin if (vcount>=up_pos && vcount<=down_pos && hcount>=left_pos && hcount<=right_pos) begin r <= 3'b000; g <= 3'b111; b <= 2'b00; end else begin r <= 3'b000; g <= 3'b000; b <= 2'b00; end end else begin r <= 3'b000; g <= 3'b000; b <= 2'b00; end end endmodule
2、基于RAM和字模的VGA显示
我们都知道电脑有显存,它存储了显示器每个像素应该显示的rgb颜色值,显存不断被刷新,于是可以显示动态的图像。基于此,最简单的想法就是可以在Verilog里面定义一个640*480的8位寄存器二维数组作为VGA显示的显存,然后在reset信号使能的时候对每个数组单元赋值,这样我们就用一个数组存储了一幅静态图像,然后在VGA扫描显示的时候,分别根据寄存器数组的值来设置各个像素的rgb值。
显然,上面的方法是可行的,只不过工程浩大,难以实现!试想,要在屏幕显示一个7*8的字符就要分别设置56个寄存器的值,而且每个寄存器都要根据不同的字符设置不同的值,这是多么可怕的事情!
除此之外,可以考虑通过一个通用的RAM_set模块来一次性设置一个字符对应的寄存器的值。以显示两个字符为例(字符颜色白色,背景黑色),通过Verilog声明一个长度为14的8位寄存器数组reg [7:0] p[13:0],因为两个字符占用2*7*8=14*8个像素,所以p[i][j]=0表示第i+1列第j+1行像素为黑色,反之1为白色。这样的话,就可以分别把p[13:7]、p[6:0]通过RAM_set来设置。RAM_set模块可以根据要显示的不同字符的字模给寄存器赋不同的值。
图1 常见字符字模图
实现代码
下面的代码实现在显示器屏幕中央显示两个字符"3D"。
// vga_char_display.v `timescale 1ns / 1ps module vga_char_display( input clk, input rst, output reg [2:0] r, output reg [2:0] g, output reg [1:0] b, output hs, output vs ); // 显示器可显示区域 parameter UP_BOUND = 31; parameter DOWN_BOUND = 510; parameter LEFT_BOUND = 144; parameter RIGHT_BOUND = 783; // 屏幕中央两个字符的显示区域 parameter up_pos = 267; parameter down_pos = 274; parameter left_pos = 457; parameter right_pos = 470; wire pclk; reg [1:0] count; reg [9:0] hcount, vcount; wire [7:0] p[13:0]; RAM_set u_ram_1 ( .clk(clk), .rst(rst), .data(6'b00_0011), .col0(p[0]), .col1(p[1]), .col2(p[2]), .col3(p[3]), .col4(p[4]), .col5(p[5]), .col6(p[6]) ); RAM_set u_ram_2 ( .clk(clk), .rst(rst), .data(6'b00_1101), .col0(p[7]), .col1(p[8]), .col2(p[9]), .col3(p[10]), .col4(p[11]), .col5(p[12]), .col6(p[13]) ); // 获得像素时钟25MHz assign pclk = count[1]; always @ (posedge clk or posedge rst) begin if (rst) count <= 0; else count <= count+1; end // 列计数与行同步 assign hs = (hcount < 96) ? 0 : 1; always @ (posedge pclk or posedge rst) begin if (rst) hcount <= 0; else if (hcount == 799) hcount <= 0; else hcount <= hcount+1; end // 行计数与场同步 assign vs = (vcount < 2) ? 0 : 1; always @ (posedge pclk or posedge rst) begin if (rst) vcount <= 0; else if (hcount == 799) begin if (vcount == 520) vcount <= 0; else vcount <= vcount+1; end else vcount <= vcount; end // 设置显示信号值 always @ (posedge pclk or posedge rst) begin if (rst) begin r <= 0; g <= 0; b <= 0; end else if (vcount>=UP_BOUND && vcount<=DOWN_BOUND && hcount>=LEFT_BOUND && hcount<=RIGHT_BOUND) begin if (vcount>=up_pos && vcount<=down_pos && hcount>=left_pos && hcount<=right_pos) begin if (p[hcount-left_pos][vcount-up_pos]) begin r <= 3'b111; g <= 3'b111; b <= 2'b11; end else begin r <= 3'b000; g <= 3'b000; b <= 2'b00; end end else begin r <= 3'b000; g <= 3'b000; b <= 2'b00; end end else begin r <= 3'b000; g <= 3'b000; b <= 2'b00; end end endmodule
// RAM_set.v `timescale 1ns / 1ps module RAM_set( input clk, input rst, input [5:0] data, output reg [7:0] col0, output reg [7:0] col1, output reg [7:0] col2, output reg [7:0] col3, output reg [7:0] col4, output reg [7:0] col5, output reg [7:0] col6 ); always @(posedge clk or negedge rst) begin if (!rst) begin col0 <= 8'b0000_0000; col1 <= 8'b0000_0000; col2 <= 8'b0000_0000; col3 <= 8'b0000_0000; col4 <= 8'b0000_0000; col5 <= 8'b0000_0000; col6 <= 8'b0000_0000; end else begin case (data) 6'b00_0000: // "0" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1110; col2 <= 8'b0101_0001; col3 <= 8'b0100_1001; col4 <= 8'b0100_0101; col5 <= 8'b0011_1110; col6 <= 8'b0000_0000; end 6'b00_0001: // "1" begin col0 <= 8'b0000_0000; col1 <= 8'b0000_0000;; col2 <= 8'b0100_0010; col3 <= 8'b0111_1111; col4 <= 8'b0100_0000; col5 <= 8'b0000_0000;; col6 <= 8'b0000_0000; end 6'b00_0010: // "2" begin col0 <= 8'b0000_0000; col1 <= 8'b0100_0010; col2 <= 8'b0110_0001; col3 <= 8'b0101_0001; col4 <= 8'b0100_1001; col5 <= 8'b0100_0110; col6 <= 8'b0000_0000; end 6'b00_0011: // "3" begin col0 <= 8'b0000_0000; col1 <= 8'b0010_0010; col2 <= 8'b0100_0001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0011_0110; col6 <= 8'b0000_0000; end 6'b00_0100: // "4" begin col0 <= 8'b0000_0000; col1 <= 8'b0001_1000; col2 <= 8'b0001_0100; col3 <= 8'b0001_0010; col4 <= 8'b0111_1111; col5 <= 8'b0001_0000; col6 <= 8'b0000_0000; end 6'b00_0101: // "5" begin col0 <= 8'b0000_0000; col1 <= 8'b0010_0111; col2 <= 8'b0100_0101; col3 <= 8'b0100_0101; col4 <= 8'b0100_0101; col5 <= 8'b0011_1001; col6 <= 8'b0000_0000; end 6'b00_0110: // "6" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1110; col2 <= 8'b0100_1001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0011_0010; col6 <= 8'b0000_0000; end 6'b00_0111: // "7" begin col0 <= 8'b0000_0000; col1 <= 8'b0110_0001; col2 <= 8'b0001_0001; col3 <= 8'b0000_1001; col4 <= 8'b0000_0101; col5 <= 8'b0000_0011; col6 <= 8'b0000_0000; end 6'b00_1000: // "8" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_0110; col2 <= 8'b0100_1001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0011_0110; col6 <= 8'b0000_0000; end 6'b00_1001: // "9" begin col0 <= 8'b0000_0000; col1 <= 8'b0010_0110; col2 <= 8'b0100_1001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0011_1110; col6 <= 8'b0000_0000; end 6'b00_1010: // "A" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1100; col2 <= 8'b0001_0010; col3 <= 8'b0001_0001; col4 <= 8'b0001_0010; col5 <= 8'b0111_1100; col6 <= 8'b0000_0000; end 6'b00_1011: // "B" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0100_1001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0011_0110; col6 <= 8'b0000_0000; end 6'b00_1100: // "C" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1110; col2 <= 8'b0100_0001; col3 <= 8'b0100_0001; col4 <= 8'b0100_0001; col5 <= 8'b0010_0010; col6 <= 8'b0000_0000; end 6'b00_1101: // "D" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0100_0001; col3 <= 8'b0100_0001; col4 <= 8'b0100_0001; col5 <= 8'b0011_1110; col6 <= 8'b0000_0000; end 6'b00_1110: // "E" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0100_1001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0100_0001; col6 <= 8'b0000_0000; end 6'b00_1111: // "F" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0000_1001; col3 <= 8'b0000_1001; col4 <= 8'b0000_1001; col5 <= 8'b0000_0001; col6 <= 8'b0000_0000; end 6'b01_0000: // "G" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1110; col2 <= 8'b0100_0001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0011_1010; col6 <= 8'b0000_0000; end 6'b01_0001: // "H" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0000_1000; col3 <= 8'b0000_1000; col4 <= 8'b0000_1000; col5 <= 8'b0111_1111; col6 <= 8'b0000_0000; end 6'b01_0010: // "I" begin col0 <= 8'b0000_0000; col1 <= 8'b0000_0000; col2 <= 8'b0100_0001; col3 <= 8'b0111_1111; col4 <= 8'b0100_0001; col5 <= 8'b0000_0000; col6 <= 8'b0000_0000; end 6'b01_0011: // "J" begin col0 <= 8'b0000_0000; col1 <= 8'b0010_0000; col2 <= 8'b0100_0001; col3 <= 8'b0100_0001; col4 <= 8'b0011_1111; col5 <= 8'b0000_0001; col6 <= 8'b0000_0000; end 6'b01_0100: // "K" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0000_1000; col3 <= 8'b0001_0100; col4 <= 8'b0010_0010; col5 <= 8'b0100_0001; col6 <= 8'b0000_0000; end 6'b01_0101: // "L" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0100_0000; col3 <= 8'b0100_0000; col4 <= 8'b0100_0000; col5 <= 8'b0100_0000; col6 <= 8'b0000_0000; end 6'b01_0110: // "M" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0000_0010; col3 <= 8'b0000_1100; col4 <= 8'b0000_0010; col5 <= 8'b0111_1111; col6 <= 8'b0000_0000; end 6'b01_0111: // "N" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0000_0010; col3 <= 8'b0000_0100; col4 <= 8'b0000_1000; col5 <= 8'b0111_1111; col6 <= 8'b0000_0000; end 6'b01_1000: // "O" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1110; col2 <= 8'b0100_0001; col3 <= 8'b0100_0001; col4 <= 8'b0100_0001; col5 <= 8'b0011_1110; col6 <= 8'b0000_0000; end 6'b01_1001: // "P" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0000_1001; col3 <= 8'b0000_1001; col4 <= 8'b0000_1001; col5 <= 8'b0000_0110; col6 <= 8'b0000_0000; end 6'b01_1010: // "Q" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1110; col2 <= 8'b0100_0001; col3 <= 8'b0101_0001; col4 <= 8'b0110_0001; col5 <= 8'b0111_1110; col6 <= 8'b0000_0000; end 6'b01_1011: // "R" begin col0 <= 8'b0000_0000; col1 <= 8'b0111_1111; col2 <= 8'b0000_1001; col3 <= 8'b0001_1001; col4 <= 8'b0010_1001; col5 <= 8'b0100_0110; col6 <= 8'b0000_0000; end 6'b01_1100: // "S" begin col0 <= 8'b0000_0000; col1 <= 8'b0010_0110; col2 <= 8'b0100_1001; col3 <= 8'b0100_1001; col4 <= 8'b0100_1001; col5 <= 8'b0011_0010; col6 <= 8'b0000_0000; end 6'b01_1101: // "T" begin col0 <= 8'b0000_0000; col1 <= 8'b0000_0001; col2 <= 8'b0000_0001; col3 <= 8'b0111_1111; col4 <= 8'b0000_0001; col5 <= 8'b0000_0001; col6 <= 8'b0000_0000; end 6'b01_1110: // "U" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1111; col2 <= 8'b0100_0000; col3 <= 8'b0100_0000; col4 <= 8'b0100_0000; col5 <= 8'b0011_1111; col6 <= 8'b0000_0000; end 6'b01_1111: // "V" begin col0 <= 8'b0000_0000; col1 <= 8'b0001_1111; col2 <= 8'b0010_0000; col3 <= 8'b0100_0000; col4 <= 8'b0010_0000; col5 <= 8'b0001_1111; col6 <= 8'b0000_0000; end 6'b10_0000: // "W" begin col0 <= 8'b0000_0000; col1 <= 8'b0011_1111; col2 <= 8'b0100_0000; col3 <= 8'b0011_0000; col4 <= 8'b0100_0000; col5 <= 8'b0011_1111; col6 <= 8'b0000_0000; end 6'b10_0001: // "X" begin col0 <= 8'b0000_0000; col1 <= 8'b0110_0011; col2 <= 8'b0001_0100; col3 <= 8'b0000_1000; col4 <= 8'b0001_0100; col5 <= 8'b0110_0011; col6 <= 8'b0000_0000; end 6'b10_0010: // "Y" begin col0 <= 8'b0000_0000; col1 <= 8'b0000_0011; col2 <= 8'b0000_0100; col3 <= 8'b0111_1000; col4 <= 8'b0000_0100; col5 <= 8'b0000_0011; col6 <= 8'b0000_0000; end 6'b10_0011: // "Z" begin col0 <= 8'b0000_0000; col1 <= 8'b0110_0001; col2 <= 8'b0101_0001; col3 <= 8'b0100_1001; col4 <= 8'b0100_0101; col5 <= 8'b0100_0011; col6 <= 8'b0000_0000; end 6'b11_1110: // " " begin col0 <= 8'b0000_0000; col1 <= 8'b0000_0000; col2 <= 8'b0000_0000; col3 <= 8'b0000_0000; col4 <= 8'b0000_0000; col5 <= 8'b0000_0000; col6 <= 8'b0000_0000; end 6'b11_1111: // ":" begin col0 <= 8'b0000_0000; col1 <= 8'b0000_0000; col2 <= 8'b0011_0110; col3 <= 8'b0011_0110; col4 <= 8'b0000_0000; col5 <= 8'b0000_0000; col6 <= 8'b0000_0000; end default: // "*" begin col0 <= 8'b0000_0000; col1 <= 8'b0010_0010; col2 <= 8'b0001_0100; col3 <= 8'b0000_1000; col4 <= 8'b0001_0100; col5 <= 8'b0010_0010; col6 <= 8'b0000_0000; end endcase end end endmodule