从零构建FPGA秒表Verilog模块化设计实战指南第一次接触FPGA开发时最令人兴奋的莫过于看到自己编写的代码通过硬件真实运行起来。本文将带您从零开始使用Verilog在DE2-115开发板上实现一个精确到百分之一秒的数字秒表。不同于简单的代码罗列我们将采用模块化设计思维逐步构建分频器、计数器、译码器等核心组件最终完成系统集成。无论您是电子工程专业的学生还是FPGA爱好者这个实践项目都将帮助您掌握数字系统设计的基本方法论。1. 开发环境与项目规划在开始编码前我们需要准备好开发环境并明确项目需求。DE2-115开发板搭载Cyclone IV E FPGA芯片配备50MHz系统时钟和四个七段数码管非常适合作为我们的硬件平台。所需工具清单Quartus Prime 13.0或更新版本DE2-115开发板及配套USB-Blaster下载器文本编辑器推荐VS Code或Notepad提示确保安装Quartus时勾选了ModelSim-Altera组件后续仿真会用到秒表的核心功能需求包括显示范围00.00 - 59.99秒控制功能启动/暂停、复位清零显示精度百分之一秒10ms刷新率系统架构设计如下图所示[时钟分频] → [百分秒计数器] → [秒计数器] → [译码显示] ↑ ↑ ↑ 50MHz 控制信号 控制信号2. 时钟分频模块设计DE2-115提供的50MHz时钟对于秒表来说频率过高我们需要先将其分频为100Hz信号周期10ms作为百分秒计数的基准。2.1 分频原理与实现分频系数计算公式分频系数 输入频率 / 输出频率 - 1 50,000,000 / 100 - 1 499,999Verilog实现代码module clock_divider ( input wire clk_50m, // 50MHz输入时钟 input wire reset_n, // 低电平复位 output reg clk_100hz // 100Hz输出时钟 ); reg [31:0] counter; always (posedge clk_50m or negedge reset_n) begin if (!reset_n) begin counter 0; clk_100hz 0; end else if (counter 499999) begin counter 0; clk_100hz ~clk_100hz; // 时钟翻转 end else begin counter counter 1; end end endmodule2.2 分频模块仿真验证在Quartus中创建测试波形文件验证分频功能测试要点复位信号有效时低电平输出时钟应为0复位释放后输出时钟应呈现100Hz方波检查输出时钟的占空比是否为50%3. 计数器模块设计秒表需要两个计数器模100计数器百分秒和模60计数器秒。我们将采用层次化计数器设计便于直接输出BCD码到数码管。3.1 模100计数器设计module counter_100 ( input wire clk, input wire reset_n, input wire enable, output reg [3:0] bcd_ones, // 个位BCD output reg [3:0] bcd_tens, // 十位BCD output wire carry_out // 进位信号 ); reg carry_internal; always (posedge clk or negedge reset_n) begin if (!reset_n) begin bcd_ones 0; carry_internal 0; end else if (enable) begin if (bcd_ones 9) begin bcd_ones 0; carry_internal 1; end else begin bcd_ones bcd_ones 1; carry_internal 0; end end end always (posedge carry_internal or negedge reset_n) begin if (!reset_n) begin bcd_tens 0; end else if (enable) begin if (bcd_tens 9) begin bcd_tens 0; end else begin bcd_tens bcd_tens 1; end end end assign carry_out (bcd_ones 9) (bcd_tens 9) enable; endmodule3.2 模60计数器设计模60计数器结构与模100类似主要区别在于十位计数到5即归零module counter_60 ( input wire clk, input wire reset_n, input wire enable, output reg [3:0] bcd_ones, output reg [3:0] bcd_tens ); reg carry_internal; always (posedge clk or negedge reset_n) begin if (!reset_n) begin bcd_ones 0; carry_internal 0; end else if (enable) begin if (bcd_ones 9) begin bcd_ones 0; carry_internal 1; end else begin bcd_ones bcd_ones 1; carry_internal 0; end end end always (posedge carry_internal or negedge reset_n) begin if (!reset_n) begin bcd_tens 0; end else if (enable) begin if (bcd_tens 5) begin bcd_tens 0; end else begin bcd_tens bcd_tens 1; end end end endmodule4. 七段译码器设计DE2-115开发板使用共阳极数码管这意味着段选信号低电平时对应段点亮。我们需要设计一个BCD到七段码的译码器。数码管段位对应关系a --- f | | b --- e | | c --- dVerilog实现module seg7_decoder ( input [3:0] bcd, output reg [6:0] seg ); always (*) begin case (bcd) 4d0: seg 7b1000000; // abcdefg 4d1: seg 7b1111001; 4d2: seg 7b0100100; 4d3: seg 7b0110000; 4d4: seg 7b0011001; 4d5: seg 7b0010010; 4d6: seg 7b0000010; 4d7: seg 7b1111000; 4d8: seg 7b0000000; 4d9: seg 7b0010000; default: seg 7b1111111; // 全灭 endcase end endmodule注意实际使用时可能需要根据具体数码管型号调整段序建议先测试单个数字的显示5. 顶层系统集成现在我们将所有模块通过线网(wire)连接起来构建完整的秒表系统。5.1 端口定义与信号连接module stopwatch_top ( input wire CLOCK_50, // 50MHz时钟 input wire [1:0] KEY, // KEY[0]:复位, KEY[1]:暂停 output wire [6:0] HEX0, // 百分秒个位 output wire [6:0] HEX1, // 百分秒十位 output wire [6:0] HEX2, // 秒个位 output wire [6:0] HEX3 // 秒十位 ); wire clk_100hz; wire [3:0] bcd_100ms_ones, bcd_100ms_tens; wire [3:0] bcd_s_ones, bcd_s_tens; wire carry_100ms; clock_divider u_div ( .clk_50m(CLOCK_50), .reset_n(KEY[0]), .clk_100hz(clk_100hz) ); counter_100 u_counter100 ( .clk(clk_100hz), .reset_n(KEY[0]), .enable(KEY[1]), .bcd_ones(bcd_100ms_ones), .bcd_tens(bcd_100ms_tens), .carry_out(carry_100ms) ); counter_60 u_counter60 ( .clk(carry_100ms), .reset_n(KEY[0]), .enable(KEY[1]), .bcd_ones(bcd_s_ones), .bcd_tens(bcd_s_tens) ); seg7_decoder u_decoder0 ( .bcd(bcd_100ms_ones), .seg(HEX0) ); seg7_decoder u_decoder1 ( .bcd(bcd_100ms_tens), .seg(HEX1) ); seg7_decoder u_decoder2 ( .bcd(bcd_s_ones), .seg(HEX2) ); seg7_decoder u_decoder3 ( .bcd(bcd_s_tens), .seg(HEX3) ); endmodule5.2 管脚分配与下载在Quartus中完成编译后需要为设计分配实际管脚。DE2-115的关键管脚定义如下信号名称管脚编号开发板对应元件CLOCK_50PIN_Y250MHz时钟KEY[0]PIN_M23复位按钮KEY[1]PIN_M21暂停按钮HEX0[6:0]PIN_G18-F22数码管0HEX1[6:0]PIN_E17-F21数码管1HEX2[6:0]PIN_N20-N25数码管2HEX3[6:0]PIN_N26-P19数码管3下载步骤编译成功后打开Programmer工具选择USB-Blaster作为硬件接口添加生成的.sof文件点击Start开始下载6. 功能扩展与调试技巧基础功能实现后我们可以考虑为秒表添加更多实用功能。6.1 添加暂停功能原设计使用按键直接控制计数器使能更完善的暂停逻辑可以这样实现// 在顶层模块中添加暂停控制逻辑 reg pause_reg; wire pause_pulse; // 按键消抖模块实例化 debounce u_debounce ( .clk(clk_100hz), .button(KEY[1]), .pulse(pause_pulse) ); always (posedge clk_100hz or negedge KEY[0]) begin if (!KEY[0]) begin pause_reg 0; end else if (pause_pulse) begin pause_reg ~pause_reg; end end // 将enable信号连接到pause_reg6.2 常见问题排查数码管显示异常检查段序是否正确必要时调整seg7_decoder中的编码确认数码管共阳/共阴特性DE2-115为共阳测量各段位信号是否正常到达管脚计时不准确用示波器检查clk_100hz信号频率验证分频系数计算是否正确检查计数器进位逻辑按钮响应不灵敏添加按键消抖电路硬件或软件实现调整按键检测的时钟频率在FPGA开发过程中模块化设计和逐步验证是关键。建议每个模块单独测试通过后再进行系统集成这样可以快速定位问题所在。
用Verilog在DE2-115上做个秒表:从分频到译码,一个模块一个模块带你过
从零构建FPGA秒表Verilog模块化设计实战指南第一次接触FPGA开发时最令人兴奋的莫过于看到自己编写的代码通过硬件真实运行起来。本文将带您从零开始使用Verilog在DE2-115开发板上实现一个精确到百分之一秒的数字秒表。不同于简单的代码罗列我们将采用模块化设计思维逐步构建分频器、计数器、译码器等核心组件最终完成系统集成。无论您是电子工程专业的学生还是FPGA爱好者这个实践项目都将帮助您掌握数字系统设计的基本方法论。1. 开发环境与项目规划在开始编码前我们需要准备好开发环境并明确项目需求。DE2-115开发板搭载Cyclone IV E FPGA芯片配备50MHz系统时钟和四个七段数码管非常适合作为我们的硬件平台。所需工具清单Quartus Prime 13.0或更新版本DE2-115开发板及配套USB-Blaster下载器文本编辑器推荐VS Code或Notepad提示确保安装Quartus时勾选了ModelSim-Altera组件后续仿真会用到秒表的核心功能需求包括显示范围00.00 - 59.99秒控制功能启动/暂停、复位清零显示精度百分之一秒10ms刷新率系统架构设计如下图所示[时钟分频] → [百分秒计数器] → [秒计数器] → [译码显示] ↑ ↑ ↑ 50MHz 控制信号 控制信号2. 时钟分频模块设计DE2-115提供的50MHz时钟对于秒表来说频率过高我们需要先将其分频为100Hz信号周期10ms作为百分秒计数的基准。2.1 分频原理与实现分频系数计算公式分频系数 输入频率 / 输出频率 - 1 50,000,000 / 100 - 1 499,999Verilog实现代码module clock_divider ( input wire clk_50m, // 50MHz输入时钟 input wire reset_n, // 低电平复位 output reg clk_100hz // 100Hz输出时钟 ); reg [31:0] counter; always (posedge clk_50m or negedge reset_n) begin if (!reset_n) begin counter 0; clk_100hz 0; end else if (counter 499999) begin counter 0; clk_100hz ~clk_100hz; // 时钟翻转 end else begin counter counter 1; end end endmodule2.2 分频模块仿真验证在Quartus中创建测试波形文件验证分频功能测试要点复位信号有效时低电平输出时钟应为0复位释放后输出时钟应呈现100Hz方波检查输出时钟的占空比是否为50%3. 计数器模块设计秒表需要两个计数器模100计数器百分秒和模60计数器秒。我们将采用层次化计数器设计便于直接输出BCD码到数码管。3.1 模100计数器设计module counter_100 ( input wire clk, input wire reset_n, input wire enable, output reg [3:0] bcd_ones, // 个位BCD output reg [3:0] bcd_tens, // 十位BCD output wire carry_out // 进位信号 ); reg carry_internal; always (posedge clk or negedge reset_n) begin if (!reset_n) begin bcd_ones 0; carry_internal 0; end else if (enable) begin if (bcd_ones 9) begin bcd_ones 0; carry_internal 1; end else begin bcd_ones bcd_ones 1; carry_internal 0; end end end always (posedge carry_internal or negedge reset_n) begin if (!reset_n) begin bcd_tens 0; end else if (enable) begin if (bcd_tens 9) begin bcd_tens 0; end else begin bcd_tens bcd_tens 1; end end end assign carry_out (bcd_ones 9) (bcd_tens 9) enable; endmodule3.2 模60计数器设计模60计数器结构与模100类似主要区别在于十位计数到5即归零module counter_60 ( input wire clk, input wire reset_n, input wire enable, output reg [3:0] bcd_ones, output reg [3:0] bcd_tens ); reg carry_internal; always (posedge clk or negedge reset_n) begin if (!reset_n) begin bcd_ones 0; carry_internal 0; end else if (enable) begin if (bcd_ones 9) begin bcd_ones 0; carry_internal 1; end else begin bcd_ones bcd_ones 1; carry_internal 0; end end end always (posedge carry_internal or negedge reset_n) begin if (!reset_n) begin bcd_tens 0; end else if (enable) begin if (bcd_tens 5) begin bcd_tens 0; end else begin bcd_tens bcd_tens 1; end end end endmodule4. 七段译码器设计DE2-115开发板使用共阳极数码管这意味着段选信号低电平时对应段点亮。我们需要设计一个BCD到七段码的译码器。数码管段位对应关系a --- f | | b --- e | | c --- dVerilog实现module seg7_decoder ( input [3:0] bcd, output reg [6:0] seg ); always (*) begin case (bcd) 4d0: seg 7b1000000; // abcdefg 4d1: seg 7b1111001; 4d2: seg 7b0100100; 4d3: seg 7b0110000; 4d4: seg 7b0011001; 4d5: seg 7b0010010; 4d6: seg 7b0000010; 4d7: seg 7b1111000; 4d8: seg 7b0000000; 4d9: seg 7b0010000; default: seg 7b1111111; // 全灭 endcase end endmodule注意实际使用时可能需要根据具体数码管型号调整段序建议先测试单个数字的显示5. 顶层系统集成现在我们将所有模块通过线网(wire)连接起来构建完整的秒表系统。5.1 端口定义与信号连接module stopwatch_top ( input wire CLOCK_50, // 50MHz时钟 input wire [1:0] KEY, // KEY[0]:复位, KEY[1]:暂停 output wire [6:0] HEX0, // 百分秒个位 output wire [6:0] HEX1, // 百分秒十位 output wire [6:0] HEX2, // 秒个位 output wire [6:0] HEX3 // 秒十位 ); wire clk_100hz; wire [3:0] bcd_100ms_ones, bcd_100ms_tens; wire [3:0] bcd_s_ones, bcd_s_tens; wire carry_100ms; clock_divider u_div ( .clk_50m(CLOCK_50), .reset_n(KEY[0]), .clk_100hz(clk_100hz) ); counter_100 u_counter100 ( .clk(clk_100hz), .reset_n(KEY[0]), .enable(KEY[1]), .bcd_ones(bcd_100ms_ones), .bcd_tens(bcd_100ms_tens), .carry_out(carry_100ms) ); counter_60 u_counter60 ( .clk(carry_100ms), .reset_n(KEY[0]), .enable(KEY[1]), .bcd_ones(bcd_s_ones), .bcd_tens(bcd_s_tens) ); seg7_decoder u_decoder0 ( .bcd(bcd_100ms_ones), .seg(HEX0) ); seg7_decoder u_decoder1 ( .bcd(bcd_100ms_tens), .seg(HEX1) ); seg7_decoder u_decoder2 ( .bcd(bcd_s_ones), .seg(HEX2) ); seg7_decoder u_decoder3 ( .bcd(bcd_s_tens), .seg(HEX3) ); endmodule5.2 管脚分配与下载在Quartus中完成编译后需要为设计分配实际管脚。DE2-115的关键管脚定义如下信号名称管脚编号开发板对应元件CLOCK_50PIN_Y250MHz时钟KEY[0]PIN_M23复位按钮KEY[1]PIN_M21暂停按钮HEX0[6:0]PIN_G18-F22数码管0HEX1[6:0]PIN_E17-F21数码管1HEX2[6:0]PIN_N20-N25数码管2HEX3[6:0]PIN_N26-P19数码管3下载步骤编译成功后打开Programmer工具选择USB-Blaster作为硬件接口添加生成的.sof文件点击Start开始下载6. 功能扩展与调试技巧基础功能实现后我们可以考虑为秒表添加更多实用功能。6.1 添加暂停功能原设计使用按键直接控制计数器使能更完善的暂停逻辑可以这样实现// 在顶层模块中添加暂停控制逻辑 reg pause_reg; wire pause_pulse; // 按键消抖模块实例化 debounce u_debounce ( .clk(clk_100hz), .button(KEY[1]), .pulse(pause_pulse) ); always (posedge clk_100hz or negedge KEY[0]) begin if (!KEY[0]) begin pause_reg 0; end else if (pause_pulse) begin pause_reg ~pause_reg; end end // 将enable信号连接到pause_reg6.2 常见问题排查数码管显示异常检查段序是否正确必要时调整seg7_decoder中的编码确认数码管共阳/共阴特性DE2-115为共阳测量各段位信号是否正常到达管脚计时不准确用示波器检查clk_100hz信号频率验证分频系数计算是否正确检查计数器进位逻辑按钮响应不灵敏添加按键消抖电路硬件或软件实现调整按键检测的时钟频率在FPGA开发过程中模块化设计和逐步验证是关键。建议每个模块单独测试通过后再进行系统集成这样可以快速定位问题所在。