Verilog——基于FPGA的贪吃蛇游戏(VGA显示)

最近在做Verilog程序课设,做了一个有关贪吃蛇的小游戏,写一篇博客来记录一下自己的创作过程。大部分的内容直接采用了设计报告的原话,有不足之处还望大家多多指教。对于重点:蛇身控制算法,我开始的想法是将每个格子的坐标输入到存储器中,但由于过于繁琐和笨拙,我改为:保留头部的完整数据(位置、方向),其他部分只保留方向数据,并在VGA模块里面直接对蛇身进行控制,但是这个方案有一个弊端:它按照蛇身顺序…

大家好,又见面了,我是你们的朋友全栈君。

最近在做Verilog程序课设,做了一个有关贪吃蛇的小游戏,写一篇博客来记录一下自己的创作过程。大部分的内容直接采用了设计报告的原话,有不足之处还望大家多多指教。

对于重点:蛇身控制算法,我开始的想法是将每个格子的坐标输入到存储器中,但由于过于繁琐和笨拙,我改为:保留头部的完整数据(位置、方向),其他部分只保留方向数据,并在VGA模块里面直接对蛇身进行控制,但是这个方案有一个弊端:它按照蛇身顺序刷新图像,每一帧图像只能刷新一个格子,时序存在问题并且刷新频率过慢,放弃了这个方案。
最终,将蛇身模块单独提出,各个模块协同工作,有效解决了时序问题和刷新问题。蛇身控制上,只控制蛇头,其他部位随头联动,完成了最终设计。

开发板采用了Xilinx公司的Basys3主板。

概述

小游戏主要分为以下几个模块:顶层模块、VGA显示模块、蛇身控制模块、苹果控制模块。
系统主要分为顶层模块、VGA显示模块、蛇身控制模块、苹果控制模块。顶层模块是与FPGA开发板之间的硬件管脚接口,并且控制其他模块有条不紊地运行。

游戏进程控制模块:game_control

游戏控制模块代码定义如下:

module game_control(
clk,rst_n,centerbt,strike_itself,strike_wall, game_status
);
    input clk,rst_n,centerbt,strike_itself,strike_wall;
    output reg[1:0] game_status;
  • Clk为输入时钟,频率为100MHz,rst_n为复位状态开关,用于激活和关闭游戏界面。
  • 整个模块由一个状态机组成,次态状态赋值给游戏运行状态status,运行再将状态赋值给输出game_status,用于操控整个系统的工作状态。
  • 游戏共有3个主要进程:Idle(游戏待机、死亡)、Start(游戏进行中),Pause(游戏暂停),centerbt是切换状态的按键,游戏的状态主要由这个按键来控制。
  • strike_itself,strike_wall用于输入蛇是否撞到了墙或者自己,是另外两个控制游戏状态的重要输入,有0和1两个值,若两种情况发生任何一种,游戏进程为死亡,次态状态回到Idle。
always@(posedge clk or posedge centerbt)
        begin
        if(centerbt && rst_n)
            begin
            case(status)
                Idle: begin
                    if(centerbt) begin next_status=Start; end
                    else begin next_status=Start; end
                    end
                Start: begin
                    if(centerbt) begin next_status=Pause; end
                    else begin next_status=Start; end
                    end
                Pause: begin
                    if(centerbt) begin next_status=Start; end
                    else begin next_status=Start; end
                    end
            endcase
            end
        else if(strike_itself || strike_wall) 
begin next_status=Idle;end
End

VGA显示模块:VGA

VGA转换模块代码定义如下:

module VGA(
game_status,clk,rst_n,snake,apple,upbt,downbt,rightbt,leftbt,
x_pos,y_pos,vga_r,vga_g,vga_b,hsync,vsync
);
input clk,rst_n,snake,apple;
input wire upbt,downbt,rightbt,leftbt;
input wire[1:0] game_status;
output reg[9:0] x_pos,y_pos;
output reg[3:0] vga_r, vga_g, vga_b;	
output hsync,vsync;
  • VGA显示模块用于显示系统与用户的交互界面,通过VGA扫描,传输数据,把界面显示在显示器上,为了适应大多数显示器,使用了640480的分辨率,77像素为一个方格。
  • VGA驱动原理:
    VGA驱动显示器显示是通过逐行扫描实现的,Basys3的VGA接口共有14条,RGB三原色各四位,行列扫描控制线HSYNC、VSYNC两位。管脚分配如图:
  • VGA扫描显示的时序控制原理:
    以640480分辨率为例,,扫描从第一行开始,当扫描未在有效显示区域时(640480显示块),即y<V_SYNC,行同步型号置低,反之则置高为有效区域,列同步信号亦然,当行列信号同为高电平x>H_SYNC就是VGA有效显示区域的扫描时间,640*480的像素扫描频率为25MHz,当列扫描到最后,行扫描加一,如此循环往复,直到最后一行,在从头开始,就实现了VGA显示器的逐行逐列扫描。
   reg[2:0] cnt2;
   always@(posedge clk) //25mhz
       begin
       if(cnt2 == 1) begin clk_vga <= ~clk_vga; cnt2<=0; end
       else begin cnt2 <= cnt2+1; end
       end
   //------------------------------------
    always @(posedge clk_vga)
        begin
        if (x_cnt==H_TOTAL)  x_cnt<=0;
        else x_cnt<=x_cnt+1;
        end
     always @(posedge clk_vga)
        begin
        if (y_cnt==V_TOTAL) y_cnt<=0;
        else if (x_cnt==H_TOTAL) y_cnt<=y_cnt+1;
        end
   //------------------------------------
    reg hsync_r,vsync_r;
    always @(posedge clk_vga)//列 扫 描
       if(x_cnt==0)
           hsync_r<=1'b0;
       else if(x_cnt==H_SYNC)
           hsync_r<=1'b1;
     always @(posedge clk_vga)//行 扫 描
       if(y_cnt==0)
           vsync_r<=1'b0;
       else if(y_cnt==V_SYNC)
           vsync_r<=1'b1;
//------------------------------------
     assign hsync=hsync_r;
	 assign vsync=vsync_r;

核心代码如上。

蛇身控制模块:snake_control

蛇身控制模块是整个游戏最核心的部分,我们通过操控蛇头的位置,来实现对整个蛇身位置的控制。模块定义如下:

module snake_control(
	game_status,clk,upbt,downbt,rightbt,leftbt,x_pos,
	y_pos,apple_x,apple_y,apple_refresh,snake,head_x,head_y,
	strike_itself,strike_wall);
    input clk, upbt,downbt,rightbt,leftbt;
    input wire[1:0] game_status;
    input wire[9:0] x_pos,y_pos, apple_x,apple_y;
    input apple_refresh;
    output reg snake,strike_itself,strike_wall;
	output reg[9:0] head_x, head_y;

蛇头是主要的操作对象,当四个方向键有按下时,如果当前不是反向和同向,则将蛇头坐标实现对应的移动:

   parameter up=2'd0,down=2'd1,left=2'd2,right=2'd3;
   reg[1:0] direction,front_direction;
   always@(posedge clk or posedge upbt or posedge downbt or posedge rightbt or posedge leftbt)
        begin
        if(upbt) begin if(direction!=down) direction=up; end
        else if(downbt) 
			begin if(direction!=up) direction=down; end
        else if(rightbt) 
			begin if(direction!=left) direction=right; end
        else if(leftbt) 
			begin if(direction!=right) direction=left; end
	    end

蛇头方向移动后,整个蛇身随之移动,周期为一秒,将蛇头的前一个位置赋给后一个方格点,以此类推,通过此算法实现了蛇身的移动(body中的0代表头坐标。):

if(cnt_1s>=24999999)
    begin
    cnt_1s=0;
    body_x[0][15:0]=head_x; body_y[0][15:0]=head_y;
    case(direction)
    	up: begin head_y=head_y-1; end
    	down: begin head_y=head_y+1; end
    	left: begin head_x=head_x-1; end
    	right: begin head_x=head_x+1; end
endcase
body_x[1][15:0]<=body_x[0][15:0];
body_y[1][15:0]<=body_y[0][15:0];
body_x[2][15:0]<=body_x[1][15:0];
body_y[2][15:0]<=body_y[1][15:0];
... ...

注:这段代码中,没有使用循环语句,主要是因为Verilog是一种硬件描述的底层语言,对于类似for循环的语句与它的功能是相违背的,Verilog只是并行地执行简单语句,无法实现高阶的语法。

蛇身的长度由参数调控,初始值为2,VGA显示模块传输当前扫描x_pos y_pos坐标到蛇身控制模块,在这时将整个蛇身的坐标与扫描坐标值对比,若重合则输出snake返回有效值1,VGA显示模块接收1并将此坐标的方格显示为黑色,即蛇身颜色,反之snake为0,VGA显示为其他:

if(game_status==Start)
    begin
    if(is_exist>0 && ((x_pos==body_x[0][15:0] && body_y[0][15:0]==y_pos) || (head_x==x_pos && head_y==y_pos)))
 begin snake=1; end
... ...
VGA.v:
if(snake)
begin rgb=4'h0; vga_r=rgb; vga_g=rgb; vga_b=rgb; end

还有一个重要数据就是蛇的死亡判定,有两种死法:撞墙或撞到了自己,发生了此种情况时,对应输出置一,反之为零,输出到game_control中,游戏状态重置:

if(head_x<1 || head_y<2 || head_x>82 || head_y>67)
    	begin strike_wall=1; end
    else
        begin strike_wall=0; end
    if(is_exist>1 && (head_x==body_x[1][15:0] && body_y[1][15:0]==head_y))
begin strike_itself=1; end
... ...

苹果控制模块:apple

module apple(
game_status,clk,rst_n,head_x,head_y,x_pos,y_pos, apple,apple_refresh);
    input clk,rst_n;
    input wire[1:0] game_status;
    input[9:0] x_pos,y_pos,head_x,head_y;
output reg apple,apple_refresh;

这个模块的主要任务有两个:将苹果显示信号输出给VGA显示模块,VGA模块输出当前扫描坐标到Apple,Apple与苹果坐标进行比对,如果相同则返回显示信号Apple为有效值1,VGA接收后显示粉红色方框代表苹果,反之为零,显示为其他:

if(x_pos==apple_x && y_pos==apple_y)
    	begin apple=1; end
else begin apple=0; end

蛇吃到苹果,即蛇头坐标与苹果坐标相等,apple_refresh置1,输入到蛇身控制模块,蛇长度加一,苹果位置重置:

if(head_x==apple_x && head_y==apple_y)
	begin apple_refresh=1; end

顶层模块:snake_top

顶层模块是协调其他四个模块的并行运行和时序控制模块:

module snake_top(
clk, rst_n,centerbt,upbt,downbt,rightbt,leftbt, vga_r,vga_g,vga_b,hsync,vsync);
    input clk,rst_n,centerbt,upbt,downbt,rightbt,leftbt;
    output hsync,vsync;
    output[3:0] vga_r,vga_g,vga_b;
    wire[1:0] game_status;
    wire snake,strike_itself,strike_wall;
    wire[9:0] x_pos,y_pos;
    wire[9:0] head_x, head_y;
    wire[3:0] vga_r, vga_g, vga_b;
    wire[9:0] apple_x,apple_y;
    wire apple,apple_refresh;
    
game_control 
G(
clk,rst_n,centerbt,strike_itself,strike_wall,game_status
);
    
VGA
V(
game_status,clk,rst_n,snake,apple,upbt,downbt,rightbt,leftbt,
x_pos,y_pos,vga_r,vga_g,vga_b,hsync,vsync);

snake_control S(
game_status,clk,upbt,downbt,rightbt,leftbt,x_pos,y_pos,
apple_x,apple_y,apple_refresh,snake,head_x,head_y,strike_itself,strike_wall);

apple A(
game_status,clk,rst_n,head_x,head_y,x_pos,y_pos, apple,apple_refresh);

至此,简单的贪吃蛇游戏就完成了,后期还将加入新功能,例如UI的优化,难易度调节等。
首次尝试Verilog新语言,三天时间,从零开始,我用汗水铸就了这些代码,虽然还很简陋,但课设顺利通过,也算功夫不负有心人吧。希望努力拼搏的人都能得到尊重,所有的努力都能得应得的回报,大家一起加油!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/128634.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 理解图像卷积操作的意义

    理解图像卷积操作的意义数字信号处理中卷积卷积一词最开始出现在信号与线性系统中,信号与线性系统中讨论的就是信号经过一个线性系统以后发生的变化。由于现实情况中常常是一个信号前一时刻的输出影响着这一时刻的输出,所在一般利用系统的单位响应与系统的输入求卷积,以求得系统的输出信号(当然要求这个系统是线性时不变的)。卷积的定义:卷积是两个变量在某范围内相乘后求和的结果。如果卷积的变量是序列x(n)和h(n),则…

  • DeepLab 笔记

    2019March10deeplabDeepLab笔记一、背景DCNN存在的问题:多次下采样使输出信号分辨率变小——空洞卷积池化对输入变换具有内在空间不变性——CRF二、空洞卷积1.作用保证感受野不发生变化得到密集的featuremap2.卷积核new\_kernel=kernel+\left(kernel-1\right…

  • JavaScript高级程序设计(读书笔记)(七)[通俗易懂]

    JavaScript高级程序设计(读书笔记)(七)[通俗易懂]本笔记汇总了作者认为“JavaScript高级程序设计”这本书的前七章知识重点,仅供参考。第七章函数表达式小结:在JavaScript编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用JavaScript函数的强大方式。以下总结了函数表达式的特点。函数表达式不同于函数声明。

  • EasyPusher/EasyDarwin/EasyPlayer实现手机直播版本及效果整理「建议收藏」

    EasyPusher/EasyDarwin/EasyPlayer实现手机直播版本及效果整理「建议收藏」EasyPusher手机直播实现功能最近很多EasyDarwin爱好者提出了手机移动端直播的功能需求,尤其是如何做出像映客这样能够快速出画面播放的效果,经过一段时间的移动端和服务端的优化,EasyPusher直播推送+EasyDarwin流媒体服务器+EasyPlayer客户端播放已经非常好实现了这些需求,EasyPusher编码推送+EasyDarwin公网传输+EasyPlayer播放整个流程,

  • log4j pattern详解_pid参数的物理意义

    log4j pattern详解_pid参数的物理意义1、常用标志:-X:X信息输出时左对齐 %p:输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL, %d:输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy-MM-ddHH:mm:ss,SSS},输出类似:2011-10-1822:10:28,921 %r:输出自应用启动到输出该log信

  • MyEclipse注册码_MyEclipse激活码

    MyEclipse注册码_MyEclipse激活码Subscriber:QQ24785490SubscriptionCode:DLR8ZC-855551-65657857678050018

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号