阐明:本文章由自己于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模块:
//分频25Malways @(posedge clk or negedge rst_n)begin if(!rst_n) clk_50M <= 1'b0 ; else clk_50M <= ~clk_50M ; endalways @(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 endend
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 endend
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));//xalways@(posedge clk or negedge rst_n)if (!rst_n) rand_x <= 0;elsebegin if (snake_coincide_rand == 1) rand_x <= 0; else begin if (rand_x == 31) rand_x <= 0; else rand_x <= rand_x+1; endend//yalways@(posedge clk or negedge rst_n)if (!rst_n) rand_y <= 0;elsebegin 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; endendendmodule
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;endelse 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;endelse 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];endelse 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;//蛇初始长度为3parameter 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.8selse if (difficulty_state == easy && general_state == diff_menu) interval <= 2000_0000;//0.8selse if (difficulty_state == mid && general_state == diff_menu) interval <= 1000_0000;//0.4selse if (difficulty_state == hard && general_state == diff_menu) interval <= 500_0000;//0.2selse 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显示对贪吃蛇游戏的设计