关于fpga:基于FPGA的贪吃蛇游戏设计使用VGA显示屏与蓝牙外设模块

54次阅读

共计 29467 个字符,预计需要花费 74 分钟才能阅读完成。

阐明:本文章由自己于 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 难度抉择状态 /01 DOWN == 1
游戏筹备开始状态 /10 switch_J15 == 1
难度抉择状态 /01 初始状态 /00 !rst_n
初始状态 /00 UP == 1
游戏筹备开始状态 /10 初始状态 /00 !rst_n
游戏中状态 /11 RIGHT == 1
游戏中状态 /11 初始状态 /00 !rst_n
游戏筹备开始状态 /10 switch_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
中等 /01 RIGHT == 1
中等 /01 简略 /10 !rst_n
简略 /10 LEFT == 1
艰难 /00 RIGHT == 1
艰难 /00 简略 /10 !rst_n
中等 /01 LEFT == 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/00001 Stop/00001 !rst_n or general_state == game_start or general_state != gaming or flag_isdead == 1
Up/00010 UP == 1 and general_state == gaming
Down/00100 DOWN == 1 and general_state == gaming
Right/10000 RIGHT == 1 and general_state == gaming
Up/00010 Left/01000 LEFT == 1
Right/10000 RIGHT == 1
Down/00100 Left/01000 LEFT == 1
Right/10000 RIGHT == 1
Left/01000 Up/00010 UP == 1
Down/00100 DOWN == 1
Right/10000 Up/00010 UP == 1
Down/00100 DOWN == 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 显示对贪吃蛇游戏的设计

正文完
 0