FPGA学习笔记-拔河游戏电路设计

要求:

设计拔河游戏电路,用按键与LED表示输入与输出。

(1)初始时,16个LED中间的两个点亮,然后游戏双方不停按动按键,点亮的两个LED向按动按键慢的一方移动;

(2)每按动一下按键,LED向对方移动一格;

(3)只要LED移动到头,游戏结束;

(4)工作时钟100Hz即可;

1 翻译

16 个 LED 上有两颗相邻亮点(一段"绳子中心标记"),两个人分别按左右按键:

  • 左边玩家按一下 → 亮点整体向右移动 1 格(朝对方移动)

  • 右边玩家按一下 → 亮点整体向左移动 1 格

  • 亮点移动到最左/最右边界 → 游戏结束

    题目里"向按键慢的一方移动"就是在描述:谁按得少/慢,中心就会被对方"拉"向自己这边。


2 4个关键技术点

  1. 时钟 :板上一般 50MHz/100MHz,但题目说"工作时钟 100Hz 即可"

    → 用分频做一个 100Hz tick(10ms 一拍)用于按键扫描/游戏逻辑。

  2. 按键消抖 :机械按键会抖动,不消抖会一次按出很多下

    → 10ms 扫描下,用"连续 N 次采样一致"当作稳定。

  3. 边沿检测 :只想要"一次按下=一个脉冲"

    → 稳定后的按键做 pressed_pulse(上升沿/下降沿取决于按键高低有效)。

  4. 位置寄存器 :用一个数 pos 表示"两颗亮点里左边那颗LED的位置"

  • LED 共 16 个,编号 0..15

  • 两颗相邻亮点 = led[pos]led[pos+1]

  • 初始中间两颗亮:pos=7(亮 7 和 8)

边界:pos 只能在 0..14

  • pos=0(亮 0,1)或 pos=14(亮 14,15) → game_over

3 游戏逻辑(核心,写成规则翻译成代码)

在每个 tick_100hz 时刻:

  • game_over=1:保持不动(可选:闪烁)

  • 否则:

    • left_pulse=1right_pulse=0pos = pos + 1

    • right_pulse=1left_pulse=0pos = pos - 1

    • 两个都按/都不按 → 不动(公平,建议这样写)

  • 更新后若 pos==0 || pos==14game_over=1

    这套规则和题目(2)(3)完全对齐,而且实现简单稳定。

4 代码

通过网盘分享的文件:tug_game.v.zip

链接: https://pan.baidu.com/s/1LLpMb-XqueEtLLwRipXWEQ 提取码: 1234

默认按键低有效 (没按=1,按下=0),LED 默认高有效(1 亮)。如果你板子相反,改参数就行。

5 个文件:

  • clk_tick.v:分频产生 100Hz tick

  • key_debounce_edge.v:按键消抖 + 按下脉冲(1 次按下=1 个 pulse)

  • tug_game.v:拔河核心逻辑(pos + game_over + 可选闪烁)

  • led_encode.v:pos→16LED 两点亮

  • tug_top.v:顶层连接

  • tb_tug_top.v:测试平台

5 仿真结果

5.1错误1

5.1调试

波形里同时观察:

  • tick_100

  • left_pulse

  • pos

你会发现现象是这种错位:

  • tick_100 在某一拍为 1

  • left_pulse 反而是在 tick_100 后一拍 才冒出来(或者反过来)

    → 所以 pos 一直不动。

原因:

pos 不加 1 的原因基本可以锁定了:
tug_game 是在 if (tick_100) 这 1 个 clk 的瞬间更新 pos,但 left_pulse 在另一个 always 块里同一拍才"生成出来",tug_game 那一拍读到的还是旧值 0。

到下一拍 left_pulse 才变成 1,但此时 tick_100 已经回到 0,于是 if(tick_100) 不成立------这一按键就被"错过"了。

这就是典型的:单周期脉冲 + 不同 always 块 + 还用 tick 再门控 导致的漏采样。

5.2结果分析

仿真开始后,系统首先经历复位阶段,rst_n 处于无效状态时,各寄存器被初始化:位置寄存器 pos 被置为 7(4'b0111),表示初始亮点位于 LED 的中间区域;同时 game_over0,表明游戏未结束;显示控制 blink 初始为 1,允许 LED 正常显示。由此,输出 led 对应出现两颗相邻点亮,且与 pos=7 相一致(即 led[pos]led[pos+1] 置亮),体现了"初始时 16 个 LED 中间两个点亮"的题目要求。

进入正常工作后,系统内部节拍信号 tick_100 按 100Hz 周期性产生窄脉冲,用作按键消抖模块的采样时钟。此时,原始按键输入 key_left 在测试平台驱动下呈现周期性"按下---松开"的动作序列;由于按键消抖采用"连续多次采样一致才判稳"的策略,稳定状态信号 left_stable 并不会与 key_left 同步瞬变,而是在连续若干次 tick_100 采样确认后才翻转。当 left_stable 从 0 变为 1 的瞬间,消抖模块输出一次性事件脉冲 left_pulse(单次按动仅产生一个脉冲),该脉冲作为"有效按键事件"的唯一标志供游戏逻辑使用。

在每次 left_pulse 出现后,游戏核心逻辑立即对位置寄存器 pos 执行加 1 更新,表现为 pos 从初始的 0111(7) 依次递增为 1000(8)、1001(9)、1010(10)、1011(11)、1100(12)、1101(13)、1110(14),位置变化呈现严格的"一次按动移动一格"的离散步进特性。与此同时,LED 输出 led 也同步更新:两颗相邻亮点随 pos 的递增整体向右平移,且始终保持"相邻两灯同时点亮"的规则,即每次位置变化后,亮点从 (pos,pos+1) 平移到 (pos+1,pos+2),实现了"亮点按按键向对方移动"的题目要求。

pos 递增到 14(4'b1110) 时,亮点已经抵达最右边界(点亮 LED14 与 LED15),此时逻辑触发结束条件:game_over 由 0 置为 1,系统进入"游戏结束锁定态"。在锁定态下,即使后续仍出现按键动作与脉冲事件,位置寄存器 pos 也不再更新,保持在边界值不变,从波形上可见 pos 在达到 1110 后保持恒定,符合题目"只要 LED 移动到头,游戏结束"的要求。

此外,由于设计中启用了结束闪烁功能(BLINK_ON_OVER=1),在 game_over=1 之后,显示控制信号 blink 开始按设定频率翻转,从而使 LED 显示呈现周期性明灭效果:当 blink=1 时 LED 正常显示边界亮点;当 blink=0 时 LED 输出被整体熄灭或显示抑制。该现象在波形后段可以观察到 blink 的翻转变化,进一步证明"结束后进入提示状态(闪烁)"的逻辑链路工作正常。

综上,该波形完整覆盖并验证了关键功能链路:复位初始化 → tick_100 采样消抖 → 按下事件生成 left_pulse → pos 单步移动 → LED 两点同步平移 → 到达边界置位 game_over 并锁死 → 结束闪烁提示,符合拔河游戏课设的功能要求。

相关推荐
硬件yun12 小时前
汽车CAN为何选用0.25W电阻?
学习
testpassportcn12 小时前
Technology Solutions Professional NS0-005 認證介紹【NetApp 官方認證
网络·学习·改行学it
星火开发设计12 小时前
堆排序原理与C++实现详解
java·数据结构·c++·学习·算法·排序算法
好奇龙猫13 小时前
【人工智能学习-AI-MIT公开课第 15 讲学习:相近差错、受适应条件】
学习
1560820721913 小时前
上位机通过UDP接口与FPGA互联的重传机制
fpga开发
崇山峻岭之间13 小时前
Matlab学习记录24
javascript·学习·matlab
半夏知半秋13 小时前
rust学习-循环
开发语言·笔记·后端·学习·rust
阿豪只会阿巴13 小时前
【多喝热水系列】从零开始的ROS2之旅——Day5
c++·笔记·python·ubuntu·ros2
Haooog13 小时前
LangChain4j 学习
java·学习·大模型·langchain4j
1560820721913 小时前
UDP传输数据丢包原因分析
fpga开发