本设计是利用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 ) beginif( !rst_n )key_rst <= 7'b1111111;else key_rst <= { key_choose1 , key_choose2 , stop_run , key_up , key_down , key_left , key_right }; //未消抖的状态
endalways @( posedge clk_25M or negedge rst_n ) beginif( !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 ) beginif( !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 ) beginif( !rst_n )low_sw <= 7'b1111111;else if( cnt == 19'h7ffff ) //计满20mslow_sw <= { key_choose1 , key_choose2 , stop_run , key_up , key_down , key_left , key_right }; //消抖后的值
endreg [6:0] low_sw_r;always @( posedge clk_25M or negedge rst_n ) beginif( !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 ) beginif( !rst_n )cnt_hs <= 10'd0;else if( add_cnt_hs ) beginif( end_cnt_hs )cnt_hs <= 10'd0;else cnt_hs <= cnt_hs + 1'b1;end
endassign 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 ) beginif( !rst_n ) cnt_vs <= 10'd0;else if( add_cnt_vs ) beginif( end_cnt_vs )cnt_vs <= 10'd0;else cnt_vs <= cnt_vs + 1'b1;end
endassign 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 ) beginif( !rst_n )hys <= 1'b1;else if( cnt_hs == 10'd95 )hys <= 1'b1;else if( end_cnt_hs )hys <= 1'b0;
endalways @( posedge clk_25M or negedge rst_n ) beginif( !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 ) beginif( !rst_n || system_rst_pulse ) beginsite_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;endelse 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];endif( 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 ) beginif( !rst_n )cnt_1ms <= 15'd0;else if( add_cnt_1ms ) beginif( end_cnt_1ms )cnt_1ms <= 15'd0;else cnt_1ms <= cnt_1ms + 1'b1;end
endassign 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 ) beginif( !rst_n)cnt_10ms <= 4'd0;else if( add_cnt_10ms ) beginif( end_cnt_10ms )cnt_10ms <= 4'd0;else cnt_10ms <= cnt_10ms + 1'b1;end
endassign 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 ) beginif( !rst_n ) begincount_x <= 6'd0;count_y <= 6'd0;endelse beginif( end_cnt_1ms ) begincount_x <= count_x + 1'b1;if( count_x == 6'd27 )count_x <= 6'd1;endif( end_cnt_10ms ) begincount_y <= count_y + 1'b1;if( count_y == 6'd35 )count_y <= 6'd1;endend
end//-----------------------------------------------------------------
reg [9:0] apple_x_r;
reg [8:0] apple_y_r;always @( posedge clk_25M or negedge rst_n ) beginif( !rst_n )apple_x_r <= 10'd0;else begincase( 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
endalways @( posedge clk_25M or negedge rst_n ) beginif( !rst_n )apple_y_r <= 10'd0;else begincase( 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;endcaseend
endalways @( posedge updata_apple or negedge rst_n ) begin //updata_apple上升沿改变(x,y)if( !rst_n ) beginapple_x <= 10'd400; //第一个苹果apple_y <= 10'd150;endelse beginapple_x <= apple_x_r;apple_y <= apple_y_r;end
endendmodule
数码管显示模块与音乐播放模块
数码管模块显示当前的分数,音乐播放模块在蛇吃到苹果、死亡时会产生声音。