Quartus 入门 —— VGA HelloWorld

Quartus 入门 —— VGA HelloWorld

ROM 创建

图片处理

首先我们需要使用 photoshop 等工具将图片转为16位的 .bmp 格式的文件,然后我们需要对图像进行处理,使用如下软件将图像转为 hex 文件:

image

图片数据太多需要使用 ROM 来存储数据

打开 quartus,找到 ROM

image

设置位宽度为16位,大小为图片大小50x50 = 2500

image

取消勾选下列选项:

image

选择我们转好格式的 hex 文件:

image

最后选中如下两个选项直接 finish 即可:

image

PLL 创建

分别使用 640×480 60HZ 和 800×600 72HZ,对应时钟分别为 25M 和 50M,需要使用 PLL 进行分频 时钟频率 = 行帧长 × 列帧长 * 刷新率, 640 ×480 60HZ 对应时钟频率 = 800 ×525 × 60 = 25.2M

ip 核里面找到 ALTPLL

image

基础时钟选择 50M

image

取消勾选输出使能

image

c0 默认输出 50M 即可,c1 分频到 25M,如需其他时钟频率可以自己进行设置

image

勾选如下选项后 finish

image

字符显示

首先我们需要对字符进行处理:

image

image

这里使用点阵显示时,汉字是 1616 点阵,而符号或是数字或是英文字母是 3216 点阵

点阵拼接时,用第一个字的前 4 位拼上下一个字的前 4 位…一直到最后一个字,它们构成 VGA 显示的一行

处理后如下:

image

彩条显示

根据当前行地址判断需要显示的颜色即可

代码设计

显示数据生成模块

完整文字显示代码如下:

data_driver.sv

module data_drive (input			wire						vga_clk,
                   input			wire						rst_n,
                   input			wire		[ 11:0 ]		addr_h,
                   input			wire		[ 11:0 ]		addr_v,
                   input			wire		[ 2:0 ]		 key,
                   output			reg		[ 15:0 ]				rgb_data);

localparam	red    = 16'd63488;
localparam	orange = 16'd64384;
localparam	yellow = 16'd65472;
localparam	green  = 16'd1024;
localparam	blue   = 16'd31;
localparam	indigo = 16'd18448;
localparam	purple = 16'd32784;
localparam	white  = 16'd65503;
localparam	black  = 16'd0;
reg [ 383:0 ] char_line[ 64:0 ];

localparam	states_1 = 1; // 彩条
localparam	states_2 = 2; // 字符
localparam	states_3 = 3; // 图片

parameter	height = 78; // 图片高度
parameter	width  = 128; // 图片宽度
reg			[ 1:0 ]			states_current			; // 当前状态
reg			[ 1:0 ]			states_next			    ; // 下个状态
reg			[ 13:0 ]		rom_address				; // ROM地址
wire			[ 15:0 ]		rom_data				; // 图片数据

wire							flag_enable_out1			; // 文字有效区域
wire							flag_enable_out2			; // 图片有效区域
wire							flag_clear_rom_address		; // 地址清零
wire							flag_begin_h			    ; // 图片显示行
wire							flag_begin_v			    ; // 图片显示列

//状态转移
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        states_current <= states_1;
    end
    else begin
        states_current <= states_next;
    end
end

//状态判断
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        states_next <= states_1;
    end
    else if ( key[ 0 ] ) begin
        states_next <= states_1;
    end
        else if ( key[ 1 ] ) begin
        states_next <= states_2;
        end
        else if ( key[ 2 ] ) begin
        states_next <= states_3;
        end
    else begin
        states_next <= states_next;
    end
end

//状态输出
always @( * ) begin
    case ( states_current )
        states_1 : begin
            if ( addr_h == 0 ) begin
                rgb_data = black;
            end
            else if ( addr_h >0 && addr_h <81 ) begin
                rgb_data = red;
            end
            else if ( addr_h >80 && addr_h <161 ) begin
                rgb_data = orange;
            end
            else if ( addr_h >160 && addr_h <241 ) begin
                rgb_data = yellow;
            end
            else if ( addr_h >240 && addr_h <321 ) begin
                rgb_data = green;
            end
            else if ( addr_h >320 && addr_h <401 ) begin
                rgb_data = blue;
            end
            else if ( addr_h >400 && addr_h <481 ) begin
                rgb_data = indigo;
            end
            else if ( addr_h >480 && addr_h <561 ) begin
                rgb_data = purple;
            end
            else if ( addr_h >560 && addr_h <641 ) begin
                rgb_data = white;
            end
            else begin
                rgb_data = black;
            end

        end
        states_2 : begin
            if ( flag_enable_out1 ) begin
                rgb_data = char_line[ addr_v-208 ][ 532 - addr_h ]? white:black;
            end
            else begin
                rgb_data = black;
            end
        end
        states_3 : begin
            if ( flag_enable_out2 ) begin
                rgb_data = rom_data;
            end
            else begin
                rgb_data = black;
            end

        end
        default: begin
            case ( addr_h )
                0 : rgb_data      = black;
                1 : rgb_data      = red;
                81 : rgb_data     = orange;
                161: rgb_data     = yellow;
                241: rgb_data     = green;
                321: rgb_data     = blue;
                401: rgb_data     = indigo;
                481: rgb_data     = purple;
                561: rgb_data     = white;
                default: rgb_data = rgb_data;
            endcase
        end
    endcase
end

assign flag_enable_out1 = states_current == states_2 && addr_h > 148 && addr_h < 533 && addr_v > 208  && addr_v < 273 ;
assign flag_begin_h     = addr_h > ( ( 640 - width ) / 2 ) && addr_h < ( ( 640 - width ) / 2 ) + width + 1;
assign flag_begin_v     = addr_v > ( ( 480 - height )/2 ) && addr_v <( ( 480 - height )/2 ) + height + 1;
assign flag_enable_out2 = states_current == states_3 && flag_begin_h && flag_begin_v;

//ROM地址计数器
always @( posedge vga_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;

//初始化显示文字
always@( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        char_line[ 0 ]  = 96'h000000000000000000000000;
        char_line[ 1 ]  = 96'h000000000000000000000000;
        char_line[ 2 ]  = 96'h000000000000000000000000;
        char_line[ 3 ]  = 96'h000000000000000000001000;
        char_line[ 4 ]  = 96'h000000000000000000007000;
        char_line[ 5 ]  = 96'h000000000000000000001000;
        char_line[ 6 ]  = 96'h000000000000000000001000;
        char_line[ 7 ]  = 96'hd800d8001a00d800d8001000;
        char_line[ 8 ]  = 96'h640064002600640064001000;
        char_line[ 9 ]  = 96'h420042004200420042001000;
        char_line[ 10 ] = 96'h420042004200420042001000;
        char_line[ 11 ] = 96'h420042004200420042001000;
        char_line[ 12 ] = 96'h640064002600640064001000;
        char_line[ 13 ] = 96'h580058001a00580058007c00;
        char_line[ 14 ] = 96'h400040000200400040000000;
        char_line[ 15 ] = 96'he000e0000700e000e0000000;
    end
end

//实例化ROM
rom	rom_inst (
.address ( rom_address ),
.clock ( vga_clk ),
.q ( rom_data )
);
endmodule // data_drive

vga 驱动

完整代码如下:

vga驱动模块:

module vga_dirve (input			wire						clk,            //系统时钟
                  input			wire						rst_n,          //复位
                  input			wire		[ 15:0 ]		rgb_data,       //16位RGB对应值
                  output			wire							vga_clk,    //vga时钟 25M
                  output			reg							h_sync,     //行同步信号
                  output			reg							v_sync,     //场同步信号
                  output			reg		[ 11:0 ]				addr_h, //行地址
                  output			reg		[ 11:0 ]				addr_v,  //列地址
                  output			wire		[ 4:0 ]				rgb_r,  //红基色
                  output			wire		[ 5:0 ]				rgb_g,  //绿基色
                  output			wire		[ 4:0 ]				rgb_b  //蓝基色
);

// 640 * 480 60HZ
localparam	 H_FRONT = 16; // 行同步前沿信号周期长
localparam	 H_SYNC  = 96; // 行同步信号周期长
localparam	 H_BLACK = 48; // 行同步后沿信号周期长
localparam	 H_ACT   = 640; // 行显示周期长
localparam	 V_FRONT = 11; // 场同步前沿信号周期长
localparam	 V_SYNC  = 2; // 场同步信号周期长
localparam	 V_BLACK = 31; // 场同步后沿信号周期长
localparam	 V_ACT   = 480; // 场显示周期长

// 800 * 600 72HZ
// localparam	 H_FRONT = 40; // 行同步前沿信号周期长
// localparam	 H_SYNC  = 120; // 行同步信号周期长
// localparam	 H_BLACK = 88; // 行同步后沿信号周期长
// localparam	 H_ACT   = 800; // 行显示周期长
// localparam	 V_FRONT = 37; // 场同步前沿信号周期长
// localparam	 V_SYNC  = 6; // 场同步信号周期长
// localparam	 V_BLACK = 23; // 场同步后沿信号周期长
// localparam	 V_ACT   = 600; // 场显示周期长


localparam	H_TOTAL = H_FRONT + H_SYNC + H_BLACK + H_ACT; // 行周期
localparam	V_TOTAL = V_FRONT + V_SYNC + V_BLACK + V_ACT; // 列周期

reg			[ 11:0 ]			cnt_h			; // 行计数器
reg			[ 11:0 ]			cnt_v			; // 场计数器
reg			[ 15:0 ]			rgb			; // 对应显示颜色值

// 对应计数器开始、结束、计数信号
wire							flag_enable_cnt_h			;
wire							flag_clear_cnt_h			;
wire							flag_enable_cnt_v			;
wire							flag_clear_cnt_v			;
wire							flag_add_cnt_v  			;
wire							valid_area      			;


// 25M时钟 行周期*场周期*刷新率 = 800 * 525* 60
wire							clk_25			;
// 50M时钟 1040 * 666 * 72
wire							clk_50			;
//PLL
pll	pll_inst (
	.areset ( ~rst_n ),
	.inclk0 ( clk ),
	.c0 ( clk_50 ), //50M
	.c1 ( clk_25 ), //25M
	);
//根据不同分配率选择不同频率时钟
assign vga_clk = clk_25;


// 行计数
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        cnt_h <= 0;
    end
    else if ( flag_enable_cnt_h ) begin
        if ( flag_clear_cnt_h ) begin
            cnt_h <= 0;
        end
        else begin
            cnt_h <= cnt_h + 1;
        end
    end
    else begin
        cnt_h <= 0;
    end
end
assign flag_enable_cnt_h = 1;
assign flag_clear_cnt_h  = cnt_h == H_TOTAL - 1;

// 行同步信号
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        h_sync <= 0;
    end
    else if ( cnt_h == H_SYNC - 1 ) begin // 同步周期时为1
        h_sync <= 1;
    end
        else if ( flag_clear_cnt_h ) begin // 其余为0
        h_sync <= 0;
        end
    else begin
        h_sync <= h_sync;
    end
end

// 场计数
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        cnt_v <= 0;
    end
    else if ( flag_enable_cnt_v ) begin
        if ( flag_clear_cnt_v ) begin
            cnt_v <= 0;
        end
        else if ( flag_add_cnt_v ) begin
            cnt_v <= cnt_v + 1;
        end
        else begin
            cnt_v <= cnt_v;
        end
    end
    else begin
        cnt_v <= 0;
    end
end
assign flag_enable_cnt_v = flag_enable_cnt_h;
assign flag_clear_cnt_v  = cnt_v == V_TOTAL - 1;
assign flag_add_cnt_v    = flag_clear_cnt_h;

// 场同步信号
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        v_sync <= 0;
    end
    else if ( cnt_v == V_SYNC - 1 ) begin
        v_sync <= 1;
    end
        else if ( flag_clear_cnt_v ) begin
        v_sync <= 0;
        end
    else begin
        v_sync <= v_sync;
    end
end

// 对应有效区域行地址 1-640
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        addr_h <= 0;
    end
    else if ( valid_area ) begin
        addr_h <= cnt_h - H_SYNC - H_BLACK + 1;
    end
    else begin
        addr_h <= 0;
    end
end
// 对应有效区域列地址 1-480
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        addr_v <= 0;
    end
    else if ( valid_area ) begin
        addr_v <= cnt_v -V_SYNC - V_BLACK + 1;
    end
    else begin
        addr_v <= 0;
    end
end
// 有效显示区域
assign valid_area = cnt_h >= H_SYNC + H_BLACK && cnt_h <= H_SYNC + H_BLACK + H_ACT && cnt_v >= V_SYNC + V_BLACK && cnt_v <= V_SYNC + V_BLACK + V_ACT;


// 显示颜色
always @( posedge vga_clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        rgb <= 16'h0;
    end
    else if ( valid_area ) begin
        rgb <= rgb_data;
    end
    else begin
        rgb <= 16'b0;
    end
end
assign rgb_r = rgb[ 15:11 ];
assign rgb_g = rgb[ 10:5 ];
assign rgb_b = rgb[ 4:0 ];

endmodule // vga_dirve

按键消抖模块

用于实现按键切换界面

完整代码如下:

key_debounce.v:

module key_debounce(
	input 	wire	clk,
	input 	wire 	rst_n,
	input 	wire 	key,
	
	output 	reg 	flag,// 0抖动, 1抖动结束
	output 	reg	key_value//key抖动结束后的值
);

parameter MAX_NUM = 20'd1_000_000;

reg [19:0] delay_cnt;//1_000_000

reg key_reg;//key上一次的值

always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		key_reg <= 1;
		delay_cnt <= 0;
	end
	
	else begin
		key_reg <= key;
		//当key为1 key 为0 表示按下抖动,开始计时
		if(key_reg  != key  ) begin 
		   delay_cnt <= MAX_NUM ;
		end
		else begin
		    if(delay_cnt > 0)
				delay_cnt <= delay_cnt -1;
			else
				delay_cnt <= 0;
		end
	end
end


//当计时完成,获取key的值
always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		flag <= 0;
		key_value <= 1;
	end

	else begin
		
		// 计时完成 处于稳定状态,进行赋值
		if(delay_cnt == 1) begin
			flag <= 1;
			key_value <= key;
		end
		else begin
			flag <= 0;
			key_value <= key_value;
		end
	end
end

endmodule

顶层模块

将多个模块整合到一起

完整代码如下:

vga_to.v:

module vga_top (input			wire						clk,
                input			wire						rst_n,
                input			wire		[ 2:0 ]		    key,
                output			wire						vga_clk,
                output			wire						h_sync,
                output			wire						v_sync,
                output			wire		[ 4:0 ]			rgb_r,
                output			wire		[ 5:0 ]			rgb_g,
                output			wire		[ 4:0 ]			rgb_b,
                output			reg		    [ 3:0 ]			led);

reg			[ 27:0 ]			cnt			        ;
wire		[ 11:0 ]		    addr_h              ;
wire		[ 11:0 ]		    addr_v              ;
wire		[ 15:0 ]			rgb_data			;
wire		[ 2:0 ]			    key_flag			;
wire		[ 2:0 ]			    key_value			;

//vga模块
vga_dirve u_vga_dirve(
.clk      ( clk ),
.rst_n    ( rst_n ),
.rgb_data ( rgb_data ),
.vga_clk  ( vga_clk ),
.h_sync   ( h_sync ),
.v_sync   ( v_sync ),
.rgb_r    ( rgb_r ),
.rgb_g    ( rgb_g ),
.rgb_b    ( rgb_b ),
.addr_h   ( addr_h ),
.addr_v   ( addr_v )
);

//数据模块
data_drive u_data_drive(
.vga_clk ( vga_clk ),
.rst_n   ( rst_n ),
.addr_h  ( addr_h ),
.addr_v  ( addr_v ),
.key     ( {key_value[ 2 ] && key_flag[ 2 ], key_value[ 1 ] && key_flag[ 1 ], key_value[ 0 ] && key_flag[ 0 ] } ),
.rgb_data  ( rgb_data )
);


//按键消抖
key_debounce u_key_debounce0(
.clk   ( vga_clk ),
.rst_n ( rst_n ),
.key   ( key[ 0 ] ),
.flag  ( key_flag[ 0 ] ),
.key_value  ( key_value[ 0 ] )
);

key_debounce u_key_debounce1(
.clk   ( vga_clk ),
.rst_n ( rst_n ),
.key   ( key[ 1 ] ),
.flag  ( key_flag[ 1 ] ),
.key_value  ( key_value[ 1 ] )
);

key_debounce u_key_debounce2(
.clk   ( vga_clk ),
.rst_n ( rst_n ),
.key   ( key[ 2 ] ),
.flag  ( key_flag[ 2 ] ),
.key_value  ( key_value[ 2 ] )
);

// led
always @( posedge clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        cnt <= 0;
    end
    else if ( cnt == 50_000_000 - 1 ) begin
        cnt <= 0;
    end
    else begin
        cnt <= cnt + 1;
    end
end
always @( posedge clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        led <= 4'b0000;
    end
    else if ( cnt == 50_000_000 -1 )begin
        led <= ~led;
    end
    else begin
        led <= led;
    end
end
endmodule // vga_top

tcl 引脚绑定文件

package require ::quartus::project

set_location_assignment PIN_E1 -to clk
set_location_assignment PIN_E15 -to rst_n
set_location_assignment PIN_C16 -to h_sync
set_location_assignment PIN_D15 -to v_sync

set_location_assignment PIN_E16 -to key[0]
set_location_assignment PIN_M16 -to key[1]
set_location_assignment PIN_M15 -to key[2]

set_location_assignment PIN_G15 -to led[0]
set_location_assignment PIN_F16 -to led[1]
set_location_assignment PIN_F15 -to led[2]
set_location_assignment PIN_D16 -to led[3]

set_location_assignment PIN_A14 -to rgb_b[4]
set_location_assignment PIN_B14 -to rgb_b[3]
set_location_assignment PIN_A15 -to rgb_b[2]
set_location_assignment PIN_B16 -to rgb_b[1]
set_location_assignment PIN_C15 -to rgb_b[0]


set_location_assignment PIN_A11 -to rgb_g[5]
set_location_assignment PIN_B11 -to rgb_g[4]
set_location_assignment PIN_A12 -to rgb_g[3]
set_location_assignment PIN_B12 -to rgb_g[2]
set_location_assignment PIN_A13 -to rgb_g[1]
set_location_assignment PIN_B13 -to rgb_g[0]

set_location_assignment PIN_C8 -to rgb_r[4]
set_location_assignment PIN_A9 -to rgb_r[3]
set_location_assignment PIN_B9 -to rgb_r[2]
set_location_assignment PIN_A10 -to rgb_r[1]
set_location_assignment PIN_B10 -to rgb_r[0]

到此我们的代码就已经完成了

测试

下面我们将引脚绑定到开发板上进行验证:

image

image

image

参考资料

  1. 基于FPGA的VGA显示彩条、字符、图片
posted @ 2023-04-25 20:24  ppqppl  阅读(25)  评论(0编辑  收藏  举报