名称:吃豆人游戏VGA显示VHDL代码basys3开发板设计报告
软件:VIVADO
语言:VHDL
代码功能:
本设计是一个基于VGA的吃豆人游戏,玩家可以控制屏幕上的吃豆人吃散落在屏幕各个方位的豆子,同时屏幕上还有2个魔鬼,如果吃豆人和魔鬼相遇,则游戏失败。
设计的基本要求是魔鬼的移动方式固定,且用固定得到圆形或者方形来表示不同的对象。考虑到该实现方案较为简单,为使显示更加美观,在屏幕上添加墙壁,划分出不同的路线。吃豆人和魔鬼也不使用固定圆形或者方形,而是用较为形象的图案,可以体现出眼睛嘴巴等特征。
同时为了记录游戏得分,添加数码管显示部分。
系统控制魔鬼在所划分的道路上巡逻,玩家通过Basys3板子上的上下左右按键控制吃豆人吃豆子以及规避魔鬼。
FPGA代码Verilog/VHDL代码资源下载:www.hdlcode.com
本代码已在Basys3开发板验证,开发板如下,其他开发板可以修改管脚适配:
1. 方案选择
本设计是一个基于VGA的吃豆人游戏,玩家可以控制屏幕上的吃豆人吃散落在屏幕各个方位的豆子,同时屏幕上还有2个魔鬼,如果吃豆人和魔鬼相遇,则游戏失败。设计的基本要求是魔鬼的移动方式固定,且用固定得到圆形或者方形来表示不同的对象。考虑到该实现方案较为简单,为使显示更加美观,在屏幕上添加墙壁,划分出不同的路线。吃豆人和魔鬼也不使用固定圆形或者方形,而是用较为形象的图案,可以体现出眼睛嘴巴等特征。同时为了记录游戏得分,添加数码管显示部分。系统控制魔鬼在所划分的道路上巡逻,玩家通过Basys3板子上的上下左右按键控制吃豆人吃豆子以及规避魔鬼。
2. 系统设计
系统的整体设计框图如下图所示,输入端口包括时钟、复位、四个上下左右按键。输出端口包括VGA的HSYNC、VSYNC信号,颜色RGB信号以及用于数码管控制的位选(bit_sel)和段选信号(semgnet)。
系统包括分频模块(clk_wizard)、按键模块(io)、图像控制模块(picture_ctrl)、VGA时序控制模块(vga_driver)和数码管控制模块(ssd_controller)。其中分频模块用于分频产生其他模块的工作时钟,按键模块实现按键消抖功能,并将按键信号输出给图像控制模块。图像控制模块实现吃豆人图像界面的生成功能,内部程序描绘墙体、吃豆人、豆子、魔鬼等画面,并可通过按键控制吃豆人移动。VGA时序控制模块实现VGA控制时序,使图像控制模块生成的湖,面可以通过VGA显示器显示出来。数码管控制模块用于控制板子上的数码管显示,显示内容为所得分数。
3. 模块设计
3.1 时钟分频模块
Basys3板子上带有100MHz的晶振,将该时钟信号最为系统的外部输入时钟。本文设计的VGA显示分辨率为800x600@60Hz,对应的VGA时钟为40MHz,因此选择40MHz为本系统各个模块的工作时钟。时钟分频模块实现将外部输入的100MHz信号分频为40MHz。分频方法为调用Vivado自带的时钟分频IP核,将IP核输出频率设置为40M,如下图所示。
3.2 按键模块
按键模块实现按键消抖功能,并最终将按键信号转换为对应的移动信号和方向信号。Basys3板子上提供了5个机械回弹式的按键。机械式的按键都会有按键抖动的现象,为了不产生这种现象而作的措施就是按键消抖,按键模块框图如下图所示。
代码的设计过程为首先以40M时钟信号不断的检测按键,当检测到键值变化后,开始计数器计数,当计数值满足延迟要求后,重新检测键值,并将键值的上升沿信号输出,即最终效果为按下一次按键后,对应输出一个时钟周期的高电平信号。
然后对4个消抖后的按键信号减小判断,使用s_Direction信号记录按键对应的方向,当上方向键按下时记为“1000”,下方向记为“0100”,左方向记为“0001”,有方向记为“0010”。并且每次有按键按下将s_Move信号拉高,表示需要移动。
3.3 数码管控制模块
数码管控制模块如下图所示,该模块用于显示游戏分数。
使用开发板的7段数码管显示,每个数码管输入为7位,对应下图中的abcdefg7段,当输入0时对应的段点亮,当输入为1时,对应的段灭。
根据上图可以观察到,若要显示数字0,需要G灭,ABCDEF亮,也就是对应编码为“1000000”,其中从左到右依次对应GFEDCBA。以此类推可以得到0~9的所有编码。
3.4 VGA时序控制模块
模块实现VGA时序的控制,用于产生VGA的CLK信号,hsync信号和vsync信号,模块框图如下图所示。
本文设计的VGA时序为800x600刷新频率为60Hz。每场对应628个行周期(628=4+23+600+1),其中600为显示行。下图为VGA模块实现的时序图:
HSYNC Signal 是用来控制“列填充”, 而一个HSYNC Signal 可以分为4个段,也就是 a (同步段) , b(后廊段),c(激活段),d(前廊段)。HSYNC Signal 的a 是拉低的128 个列像素,b是拉高的88个列像素,至于c 是拉高的 800 个列像素,而最后的 d 是拉高的 40 个列像素。 一列总共有1056 个列像素。VSYNC Signal 是用来控制“行扫描”。而一个 VSYNC Signal 同样可以分为 4个段, 也是 o (同步段) , p(后廊段),q(激活段),r(前廊段)。VSYNC Signal 的o 是拉低的4个行像素 ,p是拉高的23个行像素,至于q 是拉高的 600 个行像素,而最后的 r 是拉高的 1 个行像素。
3.5 图像控制模块
图像控制模块实现吃豆人图像界面的生成功能,内部程序描绘墙体、吃豆人、豆子、魔鬼等画面,并可通过按键控制吃豆人移动,模块框图如下:
为描绘较为复杂的吃豆人、魔鬼、豆子等图案,本设计使用ROM IP核存储对应的图案,即事先将所绘制的图案转换为对应0和1数字,再将该数字图案存储在coe文件中,当需要显示该图像时不需要再写代码,而只需要调用该图像的IP核即可。下图为魔鬼对应的coe文件内容。
上图中,我将图案的轮廓描绘出来了,数字1代表屏幕上显示的内容,数字0代表不显示,因此上图中魔鬼的眼睛和嘴巴出现了。其他的吃豆人、豆子等图案也采用这种方法实现。图中的墙壁由于形状比较规范,直接使用代码设计。设计方法为通过约束垂直方向的起始和结束位置以及水平方向的起始和结束位置,实现描绘出一个矩形的功能。
4. 代码调试遇到的问题
在调试过程中,遇到了一些问题,首先在设计图案时,原本的设计的都通过代码直接描述,但是实现过程中由于不规范的图形使用代码描述时实在过于复杂,导致代码可读性差且任意出错,后面通过查找资料,了解到使用coe文件的方法,就很方便的完成了异形图形的设计。在使用数码管显示分数时,一开始没有区分显示的个位和十位,导致最终在数码管显示时按十六进制显示了,不符合日常习惯,后面通过修改代码,使分数的十位和个位分别计数,改成十进制显示,方便观看。
部分代码展示:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity vga_driver is Port( pic_clk : in std_logic;--628*1056*60Լ40MHz. p_Reset : in std_logic; p_Color : in std_logic_vector(2 downto 0); H_position : out integer; V_position : out integer; VGA_hsync_o : out std_logic; VGA_vsync_o : out std_logic; VGA_R : out std_logic_vector(3 downto 0); VGA_G : out std_logic_vector(3 downto 0); VGA_B : out std_logic_vector(3 downto 0) ); end vga_driver; architecture Behavioral of vga_driver is -- Timing Constants -- For polarity '0' means negative polarity -- Horizontal Axis constant HD : integer := 800; -- Visiable Area constant HFP : integer := 40; -- Front Porch constant HSP : integer := 128; -- Sync Pulse constant HBP : integer := 88; -- Back porch constant HPOLARITY : std_logic := '1'; -- Polartity constant HTOTAL : integer := HD + HFP + HSP + HBP; -- Whole Line -- Vertical Axis constant VD : integer := 600; -- Visiable Area constant VFP : integer := 1; -- Front Porch constant VSP : integer := 4; -- Sync Pulse constant VBP : integer := 23; -- Back porch constant VPOLARITY : std_logic := '1'; -- Polartity constant VTOTAL : integer := VD + VFP + VSP + VBP; -- Whole Line signal s_HPos : integer; signal s_VPos : integer; signal s_videoOn : std_logic; signal s_VGARed : std_logic_vector(3 downto 0); signal s_VGAGreen : std_logic_vector(3 downto 0); signal s_VGABlue : std_logic_vector(3 downto 0); begin -- Horizontal Position Counter HPCounter : process(pic_clk, p_Reset) begin if (p_Reset = '1') then s_HPos <= 0; elsif (rising_edge(pic_clk)) then if (s_HPos = HTOTAL) then s_HPos <= 0; else s_HPos <= s_HPos + 1; end if; end if; end process; H_position <= s_HPos; -- Vertical Position Counter VPCounter : process(pic_clk, p_Reset) begin if (p_Reset = '1') then s_VPos <= 0; elsif (rising_edge(pic_clk)) then if (s_HPos = HTOTAL) then if (s_VPos = VTOTAL) then s_VPos <= 0; else s_VPos <= s_VPos + 1; end if; end if; end if; end process; V_position <= s_VPos; -- Horizontal Synchronisation HSync : process(pic_clk, p_Reset) begin if (p_Reset = '1') then VGA_hsync_o <= HPOLARITY; elsif (rising_edge(pic_clk)) then if ((s_HPos < HD + HFP) OR (s_HPos > HD + HFP + HSP)) then VGA_hsync_o <= not HPOLARITY; else VGA_hsync_o <= HPOLARITY; end if; end if; end process; -- Vertical Synchronisation VSync : process(pic_clk, p_Reset) begin if (p_Reset = '1') then VGA_vsync_o <= VPOLARITY; elsif (rising_edge(pic_clk)) then if ((s_VPos < VD + VFP) OR (s_VPos > VD + VFP + VSP)) then VGA_vsync_o <= not VPOLARITY; else VGA_vsync_o <= VPOLARITY; end if; end if; end process; -- Video On videoOn : process(pic_clk, p_Reset) begin if (p_Reset = '1') then s_videoOn <= '0'; elsif (rising_edge(pic_clk)) then if (s_HPos <= HD and s_VPos <= VD) then s_videoOn <= '1'; else s_videoOn <= '0'; end if; end if; end process; colorOutput : process(pic_clk, p_Reset) begin if (p_Reset = '1') then s_VGARed <= (others => '0'); s_VGAGreen <= (others => '0'); s_VGABlue <= (others => '0'); elsif (rising_edge(pic_clk)) then if (s_VideoOn = '1') then s_VGARed <= p_Color(2) & p_Color(2) & p_Color(2) & p_Color(2); s_VGAGreen <= p_Color(1) & p_Color(1) & p_Color(1) & p_Color(1); s_VGABlue <= p_Color(0) & p_Color(0) & p_Color(0) & p_Color(0); else s_VGARed <= (others => '0'); s_VGAGreen <= (others => '0'); s_VGABlue <= (others => '0'); end if; end if; end process; VGA_R <= s_VGARed; VGA_G <= s_VGAGreen; VGA_B <= s_VGABlue; end Behavioral;