ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

EDA篮球计分器设计:Verilog状态机与七段数码管动态扫描实战

EDA篮球计分器设计:Verilog状态机与七段数码管动态扫描实战 简介一份围绕电子设计自动化EDA课程设计的篮球比赛计分器完整项目包适合高校电子类学生、FPGA/CPLD初学者作为期末课题或综合实训参考。项目覆盖HDL编程、数字逻辑电路、仿真验证、FPGA实现与实物调试等核心环节能够帮助读者系统掌握用Verilog/VHDL编写计分逻辑、驱动七段数码管显示以及处理回合计时等设计方法。压缩包共41个文件以png图片、xml配置、emf矢量图为主要类型png可查看电路截图与界面效果emf保留矢量图形便于插入报告xml和rels则对应Word文档的结构关系与自定义属性整套资料约1.96MB轻量易用。已有317人学习使用。内含课程设计文档、相关图片素材和工程配置文件可作为计分器设计思路、文档排版和硬件实现的直接参考适合在备赛或课程答辩前对照梳理。1. 为什么“EDA篮球计分器”每年都是课程设计的高频选题单片机课设里写个篮球计分器通常就是按键扫描、数码管查表十几行 C 代码搞定。换到 EDA 课程设计同样是计分功能却要在 Verilog/VHDL 里自己搭时钟分频、状态机、按键消抖、七段码译码和动态扫描任何一个环节的时序不对仿真过了上板也会花屏。网上流传的“EDA篮球比赛计分器课程设计”资源里面大多是 word 报告加 ModelSim 波形截图很多人下载后只抄代码却没意识到真正值钱的是把比赛规则映射成有限状态机的过程。这个项目适合电子信息、自动化和计算机方向的学生也适合已经会用 Quartus II/ModelSim但没完整走过“仿真到上板”这个闭环的初学工程师。2. 篮球计分器需求建模从比赛规则到状态机与模块划分2.1 计分规则的数字化两分、三分、罚球与清零的冲突篮球一次进攻最多拿 3 分罚球 1 分死球后可能需清零。按课程设计要求计分器至少要有四个输入加 1、加 2、加 3、清零。如果只用四个按键直接驱动加法器最直接的冲突是“同时按下时谁生效”。真实比赛场景里按键确实可能被同时触发比如裁判刚按完 2 分又立刻补按 1 分罚球。硬件上若两个 key 信号同时为 1加法器会在同一拍收到多个增量行为不确定。常见做法是给按键事件定优先级或者用一个按键编码状态机把多路输入仲裁成单路增量脉冲。2.1.1 事件优先级表按键含义分数变化优先级key_rst清空当前比分0最高key_3三分命中3次高key_2两分命中2次高key_1罚球命中1最低清零按键的优先级必须单独讨论。有些课程设计把清零做成异步复位直接清 score 寄存器但如果加分键与清零键同时按下异步复位会让加法使能落到亚稳态所以更稳妥的做法是把清零也纳入状态机同一时刻只接受一个事件。这个设计决策要写进报告答辩经常被问。2.1.2 按键编码状态机localparam S_IDLE 2d0; localparam S_DETECT 2d1; localparam S_UPDATE 2d2; localparam S_WAIT_REL 2d3;状态含义S_IDLE 等待任意按键按下S_DETECT 判断按键并锁存增量值S_UPDATE 把增量写入计分寄存器S_WAIT_REL 等待所有按键释放。S_WAIT_REL 不是多余的它能保证一次按下只加一次分避免按住按键时计数器持续累加。如果后续要扩展 24 秒进攻倒计时这个状态机里的等待释放逻辑会打断计时所以正规做法是按下时记录事件释放只影响下一次触发不阻塞主时钟。2.2 顶层模块划分与信号约定模块划分直接决定仿真和上板排错的难度。常见拆法如下表顶层用 wire 把各模块串起来。模块功能关键端口clk_div50MHz 到 1kHz 扫描时钟clk_50m, rst_n, clk_1kkey_debounce按键同步与消抖key_in, key_pulsescore_ctrl计分状态机与寄存器key_event, add_val, score[7:0]seg_display动态扫描与七段译码score[7:0], seg[6:0], sel[2:0]这样划分的好处是可以单独仿真 display 模块而不被按键逻辑干扰。写报告时框图直接按这个模块列表画评审老师一眼看到边界。2.3 时钟分频从 50MHz 到 1kHz 的扫描时钟为什么需要分频数码管动态扫描靠“每位轮流点亮”如果直接用 50MHz 时钟切位选每位点亮时间只有 20ns远小于人眼视觉暂留和数码管最小响应亮度会非常低。常见做法是分频到 500Hz~1kHz扫描周期 1~2ms每位点亮约 0.33~0.66ms亮度均匀且无闪烁。module clk_div #(parameter DIV 25_000) ( input wire clk, input wire rst_n, output reg clk_out ); reg [14:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 15d0; clk_out 1b0; end else if (cnt DIV - 1) begin cnt 15d0; clk_out ~clk_out; end else begin cnt cnt 1b1; end end endmodule参数 DIV25_000 时50MHz 时钟每 25_000 拍翻转一次输出为 1kHz 方波周期 1ms。计数位宽必须能容纳 24_999二进制需要 15 位写成 8 位会在仿真里回绕。如果板载晶振是 40MHzDIV 改成 20_000如果是 27MHz改成 13_500。不要只改一处位宽也要同步检查。仿真时建议把 DIV 参数下调到 2 或 4否则 20ms 消抖要在 testbench 里跑上百万拍ModelSim 会卡到怀疑人生。3. Verilog HDL 实现计分核心与七段显示驱动3.1 计分核心可加可清的寄存器堆score_ctrl对应状态机的 S_UPDATE 动作en_score 是该状态的有效信号add_val 是 S_DETECT 阶段锁存的增量。计分寄存器这里采用 BCD 码显示模块直接查表省掉二进制转十进制的除法器也更接近计分器的真实逻辑。module score_ctrl ( input wire clk, input wire rst_n, input wire en_score, input wire [1:0] add_val, input wire clr, output reg [7:0] score ); wire [3:0] score_l score[3:0]; wire [3:0] score_h score[7:4]; always (posedge clk or negedge rst_n) begin if (!rst_n) begin score 8d0; end else if (clr) begin score 8d0; end else if (en_score) begin if (score_l add_val 4d9) begin score[3:0] score_l add_val - 4d10; score[7:4] score_h 1b1; end else begin score[3:0] score_l add_val; end end end endmoduleen_score 来自按键状态机单周期高有效add_val 用 2 位二进制编码1/2/3 对应三档加分。分数上限这里没有处理实际比赛比分可能超过 99如果只用两位数码管score_h 会从 9 翻到 10显示变成 A。要在报告里写明这个边界条件或者把 score 扩展成 12 位做三位显示。加法后 score_l 大于 9 的判断是最容易写错的地方加 3 时9 加 3 等于 12必须减 10而不是减 9进位单独写不能和个位更新挤在一个 if 语句里否则综合工具会报锁存器。3.2 七段数码管动态扫描位选与段选时序显示部分的核心是扫描计数器加查表。先产生一个 1kHz 左右的扫描使能counter 每来一个使能脉冲切换一次位选段选根据当前位对应的 BCD 值输出七段编码。scan_cnt位选 sel点亮内容2d03b110个位2d13b101十位2d23b011预留/最高位reg [1:0] scan_cnt; reg [2:0] sel; reg [6:0] seg; always (posedge clk_1k or negedge rst_n) begin if (!rst_n) scan_cnt 2d0; else scan_cnt scan_cnt 1b1; end always (*) begin case (scan_cnt) 2d0: sel 3b110; 2d1: sel 3b101; 2d2: sel 3b011; default: sel 3b111; endcase end function [6:0] seg_encode; input [3:0] bcd; begin case (bcd) 4d0: seg_encode 7b1111110; 4d1: seg_encode 7b0110000; 4d2: seg_encode 7b1101101; 4d3: seg_encode 7b1111001; 4d4: seg_encode 7b0110011; 4d5: seg_encode 7b1011011; 4d6: seg_encode 7b1011111; 4d7: seg_encode 7b1110000; 4d8: seg_encode 7b1111111; 4d9: seg_encode 7b1111011; default: seg_encode 7b0000000; endcase end endfunction这是共阴极段码若板子是共阳极直接按位取反即可。位选编码必须和硬件原理图方向一致我这里假设的是高电平有效位选实际板子可能是低有效意味着代码里所有 0/1 全部取反。function 只能做组合逻辑所以段选输出建议在同一个 always 块里先寄存再输出否则能看到毛刺导致数字边缘发虚。3.3 跨模块信号同步异步复位与按键跨时钟域按键从外部引脚进来异步于 clk直接拿 key 信号做条件判断综合后电路会出现亚稳态。常见做法是两级同步器加边沿检测reg key_meta, key_sync; reg key_press_d1; always (posedge clk or negedge rst_n) begin if (!rst_n) begin key_meta 1b1; key_sync 1b1; key_press_d1 1b0; end else begin key_meta key_in; key_sync key_meta; key_press_d1 key_press; end end wire key_press ~key_sync; wire key_pulse key_press !key_press_d1;key_pulse 是单周期脉冲表示某一次按下。上一级状态机拿这个脉冲做事件触发就不会出现按键电平持续拉高导致重复计数。两级同步只是降低亚稳态进入下一级的概率并不能完全消除但课程设计里这个程度已经够用。要注意key_meta 和 key_sync 必须用同一个时钟域不能把扫描时钟和主时钟混着用。4. Quartus II ModelSim 仿真把 testbench 写到能复制4.1 Quartus II 工程创建与引脚约束如果手头是 Altera 板子课程设计一般用 Quartus II。新建工程时器件型号按实际板卡选比如 Cyclone IV 的 EP4CE6E22C8如果只做仿真不准备上板可以随便选一个型号但报告里最好保持一致。添加完 clk_div、key_debounce、score_ctrl、seg_display 这些源文件后先做一次 Analysis Elaboration检查模块例化是否匹配。上板前必须做引脚约束。QSF 文件里写法如下set_location_assignment PIN_E1 -to clk set_location_assignment PIN_E2 -to rst_n set_location_assignment PIN_A1 -to key_1 set_location_assignment PIN_A2 -to key_2 set_location_assignment PIN_B1 -to key_3 set_location_assignment PIN_B2 -to key_rst set_location_assignment PIN_C1 -to seg[0] set_location_assignment PIN_C2 -to seg[1]引脚编号每个板子都不一样必须看原理图不能照抄。常犯的错误是把网上别人工程的 PIN 直接粘进来编译能过下载后完全没有反应就是因为引脚根本没连到对应按键或数码管段。约束后再编译一次看 Fitter Report 里的 Pin Usage 是否和预期一致。如果后续要把这个计分器做成独立 PCB画板阶段可以切到嘉立创EDA这类国产工具但前提是先把 FPGA 原理图封装和 QSF 引脚一一对应封装名写错会在布板时发现位选全乱。4.2 ModelSim 仿真testbench 怎么写才不“仿真一时爽上板全完蛋”这里给一个针对score_ctrl的单模块 testbench验证复位、加分和清零三个关键动作timescale 1ns/1ps module tb_score; reg clk; reg rst_n; reg en_score; reg [1:0] add_val; reg clr; wire [7:0] score; score_ctrl u_score ( .clk(clk), .rst_n(rst_n), .en_score(en_score), .add_val(add_val), .clr(clr), .score(score) ); initial clk 0; always #10 clk ~clk; initial begin rst_n 0; en_score 0; add_val 2d0; clr 0; #100 rst_n 1; // 第一次加分两分 (posedge clk); en_score 1; add_val 2d2; (posedge clk); en_score 0; // 空跑两拍确认分数保持 (posedge clk); (posedge clk); // 清零 (posedge clk); clr 1; (posedge clk); clr 0; #200; $stop; end endmodule#10 clk ~clk表示时钟周期 20ns对应 50MHz。加法通过(posedge clk)后给 en_score 置 1下一个上升沿 score 更新这是比直接在时钟边沿写阻塞赋值更稳的写法。testbench 里没有模拟抖动因为这里只验证 count 模块如果要从顶层验证完整按键路径必须把 key_debounce 也例化进来并把分频器改成小参数。下面的激励表可以在报告里直接用作仿真用例描述阶段激励预期 score复位rst_n0 保持 100ns0两分en_score1 且 add_val2 一个周期2保持en_score0 两个周期仍为 2清零clr1 一个周期04.3 仿真中容易忽略的初始值和复位问题很多同学在 ModelSim 里看到的波形是 X 态然后怀疑代码有问题第一步先查复位。rst_n 低电平保持至少 100ns且必须在 initial 块里显式拉低不能依赖 FPGA 上电默认值。另外声明的 wire 如 add_val、en_score 在仿真开始为高阻 Z如果 testbench 里没给初值状态机可能一直停在 S_DETECT。建议在 initial 块开头给所有输入变量赋一个确定值。还有一个细节如果只从顶层仿真分频参数是 25_000run 1ms 实际要跑 50 万个 50MHz 时钟波形文件会很大。所以单独仿真核心模块比跑完整顶层更高这也是把模块边界切清楚的价值所在。5. 按键消抖与硬件在线调试从仿真正确到上板稳定5.1 机械按键抖动与计数消抖的窗口参数机械按键的抖动一般在 5~10ms 之间有些劣质按键能达到 15ms。如果直接把按键电平送给状态机一次按下可能在几毫秒内反复跳变导致状态机连续进入 S_UPDATE 多次一次按压被记录成两三分。最稳妥的消抖方案是用扫描时钟计数检测到按键电平发生变化后连续采样 N 个周期若电平始终保持一致才认为按键到达稳定状态。module key_debounce #(parameter STABLE_CNT 20) ( input wire clk_1k, input wire rst_n, input wire key_in, output reg key_stable ); reg [4:0] cnt; reg key_cur; always (posedge clk_1k or negedge rst_n) begin if (!rst_n) begin cnt 5d0; key_cur 1b1; key_stable 1b1; end else begin if (key_in ! key_cur) begin cnt 5d0; key_cur key_in; end else if (cnt STABLE_CNT) begin cnt cnt 1b1; end else begin key_stable key_cur; end end end endmodule参数 STABLE_CNT20 表示在 1kHz 时钟下持续 20ms。这个值不能拍脑袋用示波器测一下你的按键抖动波形再定抖动小于 10ms 就取 20ms大于 10ms 就取 30ms。消抖窗口太短会漏过抖动太长会让人感觉按键响应迟钝。key_in 进消抖模块之前最好再加两级同步器否则消抖模块本身也可能采到亚稳态。5.2 SignalTap II 在线调试与 LED 点灯定位法仿真只能验证逻辑不能验证引脚约束和电气连接。上板后如果没反应第一步是用 SignalTap II 看内部信号。Quartus 菜单里打开 Tools - SignalTap II Logic Analyzer添加需要观测的信号key_sync、key_pulse、scan_cnt、score[7:0]。设置触发条件为 key_pulse 是 1采样深度 256编译下载后运行然后按一次加分键就能在波形窗口看到按键脉冲是否到达计分模块。set_location_assignment PIN_L1 -to debug_led如果板上找不到 JTAG 或者 SignalTap 版本不匹配最土但最有效的定位法是 LED 点灯。把 score 寄存器的低四位接四个 LED每加 1 分亮一盏灯或者临时把 scan_cnt 接 LED看扫描频率是否正常。这种方法能快速区分“按键没进来”和“显示模块坏了”两大类问题。我一般会保留一个 debug_led 端口排错结束再删除比翻来覆去改引脚约束快很多。5.3 硬件实测故障排查顺序现象先看哪里常见根因数码管全灭万用表量位选三极管扫描时钟没到、位选方向反按加分没反应SignalTap 看 key_pulse消抖窗口太长、按键没接对引脚一次按变多次示波器量按键处波形没有消抖或 STABLE_CNT 太小数字闪烁看扫描频率分频 DIV 太大低于 200Hz某段 LED 总不亮量对应段码引脚共阴/共阳反、该段电阻虚焊排查顺序永远是电源、时钟、复位、按键输入、内部寄存器、显示驱动。不要一上来就怀疑 HDL 代码先把按键一端对地短接观察数码管是否变化。如果短接都没反应问题大概率在引脚约束或硬件连接而不是逻辑。6. 课程设计报告与答辩把工程能力量化成文档6.1 报告结构从需求到实测的完整链路评审老师看报告希望看到的是“为什么这样做”不是代码堆砌。建议按需求分析、模块设计、仿真、上板实测四段组织。需求分析部分把状态转移表放上去并在旁边标注“清零优先”的决策理由模块设计部分画一张顶层结构图每个模块标出端口名仿真部分不要贴整段 testbench截取 ModelSim 波形用箭头圈出按键按下、score 变化、位选切换三个关键点上板部分放实物照片并写清楚遇到的一个 Bug 和解决过程。最终 PDF 里波形截图注释要跟代码模块对应一句“波形中 score 从 19 变到 22对应一次二分命中”比泛泛而谈“功能正确”有说服力。下表是报告各部分与打分点的对应关系写的时候逐项核对。报告章节评审关注点本设计素材来源需求分析规则是否完整事件优先级表、状态转移表模块设计模块边界是否清晰顶层框图、端口列表仿真验证是否有边界场景ModelSim 波形、testbench 说明上板实测是否真实可运行实物照片、故障排查记录6.2 答辩时的三个关键取舍答辩被问到最多的问题集中在三个点上状态机里为什么要有 S_WAIT_REL、计分用 BCD 还是二进制、消抖时间为什么是 20ms 而不是 1ms。每个问题都要给出“如果换一种做法会怎样”的对比没有 S_WAIT_REL按住按键会重复加分用二进制显示前必须写 bin2bcd 转换模块面积增加但加减法更简单消抖时间小于 10ms部分抖动剧烈的按键会误触发。讲清楚这三点基本能覆盖大多数提问。如果现场有条件把消抖参数从 20ms 改成 5ms 编译下载然后按键用示波器给老师看抖动毛刺这一手比任何背稿都直观。答辩前建议把 SignalTap 导出的波形和 ModelSim 仿真波形放在同一页做对比说明仿真与硬件行为的一致性。本文还有配套的精品资源点击获取
返回列表