1
头图

说明:本文章由本人于2022.1.18发布于CSDN平台,后续考虑主要使用思否继续博客撰写与技术学习,即此篇系本人跨平台迁移,并非抄袭,CSDN平台上原文已删除,特此说明。

一、引言

本工程是校内课程----数字逻辑的大作业,完成程度个人估计在70%左右,仍存在些许bug(详见本文章末尾部分),希望能够对读者起到一定的帮助

同时,本工程核心代码部分主要参考的是另一篇文章,若有相似需求可先参考该文章,讲解非常详细:
基于FPGA的VGA显示对贪吃蛇游戏的设计

二、环境描述

本工程使用的是Xilinx公司的NEXYS4 DDR开发板,输入输出的各管脚标准均参考该开发板官方文档;

使用的开发语言为verilog HDL

开发平台为vivado 2016(注:由于学校要求使用该版本,因此没有使用更新版本的vivado,但因为并没有使用到ROM、RAM等进阶功能,因此差别应该不会太大);

在此基础上使用两个外设部件,分别为vga显示屏与蓝牙模块,其中:在本工程中使用640*480@60Hz的参数型号,若使用其它分辨率需自行修改vga模块代码的参数。

三、总工程设计


总系统逻辑如上图所示,主要是通过开发板上自带按钮与开关,以及蓝牙模块作为输入,vga显示屏与开发板上的灯作为输出,核心部分为三个状态机(总模式选择系统、难度选择系统、蛇朝向系统)以及蛇移动部分。

四、各子系统及部分源码

4.1. vga模块基本使用

vga时序图如下:


注:若对于vga相关时序及代码调用方法较为熟悉,则可跳过此部分。
vga模块作为外设来说其时序标准事实上相对较为简单,搞清楚上两张图中的时序标准就能够正确使用。对于初学者而言,建议先实现 彩条的显示 以及 移动方块的显示 两个功能,在搞明白这两个功能实现的过程以后,实现贪吃蛇的vga显示就没有问题了。如何实现以上两个部分请自行查阅网上相关资料,应该说比较好找。

4.1.1. vga接口

首先,入手一个新的外设模块个人认为可以先尝试理解该模块与外部模块如何连接,即该模块的输入输出部分。vga模块总共有以下的输入输出:

    input clk,   // 系统时钟
    input rst_n, // 系统复位
    output reg [3:0] O_red, // VGA红色分量
    output reg [3:0] O_green, // VGA绿色分量
    output reg [3:0] O_blue, // VGA蓝色分量
    output O_hs, // VGA行同步信号
    output O_vs // VGA场同步信号

1)clk:clk是传进vga模块的时钟,而这个时钟频率需要根据相应的分辨率型号而固定,例如本工程使用的640*480@25Hz,必须使用25mHz的时钟频率。可以参考上一张图中时钟这一栏。而在verilog代码编写过程中,可以使用vivado自带的时钟分频功能得到相应的时钟(请自行查阅资料),也可以自己实现分频(仅限于目标时钟频率是系统时钟频率的因数),本工程系统时钟为100mHz,因此在顶层模块中自行实现了分频,再传进vga模块:

//分频25M
always @(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        clk_50M   <=  1'b0        ;
    else
        clk_50M   <=  ~clk_50M  ;     
end

always @(posedge clk_50M or negedge rst_n)
begin
    if(!rst_n)
        clk_25M   <=  1'b0        ;
    else
        clk_25M   <=  ~clk_25M  ;     
end

2)rst_n:rst_n即为复位键,较为简单,直接跳过

3)O_red、O_green、O_blue:这三个变量用来存储像素点的颜色信息。对于不同vga型号,其位数也不同,常见的有16位、12位、8位等等,本工程中使用的是12位,即红绿蓝各四位。

此处需要注意,这三个变量所存储的颜色是代表当前像素点的颜色信息,需要配合行同步计数器与场同步计数器使用,才能对目标像素点进行正确的颜色输出。

4)O_hs、O_vs:代表当前扫描状态的标志位。理解这两个变量之前需要知道vga的工作原理。在此默认读者已经掌握,其实也非常简单,即一个像素一个像素自左向右扫描,一行扫完以后移动到下一行第一个像素,一帧扫描完以后移动到第一行第一个像素,因此需要用两个标志位记录当前扫描状态,即HSYNC、VSYNC;以及两个计数器记录当前扫描位置,即行同步计数器与场同步计数器。此后部分用O_hs与O_vs来分别代表HSYNC与VSYNC的标志位, 用R_h_cnt、R_v_cnt分别代表行同步计数器与场同步计数器。O_hs与O_vs在后续部分中实际上并没有使用到,但是这两个状态位必须输出,且需要根据对应开发板及vga模块进行正确的引脚连接。(否则显示屏将会显示无信号)

 在了解这一点后,根据上面时序波形图可以用以下的方式进行实现:

// 分辨率为640*480时行时序各个参数定义
parameter   C_H_SYNC_PULSE      =   96  , 
            C_H_BACK_PORCH      =   48  ,
            C_H_ACTIVE_TIME     =   640 ,
            C_H_FRONT_PORCH     =   16  ,
            C_H_LINE_PERIOD     =   800 ;

// 分辨率为640*480时场时序各个参数定义               
parameter   C_V_SYNC_PULSE      =   2   , 
            C_V_BACK_PORCH      =   33  ,
            C_V_ACTIVE_TIME     =   480 ,
            C_V_FRONT_PORCH     =   10  ,
            C_V_FRAME_PERIOD    =   525 ;

reg [11:0]      R_h_cnt         ; // 行时序计数器
reg [11:0]      R_v_cnt         ; // 列时序计数器

always @(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        R_h_cnt <=  12'd0   ;
    else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1)
        R_h_cnt <=  12'd0   ;
    else
        R_h_cnt <=  R_h_cnt + 1'b1  ;                
end                

assign O_hs =   (R_h_cnt < C_H_SYNC_PULSE) ? 1'b0 : 1'b1    ; 

always @(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        R_v_cnt <=  12'd0   ;
    else if(R_v_cnt == C_V_FRAME_PERIOD - 1'b1)
        R_v_cnt <=  12'd0   ;
    else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1)
        R_v_cnt <=  R_v_cnt + 1'b1  ;
    else
        R_v_cnt <=  R_v_cnt ;                        
end                

assign O_vs =   (R_v_cnt < C_V_SYNC_PULSE) ? 1'b0 : 1'b1    ; 

O_hs与O_vs后面只需连接到顶层模块并输出即可。

到此,vga相关接口了解完毕,接着就可以进入到显示图像的部分。

4.1.2 vga图像显示

当完全理解以上部分以后,就可以着手实现显示图像了。核心思想是在有效区域进行颜色的输出。何为有效区域?vga在工作时,并不会立即把图像显示出来,而是在显示图像之前与之后都留有类似于准备工作一样的步骤,对应到上面的时序波形图中,有效区域就是指active video time,back porch与front porch就是指准备工作。如果将O_h_cnt比作横坐标,O_v_cnt比作纵坐标,那么就有下面这张图:

两个有色矩形的相交部分就是一帧图像显示的有效部分。换句话说,当O_h_cnt与O_v_cnt都处在有效区域时,就可以向颜色变量输出相关的颜色信息,进行颜色输出。对于640*480来说,实际有效的显示部分仅是行同步计数器处于(96+48,96+48+640)且场同步计数器处于(2+33,2+33+480)的部分。所以,图像的横纵坐标通过一定的平移就能够对应到行同步与场同步计数器。

而对于其它显示后延、显示前沿、同步阶段等等,不用考虑太多,只需要在非有效期间,将输出颜色置黑就可以了。

到此,已经可以使用vga的基本图像显示功能,而如果需要显示字符或者已有的图片,则需要使用到IP核的调用,本工程中并未涉及,有需要请自行查阅网上相关资料。

4.2. 状态机的设计

本工程中设计实现了三个状态机,分别代表:总模式选择系统、难度选择系统、蛇朝向系统

说明:在状态机实现具体过程中,本工程使用了开发板上自带的按钮(形如UP,DOWN)与开关(形如switch_J1)、蓝牙端口传进来的四个上下左右按键(形如RIGHT_bluetooth)

4.2.1. 总模式选择状态机

ASM框图与状态转移表如下:

PS/状态编码NS/状态编码转换条件
初始状态/00难度选择状态/01DOWN == 1
游戏准备开始状态/10switch_J15 == 1
难度选择状态/01初始状态/00!rst_n
初始状态/00UP == 1
游戏准备开始状态/10初始状态/00!rst_n
游戏中状态/11RIGHT == 1
游戏中状态/11初始状态/00!rst_n
游戏准备开始状态/10switch_M13 == 1

设定四个总状态,分别为游戏初始状态(全黑屏),难度选择状态(出现三个矩形框,代表三种难度),游戏初始状态(屏幕中出现固定不动的蛇),游戏中状态(蛇开始移动,可吃食物变长,撞墙与撞自身判定为死亡)。

源码如下:

//总状态机
always@(posedge clk or negedge rst_n)
begin
    if (!rst_n) begin
        general_state <= start;
    end
    else begin
        case (general_state) 
        start: 
        if (up == 1'b0 && down == 1'b1 && left == 1'b0 && right == 1'b0) begin
            general_state <= diff_menu;
        end
        else if (switch_J15 == 1'b1) begin
            general_state <= game_start;
        end
        else begin
            general_state <= start;
        end

        diff_menu:
        if (up == 1'b1 && down == 1'b0 && left == 1'b0 && right == 1'b0) begin
            general_state <= start;
        end
        else begin
            general_state <= general_state;
        end

        game_start:
        if (up == 1'b0 && down == 1'b0 && left == 1'b0 && right == 1'b1) begin
            //move_state <= face_right;
            general_state <= gaming;
        end
        else begin
            general_state <= game_start;
        end

        gaming:
        if (switch_M13 == 1'b1) begin
            general_state <= game_start;
        end
        else begin
            general_state <= gaming;
        end

        default: general_state <= start;
        endcase
    end
end

4.2.2. 难度选择状态机

ASM框图与状态转移表如下:

PS/状态编码NS/状态编码转换条件
简单/10简单/10!rst_n
中等/01RIGHT == 1
中等/01简单/10!rst_n
简单/10LEFT == 1
困难/00RIGHT == 1
困难/00简单/10!rst_n
中等/01LEFT == 1

 难度选择状态机中,通过版上自带按钮进行难度的切换,而在此状态机中的难度会改变后续游戏中蛇在每一格的停留时间。

源码如下:

//难度选择状态机
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        difficulty_state <= easy;
    end
    else if(general_state == diff_menu) begin
        case (difficulty_state)
        easy: 
        if (up == 1'b0 && down == 1'b0 && left == 1'b0 && right == 1'b1) begin
            difficulty_state <= mid;
        end
        else begin
            difficulty_state <= easy;
        end
        
        mid:
        if (up == 1'b0 && down == 1'b0 && left == 1'b1 && right == 1'b0) begin
            difficulty_state <= easy;
        end
        else if(up == 1'b0 && down == 1'b0 && left == 1'b0 && right == 1'b1) begin
            difficulty_state <= hard;
        end
        else begin
            difficulty_state <= mid;
        end

        hard:
        if (up == 1'b0 && down == 1'b0 && left == 1'b1 && right == 1'b0) begin
            difficulty_state <= mid;
        end
        else begin
            difficulty_state <= hard;
        end
        endcase
    end
end

4.2.3. 蛇朝向状态机

ASM框图与状态转移表如下:

PS/状态编码NS/状态编码转换条件
Stop/00001Stop/00001!rst_n or general_state == game_start or general_state != gaming or flag_isdead == 1
Up/00010UP == 1 and general_state == gaming
Down/00100DOWN == 1 and general_state == gaming
Right/10000RIGHT == 1 and general_state == gaming
Up/00010Left/01000LEFT == 1
Right/10000RIGHT == 1
Down/00100Left/01000LEFT == 1
Right/10000RIGHT == 1
Left/01000Up/00010UP == 1
Down/00100DOWN == 1
Right/10000Up/00010UP == 1
Down/00100DOWN == 1

该状态机用于游戏中状态时改变蛇的朝向,进而改变蛇的移动。共有上下左右停止五种状态,游戏准备状态以及死亡状态默认蛇为停止。

源码如下:

//蛇移动朝向状态机
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
    begin
        move_state<=stop;
    end

    else if(general_state==game_start)
    begin
        move_state<=stop;
    end

    else if(flag_isdead == 1)
    begin
        move_state<=stop;
    end

    else if(general_state==gaming)
    begin
        case(move_state)
        stop:
        if(RIGHT_bluetooth == 1'b1 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_right;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b1 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= stop;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b1 && UP_bluetooth == 1'b0)
            move_state <= face_down;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b1)
            move_state <= face_up;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= stop;
        else 
            move_state <= stop;

        face_left:
        if(RIGHT_bluetooth == 1'b1 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_left;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b1 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_left;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b1 && UP_bluetooth == 1'b0)
            move_state <= face_down;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b1)
            move_state <= face_up;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_left;
        else 
            move_state <= face_left;

        face_right:
        if(RIGHT_bluetooth == 1'b1 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_right;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b1 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_right;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b1 && UP_bluetooth == 1'b0)
            move_state <= face_down;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b1)
            move_state <= face_up;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_right;
        else 
            move_state <= face_right;

        face_up:
        if(RIGHT_bluetooth == 1'b1 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_right;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b1 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_left;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b1 && UP_bluetooth == 1'b0)
            move_state <= face_up;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b1)
            move_state <= face_up;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_up;
        else 
            move_state <= face_up;

        face_down:
        if(RIGHT_bluetooth == 1'b1 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_right;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b1 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_left;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b1 && UP_bluetooth == 1'b0)
            move_state <= face_down;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b1)
            move_state <= face_down;
        else if(RIGHT_bluetooth == 1'b0 && LEFT_bluetooth == 1'b0 && DOWN_bluetooth == 1'b0 && UP_bluetooth == 1'b0)
            move_state <= face_down;
        else 
            move_state <= face_down;

        default:
            move_state <= stop;

        endcase
    end
    
end

4.3. 其余子模块

4.3.1. 伪随机数模块

该模块是用来生成两个伪随机数,当代表需要生成新的食物的标志位为高电平时,将这两个随机数赋值给新的食物的横纵坐标,完成食物的生成。

生成原理本质上与行、场同步计数器相同。

同时,需要传进去蛇身坐标与蛇长信息,判断新生成坐标是否与当前蛇身项重合。而本处存在一个未解决bug,根本问题是产生重合以后的处理并不妥当,在第六部分会具体描述

代码如下:

module random_xy(
    input clk,                
    input rst_n,
    input [199:0] snake_x,
    input [199:0] snake_y,
    input [9:0] snake_length,
    output reg [4:0] rand_x,                
    output reg [4:0] rand_y
);
parameter square_length = 20;
parameter square_width = 24;
wire snake_coincide_rand;
assign snake_coincide_rand = (
(rand_x * square_length == snake_x[9:0] && rand_y * square_width == snake_y[9:0]) ||
(rand_x * square_length == snake_x[19:10] && rand_y * square_width == snake_y[19:10]) ||
(rand_x * square_length == snake_x[29:20] && rand_y * square_width == snake_y[29:20]) ||
(rand_x * square_length == snake_x[39:30] && rand_y * square_width == snake_y[39:30] && snake_length >= 4) ||
(rand_x * square_length == snake_x[49:40] && rand_y * square_width == snake_y[49:40] && snake_length >= 5) ||
(rand_x * square_length == snake_x[59:50] && rand_y * square_width == snake_y[59:50] && snake_length >= 6) ||
(rand_x * square_length == snake_x[69:60] && rand_y * square_width == snake_y[69:60] && snake_length >= 7) ||
(rand_x * square_length == snake_x[79:70] && rand_y * square_width == snake_y[79:70] && snake_length >= 8) ||
(rand_x * square_length == snake_x[89:80] && rand_y * square_width == snake_y[89:80] && snake_length >= 9) ||
(rand_x * square_length == snake_x[99:90] && rand_y * square_width == snake_y[99:90] && snake_length >= 10) ||
(rand_x * square_length == snake_x[109:100] && rand_y * square_width == snake_y[109:100] && snake_length >= 11) ||
(rand_x * square_length == snake_x[119:110] && rand_y * square_width == snake_y[119:110] && snake_length >= 12) ||
(rand_x * square_length == snake_x[129:120] && rand_y * square_width == snake_y[129:120] && snake_length >= 13) ||
(rand_x * square_length == snake_x[139:130] && rand_y * square_width == snake_y[139:130] && snake_length >= 14) ||
(rand_x * square_length == snake_x[149:140] && rand_y * square_width == snake_y[149:140] && snake_length >= 15) ||
(rand_x * square_length == snake_x[159:150] && rand_y * square_width == snake_y[159:150] && snake_length >= 16) ||
(rand_x * square_length == snake_x[169:160] && rand_y * square_width == snake_y[169:160] && snake_length >= 17) ||
(rand_x * square_length == snake_x[179:170] && rand_y * square_width == snake_y[179:170] && snake_length >= 18) ||
(rand_x * square_length == snake_x[189:180] && rand_y * square_width == snake_y[189:180] && snake_length >= 19) ||
(rand_x * square_length == snake_x[199:190] && rand_y * square_width == snake_y[199:190] && snake_length == 20)
);
//x
always@(posedge clk or negedge rst_n)
if (!rst_n)
    rand_x <= 0;
else
begin
    if (snake_coincide_rand == 1)
        rand_x <= 0;
    else
    begin
        if (rand_x == 31)
            rand_x <= 0;
        else
            rand_x <= rand_x+1;
    end

end
//y
always@(posedge clk or negedge rst_n)
if (!rst_n)
    rand_y <= 0;
else
begin
    if (snake_coincide_rand == 1)
        rand_y <= 0;
    else
    begin
        if (rand_y == 19 && rand_x == 31)
            rand_y <= 0;
        else if (rand_x == 31)
            rand_y <= rand_y+1;  
    end
end

endmodule 

4.3.2. 蛇移动及其vga显示模块

本部分为本工程核心。将进行详细阐述:

4.3.2.1 总设计思路

首先对于游戏过程中需要实现的功能进行构思,包括但不局限于地图的设计(每一格代表一个单位),蛇的存储、移动、变长、死亡,蛇的移动速度(与难度状态机相关联),判断蛇是否死亡、是否吃到食物,是否需要生成新的食物,实时显示蛇身以及食物。

4.3.2.2 具体实现思路

1)对于地图而言,总分辨率为640*480,也就是总共有640*480个像素点,考虑到美观性与格子的大小,将每一格长度设置为20像素,宽度设置为24像素,并将每一格的左上角的像素点作为这一格的坐标:

parameter square_length = 20;
parameter square_width = 24;

2)对于蛇身的存储,采用的思路是设定一个蛇长的上限(本工程设定为20),使用一个寄存器存储蛇当前长度。由于蛇身横纵坐标最多到(640 - 20 = 640)与(480 - 24 = 456),因此,每一格蛇身坐标用两个10位寄存器就可以存储。所以总蛇身用两个200位寄存器就能存储。同时,后续生成伪随机数的模块需要用到蛇身坐标以及蛇的长度,因此声明为线网类型而非寄存器类型。

output reg [199:0] snake_x,
output reg [199:0] snake_y,
output reg [9:0] snake_length,

考虑到verilog不同于C/C++,没有链表这样的简便的工具,因此设计整个游戏过程中蛇的20格都在移动,只有蛇长到达指定值以后才会把对应的蛇身格子显示出来。而蛇身的移动实现则是将蛇头与蛇头以外蛇身分开来。蛇头坐标根据方向状态机移动一格,而其余蛇身坐标的值则赋值给下一格蛇身,进而实现移动。

//蛇身的移动
always@(posedge clk or negedge rst_n)
if(!rst_n) 
begin
    snake_x[9:0] <= headx_init;                     snake_y[9:0] <= heady_init;

    snake_x[19:10] <= headx_init - square_length;   snake_y[19:10] = heady_init;
    snake_x[29:20] <= headx_init - 2*square_length; snake_y[29:20] <= heady_init;
    snake_x[39:30] <= 0; snake_y[39:30] <= 0;
    snake_x[49:40] <= 0; snake_y[49:40] <= 0;
    snake_x[59:50] <= 0; snake_y[59:50] <= 0;
    snake_x[69:60] <= 0; snake_y[69:60] <= 0;
    snake_x[79:70] <= 0; snake_y[79:70] <= 0;
    snake_x[89:80] <= 0; snake_y[89:80] <= 0;
    snake_x[99:90] <= 0; snake_y[99:90] <= 0;
    snake_x[109:100] <= 0; snake_y[109:100] <= 0;
    snake_x[119:110] <= 0; snake_y[119:110] <= 0;
    snake_x[129:120] <= 0; snake_y[129:120] <= 0;
    snake_x[139:130] <= 0; snake_y[139:130] <= 0;
    snake_x[149:140] <= 0; snake_y[149:140] <= 0;
    snake_x[159:150] <= 0; snake_y[159:150] <= 0;
    snake_x[169:160] <= 0; snake_y[169:160] <= 0;
    snake_x[179:170] <= 0; snake_y[179:170] <= 0;
    snake_x[189:180] <= 0; snake_y[189:180] <= 0;
    snake_x[199:190] <= 0; snake_y[199:190] <= 0;
end
else if (general_state == game_start) 
begin
    snake_x[9:0] <= headx_init;                     snake_y[9:0] <= heady_init;

    snake_x[19:10] <= headx_init - square_length;   snake_y[19:10] = heady_init;
    snake_x[29:20] <= headx_init - 2*square_length; snake_y[29:20] <= heady_init;
    snake_x[39:30] <= 0; snake_y[39:30] <= 0;
    snake_x[49:40] <= 0; snake_y[49:40] <= 0;
    snake_x[59:50] <= 0; snake_y[59:50] <= 0;
    snake_x[69:60] <= 0; snake_y[69:60] <= 0;
    snake_x[79:70] <= 0; snake_y[79:70] <= 0;
    snake_x[89:80] <= 0; snake_y[89:80] <= 0;
    snake_x[99:90] <= 0; snake_y[99:90] <= 0;
    snake_x[109:100] <= 0; snake_y[109:100] <= 0;
    snake_x[119:110] <= 0; snake_y[119:110] <= 0;
    snake_x[129:120] <= 0; snake_y[129:120] <= 0;
    snake_x[139:130] <= 0; snake_y[139:130] <= 0;
    snake_x[149:140] <= 0; snake_y[149:140] <= 0;
    snake_x[159:150] <= 0; snake_y[159:150] <= 0;
    snake_x[169:160] <= 0; snake_y[169:160] <= 0;
    snake_x[179:170] <= 0; snake_y[179:170] <= 0;
    snake_x[189:180] <= 0; snake_y[189:180] <= 0;
    snake_x[199:190] <= 0; snake_y[199:190] <= 0;
end
else if(move_state == stop)
begin
    snake_x[9:0] <= snake_x[9:0]; 
    snake_y[9:0] <= snake_y[9:0];
    snake_x[19:10] <= snake_x[19:10]; 
    snake_y[19:10] <= snake_y[19:10];
    snake_x[29:20] <= snake_x[29:20]; 
    snake_y[29:20] <= snake_y[29:20];
    snake_x[39:30] <= snake_x[39:30]; 
    snake_y[39:30] <= snake_y[39:30];
    snake_x[49:40] <= snake_x[49:40]; 
    snake_y[49:40] <= snake_y[49:40];
    snake_x[59:50] <= snake_x[59:50]; 
    snake_y[59:50] <= snake_y[59:50];
    snake_x[69:60] <= snake_x[69:60]; 
    snake_y[69:60] <= snake_y[69:60];
    snake_x[79:70] <= snake_x[79:70]; 
    snake_y[79:70] <= snake_y[79:70];
    snake_x[89:80] <= snake_x[89:80]; 
    snake_y[89:80] <= snake_y[89:80];
    snake_x[99:90] <= snake_x[99:90]; 
    snake_y[99:90] <= snake_y[99:90];
    snake_x[109:100] <= snake_x[109:100]; 
    snake_y[109:100] <= snake_y[109:100];
    snake_x[119:110] <= snake_x[119:110]; 
    snake_y[119:110] <= snake_y[119:110];
    snake_x[129:120] <= snake_x[129:120]; 
    snake_y[129:120] <= snake_y[129:120];
    snake_x[139:130] <= snake_x[139:130]; 
    snake_y[139:130] <= snake_y[139:130];
    snake_x[149:140] <= snake_x[149:140]; 
    snake_y[149:140] <= snake_y[149:140];
    snake_x[159:150] <= snake_x[159:150]; 
    snake_y[159:150] <= snake_y[159:150];
    snake_x[169:160] <= snake_x[169:160];
    snake_y[169:160] <= snake_y[169:160];
    snake_x[179:170] <= snake_x[179:170]; 
    snake_y[179:170] <= snake_y[179:170];
    snake_x[189:180] <= snake_x[189:180]; 
    snake_y[189:180] <= snake_y[189:180];
    snake_x[199:190] <= snake_x[199:190]; 
    snake_y[199:190] <= snake_y[199:190];
end
else if(flag_printnew == 1 && move_state != stop && general_state == gaming)
begin
    case (move_state)
        face_right:
        begin  
            snake_x[9:0] <= snake_x[9:0] + square_length;
            snake_y[9:0] <= snake_y[9:0];
        end

        face_left: 
        begin
            snake_x[9:0] <= snake_x[9:0] - square_length;
            snake_y[9:0] <= snake_y[9:0];
        end

        face_up:
        begin
            snake_y[9:0] <= snake_y[9:0] - square_width;
            snake_x[9:0] <= snake_x[9:0];
        end

        face_down:
        begin
            snake_y[9:0] <= snake_y[9:0] + square_width;
            snake_x[9:0] <= snake_x[9:0];
        end
    endcase
    snake_x[19:10] <= snake_x[9:0]; snake_y[19:10] <= snake_y[9:0];
    snake_x[29:20] <= snake_x[19:10]; snake_y[29:20] <= snake_y[19:10];
    snake_x[39:30] <= snake_x[29:20]; snake_y[39:30] <= snake_y[29:20];
    snake_x[49:40] <= snake_x[39:30]; snake_y[49:40] <= snake_y[39:30];
    snake_x[59:50] <= snake_x[49:40]; snake_y[59:50] <= snake_y[49:40];
    snake_x[69:60] <= snake_x[59:50]; snake_y[69:60] <= snake_y[59:50];
    snake_x[79:70] <= snake_x[69:60]; snake_y[79:70] <= snake_y[69:60];
    snake_x[89:80] <= snake_x[79:70]; snake_y[89:80] <= snake_y[79:70];
    snake_x[99:90] <= snake_x[89:80]; snake_y[99:90] <= snake_y[89:80];
    snake_x[109:100] <= snake_x[99:90]; snake_y[109:100] <= snake_y[99:90];
    snake_x[119:110] <= snake_x[109:100]; snake_y[119:110] <= snake_y[109:100];
    snake_x[129:120] <= snake_x[119:110]; snake_y[129:120] <= snake_y[119:110];
    snake_x[139:130] <= snake_x[129:120]; snake_y[139:130] <= snake_y[129:120];
    snake_x[149:140] <= snake_x[139:130]; snake_y[149:140] <= snake_y[139:130];
    snake_x[159:150] <= snake_x[149:140]; snake_y[159:150] <= snake_y[149:140];
    snake_x[169:160] <= snake_x[159:150]; snake_y[169:160] <= snake_y[159:150];
    snake_x[179:170] <= snake_x[169:160]; snake_y[179:170] <= snake_y[169:160];
    snake_x[189:180] <= snake_x[179:170]; snake_y[189:180] <= snake_y[179:170];
    snake_x[199:190] <= snake_x[189:180]; snake_y[199:190] <= snake_y[189:180];
end

为确保总状态切换时不会过于唐突,在初始界面(全黑屏)与游戏中状态之间添加了一个游戏准备状态,在此状态中,要对蛇的若干参数进行赋初值。此工程中设定,蛇初始长度为3,蛇头初始位置为(340,240)。

parameter length_init = 3;//蛇初始长度为3
parameter headx_init = 340;//蛇头初始x坐标
parameter heady_init = 240;//蛇头初始y坐标

3)对于蛇停留间隔,采用的思路是使用一个stay_cnt计数器,并用一个标志位flag_printnew来记录该计数器是否到达指定值。只有该标志位为高电平时,显示才会有所变动。

而指定值interval是通过难度选择状态机来改变的。

//难度控制
always@(posedge clk or negedge rst_n)
if (!rst_n)
    interval <= 2000_0000;//0.8s
else if (difficulty_state == easy && general_state == diff_menu)
    interval <= 2000_0000;//0.8s
else if (difficulty_state == mid && general_state == diff_menu)
    interval <= 1000_0000;//0.4s
else if (difficulty_state == hard && general_state == diff_menu)
    interval <= 500_0000;//0.2s
else
    interval <= interval;

//pause计数器
always@(posedge clk or negedge rst_n)
if (!rst_n)
    stay_cnt <= 0;
else if(general_state == game_start)
    stay_cnt <= 0;
else if (stay_cnt < interval - 1 && general_state == gaming && move_state != stop)
    stay_cnt <= stay_cnt + 1'b1;
else if (stay_cnt == interval - 1 && general_state == gaming && move_state != stop)
    stay_cnt <= 0;

assign flag_printnew = (stay_cnt == interval - 1)?1'b1:1'b0;

4)对于食物相关问题,当蛇头与食物坐标重合时,说明蛇吃到食物,将flag_food置高。

//判断食物是否应该刷新
always@(posedge clk or negedge rst_n)
if(!rst_n)
    flag_food <= 0;
else if(food_x == snake_x[9:0] && food_y == snake_y[9:0] && general_state == gaming)
    flag_food <= 1;
else 
    flag_food <= 0;

而一旦flag_food是高电平,在下一个时钟上升沿到来时,会将random_xy模块产生的两个随机数赋值给新事物的横纵坐标:

//食物横坐标
always@(posedge clk or negedge rst_n)
if(!rst_n)
    food_x <= 0;
else if(flag_food == 1)
    food_x <= random_x * square_length;
else if(general_state==game_start)
    food_x <= random_x * square_length;

//食物纵坐标
always@(posedge clk or negedge rst_n)
if(!rst_n)
    food_y <= 0;
else if(flag_food == 1)
    food_y <= random_y * square_width;
else if(general_state==game_start)
    food_y <= random_y * square_width;

5)对于判断是否死亡问题,核心就是蛇头是否撞墙、撞到其它蛇身。由于蛇身在设计上是20格均在移动,因此只有蛇长到达指定值且蛇头与该值对应的蛇身重合时才判定为蛇撞到自身。

//判断蛇是否死亡
always@(posedge clk or negedge rst_n)
if (!rst_n)
    flag_isdead <= 0;
else if(general_state == game_start)
    flag_isdead <= 0;
else if(flag_isdead == 0)
begin
    if (snake_x[9:0]<0 || snake_x[9:0]>640 - square_length || snake_y[9:0]<0 || snake_y[9:0]>480 - square_width)
        flag_isdead <= 1;
    
    else if (snake_x[9:0]==snake_x[19:10] && snake_y[9:0]==snake_y[19:10])
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[29:20] && snake_y[9:0]==snake_y[29:20])
        flag_isdead <= 1;
    
    else if (snake_x[9:0]==snake_x[39:30] && snake_y[9:0]==snake_y[39:30] && snake_length >= 4)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[49:40] && snake_y[9:0]==snake_y[49:40] && snake_length >= 5)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[59:50] && snake_y[9:0]==snake_y[59:50] && snake_length >= 6)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[69:60] && snake_y[9:0]==snake_y[69:60] && snake_length >= 7)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[79:70] && snake_y[9:0]==snake_y[79:70] && snake_length >= 8)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[89:80] && snake_y[9:0]==snake_y[89:80] && snake_length >= 9)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[99:90] && snake_y[9:0]==snake_y[99:90] && snake_length >= 10)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[109:100] && snake_y[9:0]==snake_y[109:100] && snake_length >= 11)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[119:110] && snake_y[9:0]==snake_y[119:110] && snake_length >= 12)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[129:120] && snake_y[9:0]==snake_y[129:120] && snake_length >= 13)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[139:130] && snake_y[9:0]==snake_y[139:130] && snake_length >= 14)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[149:140] && snake_y[9:0]==snake_y[149:140] && snake_length >= 15)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[159:150] && snake_y[9:0]==snake_y[159:150] && snake_length >= 16)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[169:160] && snake_y[9:0]==snake_y[169:160] && snake_length >= 17)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[179:170] && snake_y[9:0]==snake_y[179:170] && snake_length >= 18)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[189:180] && snake_y[9:0]==snake_y[189:180] && snake_length >= 19)
        flag_isdead <= 1;
    else if (snake_x[9:0]==snake_x[199:190] && snake_y[9:0]==snake_y[199:190] && snake_length >= 20)
        flag_isdead <= 1;
    else
        flag_isdead <= 0;
end

6)对于蛇身、食物显示。首先得确保是有效显示区域,否则显示全黑。W_active_flag为高电平代表处于有效显示区域。

parameter   h_before = C_H_SYNC_PULSE + C_H_BACK_PORCH;
parameter   h_after = C_H_LINE_PERIOD - C_H_FRONT_PORCH;
parameter   v_before = C_V_SYNC_PULSE + C_V_BACK_PORCH;
parameter   v_after = C_V_FRAME_PERIOD - C_V_FRONT_PORCH;

assign W_active_flag =  (R_h_cnt >= h_before)  &&
                        (R_h_cnt < h_after)  && 
                        (R_v_cnt >= v_before)  &&
                        (R_v_cnt < v_after);

其次是判断当前位置是否属于蛇身或者属于食物。分别用issnake与isfood两个标志位进行判断。

assign h_issnake = 
    //!flag_isdead &&
    (R_h_cnt >= h_before + snake_x[9:0] && R_h_cnt < h_before + snake_x[9:0] + square_length) ||
    (R_h_cnt >= h_before + snake_x[19:10] && R_h_cnt < h_before + snake_x[19:10] + square_length) ||
    (R_h_cnt >= h_before + snake_x[29:20] && R_h_cnt < h_before + snake_x[29:20] + square_length) ||
    (R_h_cnt >= h_before + snake_x[39:30] && R_h_cnt < h_before + snake_x[39:30] + square_length && snake_length >= 4) ||
    (R_h_cnt >= h_before + snake_x[49:40] && R_h_cnt < h_before + snake_x[49:40] + square_length && snake_length >= 5) ||
    (R_h_cnt >= h_before + snake_x[59:50] && R_h_cnt < h_before + snake_x[59:50] + square_length && snake_length >= 6) ||
    (R_h_cnt >= h_before + snake_x[69:60] && R_h_cnt < h_before + snake_x[69:60] + square_length && snake_length >= 7) ||
    (R_h_cnt >= h_before + snake_x[79:70] && R_h_cnt < h_before + snake_x[79:70] + square_length && snake_length >= 8) ||
    (R_h_cnt >= h_before + snake_x[89:80] && R_h_cnt < h_before + snake_x[89:80] + square_length && snake_length >= 9) ||
    (R_h_cnt >= h_before + snake_x[99:90] && R_h_cnt < h_before + snake_x[99:90] + square_length && snake_length >= 10) ||
    (R_h_cnt >= h_before + snake_x[109:100] && R_h_cnt < h_before + snake_x[109:100] + square_length && snake_length >= 11) ||
    (R_h_cnt >= h_before + snake_x[119:110] && R_h_cnt < h_before + snake_x[119:110] + square_length && snake_length >= 12) ||
    (R_h_cnt >= h_before + snake_x[129:120] && R_h_cnt < h_before + snake_x[129:120] + square_length && snake_length >= 13) || 
    (R_h_cnt >= h_before + snake_x[139:130] && R_h_cnt < h_before + snake_x[139:130] + square_length && snake_length >= 14) || 
    (R_h_cnt >= h_before + snake_x[149:140] && R_h_cnt < h_before + snake_x[149:140] + square_length && snake_length >= 15) || 
    (R_h_cnt >= h_before + snake_x[159:150] && R_h_cnt < h_before + snake_x[159:150] + square_length && snake_length >= 16) || 
    (R_h_cnt >= h_before + snake_x[169:160] && R_h_cnt < h_before + snake_x[169:160] + square_length && snake_length >= 17) || 
    (R_h_cnt >= h_before + snake_x[179:170] && R_h_cnt < h_before + snake_x[179:170] + square_length && snake_length >= 18) || 
    (R_h_cnt >= h_before + snake_x[189:180] && R_h_cnt < h_before + snake_x[189:180] + square_length && snake_length >= 19) || 
    (R_h_cnt >= h_before + snake_x[199:190] && R_h_cnt < h_before + snake_x[199:190] + square_length && snake_length == 20);

assign v_issnake = 
    //!flag_isdead &&
    (R_v_cnt >= v_before + snake_y[9:0] && R_v_cnt < v_before + snake_y[9:0] + square_width) ||
    (R_v_cnt >= v_before + snake_y[19:10] && R_v_cnt < v_before + snake_y[19:10] + square_width) ||
    (R_v_cnt >= v_before + snake_y[29:20] && R_v_cnt < v_before + snake_y[29:20] + square_width) ||
    (R_v_cnt >= v_before + snake_y[39:30] && R_v_cnt < v_before + snake_y[39:30] + square_width && snake_length >= 4) ||
    (R_v_cnt >= v_before + snake_y[49:40] && R_v_cnt < v_before + snake_y[49:40] + square_width && snake_length >= 5) ||
    (R_v_cnt >= v_before + snake_y[59:50] && R_v_cnt < v_before + snake_y[59:50] + square_width && snake_length >= 6) ||
    (R_v_cnt >= v_before + snake_y[69:60] && R_v_cnt < v_before + snake_y[69:60] + square_width && snake_length >= 7) ||
    (R_v_cnt >= v_before + snake_y[79:70] && R_v_cnt < v_before + snake_y[79:70] + square_width && snake_length >= 8) ||
    (R_v_cnt >= v_before + snake_y[89:80] && R_v_cnt < v_before + snake_y[89:80] + square_width && snake_length >= 9) ||
    (R_v_cnt >= v_before + snake_y[99:90] && R_v_cnt < v_before + snake_y[99:90] + square_width && snake_length >= 10) ||
    (R_v_cnt >= v_before + snake_y[109:100] && R_v_cnt < v_before + snake_y[109:100] + square_width && snake_length >= 11) ||
    (R_v_cnt >= v_before + snake_y[119:110] && R_v_cnt < v_before + snake_y[119:110] + square_width && snake_length >= 12) ||
    (R_v_cnt >= v_before + snake_y[129:120] && R_v_cnt < v_before + snake_y[129:120] + square_width && snake_length >= 13) || 
    (R_v_cnt >= v_before + snake_y[139:130] && R_v_cnt < v_before + snake_y[139:130] + square_width && snake_length >= 14) || 
    (R_v_cnt >= v_before + snake_y[149:140] && R_v_cnt < v_before + snake_y[149:140] + square_width && snake_length >= 15) || 
    (R_v_cnt >= v_before + snake_y[159:150] && R_v_cnt < v_before + snake_y[159:150] + square_width && snake_length >= 16) || 
    (R_v_cnt >= v_before + snake_y[169:160] && R_v_cnt < v_before + snake_y[169:160] + square_width && snake_length >= 17) || 
    (R_v_cnt >= v_before + snake_y[179:170] && R_v_cnt < v_before + snake_y[179:170] + square_width && snake_length >= 18) || 
    (R_v_cnt >= v_before + snake_y[189:180] && R_v_cnt < v_before + snake_y[189:180] + square_width && snake_length >= 19) || 
    (R_v_cnt >= v_before + snake_y[199:190] && R_v_cnt < v_before + snake_y[199:190] + square_width && snake_length == 20);

//判断是否属于蛇格子
assign issnake = h_issnake && v_issnake;
//判断是否属于食物格子
assign h_isfood = (R_h_cnt >= h_before + food_x) && (R_h_cnt < h_before + food_x + square_length);
assign v_isfood = (R_v_cnt >= v_before + food_y) && (R_v_cnt < v_before + food_y + square_width);
assign isfood = h_isfood && v_isfood;

而本工程的一个小设计是蛇身交替采用三种颜色,实现较为简单,仅添加三个变量issnake_green、issnake_blue、issnake_pink并根据蛇身格子序号模3的值分成三类即可。

最后是显示部分。显示部分总体采用case语句进行划分,根据不同状态显示不同内容。

always @(posedge clk or negedge rst_n)
begin
    if(!rst_n) 
    begin  
        O_red   <=  4'b0000;
        O_green <=  4'b0000;
        O_blue  <=  4'b0000; 
    end
    else if(W_active_flag)//有效显示区域
    begin
        case (general_state) //根据当前状态进行显示
        start://初始界面全黑
        begin
            O_red   <=  4'b0000;
            O_green <=  4'b0000;
            O_blue  <=  4'b0000; 
        end

        diff_menu://难度选择界面,白底,显示三个矩形框,黑色矩形框代表当前选中
        begin
            case (difficulty_state) 
            easy:
            begin
            if (R_v_cnt >= v_before + 220 && R_v_cnt < v_before + 260) 
            begin
                if (R_h_cnt >= h_before + 220 && R_h_cnt < h_before + 260) 
                begin
                    O_red   <=  4'b0000;
                    O_green <=  4'b0000;
                    O_blue  <=  4'b0000; 
                end//end of if11
                else if(R_h_cnt >= h_before + 300 && R_h_cnt < h_before + 340
                     || R_h_cnt >= h_before + 380 && R_h_cnt < h_before + 420) 
                begin
                    O_red   <=  4'b0000;
                    O_green <=  4'b0000;
                    O_blue  <=  4'b1111;
                end//end of else if11
                else begin
                    O_red   <=  4'b1111;
                    O_green <=  4'b1111;
                    O_blue  <=  4'b1111; 
                end//end of else1
            end//end of if21
            else
            begin
                O_red   <=  4'b1111;
                O_green <=  4'b1111;
                O_blue  <=  4'b1111;  
            end//end of else 21
            end//end of easy
            
            mid:
            begin
            if (R_v_cnt >= v_before + 220 && R_v_cnt < v_before + 260) 
            begin
                if (R_h_cnt >= h_before + 300 && R_h_cnt < h_before + 340) 
                begin
                    O_red   <=  4'b0000;
                    O_green <=  4'b0000;
                    O_blue  <=  4'b0000; 
                end//end of if11
                else if(R_h_cnt >= h_before + 220 && R_h_cnt < h_before + 260
                     || R_h_cnt >= h_before + 380 && R_h_cnt < h_before + 420) 
                begin
                    O_red   <=  4'b0000;
                    O_green <=  4'b0000;
                    O_blue  <=  4'b1111;
                end//end of else if11
                else begin
                    O_red   <=  4'b1111;
                    O_green <=  4'b1111;
                    O_blue  <=  4'b1111; 
                end//end of else1
            end//end of if21
            else   
            begin           
                O_red   <=  4'b1111;
                O_green <=  4'b1111;
                O_blue  <=  4'b1111;  
            end
            end//end of mid

            hard:
            begin
            if (R_v_cnt >= v_before + 220 && R_v_cnt < v_before + 260) 
            begin
                if (R_h_cnt >= h_before + 380 && R_h_cnt < h_before + 420) 
                begin
                    O_red   <=  4'b0000;
                    O_green <=  4'b0000;
                    O_blue  <=  4'b0000; 
                end
                else if(R_h_cnt >= h_before + 300 && R_h_cnt < h_before + 340
                     || R_h_cnt >= h_before + 220 && R_h_cnt < h_before + 260) 
                begin
                    O_red   <=  4'b0000;
                    O_green <=  4'b0000;
                    O_blue  <=  4'b1111;
                end
                else begin
                    O_red   <=  4'b1111;
                    O_green <=  4'b1111;
                    O_blue  <=  4'b1111; 
                end
            end
            else
            begin           
                O_red   <=  4'b1111;
                O_green <=  4'b1111;
                O_blue  <=  4'b1111;  
            end
            end//end of hard
            endcase
        end

        game_start://游戏准备界面,显示初始蛇身
        begin
            if
            (
            (R_h_cnt >= h_before + snake_x[9:0] && R_h_cnt < h_before + snake_x[9:0] + square_length
            && R_v_cnt >= v_before + snake_y[9:0] && R_v_cnt < v_before + snake_y[9:0] + square_width)
            ||(R_h_cnt >= h_before + snake_x[19:10] && R_h_cnt < h_before + snake_x[19:10] + square_length
            && R_v_cnt >= v_before + snake_y[19:10] && R_v_cnt < v_before + snake_y[19:10] + square_width)
            ||(R_h_cnt >= h_before + snake_x[29:20] && R_h_cnt < h_before + snake_x[29:20] + square_length
            && R_v_cnt >= v_before + snake_y[29:20] && R_v_cnt < v_before + snake_y[29:20] + square_width)
            ) 
            begin
                O_red   <=  4'b0000;
                O_green <=  4'b1111;
                O_blue  <=  4'b0000;
            end
            else
            begin
                O_red   <=  4'b1111;
                O_green <=  4'b1111;
                O_blue  <=  4'b1111;
            end
        end

        gaming://游戏中状态
        begin
            if (isfood == 1) //显示食物格子,为红色块
            begin
                O_red   <=  4'b1111;
                O_green <=  4'b0000;
                O_blue  <=  4'b0000;
            end
            
            else if(issnake_green == 1 && issnake == 1) //显示绿色蛇身色块
            begin
                O_red   <=  4'b0000;
                O_green <=  4'b1111;
                O_blue  <=  4'b0000;
            end
            else if(issnake_blue == 1 && issnake == 1) //显示蓝色蛇身色块
            begin
                O_red   <=  4'b0000;
                O_green <=  4'b0000;
                O_blue  <=  4'b1111;
            end
            else if(issnake_pink == 1 && issnake == 1) //显示粉色蛇身色块
            begin
                O_red   <=  4'b1111;
                O_green <=  4'b0000;
                O_blue  <=  4'b1111;
            end
            
            else 
            begin
                O_red   <=  4'b1111;
                O_green <=  4'b1111;
                O_blue  <=  4'b1111;
            end
        end

        default: 
        begin
            O_red   <=  4'b0000;
            O_green <=  4'b0000;
            O_blue  <=  4'b0000; 
        end
        endcase
        
    end
    
    else//处于非有效区域,全部置为黑色
        begin
            O_red   <=  4'b0000;
            O_green <=  4'b0000;
            O_blue  <=  4'b0000; 
        end   
    
end

4.3.3. 蓝牙模块及翻译模块

该两部分模块不进行详细阐述,实现的功能是通过手机上的蓝牙串口助手通过蓝牙模块向开发板发送按键信息,再通过翻译模块将键码翻译为对应的上下左右。

五、成果展示

1)初始界面(全黑屏)

2)难度选择界面(自左至右依次为   简单、中等、困难,黑色矩形代表选中)

3)游戏初始状态

4)游戏中

5)蛇撞墙死亡

6)向上拨M13开关,回到游戏初始状态,重新开始游戏

六、未解决问题

6.1. 蛇长bug

预期设计为蛇吃到食物后蛇身长会变长一格,但实际烧写下板测试发现一次会变长两格,推测蛇存储及移动模块存在漏洞或者时序逻辑上存在漏洞。
(update)原因:蛇头和食物接触占用两个时钟上升沿的时间

6.2. 食物伪随机坐标冲突

在random_xy模块,当蛇身与新的随机坐标重合时,采用的是将计数器置零的操作,但这种处理会导致新的食物必定生成于屏幕左上角,并且在吃到该位置食物后在左上角再次生成新食物,与蛇身产生冲突。

6.3. 蛇快速转弯时出现蛇身的不稳定显示

此问题出现于频繁快速切换蛇的方向之时。猜测是时序逻辑上的漏洞或者是使用的vga显示屏本身的问题。

参考文章:
基于FPGA的VGA显示对贪吃蛇游戏的设计


Hadlay
4 声望0 粉丝

引用和评论

0 条评论