一、设计题目说明
此设计意在实现一个相对具有娱乐性的打地鼠游戏模块功能,能够产生随机出现的地鼠,并能对虚拟的敲击按键做出灵敏的识别与判断,从而进行必要的计分与计时行为。
二、实验平台
开发软件:
Quartus II 9.0sp2 Web Edition
开发板:
ALTERA FLEX EPF10K20TI144-4 CAA239743
三、总体设计思路
结构:
取八位晶体管的前四位数码管作为随机地鼠出现的显示区域,后四位对半分为前两位的倒计时显示模块和后两位的分数计数模块;八个按键脉冲开关取前四位作为地鼠的敲击按键;以数码管的动态化显示代表是否击中。
功能:
在游戏开始前,首先根据产生的伪随机数生成地鼠出现的位置信息,由指定的时钟脉冲信号控制地鼠跳动的频率,最终交由晶体管显示。重置倒计时与分数,待随机数已生成一段时间,拨动开始键,驱动游戏进行。此时倒计时与比较模块开始工作,将地鼠出现的位置信息与敲击按键的位置信息进行比对,如果一致,则分数加一,如不一致,分数保持不变,直到60s倒计时结束,各模块停止工作。按下重置键复位重新开始游戏。
四、详细模块设计
①分频模块:
将100,000Hz的时钟信号源分频为所需的1Hz时钟信号,使得时钟信号能够每秒产生一个时钟脉冲。
--分频模块
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity divclock is
port( oldclk: IN std_logic;
currclk: buffer std_logic);
end;
architecture one of divclock is
constant useHz:integer:=100000;--旧时钟频率为100000Hz
begin
process(oldclk)
variable count:integer range 0 to useHz-1;--设置计数器,保留旧时钟频率产生上升沿的次数
begin
if oldclk'event and oldclk='1' then
if count=(useHz-1)/2 then--0.5s时,计数器置0,新时钟翻转
count:=0;
currclk<=NOT currclk;
else
count:=count+1;
end if;
end if;
end process;
end one;
②随机数模块:
取m序列的每四位的后两位,产生一定数量的伪随机数,作为地鼠的位置信息。
--output 2bits random number
--利用m序列产生四位随机数,取每四位的后两位作为本次课设所需的随机数
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity rand is
port(Reset: IN std_logic;
Clk: IN std_logic;
Data_out: OUT std_logic_vector(1 downto 0));
end rand;
architecture rtl of rand is
signal Shift_Register:std_logic_vector(3 downto 0);
begin
process(Reset,Clk)
begin
if(Reset='1') then
Shift_Register<="1000";
else if(Clk'event and Clk='1') then
Data_Out<=Shift_Register(1 downto 0);
Shift_Register(0)<=Shift_Register(1);
Shift_Register(1)<=Shift_Register(2);
Shift_Register(2)<=Shift_Register(3);
Shift_Register(3)<=Shift_Register(3) xor Shift_Register(0);
end if;
end if;
end process;
end rtl;
③按键模块:
通过转码,将按键的位置信息转化为对应位置上地鼠的位置信息。
--按键模块
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity button_decode is
port(a,b,c,d: IN std_logic;--定义4个按键
--clk: IN std_logic;
result: OUT std_logic_vector(1 downto 0));
end button_decode;
architecture one of button_decode is
begin
process(a,b,c,d)
begin--对按键进行位置信息的转换
--if(clk'event and clk='1') then
if a='0' then
result<="11";
elsif b='0' then
result<="10";
elsif c='0' then
result<="01";
elsif d='0' then
result<="00";
else
result<=NULL;
end if;
--end if;
end process;
end;
④倒计时模块:
利用分频得到1Hz的时钟频率设置倒计时,每当经过一个时钟脉冲,模块中的计时器减一,直至设定的时间结束为0或者通过reset重置。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity timedown is
port(start: IN std_logic;
clk: IN std_logic;
reset: IN std_logic;
S: OUT std_logic_vector(3 downto 0);--十位
F: OUT std_logic_vector(3 downto 0);--个位
accomplish: OUT std_logic); --倒计时状态的输出信号,如果倒计时结束,则输出1
end timedown;
architecture one of timedown is
begin
process(clk,start,reset)
variable s2:std_logic_vector(3 downto 0);--十位
variable f2:std_logic_vector(3 downto 0);--个位
begin
if(clk'event and clk='1') then
if(reset='1') then --重置
s2:="0110";--6
f2:="0000";--0
elsif start='1' then
if f2="0000" then --如果个位等于0而十位不等于0,则将个位置为9,十位减一
if s2/="0000" then
f2:="1001";
s2:=s2-1;
accomplish<='0';
else --如二者都为0,则倒计时保持“00”不变,同时将倒计时状态置为1
accomplish<='1';
f2:=f2;
s2:=s2;
end if;
else --其他情况下,个位减一,倒计时状态置0
f2:=f2-1;
accomplish<='0';
end if;
end if;
S<=s2;
F<=f2;
end if;
end process;
end one;
⑤比较模块:
将按键模块和随机数模块传入的位置信息进行比较,如果相同,则传送信号‘1’给计分模块,否则传送信号‘0’。
--compare
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity compare is
port(Data_out,button: IN std_logic_vector(1 downto 0);
clk: IN std_logic;
accomplish: IN std_logic;
true: OUT std_logic;
start: IN std_logic);
end compare;
architecture one of compare is
begin
process(clk)
begin
if(start='1') then--如果游戏开始且尚未结束,则进行判断
if(accomplish='0') then
if(Data_out=button) then--如果位置信息相同,则置信号为高电平
true<='1';
else
true<='0';
end if;
end if;
end if;
end process;
end;
⑥计分模块:
判断比较模块传递的信号,如果位置信息相同则分数加一,否则分数保持不变。
--score
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity score is
port(clk: IN std_logic;
start: IN std_logic;
true: IN std_logic;
accomplish: IN std_logic;
reset: IN std_logic;
G: OUT std_logic_vector(7 downto 0));--分数
end score;
architecture one of score is
begin
process(clk,start,true)
variable temp1,temp2: std_logic_vector(3 downto 0);--分数的十位与个位
begin
if(clk'event and clk='1') then
if reset='1' then--重置分数
temp1:="0000";--十位
temp2:="0000";--个位
else
if start='1' and true='1' then--如果游戏开始,且命中地鼠
if accomplish='0' then --如果倒计时没有结束
temp2:=temp2+1; --个位+1
if(temp2="1010")then --如果个位等于10,则十位加一,个位置0
temp1:=temp1+1;
temp2:="0000";
end if;
end if;
elsif start='1' and true='0' then--如果游戏开始,地鼠未被命中,则分数保持不变
if accomplish='1' then
temp2:=temp2;
temp1:=temp1;
end if;
elsif start='0' then --如果拨下开始键,则分数清零
temp2:="0000";
temp1:="0000";
end if;
G(7 downto 4)<=temp1;
G(3 downto 0)<=temp2;
end if;
end if;
end process;
end one;
⑦显示模块:
根据各模块传送的数据,通过动态扫描技术实现相应内容的显示。
--显示模块
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity transform is--定义多个接收端用来接收其他模块的信息,从而向晶体管输出对应的信息
port(Data_out: IN std_logic_vector(1 downto 0);--地鼠位置信息
S: IN std_logic_vector(3 downto 0); --倒计时十位
F: IN std_logic_vector(3 downto 0); --倒计时个位
G: IN std_logic_vector(7 downto 0); --分数
clk: IN std_logic; --动态扫描时钟信号
true: IN std_logic; --比较模块传来的位置比较结果
sel: OUT std_logic_vector(7 downto 0); --位选信号
led: OUT std_logic_vector(6 downto 0)); --晶体管显示
end transform;
architecture one of transform is
begin
process(clk,true)
variable m: integer range 0 to 4:=0;--定义变量m,以区分不同时间片下晶体管的显示区域
begin
if(clk'event and clk='1') then
if(m=0) then --m=0,以LED形式呈现地鼠(m=others时情况类似)
m:=m+1;
if(data_out="00") then
sel<="11101111";
led<="1111110";
if(true='1') then --如果命中,则地鼠呈现被打扁状
led<="0001000";
end if;
elsif(data_out="01") then
sel<="11011111";
led<="1111110";
if(true='1') then
led<="0001000";
end if;
elsif(data_out="10") then
sel<="10111111";
led<="1111110";
if(true='1') then
led<="0001000";
end if;
elsif(data_out="11") then
sel<="01111111";
led<="1111110";
if(true='1') then
led<="0001000";
end if;
end if;
elsif(m=1) then --如果 m=1,切换显示倒计时的十位
m:=m+1;
sel<="11110111";
case S is
when "0000" => led<="0111111";--0
when "0001" => led<="0000110";--1
when "0010" => led<="1011011";--2
when "0011" => led<="1001111";--3
when "0100" => led<="1100110";--4
when "0101" => led<="1101101";--5
when "0110" => led<="1111101";--6
when others => led<="0000000";
end case;
elsif(m=2) then --如果 m=2,切换显示倒计时的个位
m:=m+1;
sel<="11111011";
case F is
when "0000" => led<="0111111";--0
when "0001" => led<="0000110";--1
when "0010" => led<="1011011";--2
when "0011" => led<="1001111";--3
when "0100" => led<="1100110";--4
when "0101" => led<="1101101";--5
when "0110" => led<="1111101";--6
when "0111" => led<="0000111";--7
when "1000" => led<="1111111";--8
when "1001" => led<="1101111";--9
when others => led<="0000000";
end case;
elsif(m=3) then --如果 m=3,切换显示分数的十位
m:=m+1;
sel<="11111101";
case G(7 downto 4) is
when "0000" => led<="0111111";--0
when "0001" => led<="0000110";--1
when "0010" => led<="1011011";--2
when "0011" => led<="1001111";--3
when "0100" => led<="1100110";--4
when "0101" => led<="1101101";--5
when "0110" => led<="1111101";--6
when "0111" => led<="0000111";--7
when "1000" => led<="1111111";--8
when "1001" => led<="1101111";--9
when others => led<="0000000";
end case;
elsif(m=4) then --如果 m=4,切换显示分数的个位
m:=0;
sel<="11111110";
case G(3 downto 0) is
when "0000" => led<="0111111";--0
when "0001" => led<="0000110";--1
when "0010" => led<="1011011";--2
when "0011" => led<="1001111";--3
when "0100" => led<="1100110";--4
when "0101" => led<="1101101";--5
when "0110" => led<="1111101";--6
when "0111" => led<="0000111";--7
when "1000" => led<="1111111";--8
when "1001" => led<="1101111";--9
when others => led<="0000000";
end case;
end if;
end if;
end process;
end one;
之后将编译好的子模块导入新项目中,将各模块生成各个单独的元器件,创建原理图文件,将各模块元器件导入,连线。
仿真
五、板载测试
①通电下载后,随机数生成模块首先开始运作,前四位数码管显示随机出现的虚拟地鼠模型
②拨动reset键重置倒计时,进入游戏准备阶段。
③拨动选择游戏难度。
④拨动start键开始游戏,倒计时开始,比较和按键模块开始运行。
⑤如果击中地鼠,地鼠呈现被打扁状,分数+1,指示灯闪亮。
⑥倒计时未结束拨动reset键使置1,由于游戏期间不允许暂停,故倒计时重置,分数暂留,直至reset键置0,分数清零。
⑦倒计时结束后,分数保持不变,按键和比较模块停止工作,直至start置0,分数清零。
六、不足之处
地鼠一次只能出现一只且器械老化存在一定的干扰(时间紧迫,按键抖动未加)。
七、心得体会
通过这次实验设计,我真真正正地从实践中学到了很多。经历了无数的困难,我被给予了更多的经验与教训,让我有足够的能力去力图改进和应对下一次可能出现的艰难险阻。
在一开始的尝试过程中,我尝试以一个大文件的形式,写出一个极具整体性的VHDL代码,奈何能力不足,在实现上遇到了不少的困难,给自己增加了不少不必要的负担。在经过一段时间的考虑后,我选择将整体拆分成多个不同的小模块逐一实现,最后以原理图的方式连线实现。在新一轮的实践中,我试图将显示模块嵌入在其他各个小模块中,实践中发现这种方法存在诸多不便,最终确立将各模块完全剥离,得到了现阶段的最优的方案。在接下来的实现过程中,常常遇到因为考虑不周而使得部分模块超时执行的不正常现象,也有遇到不熟悉VHDL部分语句而出现的逻辑错误,在查阅资料并多次实验尝试各种不同的方法后,问题渐渐得以解决,最终实现了基本的打地鼠功能。
在板载测试成功后,此时距离提交成果的最后期限还有很多时间,在这段时间里,我开始考虑如何去完善和美化它,在之后,我陆陆续续修正了其中的一些不足之处,完善了部分代码,同时加入了地鼠的第二阶段形态以及游戏模式选择控件,使得游戏整体更加符合现实生活中真实用户的需求,最终得到了现在相对成熟的成果。
在这次的课程设计中,我不仅仅提高了自己的动手能力,更为重要的是理解了EDA融入于我们生活的真谛,我们真正学习技术,不应只会浅薄的纸上谈兵,更应该把技术应用于实践,用“死”的代码赋予现实生活丰富多彩。EDA是,又不是简简单单的一种技术,它是我们链接数字世界与三维世界的一把钥匙,是我们人与机械器件沟通的纽带与桥梁。我们要时时刻刻记住自己身为一名工科生的责任与担当,化静为动,化“死”为生。