恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

FPGA抢答器课设实战:按键消抖与Verilog时序锁存全解析

  • 首页
  • 资讯中心
  • /
  • FPGA抢答器课设实战:按键消抖与Verilog时序锁存全解析

相关资讯

如何省下70%的AI Token成本:MoAI-ADK Token经济学之模型路由、提示缓存与上下文瘦身完整指南 2026/10/14 16:29:09
Android安全本地存储:通用账号凭据加密与自动填充实践 2026/10/14 16:29:09
C#调用MATLAB引擎:神经网络混合架构实战 2026/10/14 16:29:09

最新资讯

bioSkills RNA-seq全流程实战:从FASTQ原始数据到差异表达基因的完整管线
libwebsockets HTTP 客户端实战:用超过 2KB 的超长 URL 发起 HTTPS 请求(minimal-http-client-hugeurl)
SQL Server 学习笔记速查手册:建库建表、视图索引与存储过程实战
显著目标检测深度学习演进:从传统方法到Transformer
工业互联网集成应用赛项备赛指南:从样题拆解到全链路落地
Python lxml + XPath 网页解析实战,高性能静态页面数据提取

今日推荐

IBM Verify Identity Access 修复 22 个漏洞:两个 9.8 严重 RCE 无需认证
小区物业管理系统
小型超市仓库管理系统

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

FPGA抢答器课设实战:按键消抖与Verilog时序锁存全解析

发布时间:2026/10/14 16:29:09
FPGA抢答器课设实战:按键消抖与Verilog时序锁存全解析 简介EDA课程设计抢答器项目资料面向电子/计算机类专业学生及FPGA初学者基于Quartus II与DE2开发板完成抢答器的设计与实现用于竞赛中判定首位按下按钮的参与者。压缩包共139个文件以Quartus II工程文件qpf/qsf/bsf/bdf、VHDL源程序vhd、仿真波形vwf、时序分析报告rpt和配置文件sof/pof为主整体大小仅664KB目录结构清晰。内容覆盖抢答器设计的输入模块、计时器、信号检测、结果显示及复位功能包含从原理图/HDL输入到综合、时序分析、下载验证的完整流程并涉及数字逻辑、优先级编码、计数器等关键知识点。已有3087人学习适合需要完整体验FPGA数字系统设计流程并动手实现抢答逻辑的读者可对照源码与工程文件快速理解各模块实现和硬件验证方法。1. 抢答器课设从按键消抖到时序锁存的第一次完整实战EDA 课程设计里的抢答器看着只是「四路按键 数码管显示编号」实际上它把组合逻辑、时序逻辑、时钟分频、显示驱动、复位策略全部串了一遍是性价比极高的一道课设题。很多人在 Quartus II 里能做通仿真却在锁存、消抖、秒脉冲这几个模块的衔接上理不清结果一上板就翻车。这篇实战笔记适合正在做抢答器课设的同学也适合想快速把这类小系统从原理图搬到 Verilog 的从业者。我会按需求拆解、模块划分、代码实现、波形仿真、上板排错这条链路往下讲重点说清参数怎么设、信号谁先谁后、坑在哪。2. 需求拆解与模块划分先把课设题目翻译成硬件结构2.1 功能需求清单哪些功能必须有哪些只是为了堆工作量做抢答器之前先把你手上的课设题目逐句拆开。绝大多数题目的标准描述是支持 4 路或 8 路抢答按键主持人按下复位键后本轮抢答开始任意一路选手先按下系统锁定该路编号并在数码管上显示后续按键一律无效如果超过设定时间仍无人抢答给出明确的超时提示。把这段话翻译成硬件行为其实只有五件事复位键对应一个全局异步复位按键输入必须经过消抖「锁定」需要一组带使能的寄存器编号显示需要编码和译码超时提示需要一个基于秒脉冲的倒计时器。功能清单我习惯分成「必须」和「加分」两档。必须档是复位、锁存、编号显示、超时报警加分档是蜂鸣器提示、倒计时实时显示、多人同时按下时的仲裁规则、多轮循环抢答。加分项不是堆工作量而是让评审老师有东西可问、你也答得出来的地方。很多人忽略了一个关键点评审老师不会逐行读你的代码但一定会在上板演示时做三个动作。先按复位看数码管是否清零正常按一路看是否锁定按完第一路后再按第二路看是否被忽略。这三个动作分别对应复位、锁存、仲裁。不少工程仿真波形做得漂亮上板却在这三个动作里露馅原因往往是锁存逻辑写成了“只要检测到高电平就更新”而不是“只有第一个有效沿才锁定”。所以需求拆解阶段就要把「第一个有效沿才锁定」写进设计目标。2.2 模块划分与信号走向抢答、锁存、计时、显示四层关系按信号流划分我一般把抢答器拆成四个模块按键消抖与边沿检测、抢答锁存与编码、分频与倒计时、数码管显示驱动再加上一个顶层模块负责例化与连线。它们之间是单向关系按键信号进消抖模块消抖后的电平进锁存模块锁存结果一方面送显示驱动另一方面作为暂停信号控制倒计时分频模块从系统时钟生成秒脉冲供倒计时使用。模块核心职责主要输入主要输出按键消抖与边沿检测过滤机械抖动生成稳定边沿脉冲4 路按键、系统时钟、复位4 路按下沿抢答锁存与编码锁存抢答编号屏蔽后续按键按下沿、时钟、复位编号、锁定标志、蜂鸣分频与倒计时把 50MHz 分频为 1Hz驱动倒计时系统时钟、复位、秒脉冲剩余秒数、超时标志数码管显示驱动动态扫描输出段码与位选编号、剩余秒数、时钟段码、位选这样划分的直接好处是每个模块都能独立仿真波形出问题时容易定位。要做 8 路抢答只需要改消抖模块的输入位宽和锁存编码的 case 分支显示模块和分频模块基本不动。我特别不建议把所有always堆在一个顶层文件里否则打开波形仿真几十个信号互相牵连很难判断到底是谁把谁带坏了。教学项目的核心是「结构清楚」不是「代码量多」。模块之间存在两个容易被人忽略的边界信号locked锁定标志和key_pressed有效沿。key_pressed必须来自消抖后的信号不能直接接原始按键电平locked一旦拉高就要同时屏蔽消抖模块后续的输出并让倒计时停下来。这两个信号在顶层里应当有明确的 wire 声明方便仿真时单独观察。2.3 顶层原理图与信号连接Quartus II 里怎么搭最省事Quartus II 新建工程后顶层既可以画原理图也可以用 Verilog 写一个只做例化的模块。我倾向于用 Verilog 顶层因为编译报告里能直接看到每个子模块的资源占用后续加引脚约束也方便。原理图方式在老版本里更通用适合老师指定用原理图输入的情况但只要工程结构清晰两种方式最后生成的 RTL 视图是一样的。下面是一个顶层例化示例四个子模块在这里被拼起来module top_buzzer( input clk_50m, // 板载 50MHz 时钟 input rst_btn, // 主持人复位键 input [3:0] key_in, // 4 路抢答按键 output [3:0] led_out, // 4 路指示灯 output [3:0] seg_sel, // 数码管位选 output [7:0] seg_data, // 数码管段码 output beep // 蜂鸣器 ); wire rst_n; // 内部统一使用的低有效复位 wire [3:0] key_edge; // 消抖后按键沿 wire [3:0] lock_code; // 锁存编号 wire locked_flag; // 锁定标志 wire clk_1hz; // 秒脉冲 wire [3:0] count_sec; // 倒计时数值 assign rst_n ~rst_btn; // 复位键按下时为低电平 key_debounce u_debounce( .clk(clk_50m), .rst_n(rst_n), .key_in(key_in), .key_out(key_edge) ); lock_encode u_lock( .clk(clk_50m), .rst_n(rst_n), .key_pressed(key_edge), .lock_out(lock_code), .locked(locked_flag), .beep(beep) ); clk_div u_div( .clk(clk_50m), .rst_n(rst_n), .clk_1hz(clk_1hz) ); countdown u_count( .clk_1hz(clk_1hz), .rst_n(rst_n), .pause(locked_flag), .sec(count_sec) ); seg_display u_seg( .clk(clk_50m), .rst_n(rst_n), .player_code(lock_code), .count_sec(count_sec), .seg_sel(seg_sel), .seg_data(seg_data) ); endmodule这段顶层代码把信号关系钉死了复位信号统一用低有效rst_n消抖模块输出的是边沿而不是电平倒计时的pause直接接锁定标志。注意beep在这里直接由锁存模块驱动实际板子如果蜂鸣器需要一定时长的高电平可以在显示模块里再保持一拍。顶层里的assign rst_n ~rst_btn依赖板载按键按下为低如果你的板子按键按下为高这里要反过来否则后续全部逻辑都会处于错误状态。3. Quartus II 实现路线原理图输入和 Verilog 各自怎么选3.1 两条路线的取舍谁适合新手谁适合后续改功能Quartus II 下实现抢答器有两条主流路线原理图输入和硬件描述语言输入。原理图输入适合老版本教程、图形化连线直观、老师要求明确指定时最稳缺点是模块多的时候连线非常乱改功能等于重新画图。Verilog 输入适合想讲清楚时序逻辑、后续想扩展功能、或者想把这份设计用到其他题目上的情况。对比项原理图输入Verilog 输入上手门槛低连线直观中需要会写语法调试效率波形里信号名清晰信号需要手动添加扩展难度加功能动图易乱加模块加文件即可移植性基本不能复用代码可复制到新工程对课设来说如果时间紧、只需要交一份能编译能下载的工程原理图也能过关。但如果题目里写了「要求用硬件描述语言实现」或者你想在抢答器基础上扩展倒计时、状态机我建议直接用 Verilog。很多同学担心 Verilog 写错实际抢答器涉及的无非是always时序块、计数器、case分支比写 C 语言的状态机还简单。3.2 抢答锁存模块带消抖的 4 路 Verilog 实现锁存模块是整个抢答器的核心必须同时完成消抖和锁定两件事。机械按键按下时触点会在几毫秒到十几毫秒内不断抖动如果直接把按键电平接进锁存逻辑一次按压可能被当成多次触发。常见做法是用一个 10ms 采样周期每隔 10ms 锁存一次当前按键电平只有在采样到的电平从 0 变 1 时才认为发生了一次有效按下。module lock_encode( input clk, // 50MHz 系统时钟 input rst_n, // 低电平复位 input [3:0] key_pressed,// 消抖后的按下沿1 表示刚按下 output reg [3:0] lock_out, // 锁存结果 output reg locked, // 0 可抢答1 已锁定 output reg beep // 抢答成功高电平提示 ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin lock_out 4b0000; locked 1b0; beep 1b0; end else if (!locked) begin case (key_pressed) 4b0001: begin lock_out 4b0001; locked 1b1; beep 1b1; end 4b0010: begin lock_out 4b0010; locked 1b1; beep 1b1; end 4b0100: begin lock_out 4b0100; locked 1b1; beep 1b1; end 4b1000: begin lock_out 4b1000; locked 1b1; beep 1b1; end default: begin lock_out 4b0000; beep 1b0; end endcase end else begin lock_out lock_out; // 锁定后保持不变 beep 1b0; end end endmodule这段代码的逻辑关键有三个地方。第一case (key_pressed)只识别单路按下沿两路同时按下会被 default 分支丢弃这是一种仲裁策略如果老师追问同时按下怎么处理你可以直接解释。第二default分支里没有给locked赋值综合器会推断为保持原值所以未锁定时它会一直为 0不会误锁。第三beep只在锁存那拍拉高后面再拉低配合蜂鸣器做提示足够用。3.3 分频与倒计时把 50MHz 系统时钟变成可用的秒脉冲板上晶振通常是 50MHz而倒计时需要 1Hz 秒脉冲所以必须先分频。计数到 49_999_999 后翻转一次0.5 秒翻转一次周期正好是 1 秒。这里的位宽要留够50MHz 对应 26 位计数器很多同学用 16 位计数器直接溢出这是常见的低级错误。module clk_div( input clk, input rst_n, output reg clk_1hz ); parameter CNT_MAX 26d49_999_999; // 50MHz 下计数到 49999999 翻转 reg [25:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 26d0; clk_1hz 1b0; end else if (cnt CNT_MAX) begin cnt 26d0; clk_1hz ~clk_1hz; // 翻转一次为半周期 end else begin cnt cnt 1b1; end end endmodule倒计时模块在每个秒脉冲上升沿判断状态。复位后把剩余秒数置为 30计数到 0 时拉高超时标志。这里要注意复位和pause的优先级已经超时或已经被抢答时不应再继续递减。module countdown( input clk_1hz, // 秒脉冲 input rst_n, input pause, // 1 表示已锁定停止倒计时 output reg [3:0] sec, output reg timeout ); parameter INIT_SEC 4d30; always (posedge clk_1hz or negedge rst_n) begin if (!rst_n) begin sec INIT_SEC; timeout 1b0; end else if (timeout) begin // 已经超时保持报警 end else if (pause) begin // 已有人抢答保持当前秒数 end else if (sec 4d0) begin sec 4d0; timeout 1b1; end else begin sec sec - 1b1; end end endmodule有一个容易被细心的评审老师抓住的细节复位后显示 30第一个秒脉冲到达后会变成 29。如果题目要求「从 30 秒开始倒计时到 0 报警」你可以在答辩时说明 30 是初值显示 29 时代表剩余 29 秒逻辑上没有问题。如果老师不接受把INIT_SEC改为 31显示就会从 30 开始但最后会有 31 个秒脉冲才超时改动时要自己权衡。3.4 编译、引脚锁定与下载Quartus II 里的完整流程写完全部模块后Quartus II 里的操作顺序是固定的建立工程、添加代码文件、全编译、分配引脚、再次全编译、下载上板。新建工程时建议仿真工具选「None」除非你已经装了对应的仿真器。添加文件后先做一次 Analysis Synthesis看有没有语法错误和未连接信号。引脚分配是最容易出错的一步。我一般先看开发板原理图把时钟、按键、数码管位选段选、蜂鸣器的引脚找出来然后在 Pin Planner 里逐个分配。也可以直接写一段约束文件Quartus II 会把它合并进工程set_global_assignment -name FAMILY Cyclone IV E set_global_assignment -name DEVICE EP4CE6E22C8 set_location_assignment PIN_23 -to clk_50m set_location_assignment PIN_141 -to key_in[0] set_location_assignment PIN_142 -to key_in[1] set_location_assignment PIN_143 -to key_in[2] set_location_assignment PIN_144 -to key_in[3]这段约束里的器件型号和引脚编号来自我手头这块常见课设板你的板子不一定相同务必以你自己板子的原理图为准。分配完成后重新全编译Quartus II 会生成.sof下载文件。下载时用 JTAG 模式选择.sof文件后点击 Start看到进度条走完再松开复位键测试。如果下载成功但上板没反应先把引脚约束里时钟和复位引脚确认一遍再检查按键极性这两个是最常见的首查项。4. 波形仿真与验证上板之前先看这三个信号4.1 激励设计与测试用例至少覆盖复位、抢答、超时三类上板之前波形仿真能把代码里的大部分时序问题提前暴露出来。仿真不能只做一个键按下看有没有显示至少要有三类测试用例复位、单路抢答、超时报警。用一张表提前把激励设计好仿真时会省很多时间。测试场景激励操作预期波形复位rst_n拉低 100ns 后释放lock_out0、sec30、timeout0单路抢答key_in[2]拉高并保持超过 10mslock_out0100、locked1后续按键无效再拉高key_in[0]lock_out保持0100不变超时报警不按键观察 30 个秒脉冲sec递减到 0timeout1在 Quartus II 里做仿真的常见做法是新建一个仿真文件把顶层模块的输入信号按上表拖成波形设置好时钟周期和时间长度后运行仿真。要注意仿真时间不要拉得太长50MHz 时钟下跑 30 秒倒计时波形文件会很大一般单独仿真countdown模块手动给clk_1hz加 30 个脉冲就够。4.2 波形里出现的毛刺与漏判从仿真图倒推代码问题仿真波形里最常见的三个异常值得分清楚。第一个是毛刺出现在组合逻辑输出上原因是不同信号的到达时间不一致抢答器里的锁存输出是寄存器输出正常不应该有毛刺如果lock_out上出现毛刺多半是复位和按键信号没有同步到同一时钟域。第二个是漏判按下键但locked始终不变说明消抖模块的采样周期比按键抖动还短或者按键极性反了采样到的始终是 0。第三个是复位不彻底复位释放后sec没有回到 30说明rst_n没有真正影响到所有模块检查一下顶层是否每个模块都接了rst_n。还有一类非常隐蔽仿真里beep只高一个时钟周期波形上看起来正常但实际蜂鸣器声音短得几乎听不到。这是功能仿真和物理世界的差异仿真里信号只要翻转一次就认为正确而真实蜂鸣器需要持续一段时间才能发声。所以仿真只验证逻辑正确性板上行为必须单独验证。4.3 仿真通过但上板不工作的排查套路仿真全绿、上板无反应几乎是每个做课设的人都会遇到的事。我的排查顺序是固定的先看下载是否真的成功.sof文件有没有更新再看时钟引脚是否接对很多板子的时钟不是全局时钟引脚然后看复位极性按键按下是高还是低与代码中的rst_n是否匹配最后看引脚约束里有没有拼写错误或遗漏。用 Quartus II 的 Pin Planner 打开已分配引脚与实际板子原理图逐一对照通常能解决九成问题。如果程序能下载、复位有效但抢答键没反应先把按键信号接到一个 LED 上做「裸测」不经过消抖模块直接验证引脚分配和按键电平。这一步能快速区分是硬件接线问题还是逻辑问题。我在实际项目中经常用这种「最小验证」方式缩小范围比反复改代码、反复编译要有效得多。5. 避坑指南抢答器课设里的五个高频翻车点5.1 按键抖动导致一次抢答变成多组动作现象上板按一次抢答键数码管显示的数字乱跳或者蜂鸣器连续响好几声感觉像是被抢了好几次。原因按键电平没有消抖锁存模块在一个always周期里反复检测到高电平把同一次按下当成了多组动作。解决在锁存模块前加 10ms 采样消抖并且用边沿检测而不是电平检测。如果按键本身质量差抖动超过 20ms就把消抖采样计数从 499_999 改到 999_999也就是 20ms 采样一次。注意这个参数在仿真时会影响超时等待。5.2 数码管动态扫描快闪乱码刷新频率与位选顺序现象编号和倒计时显示出来乱码或者数字一直在闪按下复位键的瞬间尤其明显。原因动态扫描刷新频率太低或者位选切换和段码输出没有同步。数码管动态扫描需要大约 1kHz 到 2kHz 的扫描时钟也就是每位显示 0.5ms 到 1ms四位数码管一亮一灭人眼看到的是连续显示。如果直接用秒钟来切换位选肉眼看到的必然是一闪一闪。解决从 50MHz 分频出约 1kHz 的时钟按位选顺序1110 - 1101 - 1011 - 0111轮流点亮段码和位选不要在同一always里竞争更新先锁存段码再切换位选能避免拖影。5.3 分频计数位宽不够秒脉冲跳变现象倒计时不是严格的 1 秒一跳有时 2 秒才减一次有时连续减两下。原因计数器位宽不够。50MHz 分频到 1Hz计数上限是 49_999_999需要 26 位寄存器如果只写了reg [15:0] cnt计数到 65535 就回绕永远不会达到目标值秒脉冲周期完全乱掉。解决把计数器和参数都用 26 位仿真验证逻辑时可以把CNT_MAX临时改小确认倒计时状态转移正确后再恢复原来的参数。这一条经常被归为「玄学问题」其实是位宽这种最基础的细节。5.4 复位信号悬空上电状态不可控现象上电后数码管随机显示一个数字按复位键没有反应或者逻辑状态像黑匣子一样不确定。原因复位引脚没有明确驱动FPGA 上电后该引脚可能处于高阻或弱上拉导致内部寄存器状态未知。解决检查复位按键是否接了合适的上拉电阻并确认顶层代码里rst_n被所有模块使用。如果板载复位按键按下为高就不要在顶层写assign rst_n ~rst_btn直接让rst_n等于按键信号极性反了会导致系统一直处于复位或者一直不复位现象和悬空很像容易误判。我一般会做一个上电自动复位脉冲让系统在下载上电瞬间自动复位一次减少人工干预。5.5 交课设时只交代码文件老师打开工程缺一堆东西现象把.v文件和.bdf文件拷给老师老师那边打开工程后编译报错没有引脚约束也没有仿真波形整个工程看起来不完整。原因Quartus II 工程除了源码还包含工程文件、约束文件和下载文件只交源码等于交了一半。解决提交时包含.qpf、.qsf工程文件、所有.v/.vhd源码、.sof下载文件以及仿真工程把db、incremental_db这类自动生成的中间目录删掉再压缩能减小不少体积。我习惯在工程根目录建一个src文件夹专门放代码再写一页 README 写清引脚分配和操作步骤老师复现起来顺利印象分也会高。6. 把课设做成加分项扩展功能与上板验收技巧6.1 倒计时显示与超时报警一处代码补齐竞赛现场感基础题只要求显示编号加分项要求显示剩余时间。把countdown模块的sec输出接到数码管显示驱动上和编号分时显示数码管看起来就变成了带有比赛倒计时功能的抢答器。注意动态扫描时一次只能点亮一位编号和倒计时要按位选轮流切换。超时报警可以把timeout信号接到蜂鸣器上通过一个计数器让蜂鸣器响 1 秒停 1 秒比长鸣更有「竞赛感」。这类扩展不复杂但答辩时能明显提升完成度。6.2 用状态机重构锁存逻辑从「贴代码」变成「讲设计」如果老师要求工程里有状态机或者你希望逻辑更规范可以把第 3 章的锁存逻辑改写成状态机形式。状态划分成空闲、运行、锁定、超时四个状态复位后进入空闲主持人启动后进入运行检测到有效按键进入锁定倒计时归零进入超时。状态转移用一小段代码就能表达localparam IDLE0, RUN1, LOCKED2, TIMEOUT3; reg [1:0] state; always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else case (state) IDLE: if (start) state RUN; RUN: if (key_pressed) state LOCKED; else if (timeout) state TIMEOUT; LOCKED: if (rst_btn) state IDLE; TIMEOUT: if (rst_btn) state IDLE; default: state IDLE; endcase end这只是一个轮廓实际应用时lock_out、sec、beep的赋值都跟状态挂钩。状态机的价值在于把「什么时候能抢、什么时候不能抢」说得清清楚楚答辩时你可以指着状态转移图讲比解释一堆if else更有说服力。6.3 上板验收顺序先复位、再锁存、最后计时我每次上板都强制走一套固定顺序反复用省了不少事。第一步先按复位看数码管是否清零、蜂鸣器是否停止第二步只按一路键看编号是否正确显示第三步再按另一路键确认显示没有被改写第四步等待超时看报警是否触发第五步复位后重新抢答确认能够开始下一轮。如果其中任何一步失败不要再继续往后测回到第 4 章的排查流程。从那以后我每次做这类数字逻辑系统的板级验证都会强制走一遍这个顺序雷打不动也避免过很多次「仿真全绿、上板翻车」的尴尬。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号