FPGA设计—贪吃蛇游戏

本设计是利用verilog硬件描述语言开发FPGA,在VGA接口显示屏上实现贪吃蛇游戏。

总体设计

设计模块分为时钟分频模块(pll)、按键控制模块(key)、vga显示模块(vga)、苹果产生模块(apple)、数码管显示模块(nixie_tube)、音乐播放模块(play)。

设计的RTL示图:

FPGA设计—贪吃蛇游戏_第1张图片输入输出接口

         信号名      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的显示以及蛇身的控制。

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 

数码管显示模块与音乐播放模块

数码管模块显示当前的分数,音乐播放模块在蛇吃到苹果、死亡时会产生声音。

 

你可能感兴趣的:(verilog,FPGA)