从课堂到实战:用Verilog和Quartus II复现一个四路抢答器(附完整代码与仿真波形)

张开发
2026/4/12 3:42:09 15 分钟阅读

分享文章

从课堂到实战:用Verilog和Quartus II复现一个四路抢答器(附完整代码与仿真波形)
从零构建FPGA抢答器Verilog模块化设计与Quartus II全流程实战第一次接触FPGA数字逻辑设计时最令人兴奋的莫过于将抽象的代码转化为实际运行的硬件电路。四路抢答器作为经典的教学案例完美融合了状态机、时序控制、信号处理等核心概念。本文将手把手带你用Verilog HDL和Quartus II完成从设计到仿真的全流程特别针对初学者容易遇到的坑点提供解决方案。1. 系统架构设计与核心模块任何复杂的数字系统都应该采用模块化设计思想。我们的抢答器需要处理几个关键任务识别最先按下的按钮、显示倒计时和结果、防止按键抖动干扰。这自然分解为六个功能模块按键消抖模块消除机械开关的触点抖动优先编码器确定最先触发信号的选手编号有限状态机控制器管理整个系统的工作流程倒计时计数器实现60秒计时功能数码管驱动电路动态显示时间和结果时钟分频网络为不同模块提供合适的工作频率// 顶层模块接口定义 module responder( input wire start, // 主持人开始信号 input wire clk, // 主时钟(50MHz) input wire rst_n, // 异步复位(低有效) input wire [3:0] key, // 四位选手按键 output wire [7:0] an, // 数码管位选 output wire [6:0] seg // 七段显示输出 );1.1 按键消抖的硬件实现机械按键在闭合时会产生5-10ms的抖动直接采样会导致误判。常见的消抖方案有消抖方法优点缺点硬件RC滤波简单可靠增加BOM成本软件延时检测无需外围电路占用CPU资源移位寄存器采样纯数字逻辑需要多个时钟周期我们采用第三种方案通过三级寄存器链检测稳定信号module key_debounce( input clk, input [3:0] key_raw, output [3:0] key_clean ); reg [3:0] key_delay1, key_delay2, key_delay3; always (posedge clk) begin key_delay1 key_raw; key_delay2 key_delay1; key_delay3 key_delay2; end assign key_clean key_delay1 key_delay2 key_delay3; endmodule提示消抖时钟频率不宜过高通常取1-10kHz。过高的采样率可能无法有效滤除抖动。1.2 优先编码器设计当多位选手同时按下按钮时需要确定优先级。这里采用if-else级联实现2-4线优先编码module priority_encoder( input [3:0] key, input lock, // 结果锁存信号 output reg [1:0] code, output reg valid ); always (*) begin if(lock) {valid, code} {valid, code}; // 结果锁定 else if(key[0]) {valid, code} {1b1, 2b00}; else if(key[1]) {valid, code} {1b1, 2b01}; else if(key[2]) {valid, code} {1b1, 2b10}; else if(key[3]) {valid, code} {1b1, 2b11}; else {valid, code} {1b0, 2b00}; end endmodule2. 状态机与控制系统设计2.1 三状态工作流程抢答器的核心是一个有限状态机(FSM)包含三个典型状态WAIT状态等待主持人按下开始按钮COUNT状态60秒倒计时进行中LOCK状态抢答结果锁定显示状态转移条件如下graph LR WAIT --|start1| COUNT COUNT --|timeout| WAIT COUNT --|key_pressed| LOCK LOCK --|reset| WAIT对应的Verilog实现module fsm_controller( input clk, input rst_n, input start, input timeout, input key_pressed, output reg count_en, output reg lock ); typedef enum {WAIT, COUNT, LOCK} state_t; state_t current_state, next_state; // 状态寄存器 always (posedge clk or negedge rst_n) begin if(!rst_n) current_state WAIT; else current_state next_state; end // 状态转移逻辑 always (*) begin case(current_state) WAIT: next_state start ? COUNT : WAIT; COUNT: begin if(key_pressed) next_state LOCK; else if(timeout) next_state WAIT; else next_state COUNT; end LOCK: next_state (!start) ? LOCK : WAIT; default: next_state WAIT; endcase end // 输出逻辑 always (posedge clk) begin count_en (next_state COUNT); lock (next_state LOCK); end endmodule2.2 倒计时模块实现倒计时器采用BCD码格式分别处理十位和个位module countdown_timer( input clk, input rst_n, input enable, output reg [3:0] tens, output reg [3:0] units, output timeout ); always (posedge clk or negedge rst_n) begin if(!rst_n) begin tens 6; units 0; end else if(enable) begin if(units 0) units units - 1; else begin units 9; if(tens 0) tens tens - 1; end end else begin tens 6; units 0; end end assign timeout (tens0) (units0); endmodule3. 显示系统设计与优化3.1 七段译码器共阳极数码管需要阴极驱动译码逻辑如下module seg7_decoder( input [3:0] bcd, output reg [6:0] seg ); always (*) begin case(bcd) 4d0: seg 7b1000000; // g f e d c b a 4d1: seg 7b1111001; 4d2: seg 7b0100100; // ... 其他数字编码 default: seg 7b1111111; endcase end endmodule3.2 动态扫描技术为降低功耗采用时分复用方式驱动多位数码管module dynamic_scan( input clk, input rst_n, input [6:0] seg1, seg2, seg3, output reg [7:0] an, output reg [6:0] seg ); reg [2:0] counter; always (posedge clk or negedge rst_n) begin if(!rst_n) counter 0; else counter counter 1; end always (*) begin case(counter[1:0]) 2b00: begin an 8b11111110; seg seg1; end 2b01: begin an 8b11111101; seg seg2; end 2b10: begin an 8b11111011; seg seg3; end default: begin an 8b11111111; seg 7b1111111; end endcase end endmodule注意扫描频率建议在60-200Hz之间过低会导致闪烁过高会增加功耗。4. Quartus II工程实践与仿真4.1 工程创建与配置新建工程时选择正确的FPGA型号如Cyclone IV EP4CE10添加所有Verilog源文件配置引脚分配参考开发板原理图设置编译选项开启优化选项配置未使用引脚为As inputs tri-stated4.2 Testbench编写技巧完整的测试平台应该覆盖所有功能场景initial begin // 初始化信号 rst_n 0; start 0; key 4b0000; #100 rst_n 1; // 测试场景1正常倒计时 #200 start 1; #300 start 0; // 测试场景2抢答触发 #500 key 4b0100; // 3号选手抢答 #50 key 4b0000; // 测试场景3多人同时抢答 #1000 key 4b0011; // 1号和2号同时按下 #50 key 4b0000; end4.3 常见错误排查Modelsim无法加载设计检查顶层模块名称是否匹配确认所有子模块已正确例化验证testbench信号连接时序违例警告添加适当的时钟约束检查跨时钟域信号同步考虑降低时钟频率或优化关键路径下载后无反应确认复位信号极性正确检查时钟信号是否接入验证IO引脚分配与实际硬件匹配5. 进阶优化方向完成基础功能后可以考虑以下增强特性声音提示增加蜂鸣器驱动抢答成功时发出提示音抢答时间记录保存从开始到抢答的时间间隔无线扩展通过红外或RF模块实现远程抢答分数统计集成计分系统支持多轮比赛// 扩展功能示例抢答时间记录 module response_timer( input clk, input start, input key_pressed, output reg [7:0] response_time ); reg [7:0] counter; always (posedge clk) begin if(start) counter 0; else if(!key_pressed) counter counter 1; else response_time counter; end endmodule在调试过程中发现动态扫描的频率选择对显示效果影响很大。最初使用1kHz扫描导致数码管亮度不足调整为200Hz后既保证了视觉暂留效果又提供了足够的驱动电流。另一个实用技巧是在状态机设计中明确区分组合逻辑和时序逻辑这能有效避免综合后出现锁存器。

更多文章