发布时间:2026/8/29 20:12:46
基于FPGA的数字打地鼠游戏设计:从状态机到硬件实现 1. 项目概述从零到一构建一个数字“打地鼠”游戏最近在重温一些经典的小游戏发现“打地鼠”这个玩法虽然简单但其中蕴含的数字逻辑设计思想却非常精妙。它不像大型游戏那样依赖复杂的图形引擎和物理计算而是将核心玩法完全建立在状态机、时序控制和人机交互这些基础的数字电路概念之上。这让我萌生了一个想法能不能抛开高级编程语言和现成的游戏引擎纯粹用数字系统设计的思路从最底层的逻辑门开始亲手搭建一个“打地鼠”游戏这听起来像是一个硬核的电子工程实验但实际上它是一次绝佳的数字逻辑综合实践能让你透彻理解计数器、分频器、随机数生成、显示驱动以及有限状态机FSM是如何协同工作最终变成一个可玩、可交互的产品的。这个项目非常适合电子工程、计算机科学相关专业的学生或者任何对数字电路和FPGA/CPLD开发感兴趣的硬件爱好者。通过完成它你不仅能巩固《数字逻辑电路》或《计算机组成原理》课程中学到的知识更能获得从需求分析、模块划分、代码编写Verilog/VHDL到硬件实现与调试的完整项目经验。最终你将得到一个在开发板如Basys3、DE10-Standard等上运行的实体游戏看着LED灯或七段数码管模拟的地鼠被你用按键“敲打”下去那种成就感是单纯仿真无法比拟的。接下来我就带你一步步拆解这个项目的设计思路与实现细节。2. 核心需求与整体架构设计2.1 游戏规则与功能定义在动手画电路图或写代码之前我们必须先把游戏规则用硬件能理解的“语言”定义清楚。一个基本的打地鼠游戏包含以下核心功能游戏场地通常用多个LED灯来模拟地洞灯亮表示地鼠冒出灯灭表示地鼠缩回或被打中。随机出现地鼠在哪个“洞”哪盏LED出现、出现多久必须是随机的以增加游戏的可玩性。玩家交互玩家通过对应的按键来“敲打”亮起的LED。击中后该LED应立即熄灭并可能伴随一个“击中”的反馈如另一个LED闪烁或蜂鸣器响。计时与计分游戏有固定的时间限制例如60秒玩家需要在时间内尽可能多地击中地鼠。系统需要实时显示剩余时间和当前得分。游戏状态控制需要明确的“开始”、“进行中”、“结束”状态并能复位重启。将这些需求翻译成硬件模块我们的大脑里就应该浮现出一个初步的框图。整个系统需要一个“大脑”来协调一切这就是主控状态机。它接收来自按键的“开始”信号管理游戏计时器并控制游戏流程。2.2 系统顶层模块划分基于上述需求我们可以将整个系统划分为以下几个核心功能模块它们通过特定的信号线相互连接时钟与复位模块为整个系统提供基准时钟和全局复位信号。这是所有同步数字电路的起点。伪随机数生成器PRNG模块这是实现“随机出现”的关键。在数字电路中我们无法获得真正的随机但可以通过线性反馈移位寄存器LFSR或基于计数器的算法产生近似随机的序列用于决定地鼠出现的位置和持续时间。地鼠行为控制模块这是游戏逻辑的核心。它根据PRNG模块产生的随机数决定在某一时刻点亮哪个LED地鼠冒出并控制该LED保持点亮的状态地鼠停留时间同时监听对应的按键输入。如果按键在点亮期间被按下则视为击中该LED立即熄灭并触发计分。计时器模块一个递减计数器从预设的初始值如60秒对应的时钟周期数开始每过一秒减一直到为零。计时为零时向主控状态机发送“时间到”信号。计分器模块一个递增计数器每次接收到有效的“击中”信号时加一。其数值需要输出给显示模块。显示驱动模块负责将计时器和计分器的二进制数值转换成能在七段数码管或LCD屏幕上显示的段选和位选信号。如果使用LED矩阵则负责行列扫描。按键消抖模块机械按键在按下和弹起时会产生物理抖动导致单次按压被误识别为多次。此模块通过延时采样来滤除抖动确保输入信号的稳定可靠。主控有限状态机FSM模块协调以上所有模块。它定义了几个状态如IDLE等待开始PLAY游戏进行END游戏结束并根据按键、计时器信号在这些状态间切换同时控制其他模块的使能信号。注意模块划分是数字系统设计中最关键的一步它直接决定了代码的可读性、可维护性和调试难度。一个好的原则是“高内聚、低耦合”即每个模块只负责一个明确的功能模块间通过清晰的接口通信。3. 关键模块的详细设计与实现3.1 伪随机数生成器PRNG的设计随机性是游戏的灵魂。在FPGA中常用线性反馈移位寄存器LFSR来产生伪随机序列。一个简单的16位LFSR可以通过异或XOR特定抽头位的反馈来生成周期非常长的序列。例如一个最大长度的16位LFSR其反馈多项式可以是x^16 x^14 x^13 x^11 1。在Verilog中可以这样实现module lfsr ( input wire clk, input wire rst_n, input wire en, // 使能信号每个游戏周期使能一次 output reg [15:0] random_num ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin random_num 16‘hACE1; // 初始种子不能为全0 end else if (en) begin // 根据多项式进行移位和反馈 random_num {random_num[14:0], random_num[15] ^ random_num[13] ^ random_num[12] ^ random_num[10]}; end end endmodule这个random_num的每一位或其中几位如低4位可以用来决定地鼠出现的位置对LED数量取模和停留的时长作为一个计数器的预置值。实操心得LFSR的初始种子seed很重要如果每次上电都是同一个种子那么“随机”序列就完全一样游戏会变得可预测。一个常见的技巧是利用板上未被使用的拨码开关状态或一个上电后自由运行的计数器值作为初始种子这样可以增加随机性。3.2 地鼠行为控制模块有限状态机的应用这是最有趣的部分。每个“地鼠”或者说每个LED通道都可以用一个独立的小型状态机来控制。以单个地鼠为例其状态可以简化为隐藏HIDDENLED熄灭。在此状态等待一个随机的时间间隔。出现APPEARLED点亮。进入此状态的同时启动一个“停留计时器”计时时长由PRNG产生的一个随机数决定。在此状态下持续检测对应按键。被击中HIT如果按键在“出现”状态下被按下则立即跳转到此状态。LED熄灭向计分模块发送一个脉冲信号然后快速返回“隐藏”状态。超时消失MISS如果“停留计时器”归零前都未被击中则跳转到此状态。LED熄灭然后返回“隐藏”状态。这个状态机确保了地鼠行为的自治性。多个地鼠实例并行运行由主控FSM统一使能或禁用例如在游戏未开始时所有地鼠状态机强制保持在HIDDEN状态。3.3 计时与显示驱动计数器与数码管扫描计时器模块本质上是一个带加载和使能功能的递减计数器。假设系统时钟是100MHz周期10ns要实现1秒递减一次我们需要先进行分频产生一个1Hz的使能信号。// 产生1秒使能信号的分频器 reg [31:0] cnt_1s; wire en_1s (cnt_1s 32‘d99_999_999); // 100M - 1 always (posedge clk or negedge rst_n) begin if (!rst_n) cnt_1s 0; else if (game_state PLAY) begin // 只在游戏进行时计时 if (cnt_1s 32‘d99_999_999) cnt_1s 0; else cnt_1s cnt_1s 1; end else begin cnt_1s 0; end end // 秒计数器 reg [5:0] time_left; // 最大60秒 always (posedge clk or negedge rst_n) begin if (!rst_n) time_left 6‘d60; else if (load_time) time_left 6‘d60; // 游戏开始加载时间 else if (en_1s time_left 0) time_left time_left - 1; end显示驱动模块以驱动4位数码管显示“分数”和“时间”为例通常采用动态扫描法。由于人眼的视觉暂留我们可以快速轮流点亮每一个数码管。需要两个部分一个是从二进制数到七段译码的模块bin_to_seg另一个是扫描控制模块它负责在不同的时间段选择输出哪一位数据的译码结果并控制对应数码管的位选信号。4. 系统整合与顶层连接4.1 信号定义与模块实例化当所有子模块设计完成后我们需要创建一个顶层模块通常命名为top_game_hit_mole或game_hit_mole。在这个顶层文件中你需要定义所有输入输出包括时钟引脚clk、复位引脚rst_n、多个按键输入btn[3:0]、多个LED输出led[3:0]、数码管段选seg[7:0]和位选an[3:0]等。这些需要与你的开发板原理图严格对应。声明内部连接线网用于连接各子模块的中间信号例如wire hit_pulse;击中脉冲wire [3:0] random_pos;随机位置wire timer_expired;时间到等。实例化所有子模块使用模块名 实例化名 ( .端口名(连接信号), ... );的语法将各个子模块像搭积木一样连接起来。确保时钟和复位信号正确连接到所有需要它们的模块。4.2 主控状态机的实现主控FSM是系统的调度中心。一个典型的三段式描述状态声明、状态转移、输出逻辑如下localparam IDLE 2‘b00, PLAY 2‘b01, END 2‘b10; reg [1:0] current_state, next_state; // 状态转移逻辑时序部分 always (posedge clk or negedge rst_n) begin if (!rst_n) current_state IDLE; else current_state next_state; end // 状态转移条件组合逻辑部分 always (*) begin next_state current_state; // 默认保持 case (current_state) IDLE: if (start_btn_pressed) next_state PLAY; PLAY: if (timer_expired) next_state END; END: if (reset_btn_pressed) next_state IDLE; default: next_state IDLE; endcase end // 输出逻辑组合逻辑部分 assign game_enable (current_state PLAY); // 游戏进行使能 assign score_reset (current_state IDLE); // 空闲时复位分数 assign time_load (current_state IDLE start_btn_pressed); // 开始时加载时间这个简单的FSM就构成了游戏流程的骨架。5. 仿真测试与硬件调试实录5.1 编写测试平台Testbench在烧录到板子之前必须用仿真来验证逻辑的正确性。使用Verilog或SystemVerilog编写测试平台Testbench给顶层模块施加激励。时钟和复位生成使用always #5 clk ~clk;生成时钟。模拟按键使用initial块在特定仿真时间点将start_btn等信号拉高再拉低模拟按键动作。监视关键信号使用$display或$monitor在控制台打印状态、分数、时间的变化。观察波形在ModelSim、Vivado Simulator等工具中查看波形图直观地检查状态机跳转、计数器变化、LED输出是否与预期一致。重点检查按下开始键后游戏是否进入PLAY状态地鼠LED是否随机点亮按下对应按键后LED是否熄灭且分数增加计时器归零后是否进入END状态。5.2 硬件调试常见问题与技巧仿真通过后烧录到FPGA开发板才是真正的挑战。以下是我踩过的一些坑和解决办法问题1按键不灵敏或连击。原因未做消抖处理或消抖参数不当。解决确保使用了可靠的按键消抖模块。消抖延时通常需要20ms左右对应系统时钟周期数需要准确计算。例如100MHz时钟20ms对应2,000,000个周期。一个简单的消抖思路是当检测到按键电平变化后启动计数器延时一段时间后再采样如果采样值稳定则确认为有效按键。问题2LED或数码管显示混乱、闪烁。原因最可能是时序问题。例如多个进程同时驱动同一个信号数码管扫描频率不合适太快会暗太慢会闪烁或者产生了毛刺。解决检查多驱动在综合报告中查找“multi-driven net”警告。调整扫描频率数码管扫描时钟通常在1KHz左右每位数码管点亮约1ms。计算分频比scan_clk sys_clk / (4 * 1000)。消除毛刺对于直接由组合逻辑驱动的输出如一些状态指示LED可以考虑在输出前加一级寄存器用时钟同步一下避免毛刺。问题3游戏行为完全固定没有随机性。原因LFSR的种子每次上电都相同或者LFSR没有被正确使能一直处于复位或使能信号常高/常低。解决用拨码开关设置LFSR种子每次上电手动拨动一下。检查控制LFSR使能en的逻辑。通常应该在地鼠从HIDDEN状态准备向APPEAR状态转移时产生一个时钟周期的高脉冲去使能LFSR获取一个新的随机数。如果en常高LFSR每个时钟都变化你取样的瞬间值可能过于“随机”且难以关联如果en常低LFSR根本不工作。问题4综合后资源占用过高或时序违例。原因设计过于复杂或逻辑级数太多。解决优化状态机使用独热码One-Hot编码对于FPGA内的触发器资源丰富但组合逻辑资源相对紧张的情况有时更友好但可能会增加触发器用量。二进制编码更节省触发器。根据综合报告权衡。流水线化对于复杂的组合逻辑路径如从随机数到LED选择的复杂运算可以插入寄存器打拍分割关键路径。检查不必要的寄存器有些中间变量可能不需要寄存直接用wire连接组合逻辑即可。调试技巧“LED大法”将内部关键信号如主控状态机状态current_state、击中脉冲hit_pulse、时间到信号timer_expired引到板子上闲置的LED上。通过观察LED的亮灭可以直观判断程序运行到了哪一步。这是硬件调试最朴素也最有效的方法。利用嵌入式逻辑分析仪像Xilinx的ILAIntegrated Logic Analyzer或Intel的SignalTap II可以在FPGA内部抓取信号的真实波形功能类似示波器但无需物理探头。当“LED大法”不够用时这是终极武器。你需要事先在代码中标记好想要观察的信号并在工具中设置触发条件。6. 功能扩展与进阶思考一个基础版本实现后你可以考虑以下扩展让游戏更具挑战性和专业性增加难度等级通过拨码开关选择难度。高难度可以表现为地鼠停留时间更短、同时出现的地鼠数量更多、地鼠移动速度更快需要LED跑马灯模式模拟移动地鼠。音效反馈增加一个PWM模块驱动无源蜂鸣器。击中时发出一段欢快的短音错过时发出低沉的音效游戏结束时播放一段旋律。这需要你了解简单的音频合成和乐理知识。VGA/OLED显示将游戏画面输出到VGA显示器或OLED屏幕上用真正的图形代替LED和数码管。这需要引入显存、像素时钟、同步信号生成等更复杂的模块是一个质的飞跃。联网对战设计两个相同的游戏模块通过UART或SPI通信实现双人比分对战。这涉及到通信协议的设计和同步问题。使用软核处理器如果你用的FPGA支持如Xilinx MicroBlaze, Intel Nios II可以尝试用C语言在软核上实现游戏逻辑和随机数算法而FPGA逻辑只负责最底层的显示扫描和按键消抖。这可以让你体会软硬协同设计的思路。从设计一个简单的状态机到协调多个模块共同工作从仿真验证到解决实际的硬件时序问题“打地鼠”这个小小的游戏项目几乎涵盖了中小规模数字系统设计的全部核心环节。它像一块试金石能清晰地检验你对同步设计思想、模块化方法、仿真调试技能的理解程度。当你最终在开发板上成功运行起自己设计的游戏并邀请朋友来试玩时你会深刻体会到那些看似枯燥的逻辑门和寄存器原来可以如此生动有趣。这或许就是硬件设计的魅力所在——在确定的物理规则下创造出充满不确定性的互动体验。

相关新闻

2026/8/29 20:07:45

从零实现一个Curio风格HTML文件管理仓库:Node.js构建指南

HTML 文件是 Web 世界里最常见也最容易被忽视的一类资产。最近看到一个名为 Curio 的项目,它的定位很简洁:a place for HTML files,也就是给 HTML 文件一个专门的“地方”来存放、预览和管理。对于经常写小工具、做页面原型、收藏代码片段、整…

2026/8/29 20:07:45

ABD共识算法实战:Quorum复制与近似一致性边界

分享一套从零理解 ABD 共识算法的实战笔记,底层拆解 Quorum 复制机制与“几乎一致性”边界,配合可运行的 Python 模拟代码,帮助你彻底搞懂读写多数派、版本号推进和副本容错的核心逻辑。适合后端开发者、分布式系统学习者以及准备系统设计面试…

2026/8/29 20:07:45

贝叶斯AI与不确定性建模:用NumPyro实现贝叶斯线性回归

过去几年,AI 圈有一个有趣的现象:当普通开发者在疯狂堆参数、刷榜的时候,一批站在金字塔尖的研究者,却开始谈论一个听起来很“古典”的方向——贝叶斯方法。看到“Jeff Dean们,赶在贝叶斯AI到来之前跳船”这样的标题&a…

2026/8/29 20:22:47

Agent 的基本架构由哪些核心组件构成?

核心结论 (BLUF): AI Agent(人工智能智能体)的本质是以大语言模型(LLM)作为认知大脑(Brain),通过**规划(Planning)拆解复杂目标,借助记忆&#xf…

2026/8/29 20:22:47

Oracle数据库迁移至达梦数据库

目录 一、Oracle数据库配置 1、安装Oracle数据库 2、配置Oracle数据库实例 3、执行Oracle数据库SQL业务 4、Oracle信息确认 二、迁移前评估 1、启动DTS工具 2、配置评估对象 三、执行迁移 1、创建迁移节点 2、配置迁移数据源 3、配置迁移策略 4、选择迁移对象 5、…

2026/8/29 20:22:47

Meta叫停AI native计划:AI战略收缩期的技术栈与团队应对

Meta 被曝放弃 "AI native" 计划,还曾拟将部分团队裁员 60%。这大概是最近科技行业里信息量最大的一条组织调整新闻。消息最早来自海外科技媒体的爆料,Meta 官方并没有完整披露最终调整方案,所以“60%”这个数字不能直接当成已经落…

2026/8/29 20:22:47

Qwen3.8-Flash-Next解析:轻量快速推理与下一代架构创新

最近大模型圈子的命名越来越有意思了。从 Qwen、Qwen2、Qwen3 一路走到 Qwen3.8-Flash-Next,名字里的信息量其实比参数数字更大。Flash代表轻量快速推理路线,Next则暗示这不是简单的小版本迭代,而是提前吸收了下一代架构的设计思路。这篇文章…

2026/8/29 20:22:47

开发者视角,各云盘免费额度梳理

作为开发者,选云盘不能只看免费空间,还得看同步稳定性、API 支持、上传下载速度,以及批量备份脚本能不能跑顺。这篇文章从免费空间出发,横向对比移动云盘、百度网盘、夸克网盘在开发者场景下的表现。免费空间与基础能力对比项目移…

2026/8/28 16:16:17

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/28 16:16:21

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/28 16:16:22

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/29 0:01:10

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

一、设置环境变量etc目录下的profile.d文件目录 /etc/profile.d1、编写 vi test.sh文件内容# jdk变量 export ZHK_HOME/root export PATH$PATH:$ZHK_HOME/test # 可以取出来ZHK_HOME变量给ZZZ_HOME赋值 export ZZZ_HOME${ZHK_HOME}/test2、刷新 执行source /etc/profile 命令使…

2026/8/29 0:01:10

【JavaScript】内存管理-垃圾回收机制-内存泄露

内存管理 C 语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。 而 JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。释放的过程称为垃圾回收。 整…

2026/8/29 0:01:10

Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP 的目标是把 MCP(Model Context Protocol)能力延伸到真实嵌入式硬件实验室:AI Agent 通过一个标准化的 MCP Server,就能查看目标板状态、控制上电断电、复位开发板、读取串口日志,甚至执行镜像刷写。对于经…

2026/8/28 16:16:48

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/28 16:16:50

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/28 11:06:45

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…