目录
设计功能
本次实验,我们在DE2-115板子上用 Verilog编程实现一个 分秒计数器,并具备按键暂停、按键消抖功能
一、系统框架设计
1.1计数逻辑
秒的计数器是0到59,当秒到59后,分钟加一,秒归零。分钟同样到59后归零。可能需要两个计数器:秒个位和十位,分钟个位和十位。或者,直接用一个4位的BCD计数器,每个单位用4位表示。
1.2结构框架:
时钟分频,生成1Hz的使能信号。
秒计数器,每1Hz信号时递增,并处理进位。
分钟计数器,当秒计数器满59后递增,同样处理进位。
将分钟和秒的十位和个位拆分成四个BCD数字。
将每个BCD数字转换为对应的七段码。
控制数码管的显示,可能需要动态扫描,但DE2-115的数码管每个都有独立的控制,所以可以直接将对应的段码赋给对应的hex输出。
1.3数码管设计:
DE2-115拥有八个七段数码管,其被分为两组,用来进行数字显示。如图,其每个管脚(共阳模式)均连接到Cyclone IV E。FPGA输出低电压时,对应段点亮,反之则熄灭。
每个数码段的字段都从0-6依次编号,下面给出其引脚配置和编号次序,便于我们进行数码管和FPGA的引脚连接。
引脚配置:
二、代码设计
2.1分频设计
分频是指将一个高频时钟信号转换为一个低频信号的过程。分频器通过计数器对输入的高频时钟信号进行计数,当计数达到一定值时,输出一个低频信号。在这里,我们将50MHz的高频时钟信号转换为1Hz的低频信号,从而实现每秒一次的计时功能。
当reset信号为高电平时,计数器counter和one_sec_enable被清零
计数时,counter递增,当counter到达49999999计数器清零,表示1秒时间已到。
reg [25:0] counter;
reg one_sec_enable;
always @(posedge clk or posedge reset) begin
if (reset) begin
counter <= 0;
one_sec_enable <= 0;
end else begin
if (counter == 26'd49_999_999) begin
counter <= 0;
one_sec_enable <= 1;
end else begin
counter <= counter + 1;
one_sec_enable <= 0;
end
end
end
2.2计数逻辑设计
通过BCD二进制编码的计数,来计算分和秒的递增。
sec_ones:秒的个位(0-9)
sec_tens:秒的十位(0-5)
min_ones:分钟的个位(0-9)
min_tens:分钟的十位(0-5)。 当reset信号为高电平时,所有计数器清零。 当start_pause信号为低电平时,暂停计数。 当one_sec_enable信号为高电平时,秒的个位递增,处理进位到秒的十位,再处理进位到分钟的个位和十位。
always @(posedge clk or posedge reset) begin
if (reset) begin
{sec_ones, sec_tens, min_ones, min_tens} <= 16'd0; // 全部清零
end else if (!start_pause) begin // KEY1按下时暂停(低电平有效)
if (one_sec_enable) begin
// 秒个位递增
if (sec_ones == 9) begin
sec_ones <= 0;
// 秒十位递增
if (sec_tens == 5) begin
sec_tens <= 0;
// 分钟个位递增
if (min_ones == 9) begin
min_ones <= 0;
// 分钟十位递增(0-5)
min_tens <= (min_tens == 5) ? 0 : min_tens + 1;
end else begin
min_ones <= min_ones + 1;
end
end else begin
sec_tens <= sec_tens + 1;
end
end else begin
sec_ones <= sec_ones + 1;
end
end
end
end
2.3数码管输出设计
该部分代码实现将BCD计数器的值转换为七段数码管的显示信号。
hex3显示分钟的十位;hex2显示分钟的个位,并带小数点。hex1显示秒的十位;hex0显示秒的个位。
assign hex3 = seg7(min_tens); // 分钟十位
wire [7:0] seg_min_one = seg7(min_ones);
assign hex2 = {1'b0 , seg_min_one[6:0]}; // 分钟个位带小数点
assign hex1 = seg7(sec_tens); // 秒十位
assign hex0 = seg7(sec_ones); // 秒个位
2.4完整代码
module minute_second_counter (
input clk, // 50MHz时钟(PIN_R8)
input reset, // 复位信号(连接按键KEY0,PIN_R22)
input start_pause, // 启动/暂停按键(连接按键KEY1,PIN_R21)
output [7:0] hex3, // 分钟十位(HEX3,Pins: P21,P15,N19,N18...)
output [7:0] hex2, // 分钟个位(带小数点)(HEX2)
output [7:0] hex1, // 秒十位(HEX1)
output [7:0] hex0 // 秒个位(HEX0)
);
// 分频到1Hz的计数器(50MHz→1Hz需计数50,000,000次)
reg [25:0] counter;
reg one_sec_enable;
// BCD计数器:分钟十位/个位,秒十位/个位
reg [3:0] min_tens; // 0-5
reg [3:0] min_ones; // 0-9
reg [3:0] sec_tens; // 0-5
reg [3:0] sec_ones; // 0-9
// 七段译码查找表(共阳极数码管,格式:dp,g,f,e,d,c,b,a)
function [7:0] seg7;
input [3:0] bcd;
begin
case (bcd)
4'd0: seg7 = 8'hC0; // 0
4'd1: seg7 = 8'hF9; // 1
4'd2: seg7 = 8'hA4; // 2
4'd3: seg7 = 8'hB0; // 3
4'd4: seg7 = 8'h99; // 4
4'd5: seg7 = 8'h92; // 5
4'd6: seg7 = 8'h82; // 6
4'd7: seg7 = 8'hF8; // 7
4'd8: seg7 = 8'h80; // 8
4'd9: seg7 = 8'h90; // 9
default: seg7 = 8'hFF; // 灭
endcase
end
endfunction
// 分频逻辑:每5000万次产生1秒使能信号
always @(posedge clk or posedge reset) begin
if (reset) begin
counter <= 0;
one_sec_enable <= 0;
end else begin
if (counter == 26'd49_999_999) begin
counter <= 0;
one_sec_enable <= 1;
end else begin
counter <= counter + 1;
one_sec_enable <= 0;
end
end
end
// 计数逻辑:BCD递增,带进位控制
always @(posedge clk or posedge reset) begin
if (reset) begin
{sec_ones, sec_tens, min_ones, min_tens} <= 16'd0; // 全部清零
end else if (!start_pause) begin // KEY1按下时暂停(低电平有效)
if (one_sec_enable) begin
// 秒个位递增
if (sec_ones == 9) begin
sec_ones <= 0;
// 秒十位递增
if (sec_tens == 5) begin
sec_tens <= 0;
// 分钟个位递增
if (min_ones == 9) begin
min_ones <= 0;
// 分钟十位递增(0-5)
min_tens <= (min_tens == 5) ? 0 : min_tens + 1;
end else begin
min_ones <= min_ones + 1;
end
end else begin
sec_tens <= sec_tens + 1;
end
end else begin
sec_ones <= sec_ones + 1;
end
end
end
end
// 数码管输出(HEX2显示分钟个位并带小数点)
assign hex3 = seg7(min_tens); // 分钟十位
wire [7:0] seg_min_one = seg7(min_ones);
assign hex2 = {1'b0 , seg_min_one[6:0]};
assign hex1 = seg7(sec_tens); // 秒十位
assign hex0 = seg7(sec_ones); // 秒个位
endmodule
2.5波形仿真
我们通过VScode编辑一个_tb.v文件来帮助我们验证被测模块(minute_second_counter)在各种输入条件下的行为是否符合预期。通过仿真,我们可以观察模块的输出信号是否正确,从而确保模块的逻辑设计无误。
代码:
`timescale 1ns / 1ps
module minute_second_counter_tb;
// 输入信号
reg clk;
reg reset;
reg start_pause;
// 输出信号
wire [7:0] hex3;
wire [7:0] hex2;
wire [7:0] hex1;
wire [7:0] hex0;
// 实例化待测试模块
minute_second_counter uut (
.clk(clk),
.reset(reset),
.start_pause(start_pause),
.hex3(hex3),
.hex2(hex2),
.hex1(hex1),
.hex0(hex0)
);
// 时钟生成,模拟50MHz时钟信号
initial begin
clk = 0;
forever #10 clk = ~clk; // 50MHz时钟周期为20ns
end
// 初始化输入信号并模拟操作
initial begin
// 初始化输入
reset = 1; // 激活复位
start_pause = 0; // 初始状态为停止
// 等待全局复位
#100;
reset = 0; // 释放复位
// 等待一段时间,观察计时器在停止状态下的行为
#200;
// 模拟启动计时器
#50;
start_pause = 1; // 启动计时器
// 让计时器运行一段时间
#5000;
// 模拟暂停计时器
start_pause = 0; // 暂停计时器
// 让计时器暂停一段时间
#2000;
// 再次启动计时器
start_pause = 1; // 启动计时器
// 让计时器再次运行一段时间
#5000;
// 重复上述操作
repeat (5) begin
// 模拟暂停计时器
start_pause = 0; // 暂停计时器
#2000;
// 再次启动计时器
start_pause = 1; // 启动计时器
#5000;
end
// 模拟更多操作
#10000;
end
// 添加所有信号到仿真波形中
initial begin
$dumpfile("minute_second_counter_tb.vcd");
$dumpvars(0, minute_second_counter_tb);
end
endmodule
我们将该文件添加至QUartus建立的工程当中:
然后我们对仿真进行设置
添加Modelsim路径,确保仿真时能正常打开Modlesim
添加Modelsim路径,确保仿真时能正常打开Modlesim
进行仿真:
三、代码改进
3.1添加按键消抖模块
按键抖动是按键抖动是由于机械按键在按下和释放时产生的瞬时震荡现象。这种抖动会导致信号不稳定,从而影响计数器的准确性。因此,我需要设计一个消抖模块来帮助我们处理按键信号中的抖动,确保按键信号的稳定性和可靠性。
module debounce (
input clk, // 时钟信号
input reset, // 复位信号
input key, // 原始按键信号
output reg db_key // 消抖后的按键信号
);
// 消抖计数器(根据需要调整计数值,例如50MHz时钟下,10ms抖动时间需要500,000次计数)
reg [18:0] db_counter;
reg [3:0] key_state;
always @(posedge clk or posedge reset) begin
if (reset) begin
db_counter <= 0;
key_state <= 0;
db_key <= 0;
end else begin
// 采样按键状态
key_state[0] <= key;
key_state[1] <= key_state[0];
key_state[2] <= key_state[1];
key_state[3] <= key_state[2];
// 检测按键状态变化
if (key_state[3] != key_state[2]) begin
db_counter <= 0;
db_key <= key_state[3];
end else if (db_counter < 19'd499999) begin
db_counter <= db_counter + 1;
end else begin
db_key <= key_state[3];
end
end
end
endmodule
3.2将代码分为顶层模块,消抖模块和核心计数模块
我们将代码分为顶层模块,消抖模块和核心计数模块3部分。这样每个模块的代码相对独立,便于我们进行单独的修改和测试。当代码出现问题时,我们可以通过模块逐个进行排查,缩小问题的范围。
3.2.1顶层模块
负责实例化和连接消抖模块和核心计数模块
module top_module (
input clk, // 50MHz时钟(PIN_R8)
input reset, // 复位信号(连接按键KEY0,PIN_R22)
input start_pause, // 启动/暂停按键(连接按键KEY1,PIN_R21)
output [7:0] hex3, // 分钟十位(HEX3,Pins: P21,P15,N19,N18...)
output [7:0] hex2, // 分钟个位(带小数点)(HEX2)
output [7:0] hex1, // 秒十位(HEX1)
output [7:0] hex0 // 秒个位(HEX0)
);
// 消抖后的启动/暂停按键信号
wire db_start_pause;
// 消抖模块实例化
debounce u_debounce (
.clk(clk),
.reset(reset),
.key(start_pause),
.db_key(db_start_pause)
);
// 核心计数模块实例化
minute_second_counter_core u_core (
.clk(clk),
.reset(reset),
.start_pause(db_start_pause),
.hex3(hex3),
.hex2(hex2),
.hex1(hex1),
.hex0(hex0)
);
endmodule
3.2.2消抖模块
处理按键信号的抖动问题
module debounce (
input clk, // 时钟信号
input reset, // 复位信号
input key, // 原始按键信号
output reg db_key // 消抖后的按键信号
);
// 消抖计数器(根据需要调整计数值,例如50MHz时钟下,10ms抖动时间需要500,000次计数)
reg [18:0] db_counter;
reg [3:0] key_state;
always @(posedge clk or posedge reset) begin
if (reset) begin
db_counter <= 0;
key_state <= 0;
db_key <= 0;
end else begin
// 采样按键状态
key_state[0] <= key;
key_state[1] <= key_state[0];
key_state[2] <= key_state[1];
key_state[3] <= key_state[2];
// 检测按键状态变化
if (key_state[3] != key_state[2]) begin
db_counter <= 0;
db_key <= key_state[3];
end else if (db_counter < 19'd499999) begin
db_counter <= db_counter + 1;
end else begin
db_key <= key_state[3];
end
end
end
endmodule
3.2.3核心计数模块
实现分频、BCD计数和数码管显示逻辑
module minute_second_counter_core (
input clk, // 50MHz时钟(PIN_R8)
input reset, // 复位信号
input start_pause, // 启动/暂停按键(消抖后)
output [7:0] hex3, // 分钟十位(HEX3)
output [7:0] hex2, // 分钟个位(带小数点)(HEX2)
output [7:0] hex1, // 秒十位(HEX1)
output [7:0] hex0 // 秒个位(HEX0)
);
// 分频到1Hz的计数器(50MHz→1Hz需计数50,000,000次)
reg [25:0] counter;
reg one_sec_enable;
// BCD计数器:分钟十位/个位,秒十位/个位
reg [3:0] min_tens; // 0-5
reg [3:0] min_ones; // 0-9
reg [3:0] sec_tens; // 0-5
reg [3:0] sec_ones; // 0-9
// 七段译码查找表(共阳极数码管,格式:dp,g,f,e,d,c,b,a)
function [7:0] seg7;
input [3:0] bcd;
begin
case (bcd)
4'd0: seg7 = 8'hC0; // 0
4'd1: seg7 = 8'hF9; // 1
4'd2: seg7 = 8'hA4; // 2
4'd3: seg7 = 8'hB0; // 3
4'd4: seg7 = 8'h99; // 4
4'd5: seg7 = 8'h92; // 5
4'd6: seg7 = 8'h82; // 6
4'd7: seg7 = 8'hF8; // 7
4'd8: seg7 = 8'h80; // 8
4'd9: seg7 = 8'h90; // 9
default: seg7 = 8'hFF; // 灭
endcase
end
endfunction
// 分频逻辑:每5000万次产生1秒使能信号
always @(posedge clk or posedge reset) begin
if (reset) begin
counter <= 0;
one_sec_enable <= 0;
end else begin
if (counter == 26'd49_999_999) begin
counter <= 0;
one_sec_enable <= 1;
end else begin
counter <= counter + 1;
one_sec_enable <= 0;
end
end
end
// 计数逻辑:BCD递增,带进位控制
always @(posedge clk or posedge reset) begin
if (reset) begin
{sec_ones, sec_tens, min_ones, min_tens} <= 16'd0; // 全部清零
end else if (!start_pause) begin // 使用消抖后的按键信号
if (one_sec_enable) begin
// 秒个位递增
if (sec_ones == 9) begin
sec_ones <= 0;
// 秒十位递增
if (sec_tens == 5) begin
sec_tens <= 0;
// 分钟个位递增
if (min_ones == 9) begin
min_ones <= 0;
// 分钟十位递增(0-5)
min_tens <= (min_tens == 5) ? 0 : min_tens + 1;
end else begin
min_ones <= min_ones + 1;
end
end else begin
sec_tens <= sec_tens + 1;
end
end else begin
sec_ones <= sec_ones + 1;
end
end
end
end
// 数码管输出(HEX2显示分钟个位并带小数点)
assign hex3 = seg7(min_tens); // 分钟十位
wire [7:0] seg_min_one = seg7(min_ones);
assign hex2 = {1'b0 , seg_min_one[6:0]}; // 分钟个位带小数点
assign hex1 = seg7(sec_tens); // 秒十位
assign hex0 = seg7(sec_ones); // 秒个位
endmodule
实验结果
计时器