赞
踩
本文介绍基于verilog语言开发的贪吃蛇小游戏,FPGA板卡至少需要900个LC(文中程序需求1700个LC,可通过删减部分代码得到)、4个独立按键以及1个VGA接口。本文使用的板卡配有50MHz晶振,所配备的程序生成800*600*60Hz驱动信号,采用其它晶振频率的板卡需要对VGA驱动渲染模块(本文中为render)进行相应修改,使得刷新脉冲符合显示屏输入信号要求。
注意:本文中使用snack代指snake,这是由于最初的错误累积而成,不得已对其进行忽略。
贪吃蛇工程由7个模块构成,结构层级如图1所示。其中包含1个顶层模块snack、3个功能模块(random_box、render、snack_control)以及3个底层模块(keycheck、random、box_create)。本文设计的结构层次存在一定的瑕疵,其修正方法会在各个模块中提出,但由于本人水平有限,也有部分问题未能解决,欢迎各位能够与我进行探讨。
图1 贪吃蛇工程结构层次
顶层模块主要规定了工程对外的各类接口,以及各个功能模块之间的关系。在本文中,顶层模块调用了三个功能模块(random_box、render、snack_control),作为游戏功能的分区设计。将整个工程所需要实现的功能肢解为随机小盒子(即苹果或被吃物的创建与更新)、VGA驱动脉冲生成与随机小盒子画面渲染、蛇的控制(移动方向、体长、行为检测)。其RTL视图如图2所示。
图2 顶层模块RTL视图
在此处,出现了本文的第一个设计瑕疵:功能划分不清。在设计中,将随机小盒子的画面渲染放到render模块中,却将蛇的渲染放置到snack_control模块中,造成了模块的功能混淆。更加理性地办法是将随机小盒子的渲染放到random_box模块中,render模块只负责VGA驱动信号的生成以及画面总体渲染。这样工程模块的划分也更加清晰,任务也更加明确。
- module snack(
- input clk,
- input rst_n,
- input key_r,
- input key_l,
- input key_u,
- input key_d,
- output hsync,
- output vsync,
- output vga_r,
- output vga_g,
- output vga_b
- );
- //贪吃蛇游戏分为:box生成模块、蛇身控制模块、画面渲染模块
- //蛇:红色
- //box:绿色
- //背景:蓝色
- //--------------------------------------------------------------------------------
- //画面渲染位置
- wire [9:0]x_pos;
- wire [9:0]y_pos;
- //box坐标传递
- wire[9:0]box_x;
- wire[9:0]box_y;
- //--------------------------------------------------------------------------------
- //蛇身控制模块,蛇是红色的
- wire drive;
- wire snack_r;
- snack_control u1_snack_control(
- .clk(clk),
- .rst_n(rst_n),
- .key_r(key_r),
- .key_l(key_l),
- .key_u(key_u),
- .key_d(key_d),
- .box_x(box_x),
- .box_y(box_y),
- .x_pos(x_pos),
- .y_pos(y_pos),
- .drive(drive),
- .snack_r(snack_r)
- );
- //--------------------------------------------------------------------------------
- //box生成模块
- random_box u1_random_box(
- .clk(clk),
- .rst_n(rst_n),
- .drive(drive),//生成驱动信号
- .box_x(box_x),//坐标信号
- .box_y(box_y)
- );
- //--------------------------------------------------------------------------------
- //画面渲染模块
-
- render u1_render(
- .clk(clk),
- .rst_n(rst_n),
- .box_x(box_x),
- .box_y(box_y),
- .snack_r(snack_r),
- .hsync(hsync),
- .vsync(vsync),
- .x_pos(x_pos),
- .y_pos(y_pos),
- .vga_r(vga_r),
- .vga_g(vga_g),
- .vga_b(vga_b)
- );
- endmodule
该模块主要负责随机小盒子的生成与更新。盒子的坐标来自于LFSR伪随机码生成器产生的两个相邻数据,为了获取完整的随机坐标,必须等待2个时钟周期。
- module random_box(
- input clk,
- input rst_n,
- input drive,
- output wire[9:0]box_x,
- output wire[9:0]box_y
- );
- //---------------------------------------------------------------------------------
- //随机数生成模块
- wire [8:0]rand_num;
- random U1_random(
- .clk(clk),
- .rst_n(rst_n),
- // .seed(seed),
- //.load(load),
- .rand_num(rand_num)
- );
- //随机盒子创建
- wire [9:0]rand_x;
- wire [9:0]rand_y;
- // wire rand_drive;//随机小方块激励模块
- box_create U1_box_create(
- .clk(clk),
- .rst_n(rst_n),
- .rand_num(rand_num),
- .rand_drive(drive),
- .rand_x(rand_x),
- .rand_y(rand_y)
- );
- assign box_x = rand_x;
- assign box_y = rand_y;
-
-
- endmodule
该模块采用LFSR机制生成伪随机数,其种子已提前预置。本文中采用了9位宽度的设计,使得生成的伪随机数范围小于VGA屏幕的显示范围。如果有需求可为LFSR增加1位长度,并增添额外的代码用于判定取用的伪随机数是否越界。
- module random(
- input clk,
- input rst_n,
- // input load,
- // input[8:0] seed,
- output reg[8:0] rand_num
- );
- //-----------------------------------------------------------------------------------
- always@(posedge clk or negedge rst_n)
- if(!rst_n) rand_num <= 9'd132;
- // else if(load) rand_num <= seed;
- // else if(load) rand_num <= 9'd131;
- else
- begin
- rand_num[0] <= rand_num[8];
- rand_num[1] <= rand_num[0];
- rand_num[2] <= rand_num[1];
- rand_num[3] <= rand_num[2];
- rand_num[4] <= rand_num[3]^rand_num[8];
- rand_num[5] <= rand_num[4]^rand_num[8];
- rand_num[6] <= rand_num[5]^rand_num[8];
- rand_num[7] <= rand_num[6];
- rand_num[8] <= rand_num[7];
- end
- endmodule
该模块主要用于创建和更新随机小盒子的中心坐标。更新信号drive由snack_control模块提供,为一个仅保持1个时钟周期拉高信号。在模块中配置flag信号是为了为信号坐标的获取实行一个周期的延时,避免了横纵坐标的相等。
- module box_create(
- input clk,
- input rst_n,
- input[8:0] rand_num,
- input rand_drive,
- output reg[9:0]rand_x,
- output reg[9:0]rand_y
- );
- //-----------------------------------------------------------------------------
- reg flag;
- always@(posedge clk or negedge rst_n)
- if(!rst_n)
- begin
- rand_x <= 9'd300;
- rand_y <= 9'd300;
- flag <= 1'b0;
- end
- else if(rand_drive) begin flag <= 1'b1; rand_x <= rand_num; end
- else if(flag == 1'b1) begin rand_y <= rand_num; flag <= 1'b0;end
- endmodule
该模块主要实现了以下几个功能:蛇头移动方向控制、蛇体寄存器、死亡检测、长度检测、蛇体渲染等四部分构成。蛇头移动方向控制由按键检测模块和方向寄存器修正规则代码组成;蛇体寄存器由蛇头移动更新状态机、蛇身移位寄存器组组成;长度检测包含长度记录、增长控制、随机小盒子更新信号生成三个功能;蛇体渲染为根据VGA驱动信号计算蛇体在屏幕上的显示,如果采用未注释部分,综合出的代码需要900个LC,若采用注释部分的代码增粗蛇体,就需要1700个LC,这也是本设计中的另一个不足。
- module snack_control(
- input clk,
- input rst_n,
- input key_r,
- input key_l,
- input key_u,
- input key_d,
- input [9:0]box_x,
- input [9:0]box_y,
- input [9:0]x_pos,
- input [9:0]y_pos,
- output reg drive,
- output snack_r
- );
- //------------------------------------------------------------------------------
- //蛇头移动方向控制
- wire key_r1;
- wire key_l1;
- wire key_u1;
- wire key_d1;
- reg[1:0]dir;//蛇头方向寄存器
- parameter right = 2'd0;
- parameter left = 2'd1;
- parameter up = 2'd2;
- parameter down = 2'd3;
- keycheck r_keycheck(
- .clk(clk),
- .rst_n(rst_n),
- .key(key_r),
- .key_v(key_r1)
- );
- keycheck l_keycheck(
- .clk(clk),
- .rst_n(rst_n),
- .key(key_l),
- .key_v(key_l1)
- );
- keycheck u_keycheck(
- .clk(clk),
- .rst_n(rst_n),
- .key(key_u),
- .key_v(key_u1)
- );
- keycheck d_keycheck(
- .clk(clk),
- .rst_n(rst_n),
- .key(key_d),
- .key_v(key_d1)
- );
- always@(posedge clk or negedge rst_n)
- if(!rst_n) dir <= right;
- else if(key_r1 && dir != left) dir <= right;
- else if(key_l1 && dir != right) dir <= left;
- else if(key_u1 && dir != down) dir <= up;
- else if(key_d1 && dir != up) dir <= down;
- //------------------------------------------------------------------------------
- //蛇体寄存器,蛇体坐标和当前移动方向
- reg fin;//游戏结束标志位
- reg[9:0]snack_x[11:0];
- reg[9:0]snack_y[11:0];
- reg [18:0]cnt_m; //最大值312500;
- parameter update_time = 19'd312500;
- //更新计数器
- always@(posedge clk or negedge rst_n)
- if(!rst_n) cnt_m <= 19'd0;
- else if(fin) cnt_m <= 19'd0;
- else if(cnt_m == update_time) cnt_m <= 19'd0;
- else cnt_m <= cnt_m + 19'd1;
- //蛇头
- always@(posedge clk or negedge rst_n)
- if(!rst_n)
- begin
- snack_x[0] <= 10'd400;
- snack_y[0] <= 10'd300;
- end
- else if(cnt_m == update_time)
- case(dir)
- right : begin
- if(snack_x[0] == 10'd799) snack_x[0] <= 10'd0;
- else snack_x[0] <= snack_x[0] + 10'd1;
- end
- left : begin
- if(snack_x[0] == 10'd0) snack_x[0] <= 10'd799;
- else snack_x[0] <= snack_x[0] - 10'd1;
- end
- up : begin
- if(snack_y[0] == 10'd599) snack_y[0] <= 10'd0;
- else snack_y[0] <= snack_y[0] + 10'd1;
- end
- down : begin
- if(snack_y[0] == 10'd0) snack_y[0] <= 10'd599;
- else snack_y[0] <= snack_y[0] - 10'd1;
- end
- endcase
- //蛇身
- always@(posedge clk or negedge rst_n)
- if(!rst_n)
- begin
- snack_x[1] <= 10'd0;
- snack_y[1] <= 10'd0;
- snack_x[2] <= 10'd0;
- snack_y[2] <= 10'd0;
- snack_x[3] <= 10'd0;
- snack_y[3] <= 10'd0;
- snack_x[4] <= 10'd0;
- snack_y[4] <= 10'd0;
- snack_x[5] <= 10'd0;
- snack_y[5] <= 10'd0;
- snack_x[6] <= 10'd0;
- snack_y[6] <= 10'd0;
- snack_x[7] <= 10'd0;
- snack_y[7] <= 10'd0;
- snack_x[8] <= 10'd0;
- snack_y[8] <= 10'd0;
- snack_x[9] <= 10'd0;
- snack_y[9] <= 10'd0;
- snack_x[10] <= 10'd0;
- snack_y[10] <= 10'd0;
- snack_x[11] <= 10'd0;
- snack_y[11] <= 10'd0;
- end
- else if(cnt_m == update_time)
- begin
- snack_x[1] <= snack_x[0];
- snack_y[1] <= snack_y[0];
- snack_x[2] <= snack_x[1];
- snack_y[2] <= snack_y[1];
- snack_x[3] <= snack_x[2];
- snack_y[3] <= snack_y[2];
- snack_x[4] <= snack_x[3];
- snack_y[4] <= snack_y[3];
- snack_x[5] <= snack_x[4];
- snack_y[5] <= snack_y[4];
- snack_x[6] <= snack_x[5];
- snack_y[6] <= snack_y[5];
- snack_x[7] <= snack_x[6];
- snack_y[7] <= snack_y[6];
- snack_x[8] <= snack_x[7];
- snack_y[8] <= snack_y[7];
- snack_x[9] <= snack_x[8];
- snack_y[9] <= snack_y[8];
- snack_x[10] <= snack_x[9];
- snack_y[10] <= snack_y[9];
- snack_x[11] <= snack_x[10];
- snack_y[11] <= snack_y[10];
- end
- //------------------------------------------------------------------------------
- //长度检测模块
- reg[3:0] length;
- always@(posedge clk or negedge rst_n)
- if(!rst_n) begin length <= 4'd1;drive <= 1'd0; end
- else if(drive) drive <= 1'd0;
- else if(snack_x[0] == box_x && snack_y[0] == box_y)
- begin
- drive <= 1'd1;
- if(length < 4'd12) length <= length + 4'd1;
- else length <= length;
- end
- //-------------------------------------------------------------------------------
- //死亡检测模块
- //reg fin;
- always@(posedge clk or negedge rst_n)
- if(!rst_n) fin <= 1'b0;
- else if(snack_x[0] == snack_x[1] && snack_y[0] == snack_y[1]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[2] && snack_y[0] == snack_y[2]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[3] && snack_y[0] == snack_y[3]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[4] && snack_y[0] == snack_y[4]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[5] && snack_y[0] == snack_y[5]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[6] && snack_y[0] == snack_y[6]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[7] && snack_y[0] == snack_y[7]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[8] && snack_y[0] == snack_y[8]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[9] && snack_y[0] == snack_y[9]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[10] && snack_y[0] == snack_y[10]) fin <= 1'b1;
- else if(snack_x[0] == snack_x[11] && snack_y[0] == snack_y[11]) fin <= 1'b1;
- //-------------------------------------------------------------------------------
- //蛇体渲染模块
-
- assign snack_r = (x_pos == snack_x[0] && y_pos == snack_y[0])||
- (x_pos == snack_x[1] && y_pos == snack_y[1]&&length > 4'd1)||
- (x_pos == snack_x[2] && y_pos == snack_y[2]&&length > 4'd2)||
- (x_pos == snack_x[3] && y_pos == snack_y[3]&&length > 4'd3)||
- (x_pos == snack_x[4] && y_pos == snack_y[4]&&length > 4'd4)||
- (x_pos == snack_x[5] && y_pos == snack_y[5]&&length > 4'd5)||
- (x_pos == snack_x[6] && y_pos == snack_y[6]&&length > 4'd6)||
- (x_pos == snack_x[7] && y_pos == snack_y[7]&&length > 4'd7)||
- (x_pos == snack_x[8] && y_pos == snack_y[8]&&length > 4'd8)||
- (x_pos == snack_x[9] && y_pos == snack_y[9]&&length > 4'd9)||
- (x_pos == snack_x[10] && y_pos == snack_y[10]&&length > 4'd10)||
- (x_pos == snack_x[11] && y_pos == snack_y[11]&&length > 4'd11);
- /*
- assign snack_r = ((x_pos >= snack_x[0]-10'd3 && x_pos <= snack_x[0]+10'd3)&&(y_pos >= snack_y[0]-10'd3 && y_pos <= snack_y[0]+10'd3))||
- ((x_pos >= snack_x[1]-10'd3 && x_pos <= snack_x[1]+10'd3)&&(y_pos >= snack_y[1]-10'd3 && y_pos <= snack_y[1]+10'd3)&&length > 4'd1)||
- ((x_pos >= snack_x[2]-10'd3 && x_pos <= snack_x[2]+10'd3)&&(y_pos >= snack_y[2]-10'd3 && y_pos <= snack_y[2]+10'd3)&&length > 4'd2)||
- ((x_pos >= snack_x[3]-10'd3 && x_pos <= snack_x[3]+10'd3)&&(y_pos >= snack_y[3]-10'd3 && y_pos <= snack_y[3]+10'd3)&&length > 4'd3)||
- ((x_pos >= snack_x[4]-10'd3 && x_pos <= snack_x[4]+10'd3)&&(y_pos >= snack_y[4]-10'd3 && y_pos <= snack_y[4]+10'd3)&&length > 4'd4)||
- ((x_pos >= snack_x[5]-10'd3 && x_pos <= snack_x[5]+10'd3)&&(y_pos >= snack_y[5]-10'd3 && y_pos <= snack_y[5]+10'd3)&&length > 4'd5)||
- ((x_pos >= snack_x[6]-10'd3 && x_pos <= snack_x[6]+10'd3)&&(y_pos >= snack_y[6]-10'd3 && y_pos <= snack_y[6]+10'd3)&&length > 4'd6)||
- ((x_pos >= snack_x[7]-10'd3 && x_pos <= snack_x[7]+10'd3)&&(y_pos >= snack_y[7]-10'd3 && y_pos <= snack_y[7]+10'd3)&&length > 4'd7)||
- ((x_pos >= snack_x[8]-10'd3 && x_pos <= snack_x[8]+10'd3)&&(y_pos >= snack_y[8]-10'd3 && y_pos <= snack_y[8]+10'd3)&&length > 4'd8)||
- ((x_pos >= snack_x[9]-10'd3 && x_pos <= snack_x[9]+10'd3)&&(y_pos >= snack_y[9]-10'd3 && y_pos <= snack_y[9]+10'd3)&&length > 4'd9)||
- ((x_pos >= snack_x[10]-10'd3 && x_pos <= snack_x[10]+10'd3)&&(y_pos >= snack_y[10]-10'd3 && y_pos <= snack_y[10]+10'd3)&&length > 4'd10)||
- ((x_pos >= snack_x[11]-10'd3 && x_pos <= snack_x[11]+10'd3)&&(y_pos >= snack_y[11]-10'd3 && y_pos <= snack_y[11]+10'd3)&&length > 4'd11);
- */
- endmodule
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。