1. 精准秒表系统设计概述第一次接触FPGA数字系统设计时我被Verilog语言的简洁和Quartus II的强大功能深深吸引。这次我们要实现的精准秒表系统不仅是一个简单的计时器更是一个完整的数字电路设计案例。使用DE2-115开发板和Quartus II 13.0环境我们可以构建一个精确到百分之一秒的计时系统这对于理解FPGA开发流程和数字系统设计原理非常有帮助。这个项目最吸引人的地方在于它涵盖了FPGA开发的完整流程从时钟分频、计数器设计、数码管显示驱动到最后的系统集成和调试。每个模块都是数字电路设计中的经典案例组合起来又能实现一个实用的功能。我在实际项目中发现很多初学者在学习FPGA时容易陷入理论而缺乏实践这个秒表项目正好提供了一个理论与实践结合的完美切入点。系统的主要技术指标包括50MHz主时钟输入、100Hz计时时钟输出、四位七段数码管显示两位显示百分之一秒两位显示秒以及清零和暂停功能。这些功能看似简单但实现过程中需要考虑很多细节比如时钟域的处理、计数器的进位逻辑、数码管的驱动方式等。2. 分频电路设计与实现2.1 从50MHz到100Hz的时钟转换DE2-115开发板提供的系统时钟是50MHz而我们的秒表需要的是100Hz的计时时钟。这意味着我们需要设计一个分频系数为500,000的分频器。听起来这个数字很大但在Verilog中实现起来并不复杂。我常用的分频器设计思路是使用计数器当计数器值达到分频系数的一半时翻转输出时钟信号。这种方法的优点是实现简单且占空比可以精确控制。在实际项目中我发现很多初学者容易犯的一个错误是忽略了清零信号的处理导致系统无法正常复位。module fenpin( input clk, // 50MHz输入时钟 input clr, // 异步清零信号低电平有效 output reg clk2 // 100Hz输出时钟 ); reg [31:0] cnter0; // 32位计数器 always(posedge clk or negedge clr) begin if (~clr) begin cnter0 0; clk2 1b0; end else if (cnter0 249999) begin cnter0 0; clk2 ~clk2; // 时钟翻转 end else begin cnter0 cnter0 1; end end endmodule2.2 分频器的仿真与调试设计完分频器后强烈建议先进行仿真验证。在Quartus II中我们可以使用ModelSim进行功能仿真。仿真时需要注意几个关键点首先是清零信号的有效性验证其次是输出时钟的频率和占空比是否符合要求。我在调试过程中发现一个常见问题由于分频系数很大仿真时如果跑完整的分频周期会非常耗时。我的建议是先用较小的分频系数比如100验证逻辑正确性然后再改回实际需要的分频系数。这样可以大大提高调试效率。3. 计数器模块设计3.1 模100计数器实现百分秒计时百分秒计时需要模100计数器我通常将其设计为两个模10计数器的级联。这种设计的好处是输出可以直接连接到七段译码器不需要额外的二进制到BCD转换电路。module counter100( input clk, // 100Hz时钟输入 input clr, // 异步清零信号低电平有效 output reg [3:0] qa, qb, // 个位和十位BCD输出 output co // 进位输出 ); reg cin; // 内部进位信号 always (posedge clk or negedge clr) begin if(~clr) begin qa 0; cin 0; end else if(qa 9) begin qa 0; cin 1; // 产生进位脉冲 end else begin qa qa 1; cin 0; end end always (posedge cin or negedge clr) begin if(~clr) begin qb 0; co 0; end else if(qb 9) begin qb 0; co 1; // 模100计数器满产生进位 end else begin qb qb 1; co 0; end end endmodule3.2 模60计数器实现秒计时秒计时部分使用模60计数器设计与模100计数器类似但需要注意十位计数到5就需要归零。在实际项目中我发现很多同学容易忽略这一点导致秒计时显示不正常。module counter60( input clk, // 来自模100计数器的进位输入 input clr, // 异步清零信号低电平有效 output reg [3:0] qa, qb // 秒的个位和十位BCD输出 ); reg cin; // 内部进位信号 always (posedge clk or negedge clr) begin if(~clr) begin qa 0; cin 0; end else if(qa 9) begin qa 0; cin 1; end else begin qa qa 1; cin 0; end end always (posedge cin or negedge clr) begin if(~clr) begin qb 0; end else if(qb 5) begin qb 0; end else begin qb qb 1; end end endmodule4. 数码管显示驱动设计4.1 七段译码器设计DE2-115开发板使用的是共阳极数码管这意味着我们需要输出低电平来点亮对应的段。七段译码器的设计需要特别注意段码的顺序不同厂家的数码管段序可能不同。module decoder7( input [3:0] in, // BCD码输入 output reg [6:0] out // 七段码输出(a-g) ); always(in) begin case (in) 4d0: out 7b1000000; // 0 4d1: out 7b1111001; // 1 4d2: out 7b0100100; // 2 4d3: out 7b0110000; // 3 4d4: out 7b0011001; // 4 4d5: out 7b0010010; // 5 4d6: out 7b0000010; // 6 4d7: out 7b1111000; // 7 4d8: out 7b0000000; // 8 4d9: out 7b0010000; // 9 default: out 7b1000000; // 默认显示0 endcase end endmodule4.2 数码管显示调试技巧在实际调试中数码管显示不正常是常见问题。我总结了几点调试经验首先确认数码管的类型共阴/共阳检查段码顺序是否正确使用静态显示测试每个段是否正常点亮注意刷新频率避免闪烁5. 系统集成与实测5.1 顶层模块设计与模块例化顶层模块的作用是将各个子模块连接起来形成一个完整的系统。这里需要使用模块例化语句并通过wire类型的信号进行互连。module seconds( input CLOCK_50, // 50MHz系统时钟 input [1:0] SW, // 开关输入 SW[0]:分频器清零 SW[1]:计数器清零 output [6:0] HEX0, HEX1, HEX2, HEX3 // 四位七段数码管 ); wire clk1; // 100Hz时钟 wire co; // 模100计数器进位 wire [3:0] qa, qb, qc, qd; // 计数器输出 // 模块例化 fenpin fp( .clk(CLOCK_50), .clr(SW[0]), .clk2(clk1) ); counter100 ct100( .clk(clk1), .clr(SW[1]), .qa(qa), .qb(qb), .co(co) ); counter60 ct60( .clk(co), .clr(SW[1]), .qa(qc), .qb(qd) ); decoder7 dc0(.in(qa), .out(HEX0)); // 百分秒个位 decoder7 dc1(.in(qb), .out(HEX1)); // 百分秒十位 decoder7 dc2(.in(qc), .out(HEX2)); // 秒个位 decoder7 dc3(.in(qd), .out(HEX3)); // 秒十位 endmodule5.2 管脚分配与下载测试在Quartus II中完成设计后需要为输入输出信号分配实际的物理管脚。DE2-115开发板的管脚定义可以在用户手册中找到。分配完管脚后编译生成sof文件通过USB-Blaster下载到开发板进行实测。我在实际测试中发现几个常见问题及解决方法数码管显示乱码检查段码顺序和数码管类型计时速度不对检查分频系数是否正确清零功能不正常检查清零信号的极性是否正确6. 功能扩展与优化6.1 实现暂停功能的两种方法原始秒表设计已经实现了基本计时和清零功能实际应用中经常需要暂停功能。我实验过两种实现方法第一种是通过控制计数器的使能端。添加一个使能信号当使能为0时计数器保持当前值不变。这种方法的优点是不影响时钟信号系统更加稳定。第二种是通过控制时钟信号也就是本次实验采用的方法。当暂停信号有效时将计数器的时钟输入置为常值从而停止计数。这种方法实现简单但可能会引入时钟毛刺。6.2 显示刷新率优化如果发现数码管有闪烁现象可以考虑提高刷新频率。一个实用的技巧是使用更高的刷新频率如1kHz然后通过分频控制每个数码管的显示时间。这样既能保证亮度又能避免闪烁。7. 常见问题与调试技巧在完成这个项目的过程中我遇到过不少问题也积累了一些调试经验。最常见的问题就是数码管显示不正常这通常有几个原因段码顺序错误、共阴共阳搞反、或者刷新频率不合适。另一个常见问题是计时不准确。这时候应该先检查分频模块的输出频率是否正确。可以在分频器输出端加一个LED指示灯通过观察LED的闪烁频率来粗略判断分频是否正确。对于Quartus II的使用我建议在编译前先进行语法检查可以节省不少时间。另外合理使用SignalTap逻辑分析仪可以大大简化调试过程特别是对于时序相关的问题。
基于Verilog与Quartus II的精准秒表系统:从分频到数码管显示的全流程实现
1. 精准秒表系统设计概述第一次接触FPGA数字系统设计时我被Verilog语言的简洁和Quartus II的强大功能深深吸引。这次我们要实现的精准秒表系统不仅是一个简单的计时器更是一个完整的数字电路设计案例。使用DE2-115开发板和Quartus II 13.0环境我们可以构建一个精确到百分之一秒的计时系统这对于理解FPGA开发流程和数字系统设计原理非常有帮助。这个项目最吸引人的地方在于它涵盖了FPGA开发的完整流程从时钟分频、计数器设计、数码管显示驱动到最后的系统集成和调试。每个模块都是数字电路设计中的经典案例组合起来又能实现一个实用的功能。我在实际项目中发现很多初学者在学习FPGA时容易陷入理论而缺乏实践这个秒表项目正好提供了一个理论与实践结合的完美切入点。系统的主要技术指标包括50MHz主时钟输入、100Hz计时时钟输出、四位七段数码管显示两位显示百分之一秒两位显示秒以及清零和暂停功能。这些功能看似简单但实现过程中需要考虑很多细节比如时钟域的处理、计数器的进位逻辑、数码管的驱动方式等。2. 分频电路设计与实现2.1 从50MHz到100Hz的时钟转换DE2-115开发板提供的系统时钟是50MHz而我们的秒表需要的是100Hz的计时时钟。这意味着我们需要设计一个分频系数为500,000的分频器。听起来这个数字很大但在Verilog中实现起来并不复杂。我常用的分频器设计思路是使用计数器当计数器值达到分频系数的一半时翻转输出时钟信号。这种方法的优点是实现简单且占空比可以精确控制。在实际项目中我发现很多初学者容易犯的一个错误是忽略了清零信号的处理导致系统无法正常复位。module fenpin( input clk, // 50MHz输入时钟 input clr, // 异步清零信号低电平有效 output reg clk2 // 100Hz输出时钟 ); reg [31:0] cnter0; // 32位计数器 always(posedge clk or negedge clr) begin if (~clr) begin cnter0 0; clk2 1b0; end else if (cnter0 249999) begin cnter0 0; clk2 ~clk2; // 时钟翻转 end else begin cnter0 cnter0 1; end end endmodule2.2 分频器的仿真与调试设计完分频器后强烈建议先进行仿真验证。在Quartus II中我们可以使用ModelSim进行功能仿真。仿真时需要注意几个关键点首先是清零信号的有效性验证其次是输出时钟的频率和占空比是否符合要求。我在调试过程中发现一个常见问题由于分频系数很大仿真时如果跑完整的分频周期会非常耗时。我的建议是先用较小的分频系数比如100验证逻辑正确性然后再改回实际需要的分频系数。这样可以大大提高调试效率。3. 计数器模块设计3.1 模100计数器实现百分秒计时百分秒计时需要模100计数器我通常将其设计为两个模10计数器的级联。这种设计的好处是输出可以直接连接到七段译码器不需要额外的二进制到BCD转换电路。module counter100( input clk, // 100Hz时钟输入 input clr, // 异步清零信号低电平有效 output reg [3:0] qa, qb, // 个位和十位BCD输出 output co // 进位输出 ); reg cin; // 内部进位信号 always (posedge clk or negedge clr) begin if(~clr) begin qa 0; cin 0; end else if(qa 9) begin qa 0; cin 1; // 产生进位脉冲 end else begin qa qa 1; cin 0; end end always (posedge cin or negedge clr) begin if(~clr) begin qb 0; co 0; end else if(qb 9) begin qb 0; co 1; // 模100计数器满产生进位 end else begin qb qb 1; co 0; end end endmodule3.2 模60计数器实现秒计时秒计时部分使用模60计数器设计与模100计数器类似但需要注意十位计数到5就需要归零。在实际项目中我发现很多同学容易忽略这一点导致秒计时显示不正常。module counter60( input clk, // 来自模100计数器的进位输入 input clr, // 异步清零信号低电平有效 output reg [3:0] qa, qb // 秒的个位和十位BCD输出 ); reg cin; // 内部进位信号 always (posedge clk or negedge clr) begin if(~clr) begin qa 0; cin 0; end else if(qa 9) begin qa 0; cin 1; end else begin qa qa 1; cin 0; end end always (posedge cin or negedge clr) begin if(~clr) begin qb 0; end else if(qb 5) begin qb 0; end else begin qb qb 1; end end endmodule4. 数码管显示驱动设计4.1 七段译码器设计DE2-115开发板使用的是共阳极数码管这意味着我们需要输出低电平来点亮对应的段。七段译码器的设计需要特别注意段码的顺序不同厂家的数码管段序可能不同。module decoder7( input [3:0] in, // BCD码输入 output reg [6:0] out // 七段码输出(a-g) ); always(in) begin case (in) 4d0: out 7b1000000; // 0 4d1: out 7b1111001; // 1 4d2: out 7b0100100; // 2 4d3: out 7b0110000; // 3 4d4: out 7b0011001; // 4 4d5: out 7b0010010; // 5 4d6: out 7b0000010; // 6 4d7: out 7b1111000; // 7 4d8: out 7b0000000; // 8 4d9: out 7b0010000; // 9 default: out 7b1000000; // 默认显示0 endcase end endmodule4.2 数码管显示调试技巧在实际调试中数码管显示不正常是常见问题。我总结了几点调试经验首先确认数码管的类型共阴/共阳检查段码顺序是否正确使用静态显示测试每个段是否正常点亮注意刷新频率避免闪烁5. 系统集成与实测5.1 顶层模块设计与模块例化顶层模块的作用是将各个子模块连接起来形成一个完整的系统。这里需要使用模块例化语句并通过wire类型的信号进行互连。module seconds( input CLOCK_50, // 50MHz系统时钟 input [1:0] SW, // 开关输入 SW[0]:分频器清零 SW[1]:计数器清零 output [6:0] HEX0, HEX1, HEX2, HEX3 // 四位七段数码管 ); wire clk1; // 100Hz时钟 wire co; // 模100计数器进位 wire [3:0] qa, qb, qc, qd; // 计数器输出 // 模块例化 fenpin fp( .clk(CLOCK_50), .clr(SW[0]), .clk2(clk1) ); counter100 ct100( .clk(clk1), .clr(SW[1]), .qa(qa), .qb(qb), .co(co) ); counter60 ct60( .clk(co), .clr(SW[1]), .qa(qc), .qb(qd) ); decoder7 dc0(.in(qa), .out(HEX0)); // 百分秒个位 decoder7 dc1(.in(qb), .out(HEX1)); // 百分秒十位 decoder7 dc2(.in(qc), .out(HEX2)); // 秒个位 decoder7 dc3(.in(qd), .out(HEX3)); // 秒十位 endmodule5.2 管脚分配与下载测试在Quartus II中完成设计后需要为输入输出信号分配实际的物理管脚。DE2-115开发板的管脚定义可以在用户手册中找到。分配完管脚后编译生成sof文件通过USB-Blaster下载到开发板进行实测。我在实际测试中发现几个常见问题及解决方法数码管显示乱码检查段码顺序和数码管类型计时速度不对检查分频系数是否正确清零功能不正常检查清零信号的极性是否正确6. 功能扩展与优化6.1 实现暂停功能的两种方法原始秒表设计已经实现了基本计时和清零功能实际应用中经常需要暂停功能。我实验过两种实现方法第一种是通过控制计数器的使能端。添加一个使能信号当使能为0时计数器保持当前值不变。这种方法的优点是不影响时钟信号系统更加稳定。第二种是通过控制时钟信号也就是本次实验采用的方法。当暂停信号有效时将计数器的时钟输入置为常值从而停止计数。这种方法实现简单但可能会引入时钟毛刺。6.2 显示刷新率优化如果发现数码管有闪烁现象可以考虑提高刷新频率。一个实用的技巧是使用更高的刷新频率如1kHz然后通过分频控制每个数码管的显示时间。这样既能保证亮度又能避免闪烁。7. 常见问题与调试技巧在完成这个项目的过程中我遇到过不少问题也积累了一些调试经验。最常见的问题就是数码管显示不正常这通常有几个原因段码顺序错误、共阴共阳搞反、或者刷新频率不合适。另一个常见问题是计时不准确。这时候应该先检查分频模块的输出频率是否正确。可以在分频器输出端加一个LED指示灯通过观察LED的闪烁频率来粗略判断分频是否正确。对于Quartus II的使用我建议在编译前先进行语法检查可以节省不少时间。另外合理使用SignalTap逻辑分析仪可以大大简化调试过程特别是对于时序相关的问题。