基于Verilog的贪吃蛇小游戏设计(附代码)

    本文介绍基于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 贪吃蛇工程结构层次

1. 顶层模块snack

    顶层模块主要规定了工程对外的各类接口,以及各个功能模块之间的关系。在本文中,顶层模块调用了三个功能模块(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

 

2. random_box模块

    该模块主要负责随机小盒子的生成与更新。盒子的坐标来自于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

2.1 random模块

    该模块采用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

2.2 box_create模块

    该模块主要用于创建和更新随机小盒子的中心坐标。更新信号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

3. snack_control模块

    该模块主要实现了以下几个功能:蛇头移动方向控制、蛇体寄存器、死亡检测、长度检测、蛇体渲染等四部分构成。蛇头移动方向控制由按键检测模块和方向寄存器修正规则代码组成;蛇体寄存器由蛇头移动更新状态机、蛇身移位寄存器组组成;长度检测包含长度记录、增长控制、随机小盒子更新信号生成三个功能;蛇体渲染为根据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

   

  • 30
    点赞
  • 250
    收藏
    觉得还不错? 一键收藏
  • 74
    评论
评论 74
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值