FPGA Cyclone IV的Snake游戏(带有VGA和SPI游戏杆)

引言


您还记得童年时代的蛇游戏吗?那条蛇在屏幕上奔跑试图吃一个苹果? 本文介绍了我们在FPGA 1上实现游戏的方法。


Gameplay.gif
图1.游戏玩法


首先,让我们自我介绍并解释我们为何从事该项目的理由。 我们共有3个人: Tymur LysenkoDaniil ManakovskiySergey Makarov 。 作为Innopolis大学的一年级学生,我们开设了“计算机体系结构”课程,该课程经过专业教授,使学习者能够理解计算机的低级结构。 在课程中的某些时候,讲师为我们提供了开发FPGA项目的机会,以增加课程中的要点。 我们的动机不仅在于成绩,还在于我们希望获得更多的硬件设计经验,分享成果并最终拥有一个令人愉悦的游戏。


现在,让我们进入黑暗的深层细节。


项目概况


对于我们的项目,我们选择了一个易于实现且有趣的游戏,即“ Snake”。 该实现的结构如下:首先,从SPI游戏杆获取输入,然后进行处理,最后,将图片输出到VGA监视器,并在7段显示器(以十六进制显示)上显示得分。 尽管游戏逻辑直观直观,但是VGA和操纵杆却是有趣的挑战,它们的实现带来了良好的游戏体验。


该游戏具有以下规则。 玩家从一条蛇的头开始。 目的是要吃苹果,苹果是在吃完上一个苹果之后在屏幕上随机产生的。 此外,在满足饥饿感之后,蛇被延长了一条尾巴。 尾巴一个接一个地移动,跟随头部。 蛇总是在移动。 如果到达屏幕边界,则说明蛇正在转移到屏幕的另一侧。 如果头撞到尾巴,则游戏结束。


使用的工具


  • Altera Cyclone IV(EP4CE6E22C8N)具有6272个逻辑元素,板载50 MHz时钟,3位彩色VGA,8位7段显示器。 FPGA无法将模拟输入带到其引脚。
  • SPI游戏杆(KY-023)
  • 一个支持60 Hz刷新率的VGA监视器
  • Quartus Prime Lite Edition 18.0.0内部版本614
  • Verilog HDL IEEE 1364-2001
  • 面包板
  • 电气元件:
    • 8个公-母连接器
    • 1个母-母连接器
    • 1个公-公连接器
    • 4个电阻(4.7KΩ)

架构概述


项目的体系结构是要考虑的重要因素。 图2从顶级角度显示了该架构:


Design.png
图2.设计的顶层视图( pdf


如您所见,有很多输入,输出和一些模块。 本节将描述每个元素的含义,并指定板上的端口使用哪些引脚。


主要输入


实现所需的主要输入是res_x_oneres_x_twores_y_oneres_y_two ,用于接收操纵杆的当前方向。 图3显示了它们的值和方向之间的映射。


输入值向左对啊往下方向不变
res_x_one(PIN_30)1个0XX1个
res_x_two(PIN_52)1个0XX0
res_y_one(PIN_39)XX1个01个
res_y_two(PIN_44)XX1个00

图3.操纵杆输入和方向的映射


其他投入


  • clk-板钟(PIN_23)
  • 重置 -重置游戏并停止打印的信号(PIN_58)
  • color-当为1时,所有可能的颜色都会输出到屏幕上,并且仅用于演示目的(PIN_68)

主要模块


joystick_input


joystick_input用于根据操纵杆的输入产生方向代码。


game_logic


game_logic包含玩游戏所需的所有逻辑。 该模块沿给定方向移动蛇。 此外,它还负责苹果进食和碰撞检测。 此外,它接收屏幕上像素的当前x和y坐标,并返回放置在该位置的实体。


VGA_绘图


抽屉根据当前位置( iVGA_X,iVGA_Y )和当前实体( ent )将像素的颜色设置为特定值。


VGA_Ctrl


生成控制位流到 VGA输出( V_Sync,H_Sync,R,G,B )。


SSEG_Display 2


SSEG_Display是一个驱动程序,用于在7段显示器上输出当前分数。


Vga_clk


VGA_clk接收50MHz时钟并将其降低到25.175MHz。


game_upd_clk


game_upd_clk是一个模块,可生成触发游戏状态更新的特殊时钟。


产出


  • VGA_B -VGA蓝色针(PIN_144)
  • VGA_G -VGA绿色针(PIN_1)
  • VGA_R -VGA红色引脚(PIN_2)
  • VGA_HS -VGA水平同步(PIN_142)
  • VGA_VS -VGA垂直同步(PIN_143)
  • sseg_a_to_dp-指定要点亮的8个段中的哪一个(PIN_115,PIN_119,PIN_120,PIN_121,PIN_124,PIN_125,PIN_126,PIN_127)
  • sseg_an-指定要使用4个7段显示器中的哪一个(PIN_128,PIN_129,PIN_132,PIN_133)

实作


通过SPI游戏杆输入


stick.jpg


图4. SPI游戏杆(KY-023)


在实现输入模块时,我们发现操纵杆会产生一个模拟信号。 操纵杆每个轴有3个位置:


  • 顶部-〜5V输出
  • 中-〜2.5V输出
  • 低-〜0V输出

输入与三元系统非常相似:对于X轴,我们有true (左), false (右)和undetermined状态,操纵杆既不在左侧也不在右侧。 问题在于FPGA板只能处理数字输入。 因此,我们不能仅通过编写一些代码就将这种三元逻辑转换为二进制。 提出的第一个解决方案是找到一个模数转换器,但随后我们决定利用我们在物理领域的知识,并实施分压器3 。 要定义这三种状态,我们将需要两个位:00为false ,01为undefined和11为true 。 经过一些测量,我们发现板上的零和一之间的边界约为1.7V。 因此,我们构建了以下方案(使用circuitlab 4创建的图像):


Stick_connection.png


图5.用于操纵杆的ADC电路


物理实现是使用Arduino套件项目构建的,如下所示:


stick_imp


图6. ADC实现


我们的电路为每个轴输入一个输入,并产生两个输出:第一个直接来自操纵杆,只有在操纵杆输出为zero时才变为zero 。 第二个是0(处于undetermined状态),而1仍然为true 。 这是我们期望的确切结果。


输入模块的逻辑是:


  1. 我们将每个方向的三元逻辑转换为简单的二进制线。
  2. 在每个时钟周期,我们检查是否只有一个方向true (蛇不能沿对角线走);
  3. 我们将新方向与上一个方向进行比较,以防止玩家不允许将方向更改为相反方向,从而防止蛇吃掉自己。

输入模块代码的一部分
 reg left, right, up, down; initial begin direction = `TOP_DIR; end always @(posedge clk) begin //1 left = two_resistors_x; right = ~one_resistor_x; up = two_resistors_y; down = ~one_resistor_y; if (left + right + up + down == 3'b001) //2 begin if (left && (direction != `RIGHT_DIR)) //3 begin direction = `LEFT_DIR; end //same code for other directions end end 

输出到VGA


我们决定在以60 FPS运行的60Hz屏幕上输出分辨率为640x480的输出。


VGA模块包括2个主要部分: 驱动器抽屉 。 驱动程序生成由垂直,水平同步信号和提供给VGA输出的颜色组成的位流。 @SlavikMIPT撰写的文章5描述了使用VGA的基本原理。 我们已将驱动程序从本文改编为我们的董事会。


我们决定将屏幕分成40x30的元素网格,该网格由16x16像素的正方形组成。 每个元素代表一个游戏实体:一个苹果,一条蛇的头,一条尾巴或什么都没有。


我们实现的下一步是为实体创建精灵。


Cyclone IV只有3位来表示VGA上的颜色(红色代表1,绿色代表1,蓝色代表1)。 由于这样的限制,我们需要实现一个转换器,以使图像的颜色适合可用的颜色。 为此,我们创建了一个python脚本,该脚本将每个像素的RGB值除以128。


python脚本
 from PIL import Image, ImageDraw filename = "snake_head" index = 1 im = Image.open(filename + ".png") n = Image.new('RGB', (16, 16)) d = ImageDraw.Draw(n) pix = im.load() size = im.size data = [] code = "sp[" + str(index) + "][{i}][{j}] = 3'b{RGB};\\\n" with open("code_" + filename + ".txt", 'w') as f: for i in range(size[0]): tmp = [] for j in range(size[1]): clr = im.getpixel((i, j)) vg = "{0}{1}{2}".format(int(clr[0] / 128), # an array representation for pixel int(clr[1] / 128), # since clr[*] in range [0, 255], int(clr[2] / 128)) # clr[*]/128 is either 0 or 1 tmp.append(vg) f.write(code.format(i=i, j=j, RGB=vg)) # Verilog code to initialization d.point((i, j), tuple([int(vg[0]) * 255, int(vg[1]) * 255, int(vg[2]) * 255])) # Visualize final image data.append(tmp) n.save(filename + "_3bit.png") for el in data: print(" ".join(el)) 

原版脚本后



图7.输入和输出之间的比较


抽屉的主要目的是根据当前位置( iVGA_X,iVGA_Y )和当前实体( ent )向VGA发送像素的颜色。 所有子画面都是硬编码的,但是可以使用上面的脚本生成新代码来轻松更改。


抽屉逻辑
 always @(posedge iVGA_CLK or posedge reset) begin if(reset) begin oRed <= 0; oGreen <= 0; oBlue <= 0; end else begin // DRAW CURRENT STATE if (ent == `ENT_NOTHING) begin oRed <= 1; oGreen <= 1; oBlue <= 1; end else begin // Drawing a particular pixel from sprite oRed <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][0]; oGreen <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][1]; oBlue <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][2]; end end end 

输出到7段显示器


为了使玩家能够看到自己的分数,我们决定将游戏分数输出到7段显示器。 由于时间有限,我们使用了EP4CE6入门板文档2中的代码。 该模块将十六进制数输出到显示器。


游戏逻辑


在开发过程中,我们尝试了几种方法,但是最后我们得到了一种方法,该方法需要最少的内存,易于在硬件中实现并且可以从并行计算中受益。


该模块执行多种功能。 当VGA在每个时钟周期从左上角开始移动到右下角时绘制一个像素时,负责为像素产生颜色的VGA_Draw模块需要确定用于当前坐标的颜色。 那就是游戏逻辑模块应该输出的-给定坐标的实体代码。
而且,仅在绘制全屏后才必须更新游戏状态。 game_upd_clk模块产生的信号用于确定何时更新。


游戏状态


游戏状态包括:


  • 蛇头的坐标
  • 蛇尾巴的坐标数组。 在我们的实现中,数组受128个元素限制
  • 尾数
  • 苹果的坐标
  • 游戏结束标志
  • 比赛获胜标志

游戏状态的更新包括以下几个阶段:


  1. 根据给定的方向将蛇的头移动到新坐标。 如果发现坐标在其边缘上并且需要进一步更改,则头部必须跳到屏幕的另一边缘。 例如,方向设置为左侧,当前X坐标为0。因此,新的X坐标应等于最后一个水平地址。
  2. 将蛇头的新坐标与苹果坐标进行测试:
    2.1。 如果它们相等且数组未满,请在数组中添加新尾部并增加尾部计数器。 当计数器达到最高值(在本例中为128)时,将设置游戏获胜标志,这意味着蛇不再能够生长,并且游戏仍在继续。 新的尾巴将放置在蛇头的先前坐标上。 X和Y的随机坐标应放置一个苹果。
    2.2。 如果它们不相等,则顺序交换相邻尾巴的坐标。 如果第n个尾部在第(n +1)个之前添加,则(n +1)个第尾部应接收第n个坐标。 第一条尾巴接收头部的旧坐标。
  3. 检查蛇头的新坐标是否与任何尾巴的坐标一致。 在这种情况下,游戏结束标志会升起,游戏会停止。

随机坐标生成


通过取由6位线性反馈移位移位寄存器(LFSR) 6生成的随机位产生的随机数。 为了将数字拟合到屏幕中,将它们除以游戏网格的尺寸,然后取余数。


结论


经过8周的工作,该项目成功实施。 我们在游戏开发方面具有一定的经验,最终为FPGA开发了一个令人愉快的“蛇”游戏版本。 该游戏具有可玩性,并且我们在编程,设计体系结构和软技能方面的技能得到了提高。


确认的细分


我们要特别感谢和感谢穆罕默德·法希姆Muhammad Fahim)教授和亚历山大·托马索夫Alexander Tormasov)教授, 他们为我们提供了丰富的知识和将其付诸实践的机会。 我们衷心感谢弗拉迪斯拉夫·奥斯坦科维奇Vladislav Ostankovich)为我们提供了该项目中使用的必要硬件,并感谢Temur Kholmatov帮助调试。 我们不会忘记记得Anastassiya Boiko为游戏绘制漂亮的精灵。 另外,我们还要向Rabab Marouf致以诚挚的敬意,以便对本文进行校对和编辑。


感谢所有帮助我们测试游戏并尝试创造记录的人。 希望你喜欢玩!


参考文献


[1]: Github上的项目
[2]: [FPGA] EP4CE6入门板文档
[3]: 分压器
[4]: 电路建模工具
[5]: 用于FPGA Altera Cyclone III的VGA适配器
[6]: 维基百科上的线性反馈移位寄存器(LFSR)
FPGA中的LFSR-VHDL和Verilog代码
苹果质地
产生随机数的想法
Palnitkar,S。(2003)。 Verilog HDL:数字设计和综合指南,第二版。

Source: https://habr.com/ru/post/zh-CN431226/


All Articles