FPGA掷骰子游戏实现:VHDL模块化设计与板级调试全解析

发布时间:2026/9/19 2:39:19
FPGA掷骰子游戏实现:VHDL模块化设计与板级调试全解析 简介一份北京邮电大学数字电路实验下的完整报告围绕“掷骰子游戏电路”的FPGA设计与实现展开适合数字电路课程学生和FPGA初学者对照学习。实验覆盖两人按键游戏、随机骰子点数生成、数码管与8x8点阵联合显示、蜂鸣器音效以及多局判胜等任务要求并对第六局平局、随机数生成、时钟选择和游戏顺序等常见问题做了改进与排错说明。报告采用模块化设计详细介绍了分频器、点阵显示、数码管显示、随机数生成、按键防抖、音频和逻辑判断等模块的设计思路配有系统框图、状态图、ASM流程图和仿真波形分析同时给出资源利用情况、功能说明与源程序目录。整份资源为1个PDF文件大小2.3MB已有331人学习下载可用于快速掌握FPGA数字电路实验从需求分析、模块划分到仿真验证的完整方法也可作为同类课程实验报告撰写的参考。1. 掷骰子游戏电路的FPGA实现从VHDL模块划分到板级调试的完整复盘一个看似简单的掷骰子游戏真要在FPGA上跑起来远比想象中费功夫。两人各按一次按键随机出两个16的骰子数数码管显示点数8×8点阵显示胜者蜂鸣器给提示音还要支持最多六局、第六局平局自动复位——这些功能叠加在一起背后是分频器、按键防抖、随机数生成、状态机胜负判断、动态扫描显示和点阵驱动这些基础模块的协同工作。北京邮电大学这份数电实验报告最有价值的地方是它把完整的错误修正过程保留了下来随机数生成器同时生成两个数导致奇偶受限、未分频时钟导致逻辑跑飞、第六局平手规则缺失等。对正在做FPGA课程设计的人或者想系统梳理VHDL模块化设计、仿真验证和板级排错思路的人这份报告比那些只给演示视频的资源值得读得多。下面按模块拆开结合报告里的关键代码和实际调试要点逐层说清楚。2. 系统级模块划分与时钟链路设计分频器、按键防抖和它们背后的时序约束2.1 模块划分为什么采用自顶向下设计而不是单实体堆代码实验使用的是EPM系列芯片程序语言采用VHDL各模块通过VHDL或者基本器件实现最后用电路原理图把各个模块连接起来。这种自顶向下的思路和C语言那种顺序执行的思维差别很大VHDL描述的是硬件结构每个模块对应真实电路的一部分并行语句和顺序语句对应的是并联和级联关系。如果不用模块化设计把分频、防抖、逻辑判断、显示刷新全部塞进一个entity里代码会迅速膨胀而且仿真和综合都很难定位问题。报告给出的模块划分是分频器clk_1hz、多人游戏控制器people、点阵显示模块point、数码管显示模块tube_t、逻辑与随机数生成模块comp、按键控制与防抖模块anjian、音频模块。由于数据量小各个模块内部通过编程语言实现寄存功能没有单独设立存储器这意味着节省了存储资源但代价是状态分散在各个模块的process里调试时要特别小心信号值的同步。这个取舍在小型FPGA项目里很常见资源利用率报告中提到逻辑元件用了41%管脚31%说明整体规模不大模块化带来的连线复杂度在可接受范围内。2.2 1kHz分频器从25MHz系统时钟到可用的低频脉冲分频是FPGA设计中绕不开的一环。系统时钟通常几十兆赫兹但游戏逻辑、按键检测、显示扫描需要的是慢得多的时钟脉冲。报告里的分频模块是这样写的library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_signed.all; entity clk_1khz is port( clk_in : in std_logic; clk : out std_logic ); end entity clk_1khz; architecture behavior of clk_1khz is signal count0 : integer range 0 to 49999; begin process(clk_in) variable clk_i : std_logic : 0; begin if (clk_inevent and clk_in 1) then if count0 49999 then count0 0; else count0 count0 1; end if; if count0 0 then clk_i : not clk_i; end if; end if; clk clk_i; end process; end behavior;这段代码的核心逻辑是计数器在0到49999之间循环当计数到0时翻转输出信号。count0从0走到49999一共50000个输入时钟周期输出每隔50000个周期翻转一次也就是说输出的完整周期是100000个输入时钟周期。如果clk_in是25MHz那么得到的就是250Hz而不是1kHz。报告里写的“1khz分频器”严格来说是个参数错误要得到1kHz需要把计数上限改成12499。这个细节很多人会忽略直接抄代码才发现频率不对。实际使用时游戏逻辑需要的是“按键值得采样”频率通常几十到几百赫兹就够太高反而容易把组合逻辑的竞争冒险带进来。分频器的另一个坑是初始值变量clk_i如果是信号而不是变量在FPGA综合时可能会被优化掉或者行为仿真时出现未定义状态所以这里用变量并在声明时赋初值是个好习惯。2.3 按键防抖边缘检测的经典实现机械按键按下和释放时会产生十几毫秒的电平抖动直接接进逻辑会触发多次误判。报告里的防抖模块用了两级触发器外加组合逻辑library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity fangdou is port( reset : in std_logic; clk : in std_logic; reseto : out std_logic ); end fangdou; architecture behavior of fangdou is signal tmp1 : std_logic; signal tmp2 : std_logic; begin process(clk) begin if (clkevent and clk 0) then tmp2 tmp1; tmp1 reset; end if; end process; reseto clk and tmp1 and (not tmp2); end behavior;这里用下降沿采样把按键电平存进tmp1同时把上一个时钟周期的值移到tmp2。输出表达式是clk、tmp1、tmp2三者的组合当clk为高、当前按键状态为高、上一拍状态为低时输出一个高电平脉冲。也就是说只有当检测到“从低到高跳变”时才会产生触发信号而且这个触发信号被限制在clk高电平期间相当于一个同步化处理。这种防抖其实只能消除亚稳态和短毛刺对于持续时间超过几个周期的机械抖动还是需要靠慢时钟计数延时来滤除。报告里的设计用了较慢的clk从效果看能工作但如果按键抖动时间特别长建议再加一个“抖动窗口计数器”只有当按键持续稳定超过10ms才认定有效。仿真波形里可以清楚看到防抖模块输出的fangdouTESTout只有单拍脉冲这就是正确行为。3. 随机数生成与游戏规则状态机VHDL里的两个随机骰子是怎样炼成的3.1 随机数生成器的设计陷阱为什么不能用两个独立计数器骰子游戏的核心是每次按键得到16的随机数。FPGA里做真随机数不现实常用做法是用一个高速计数器在按键时刻锁存当前计数值由于计数器跑得足够快按键时刻的计数值近似随机。报告最初采用两个独立生成器同时生成两个骰子数为了避免两个数一样一个计数器加1另一个加2。结果发现问题了加2的计数器只能生成偶数比如1、3、5轮流完全无法覆盖奇数。而且两个生成器同时取值后续逻辑很难区分哪一个数对应哪一颗骰子带来了不少隐藏问题。后来改为同一个生成器分时取数每次按键只产生一个随机数按两次就得到两个数。这个改动虽然让“一轮掷两个骰子”需要按两次键但从硬件实现上大大简化了逻辑。下面是一个更实用的随机数生成方案基于线性反馈移位寄存器LFSR避免环形计数器周期太短的毛病entity lfsr_random is port( clk : in std_logic; enable : in std_logic; dice : out integer range 1 to 6 ); end entity; architecture behavior of lfsr_random is signal lfsr : std_logic_vector(3 downto 0) : 1011; begin process(clk) begin if rising_edge(clk) then if enable 1 then -- 4-bit LFSR反馈多项式 x^4x^31 lfsr (lfsr(0) xor lfsr(1)) lfsr(3 downto 1); end if; end if; end process; -- 取低3位模6再映射到1~6 dice (conv_integer(lfsr(2 downto 0)) mod 6) 1; end behavior;这段代码的关键在于enable信号只在按键防抖输出有效时拉高采到的就是当前LFSR状态。LFSR比简单计数器周期更长4位的最大周期是15个状态虽然还不够“随机”但做课程设计足够了。如果想提高随机性可以增加到5位或6位取多位的组合再取模。注意取模操作在综合时会生成除法器FPGA里逻辑会比较浪费更高效的做法是用查表法把LFSR的低位直接映射到16或者用组合逻辑判断。报告里的计数器分时采样方案虽然简单但胜在容易理解和综合。3.2 游戏规则的状态机映射游戏规则看起来简单翻译成状态机却要小心。第一局每人掷两个骰子和为7或11者胜如果没人胜进入第二局第二局开始每个人掷出的和必须和“自己第一局的和”相同才胜出最多六局第六局如果还没人胜就比较谁的和更大第六局如果最大和最并排报告里自拟了平局自动复位。这个规则里有两个容易混的点第一局和第二局判定标准不一样以及“和本人第一局相同”这个条件意味着必须把第一局的每个人和值存下来。报告里的comp模块用count_t表示当前局数peo表示当前玩家编号sum_1到sum_6存储每个玩家第一局的和sum1到sum6存储当前局的和。状态转移可以用表概括局数获胜条件实现方式第1局本局和等于7或11直接比较sum_x标记f1~f6对应玩家第2~5局本局和等于该玩家第1局的和先判断sum_x 0再与sum_1比较第6局无人重复成功时和最大者胜比较sum1~sum6的最大值任意局多人同时满足条件时按优先顺序取第一个或增加平局判定VHDL里用case语句实现这个状态机很容易写乱因为每个玩家有独立的和值但是胜负判断要所有人一起比较。报告中的做法是一组优先级elsif从f1到f6逐个人检查谁先满足就把res设为谁。这样做有个问题如果同时有两个人都满足获胜条件结果取决于if判断顺序而不是真正的平局处理。报告在仿真中也发现“平始终为高电平输出”分析后认为是标志位冲突导致也就是当多个条件同时满足时输出被多次赋值。后来在第6局的平局场景补了平局自动复位逻辑算是对规则缺失的补救。3.3 时钟选择错误带来的逻辑跑飞报告第五部分的故障分析里提到一个很典型的问题逻辑模块最初用了未分频的系统时钟频率太高结果出错率极高代码根本跑不完一轮游戏。这个现象的根因有两个第一按键是慢速异步信号高频时钟采样任意时刻的按键电平很容易采到跳变沿附近的亚稳态第二逻辑模块里组合路径较多高频时钟下拍拍组合逻辑的传播延迟可能超过时钟周期导致下一拍采到不稳定的中间值。引入1kHz分频后时钟周期变成毫秒级远大于组合逻辑延迟信号能稳定下来问题就消失了。这一点在很多FPGA新手工程里很有代表性仿真波形全部正确一上板就乱跑多半是异步信号同步化和时钟频率没有配合好。正确做法是按键信号先经过两级触发器同步再用慢时钟做防抖处理最后才进入状态机。报告里把分频后的时钟直接用于逻辑模块相当于用慢时钟天然规避了大部分亚稳态虽然简单但效果确实明显。4. 显示子系统数码管动态扫描、点阵驱动与无声的信号4.1 数码管显示骰子点数、局数与动态扫描的编码关系实验要求甲乙两人各有两颗骰子需要四位数码管显示点数DISP0-DISP1显示甲的两位数字DISP2-DISP3显示乙的DISP7显示当前局数。如果直接用四位独立的数码管管脚会占用很多所以通常采用动态扫描每一时刻只点亮一位循环扫描所有位利用视觉暂留让人感觉到四位同时在亮。扫描频率一般在200Hz到1kHz之间太慢会闪烁太快会拖累占空比导致亮度不均。段码映射是显示模块的核心。骰子数字1到6对应的共阴数码管段码常见写法如下case number is when 0 seg 00111111; -- 0 when 1 seg 00000110; -- 1 when 2 seg 01011011; -- 2 when 3 seg 01001111; -- 3 when 4 seg 01100110; -- 4 when 5 seg 01101101; -- 5 when 6 seg 01111101; -- 6 when others seg 00000000; end case;这里seg是7位段码加一位小数点按位赋值时要注意高低位顺序和共阴共阳差异。报告里的numberTEST信号输出的是“随机数对应的编码”但在仿真里发现数码管选位信号segTEST没有改变也就是位选一直锁在某一位。仿真中位选信号不变说明驱动位选的计数器或者移位寄存器没有工作。一种可能是位选信号被综合成了常量因为代码里如果只有一个case语句而没有根据计数器改变位选模式那么仿真时即使计数器变化位选也可能因为优化或未连接而保持常量。另一种可能是仿真激励没有给到能触发的复位信号位选寄存器初始未知默认输出为0。报告提到“未查明原因电路正常工作”这比较常见仿真波形不代表板级结果因为实际器件上电后所有信号都有确定的初始值而仿真里某些内部信号可能未初始化。4.2 8×8点阵的比赛结果与动画点阵显示模块是另一个容易踩坑的地方。8×8点阵通常用行驱动和列驱动常见做法是行接扫描输出列接数据输出。显示一个静态字符时把字符按行取模每行8位扫描一行输出一行数据。报告里的point模块用JuzhengTESTcol和JuzhengTESTrow两个输出分别控制列和行仿真波形显示点阵模块能正常输出模式选择函数对应的数据。如果要显示胜利者“甲”或“乙”需要预先定义两套8×8字模然后根据胜负结果选择字模数组。开机动画和结束动画本质上是多帧字模按顺序切换每帧显示一段时间。动画帧率控制在20帧/秒左右即可切换时钟可以单独用一个计数器分频产生。点阵扫描代码的基本框架如下process(clk_display) begin if rising_edge(clk_display) then if row_index 7 then row_index 0; else row_index row_index 1; end if; case row_index is when 0 row 00000001; col_data char_pattern(row_index); when 1 row 00000010; col_data char_pattern(row_index); -- ... when others null; end case; end if; end process;这里row控制哪一行被驱动col_data同时输出对应行的8位列数据。需要注意点阵的极性有些点阵行用高电平导通列用低电平导通代码里必须根据硬件原理图取反。报告中没有详细说明这一点但实际接板子时经常出现“点阵全亮”或者“显示镜像”的问题排查方法就是把扫描频率降低用单行测试输出确认每一行的驱动极性。4.3 显示刷新时钟与游戏逻辑时钟分开显示刷新频率通常1kHz左右用于动态扫描。游戏逻辑时钟可能更慢因为涉及按键采样和状态机跳转。如果两个模块共用一个时钟一方面扫描频率不够会导致闪烁另一方面游戏逻辑太快会导致组合逻辑竞争。报告的设计里分频模块输出了多个频率的时钟虽然原理图里只画了一个clk_1hz但数码管扫描和点阵扫描可能用了不同的分频系数。建议的做法是顶层模块中例化两个分频器一个输出1kHz用于显示扫描一个输出几十赫兹用于逻辑状态机。两者完全是不同时钟域信号跨越时钟域时必须处理好同步尤其是按键输入应该先同步到目标时钟域再使用。5. 仿真波形分析与故障排查实录从segTEST异常到第六局平局规则5.1 仿真环境与激励设计报告中的仿真版本只实现了基本功能包括按键触发随机数、两人游戏逻辑及显示模块时钟clk为0.04us也就是25MHz。仿真观测的模块包括防抖、随机数生成、数码管编码、点阵行选列选、扬声器输出。搭建测试台时最重要的是给到与真实应用一致的激励时序按键信号要加模拟抖动的脉冲序列时钟要分频出多个频率随机数模块要反复触发观察数值分布。下面是一个简单的VHDL testbench模板entity tb_top is end entity; architecture sim of tb_top is signal clk : std_logic : 0; signal btn0 : std_logic : 0; signal btn1 : std_logic : 0; signal seg : std_logic_vector(7 downto 0); -- ... 其它信号 begin clk not clk after 20 ns; -- 25MHz process begin btn0 0; wait for 100 ms; btn0 1; wait for 50 ms; btn0 0; wait for 200 ms; btn1 1; wait; end process; dut: entity work.top port map( clk clk, btn0 btn0, btn1 btn1, seg_out seg ); end architecture;testbench里用wait语句模拟人的按键动作按键电平保持足够长时间保证防抖模块能识别。仿真时看到防抖输出fangdouTESTout为高电平脉冲说明按键检测正常。但要注意防抖输出脉冲持续时间和输入按键电平时间有关如果输入按键时间太短输出可能没有完整的高电平脉冲这点在波形图上要结合时钟周期数判断。5.2 四个值得记录的Bug报告里的故障分析部分是这个实验报告最有营养的内容整理起来就是一张排错表Bug现象根本原因解决办法两人随机数出现2等差规律两个计数器分别1和22组无法生成奇数改为同一个计数器分时采样逻辑运行出错率高无法跑完整轮逻辑模块使用未分频的25MHz时钟引入1kHz分频时钟第6局平手时规则未定义规则原文缺失代码进入else冲突自拟平局提示并自动复位仿真中segTEST位选信号不变位选信号可能未被驱动或综合优化检查位选计数器、调整仿真激励其中随机数Bug最隐蔽仿真结果里a1呈2等差也就是1、3、5这样跳变说明2计数器的覆盖范围只有3个值。如果在第一局就掷出这个骰子获胜概率会明显偏离1/6。解决后改为同一个生成器分时取值两个骰子之间的相关性就没有那么强了。这个案例提醒我们随机数生成器在FPGA里要特别注意输出值的均匀性不能随便用两个独立计数器凑。5.3 用波形定位显示模块问题当显示器没有按预期变化时不要直接怀疑外部电路先看仿真波形里的内部信号。报告里的point模块输出了JuzhengTESTcol和row观察发现点阵数据变化正常那么问题很可能不在点阵驱动而在上层模式选择。而segTEST不变则要从位选信号的生成逻辑找位选是否接了一个固定电平位选切换计数器是否被复位信号锁住了波形上看某个信号长期为常量可以直接在仿真中强制翻转该信号如果输出跟着变化说明组合逻辑没问题如果输出不变说明信号被综合成常量或者根本没有连接。另一个常见问题是扫描频率过低仿真波形上看起来像没变化实际在板子上能正常显示。这时要缩小仿真时间步长或者减少分频系数。6. 扩展功能与资源优化技巧把两人游戏升级成六人可玩版报告在提高要求里提到多人游戏、开机动画、音乐播放和其他自定义功能实际实现的扩展包括通过额外按键增加游戏人数最大6人增加复位功能开机动画及音效两音节的简单铃音第六局平手提示后自动复位游戏顺序可以自由选择而不是固定轮流。这些扩展真正落地时需要动的是三个地方按键分配模块、玩家数据存储、动画音频状态机。多人游戏最直接的办法是把原来固定的甲乙双人互搏改成“当前玩家轮询”。用一个played_num计数器记录当前轮到几号玩家按键按下后随机得到两个数存入该玩家的寄存器然后切换到下一位。原报告里people模块承担了这个任务代码中使用了peo信号从1循环到people总人数。在VHDL里实现轮询状态机可以用下面这种简单的数字比较逻辑if peo people_total then peo peo 1; else peo 1; end if;这样做需要为每个玩家分别保存第一局的和值以及当前局的和值所以报告里出现了sum_1到sum_6和sum1到sum6两组信号。6个人时两名玩家按键掷出的点数存储会有重叠风险必须保证每个玩家独占一组寄存器。资源优化上可以用数组替代多个独立的signal比如定义type sum_array is array (1 to 6) of integer range 0 to 12;这样写代码更整齐综合时布局布线也更高效。报告中的实现使用521个逻辑元件占比41%说明芯片还有充足余量即使数组化也不会爆资源。开机动画和结束动画可以共用一套点阵驱动只需要在顶层加一个模式选择信号比如用两位状态区分“开机动画”、“游戏中”、“胜利动画”、“平局动画”。每播放一帧就把动画ROM里的数据送到点阵同时用分频器控制帧切换速度。声音效果与蜂鸣器连接音节频率可以通过不同的分频比得到两个音节交替就是简单铃音。这里要注意的是蜂鸣器驱动信号需要经过音频模块处理不能直接把游戏时钟当作PWM输出否则播放的就是刺耳的方波噪声。用计数器的溢出脉冲作为蜂鸣器驱动溢出频率就是声音频率在多个音频频率之间切换即可实现“开奖”音效和“胜利”音效。最后验证扩展功能时把仿真重点放在多玩家切换逻辑和第六局平局自动复位上因为这两处最容易引入状态冲突。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询