1. 篮球计时器FPGA设计概述
篮球计时器是篮球比赛中不可或缺的设备,它需要精确记录比赛时间、24秒进攻时间以及两队比分。使用FPGA实现篮球计时器具有响应速度快、可定制性强、稳定性高等优势。这个项目采用Verilog HDL语言编写,兼容Quartus、Modelsim和Vivado三大主流EDA工具链。
核心功能模块包括:
- 12分钟节次倒计时(4节比赛,每节12分钟)
- 24秒进攻违例倒计时
- 两队比分显示与计分功能
- 多种控制按键(开始/结束、暂停、加分等)
2. 系统架构设计
2.1 整体框图设计
整个系统采用模块化设计思想,主要包含以下功能模块:
code复制┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 时钟分频模块 │ │ 按键消抖模块 │ │ 数码管驱动模块 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────┐
│ 主控制逻辑模块 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│
│ │ 12分钟计时器 │ │ 24秒计时器 │ │ 比分计数器 ││
│ └─────────────┘ └─────────────┘ └─────────────┘│
└───────────────────────────────────────────────────┘
2.2 时钟信号处理
FPGA通常使用外部晶振提供的高频时钟(如50MHz),需要通过分频得到1Hz的基准时钟信号:
verilog复制module clock_divider(
input clk_50M, // 50MHz输入时钟
input rst, // 复位信号
output reg clk_1s // 1Hz输出时钟
);
reg [25:0] counter;
always @(posedge clk_50M or posedge rst) begin
if(rst) begin
counter <= 0;
clk_1s <= 0;
end else begin
if(counter == 26'd24_999_999) begin
counter <= 0;
clk_1s <= ~clk_1s;
end else begin
counter <= counter + 1;
end
end
end
endmodule
注意:实际分频系数应根据具体FPGA开发板的时钟频率调整。例如50MHz时钟需要计数到24,999,999(因为50MHz/2 = 25MHz,再除以25MHz得到1Hz)
2.3 按键消抖处理
机械按键存在抖动问题,需要硬件或软件消抖。这里采用Verilog实现的软件消抖方案:
verilog复制module debounce(
input clk,
input button_in,
output reg button_out
);
reg [19:0] counter;
reg button_sync;
always @(posedge clk) begin
button_sync <= button_in;
if(button_out != button_sync)
counter <= counter + 1;
else
counter <= 0;
if(counter == 20'hFFFFF)
button_out <= button_sync;
end
endmodule
消抖原理:检测到按键状态变化后,等待约20ms(假设时钟为50MHz,2^20/50MHz≈21ms)确认状态稳定后才输出新的按键状态。
3. 核心功能模块实现
3.1 12分钟倒计时模块
12分钟倒计时是篮球比赛的基本计时单位,实现代码如下:
verilog复制module count_12min(
input clk_1s, // 1Hz时钟
input rst, // 复位信号
input start, // 开始计时
input pause, // 暂停信号
output reg [9:0] sec,// 当前秒数(0-720)
output reg quarter_end // 节次结束标志
);
always @(posedge clk_1s or posedge rst) begin
if(rst) begin
sec <= 10'd720;
quarter_end <= 1'b0;
end else if(start && !pause) begin
if(sec > 0) begin
sec <= sec - 1;
quarter_end <= 1'b0;
end else begin
quarter_end <= 1'b1;
end
end
end
endmodule
关键设计点:
- 使用10位寄存器存储秒数(2^10=1024 > 720)
- 增加quarter_end信号指示节次结束
- 暂停信号优先于开始信号
3.2 24秒进攻计时模块
24秒规则是篮球比赛的重要规则,实现时需要特殊处理:
verilog复制module count_24s(
input clk_1s,
input rst,
input start,
input pause,
input attack_continue, // 进攻延续信号
output reg [4:0] sec, // 当前秒数(0-24)
output reg violation // 违例标志
);
always @(posedge clk_1s or posedge rst) begin
if(rst) begin
sec <= 5'd24;
violation <= 1'b0;
end else if(start && !pause) begin
if(sec > 0) begin
sec <= sec - 1;
violation <= 1'b0;
end else begin
violation <= 1'b1;
end
end else if(attack_continue) begin
sec <= 5'd14; // 进攻延续时重置为14秒
end
end
endmodule
篮球规则补充:当进攻方在14秒内完成投篮且球碰到篮筐时,24秒计时器会重置为14秒而非24秒
3.3 比分计数模块
比分计数器需要支持多种加分方式:
verilog复制module score_counter(
input clk,
input rst,
input add_1,
input add_2,
input add_3,
output reg [6:0] score // 0-99分
);
always @(posedge clk or posedge rst) begin
if(rst) begin
score <= 7'd0;
end else begin
case({add_3, add_2, add_1})
3'b001: score <= (score < 7'd99) ? score + 7'd1 : score;
3'b010: score <= (score < 7'd98) ? score + 7'd2 : 7'd99;
3'b100: score <= (score < 7'd97) ? score + 7'd3 : 7'd99;
default: score <= score;
endcase
end
end
endmodule
加分逻辑优化:
- 使用case语句处理多种加分方式
- 增加分数上限检查(最大99分)
- 采用7位寄存器存储分数(2^7=128 > 99)
4. 数码管显示驱动
4.1 数码管显示原理
七段数码管显示需要将数字转换为对应的段选信号。以共阳极数码管为例:
verilog复制module seg7_decoder(
input [3:0] num,
output reg [6:0] seg
);
always @(*) begin
case(num)
4'd0: seg = 7'b1000000; // a b c d e f g
4'd1: seg = 7'b1111001;
4'd2: seg = 7'b0100100;
4'd3: seg = 7'b0110000;
4'd4: seg = 7'b0011001;
4'd5: seg = 7'b0010010;
4'd6: seg = 7'b0000010;
4'd7: seg = 7'b1111000;
4'd8: seg = 7'b0000000;
4'd9: seg = 7'b0010000;
default: seg = 7'b1111111;
endcase
end
endmodule
4.2 动态扫描显示
多位数码管采用动态扫描方式显示,可节省IO资源:
verilog复制module display_driver(
input clk,
input [3:0] min_ten, min_unit, // 分钟十位/个位
input [3:0] sec_ten, sec_unit, // 秒钟十位/个位
input [3:0] scoreA_ten, scoreA_unit, // A队分数
input [3:0] scoreB_ten, scoreB_unit, // B队分数
output reg [3:0] sel, // 数码管位选
output [6:0] seg // 段选信号
);
reg [1:0] state;
reg [3:0] num;
seg7_decoder u1(.num(num), .seg(seg));
always @(posedge clk) begin
state <= state + 1;
case(state)
2'b00: begin sel <= 4'b1110; num <= min_ten; end
2'b01: begin sel <= 4'b1101; num <= min_unit; end
2'b10: begin sel <= 4'b1011; num <= sec_ten; end
2'b11: begin sel <= 4'b0111; num <= sec_unit; end
endcase
end
endmodule
动态扫描频率建议在100Hz以上(人眼视觉暂留效应),可通过分频得到适当扫描时钟。
5. 顶层模块设计与系统集成
5.1 顶层模块接口
verilog复制module basketball_timer(
input clk_50M, // 50MHz主时钟
input rst_global, // 全局复位
input rst_quarter, // 节次复位
input start_pause, // 开始/暂停
input [1:0] add_A, // A队加分(00=无,01=1分,10=2分,11=3分)
input [1:0] add_B, // B队加分
input attack_continue, // 进攻延续
output [3:0] sel, // 数码管位选
output [6:0] seg, // 数码管段选
output buzzer // 蜂鸣器(节次结束/24秒违例)
);
// 内部信号声明
wire clk_1s;
wire [9:0] sec_12min;
wire [4:0] sec_24;
wire [6:0] score_A, score_B;
wire quarter_end, violation;
// 实例化各模块
clock_divider u1(.clk_50M(clk_50M), .rst(rst_global), .clk_1s(clk_1s));
count_12min u2(
.clk_1s(clk_1s),
.rst(rst_quarter),
.start(~start_pause),
.pause(start_pause),
.sec(sec_12min),
.quarter_end(quarter_end)
);
count_24s u3(
.clk_1s(clk_1s),
.rst(rst_global),
.start(1'b1),
.pause(start_pause),
.attack_continue(attack_continue),
.sec(sec_24),
.violation(violation)
);
score_counter u4(
.clk(clk_1s),
.rst(rst_global),
.add_1(add_A==2'b01),
.add_2(add_A==2'b10),
.add_3(add_A==2'b11),
.score(score_A)
);
score_counter u5(
.clk(clk_1s),
.rst(rst_global),
.add_1(add_B==2'b01),
.add_2(add_B==2'b10),
.add_3(add_B==2'b11),
.score(score_B)
);
// 显示驱动
display_driver u6(
.clk(clk_50M),
.min_ten(sec_12min[9:6]),
.min_unit(sec_12min[5:2]),
.sec_ten({3'b0, sec_24[4]}),
.sec_unit(sec_24[3:0]),
.scoreA_ten(score_A[6:3]),
.scoreA_unit(score_A[2:0]),
.scoreB_ten(score_B[6:3]),
.scoreB_unit(score_B[2:0]),
.sel(sel),
.seg(seg)
);
// 蜂鸣器控制
assign buzzer = (quarter_end | violation);
endmodule
5.2 跨时钟域同步
当不同模块使用不同时钟时,需要进行跨时钟域同步:
verilog复制module sync_signal(
input clk,
input async_signal,
output reg sync_signal
);
reg meta;
always @(posedge clk) begin
meta <= async_signal;
sync_signal <= meta;
end
endmodule
6. 仿真测试与验证
6.1 Testbench编写
使用Modelsim进行功能仿真:
verilog复制`timescale 1ns/1ps
module tb_basketball_timer();
reg clk_50M = 0;
reg rst_global = 1;
reg rst_quarter = 0;
reg start_pause = 1;
reg [1:0] add_A = 0;
reg [1:0] add_B = 0;
reg attack_continue = 0;
basketball_timer uut(.*);
always #10 clk_50M = ~clk_50M;
initial begin
#100 rst_global = 0;
#200 start_pause = 0;
// 模拟加分操作
#1000000 add_A = 2'b01; #1000000 add_A = 0;
#1000000 add_B = 2'b10; #1000000 add_B = 0;
// 模拟节次结束
#432000000 start_pause = 1; // 12分钟后暂停
#10000000 rst_quarter = 1; #10000000 rst_quarter = 0;
#10000000 start_pause = 0;
#10000000 $stop;
end
endmodule
6.2 常见问题排查
-
数码管显示乱码
- 检查段选信号极性(共阴/共阳)
- 确认数码管位选信号与段选信号同步
- 验证BCD码转换是否正确
-
计时不准确
- 检查时钟分频模块
- 确认仿真时间尺度设置正确
- 测试按键消抖效果
-
按键响应异常
- 增加消抖时间常数
- 检查按键信号同步处理
- 验证按键优先级逻辑
7. 实际部署与优化建议
7.1 FPGA引脚约束
以Xilinx Vivado为例,需要编写XDC约束文件:
tcl复制# 时钟引脚
set_property PACKAGE_PIN E3 [get_ports clk_50M]
set_property IOSTANDARD LVCMOS33 [get_ports clk_50M]
# 按键引脚
set_property PACKAGE_PIN B8 [get_ports rst_global]
set_property IOSTANDARD LVCMOS33 [get_ports rst_global]
# 数码管引脚
set_property PACKAGE_PIN A14 [get_ports {seg[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[6:0]}]
7.2 资源优化技巧
-
共享分频计数器
verilog复制// 同时生成1Hz、100Hz、1kHz时钟 always @(posedge clk_50M) begin if(cnt_1k == 24999) begin cnt_1k <= 0; clk_1k <= ~clk_1k; if(cnt_100 == 249) begin cnt_100 <= 0; clk_100 <= ~clk_100; if(cnt_1 == 24) begin cnt_1 <= 0; clk_1 <= ~clk_1; end else begin cnt_1 <= cnt_1 + 1; end end else begin cnt_100 <= cnt_100 + 1; end end else begin cnt_1k <= cnt_1k + 1; end end -
状态机优化
- 使用独热码(one-hot)编码状态机
- 将大状态机拆分为多个小状态机
-
流水线设计
- 对BCD转换等复杂运算采用流水线
- 平衡各级流水线延迟
7.3 扩展功能建议
-
比赛节次显示
- 增加2位数码管显示当前节次(1-4)
- 节间休息时间自动计时
-
球队犯规计数
- 每队5个犯规计数器
- 犯规满时触发特殊信号
-
无线遥控功能
- 增加红外或RF接收模块
- 实现远程控制计时器
-
比分存储与回放
- 使用FPGA片内RAM存储历史比分
- 通过按键查看之前节次比分
8. 多平台适配指南
8.1 Quartus Prime适配
- 创建新项目时选择正确的FPGA器件型号
- 设置Verilog版本为SystemVerilog以获得更好支持
- 时序约束建议:
sdc复制create_clock -name clk_50M -period 20 [get_ports clk_50M] set_input_delay -clock clk_50M 5 [all_inputs]
8.2 Vivado适配
- 使用IP Integrator快速构建系统
- 添加时钟向导(Clock Wizard)生成所需时钟
- 实现后检查时序报告,重点关注:
- Setup/Hold时间违例
- 时钟域交叉路径
8.3 ModelSim仿真
- 编译顺序很重要:
- 先编译基础模块
- 再编译上层模块
- 最后编译Testbench
- 常用仿真命令:
tcl复制
vlib work vlog *.v vsim tb_basketball_timer add wave * run -all
9. 实际开发中的经验分享
-
调试技巧
- 使用SignalTap II/ChipScope实时抓取内部信号
- 对关键信号添加ILA(Integrated Logic Analyzer)
- 分模块验证,先确保各子功能正常再集成
-
常见坑与解决方案
-
问题:计时器跑得比实际快
- 原因:时钟分频计算错误
- 解决:重新计算分频系数,考虑计数器从0开始计数
-
问题:按键响应迟钝
- 原因:消抖时间过长
- 解决:调整消抖计数器位宽,找到最佳响应时间
-
问题:数码管显示闪烁
- 原因:扫描频率过低
- 解决:提高动态扫描频率至200Hz以上
-
-
性能优化记录
- 初始设计占用1200LEs
- 通过共享分频器减少到950LEs
- 优化状态机编码后降至800LEs
- 最终版本仅占用700LEs(Artix-7 XC7A35T)
10. 项目总结与进阶方向
这个篮球计时器项目完整实现了比赛计时、24秒违例计时、比分显示等核心功能。通过FPGA实现相比传统单片机方案具有以下优势:
- 并行处理能力:各计时器可真正并行运行
- 精确计时:硬件计时精度高于软件循环
- 实时响应:按键响应延迟小于1ms
- 扩展灵活:可轻松添加新功能模块
后续可探索的进阶方向:
- 增加VGA/HDMI显示接口
- 实现网络同步多计时器
- 添加语音播报功能
- 开发移动端控制APP
在实际部署到不同型号FPGA开发板时,需要注意时钟频率、IO电压等参数的适配。建议先在仿真环境中充分验证功能,再逐步移植到物理硬件。