本设计是利用verilog硬件描述语言开发FPGA,在VGA接口显示屏上实现贪吃蛇游戏。
设计模块分为时钟分频模块(pll)、按键控制模块(key)、vga显示模块(vga)、苹果产生模块(apple)、数码管显示模块(nixie_tube)、音乐播放模块(play)。
设计的RTL示图:
信号名 | I/O | 位宽 | 功能 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位信号 |
key_up | I | 1 | 蛇身控制键 上 |
key_down | I | 1 | 蛇身控制键 下 |
key_left | I | 1 | 蛇身控制键 左 |
key_right | I | 1 | 蛇身控制键 右 |
stop_run | I | 1 | 游戏暂停运行键 |
key_choose1 | I | 1 | 模式选择键1 |
key_choose2 | I | 1 | 模式选择键2 |
hys | O | 1 | VGA行同步信号 |
vys | O | 1 | VGA场同步信号 |
rgb_data | O | 8 | RGB数据 |
tube_place | O | 2 | 数码管为选 |
tube_data | O | 8 | 数码管段选数据 |
been | O | 1 | 音频输出 |
时钟分频模块是将原有的50MHz的时钟频率2分频为25MHz,方便VGA的时序设计。设置FPGA内部的PLL完成分频。
按键控制模块完成对按键的消抖判断。判断程序如下
//按键消抖
module key( clk_25M , rst_n ,
key_up , key_down , key_left , key_right , stop_run , key_choose1 , key_choose2 ,
deal_key_up , deal_key_down , deal_key_left , deal_key_right , deal_stop_run ,
deal_key_choose1 , deal_key_choose2
);
input clk_25M; //25MHz时钟信号
input rst_n; //复位信号
input key_up; //上
input key_down; //下
input key_left; //左
input key_right; //右
input stop_run; //停止运行
input key_choose1; //选择1键
input key_choose2; //选择2键
output deal_key_up;
output deal_key_down;
output deal_key_left;
output deal_key_right;
output deal_stop_run;
output deal_key_choose1;
output deal_key_choose2;
//------------------------------------------------------------
reg [6:0] key_rst; //按键值存放
reg [6:0] key_rst_r; //存放后一个时钟周期的按键值
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
key_rst <= 7'b1111111;
else
key_rst <= { key_choose1 , key_choose2 , stop_run , key_up , key_down , key_left , key_right }; //未消抖的状态
end
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
key_rst_r <= 7'b1111111;
else
key_rst_r <= key_rst; //过去一个时钟周期的状态
end
wire [6:0] key_an; //第一次状态判断值
assign key_an = key_rst_r[6:0] & (~key_rst[6:0]); //在那个位置位1,说明这个键被按下,下面进行消抖
//------------------------------------------------------------
reg [18:0] cnt;
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
cnt <= 19'd0;
else if( key_an ) //第一次判断 有按键按下
cnt <= 19'd0;
else
cnt <= cnt + 1'b1; //消抖计时
end
//---------------------------
reg [6:0] low_sw;
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
low_sw <= 7'b1111111;
else if( cnt == 19'h7ffff ) //计满20ms
low_sw <= { key_choose1 , key_choose2 , stop_run , key_up , key_down , key_left , key_right }; //消抖后的值
end
reg [6:0] low_sw_r;
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
low_sw_r <= 7'b1111111;
else
low_sw_r <= low_sw;
end
wire [6:0] key;
assign key = low_sw_r[6:0] & (~low_sw[6:0]);//在那个位置位1,说明这个键被按下
assign deal_key_choose1 = key[6];
assign deal_key_choose2 = key[5];
assign deal_stop_run = key[4];
assign deal_key_up = key[3];
assign deal_key_down = key[2];
assign deal_key_left = key[1];
assign deal_key_right = key[0];
endmodule
在VGA模块中包含了VGA的显示以及蛇身的控制。
VGA接口时序使用的是640*480的分辨率,刷新频率为60Hz,时序控制如下:
reg [9:0] cnt_hs; //行信号clk计数
reg [9:0] cnt_vs; //场信号clk计数
wire add_cnt_hs; //行信号clk计数器加1条件
wire end_cnt_hs; //行信号clk计数器结束条件
wire add_cnt_vs; //场信号clk计数器加1条件
wire end_cnt_vs; //场信号clk计数器结束条件
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
cnt_hs <= 10'd0;
else if( add_cnt_hs ) begin
if( end_cnt_hs )
cnt_hs <= 10'd0;
else
cnt_hs <= cnt_hs + 1'b1;
end
end
assign add_cnt_hs = 1'b1;
assign end_cnt_hs = add_cnt_hs && ( cnt_hs == 800-1 ); //end_cnt_hs=1 结束计数
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
cnt_vs <= 10'd0;
else if( add_cnt_vs ) begin
if( end_cnt_vs )
cnt_vs <= 10'd0;
else
cnt_vs <= cnt_vs + 1'b1;
end
end
assign add_cnt_vs = end_cnt_hs;
assign end_cnt_vs = add_cnt_vs && ( cnt_vs == 525-1 );
wire [9:0] vga_x; //VGA的x坐标
wire [9:0] vga_y; //VGA的y坐标
assign vga_x = cnt_hs - 10'd144;
assign vga_y = cnt_vs - 10'd35;
//-------------------------------------------------------------------
//行信号与场信号
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
hys <= 1'b1;
else if( cnt_hs == 10'd95 )
hys <= 1'b1;
else if( end_cnt_hs )
hys <= 1'b0;
end
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
vys <= 1'b1;
else if( cnt_vs == 10'd1 )
vys <= 1'b1;
else if( end_cnt_vs )
vys <= 1'b0;
end
蛇身控制是由按键作为输入,控制蛇的运动方向。程序如下:
//按键控制坐标
//25MHz
always @( posedge clk_25M or posedge system_rst_pulse or negedge rst_n ) begin
if( !rst_n || system_rst_pulse ) begin
site_x[0] <= 10'd520; //蛇头起始坐标
site_y[0] <= 10'd230;
site_x[1] <= 10'd530;
site_y[1] <= 10'd230;
site_x[2] <= 10'd540;
site_y[2] <= 10'd230;
end
else if( end_cnt_s && stop_run && (!die_flag_r) && (!success_flag_r) ) begin //蛇头坐标赋值给后一个块,以此类推
reg [8:0] temp1; //for循环变量
for( temp1=0 ; temp1<7'd24 ; temp1=temp1+1'b1 ) begin //循环次数固定
site_x[temp1+1'b1] <= site_x[temp1];
site_y[temp1+1'b1] <= site_y[temp1];
end
if( up )
site_y[0] <= site_y[0] - 10'd10;
else if( down )
site_y[0] <= site_y[0] + 10'd10;
else if( left ) //默认状态
site_x[0] <= site_x[0] - 10'd10;
else if( right )
site_x[0] <= site_x[0] + 10'd10;
end
end
苹果产生模块,当蛇吃到苹果后,通过x轴与y轴不同的计数器产生随机的x轴与y轴坐标,在这个坐标上产生苹果。
//苹果输出
module apple( clk_25M , rst_n ,
apple_x , apple_y ,
updata_apple );
input clk_25M; //25M时钟信号
input rst_n; //复位信号
input updata_apple; //苹果是否被吃标志 产生新的苹果触发
output [9:0] apple_x; //苹果坐标
output [8:0] apple_y;
reg [9:0] apple_x;
reg [8:0] apple_y;
//-----------------------------------------------------------------
//计数器
reg [14:0] cnt_1ms;
wire add_cnt_1ms; //加一条件
wire end_cnt_1ms; //结束条件
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
cnt_1ms <= 15'd0;
else if( add_cnt_1ms ) begin
if( end_cnt_1ms )
cnt_1ms <= 15'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
end
end
assign add_cnt_1ms = 1;
assign end_cnt_1ms = add_cnt_1ms && ( cnt_1ms == 25000-1 ); //1ms计时
reg [3:0] cnt_10ms;
wire add_cnt_10ms; //加一条件
wire end_cnt_10ms; //结束条件
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n)
cnt_10ms <= 4'd0;
else if( add_cnt_10ms ) begin
if( end_cnt_10ms )
cnt_10ms <= 4'd0;
else
cnt_10ms <= cnt_10ms + 1'b1;
end
end
assign add_cnt_10ms = end_cnt_1ms;
assign end_cnt_10ms = add_cnt_10ms && ( cnt_10ms == 10-1 ); //10ms计数
//-----------------------------------------------------------------
reg [4:0] count_x;
reg [5:0] count_y;
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n ) begin
count_x <= 6'd0;
count_y <= 6'd0;
end
else begin
if( end_cnt_1ms ) begin
count_x <= count_x + 1'b1;
if( count_x == 6'd27 )
count_x <= 6'd1;
end
if( end_cnt_10ms ) begin
count_y <= count_y + 1'b1;
if( count_y == 6'd35 )
count_y <= 6'd1;
end
end
end
//-----------------------------------------------------------------
reg [9:0] apple_x_r;
reg [8:0] apple_y_r;
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
apple_x_r <= 10'd0;
else begin
case( count_x )
6'd1 : apple_x_r <= 10'd330;
6'd2 : apple_x_r <= 10'd340;
6'd3 : apple_x_r <= 10'd350;
6'd4 : apple_x_r <= 10'd360;
6'd5 : apple_x_r <= 10'd370;
6'd6 : apple_x_r <= 10'd380;
6'd7 : apple_x_r <= 10'd390;
6'd8 : apple_x_r <= 10'd400;
6'd9 : apple_x_r <= 10'd410;
6'd10 : apple_x_r <= 10'd420;
6'd11 : apple_x_r <= 10'd430;
6'd12 : apple_x_r <= 10'd440;
6'd13 : apple_x_r <= 10'd450;
6'd14 : apple_x_r <= 10'd460;
6'd15 : apple_x_r <= 10'd470;
6'd16 : apple_x_r <= 10'd480;
6'd17 : apple_x_r <= 10'd490;
6'd18 : apple_x_r <= 10'd500;
6'd19 : apple_x_r <= 10'd510;
6'd20 : apple_x_r <= 10'd520;
6'd21 : apple_x_r <= 10'd530;
6'd22 : apple_x_r <= 10'd540;
6'd23 : apple_x_r <= 10'd550;
6'd24 : apple_x_r <= 10'd560;
6'd25 : apple_x_r <= 10'd570;
6'd26 : apple_x_r <= 10'd580;
default : apple_x_r <= 10'd330;
endcase
end
end
always @( posedge clk_25M or negedge rst_n ) begin
if( !rst_n )
apple_y_r <= 10'd0;
else begin
case( count_y )
6'd1 : apple_y_r <= 10'd70;
6'd2 : apple_y_r <= 10'd80;
6'd3 : apple_y_r <= 10'd90;
6'd4 : apple_y_r <= 10'd100;
6'd5 : apple_y_r <= 10'd110;
6'd6 : apple_y_r <= 10'd120;
6'd7 : apple_y_r <= 10'd130;
6'd8 : apple_y_r <= 10'd140;
6'd9 : apple_y_r <= 10'd150;
6'd10 : apple_y_r <= 10'd160;
6'd11 : apple_y_r <= 10'd170;
6'd12 : apple_y_r <= 10'd180;
6'd13 : apple_y_r <= 10'd190;
6'd14 : apple_y_r <= 10'd200;
6'd15 : apple_y_r <= 10'd210;
6'd16 : apple_y_r <= 10'd220;
6'd17 : apple_y_r <= 10'd230;
6'd18 : apple_y_r <= 10'd240;
6'd19 : apple_y_r <= 10'd250;
6'd20 : apple_y_r <= 10'd260;
6'd21 : apple_y_r <= 10'd270;
6'd22 : apple_y_r <= 10'd280;
6'd23 : apple_y_r <= 10'd290;
6'd24 : apple_y_r <= 10'd300;
6'd25 : apple_y_r <= 10'd310;
6'd26 : apple_y_r <= 10'd320;
6'd27 : apple_y_r <= 10'd330;
6'd28 : apple_y_r <= 10'd340;
6'd29 : apple_y_r <= 10'd350;
6'd30 : apple_y_r <= 10'd360;
6'd31 : apple_y_r <= 10'd370;
6'd32 : apple_y_r <= 10'd380;
6'd33 : apple_y_r <= 10'd390;
6'd34 : apple_y_r <= 10'd400;
default : apple_y_r <= 10'd70;
endcase
end
end
always @( posedge updata_apple or negedge rst_n ) begin //updata_apple上升沿改变(x,y)
if( !rst_n ) begin
apple_x <= 10'd400; //第一个苹果
apple_y <= 10'd150;
end
else begin
apple_x <= apple_x_r;
apple_y <= apple_y_r;
end
end
endmodule
数码管模块显示当前的分数,音乐播放模块在蛇吃到苹果、死亡时会产生声音。