本次实验我们在DE2-115开发板上用 Verilog编程实现了一个分秒计数器,并使用状态机的思想添加了按键暂停和按键消抖功能。
模块规划
project/
├── src/
├── clk_divider.v // 分频模块
├── debounce_fsm.v // 按键消抖模块
├── ctrl_fsm.v // 控制状态机
├── counter_60.v // 分秒计数器模块
└── led.v // 顶层模块(这里是建项目时出了点小差错,建议改为其它名称)
模块设计
首先我们需要两个计数器,一个秒计数器,一个分钟计数器,而DE2-115板子通常有50MHz的时钟,所以需要分频得到1Hz的信号来驱动计数器,所以我们可以设计一个分频模块,将50MHz分频到1Hz,作为计数器的时钟源。
// 分频模块
module clk_divider(
input clk_50m,
input rst_n,
output reg clk_1hz
);
reg [25:0] cnt; // 50MHz→1Hz需50M周期=50,000,000=26位计数器
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
cnt <= 0;
clk_1hz <= 0;
end else if (cnt == 26'd24_999_999) begin
cnt <= 0;
clk_1hz <= ~clk_1hz;
end else begin
cnt <= cnt + 1;
end
end
endmodule
然后我们进行按键的暂停和消抖处理,这里我们要用到状态机思想,就需要为状态机设置一些状态,空闲状态(正常计数)、暂停状态(停止计数)、等待释放状态(按键按下后的稳定期)。当检测到按键按下并经过消抖后,进入暂停状态,停止计数器;当按键释放并消抖后,回到空闲状态,继续计数。
// 按键消抖模块
module debounce_fsm(
input clk_1hz,
input rst_n,
input raw_btn,
output reg btn_ok
);
// 状态定义:IDLE/DEBOUNCE_PRESSED/DEBOUNCE_RELEASED
localparam IDLE = 2'b00;
localparam DEBOUNCE_PRESSED = 2'b01;
localparam DEBOUNCE_RELEASED = 2'b10;
reg [19:0] cnt; // 1Hz下20ms消抖对应20个时钟周期
reg [1:0] state, next_state;
reg btn_prev;
// 状态转移逻辑
always @(posedge clk_1hz or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
cnt <= 0;
btn_prev <= 1'b1;
end else begin
state <= next_state;
if (state == DEBOUNCE_PRESSED) cnt <= cnt + 1;
else cnt <= 0;
btn_prev <= raw_btn;
end
end
// 状态机逻辑
always @(*) begin
next_state = state;
btn_ok = 1'b1;
case(state)
IDLE: begin
if (raw_btn == 1'b0 && btn_prev == 1'b1) begin
next_state = DEBOUNCE_PRESSED;
end
end
DEBOUNCE_PRESSED: begin
if (cnt == 20'd19) begin // 20ms稳定后确认按下
next_state = DEBOUNCE_RELEASED;
btn_ok = 1'b0; // 输出有效按下信号
end
end
DEBOUNCE_RELEASED: begin
if (raw_btn == 1'b1 && btn_prev == 1'b0) begin
next_state = IDLE;
end
end
endcase
end
endmodule
// 控制状态机
module ctrl_fsm(
input clk_1hz,
input rst_n,
input btn_ok,
output reg run_en
);
localparam RUN = 1'b1;
localparam PAUSE = 1'b0;
reg [1:0] state, next_state;
always @(posedge clk_1hz or negedge rst_n) begin
if (!rst_n)
begin
state <= RUN;
end
else
begin
state <= next_state;
end
end
always @(*) begin
next_state = state;
case(state)
RUN:
begin
if (btn_ok) next_state = PAUSE; // 按键按下进入暂停
end
PAUSE:
begin
if (!btn_ok) next_state = RUN; // 按键释放恢复运行
end
endcase
end
always @(*) begin
run_en = (state==RUN);
end
endmodule
在模块划分上,我们需要将分频器、按键消抖模块、状态机控制模块和计数器模块分开。分频器模块处理时钟分频,按键消抖模块处理按键信号,状态机模块控制暂停和继续,计数器模块处理分秒的计数。这样的模块化设计更清晰,也便于调试。
另外我们可以使用计数器来延迟检测,确保按键状态稳定。
// 分秒计数器模块
module counter_60(
input clk_1hz,
input run_en,
input rst_n,
output reg [5:0] sec,
output reg [5:0] min
);
always @(posedge clk_1hz or negedge rst_n) begin
if (!rst_n) begin
sec <= 0;
min <= 0;
end else if (run_en) begin
if (sec == 6'd59) begin
sec <= 0;
if (min == 6'd59) min <= 0;
else min <= min + 1;
end else begin
sec <= sec + 1;
end
end
end
endmodule
最后则是我们的顶层模块设计,来实现DE2-115板子上的分秒计数器。
// 顶层模块
module led(
input clk_50m,
input rst_n,
input raw_btn,
output [6:0] hex0, // 秒个位显示
output [6:0] hex1, // 秒十位显示
output [6:0] hex2, // 分个位显示
output [6:0] hex3 // 分十位显示
);
wire clk_1hz;
wire btn_ok;
wire run_en;
reg [5:0] sec,
reg [5:0] min
// 实例化模块
clk_divider u1 (.clk_50m(clk_50m),
.rst_n(rst_n),
.clk_1hz(clk_1hz));
debounce_fsm u2 (.clk_1hz(clk_1hz),
.rst_n(rst_n),
.raw_btn(raw_btn),
.btn_ok(btn_ok));
ctrl_fsm u3 (.clk_1hz(clk_1hz),
.rst_n(rst_n),
.btn_ok(btn_ok),
.run_en(run_en));
counter_60 u4 (.clk_1hz(clk_1hz),
.run_en(run_en),
.rst_n(rst_n),
.sec(sec),
.min(min));
// BCD转换和显示模块
wire [3:0] min_tens = min / 10; // 分钟十位
wire [3:0] min_ones = min % 10; // 分钟个位
wire [3:0] sec_tens = sec / 10; // 秒十位
wire [3:0] sec_ones = sec % 10; // 秒个位
// 七段显示译码器
seg7_decoder seg0(.num(sec_ones), .seg(hex0));
seg7_decoder seg1(.num(sec_tens), .seg(hex1));
seg7_decoder seg2(.num(min_ones), .seg(hex2));
seg7_decoder seg3(.num(min_tens), .seg(hex3));
endmodule
最后记得把顶层模块设为顶层文件。

对于七段数码管模块,我们的输入情况有9种,所以七段译码器需要将4位BCD码转换为对应的段码,当FPGA输出低电压的时候,对应的字码段点亮。
// 七段显示译码模块
module seg7_decoder(
input [3:0] num,
output reg [6:0] seg // 格式:abcdefg(低有效)
);
always @(*) begin
case(num)
4'd0 : seg = 7'b1000000; // 0
4'd1 : seg = 7'b1111001; // 1
4'd2 : seg = 7'b0100100; // 2
4'd3 : seg = 7'b0110000; // 3
4'd4 : seg = 7'b0011001; // 4
4'd5 : seg = 7'b0010010; // 5
4'd6 : seg = 7'b0000010; // 6
4'd7 : seg = 7'b1111000; // 7
4'd8 : seg = 7'b0000000; // 8
4'd9 : seg = 7'b0010000; // 9
default: seg = 7'b1111111; // 灭
endcase
end
endmodule
接着进行管脚配置。

程序烧录
然后就可以连上DE2-115开发板,进行程序烧录。



总结
通过本次实验,我掌握了Verilog中分频器**、**模计数器和数码管驱动的核心设计方法,理解了时序逻辑与组合逻辑的协同工作原理。实验进一步强化了硬件描述语言的工程实践能力,为复杂计时器设计奠定了基础。