项目需求
完成一个具有启动、停止、递增和清除功能的秒表。
实现方式
使用verilog语言,diamond仿真软件,小脚丫核心板等工具去实现,下面有设计思路和实现代码。
项目功能介绍
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。
秒表使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。
设计思路(即需求分析)
将这个项目分成了4个模块,分别是key、control、seg_disp和top。key模块负责处理手动按压按键产生的信号,并对信号进行消抖处理,然后输出消抖后的按键信号。control模块控制秒表的开始,停止,增量和复位功能seg_disp模块负责把秒表计数的数字译码为数码管显示的数据top模块对上述模块进行了实例化,并对上述模块进行了链接。
top模块与各个模块之间的关系文字解释如下:
与 key:
实例化了三个 key 模块,然后就将按键信号输入key_in连接到 key 模块的输入端,然后接收 key 模块输出的消抖后的按键信号key_out。
与 control:
实例化了一个 control 模块,之后就把 key 模块输出的按键信号连接到 control 模块的输入端,用来控制秒表的开始、停止和增量功能。
与 seg_disp:
实例化了两个 seg_disp 模块,然后就把秒表的结果cnt_xs和cnt_gw连接到 seg_disp 模块的输入端din
硬件框图
以下三张图来源于硬禾科技提供的小脚丫FPGA使用手册之STEP-MXO2:基于Lattice XO2-4000HC的FPGA学习模块(STEP-MXO2:基于Lattice XO2-4000HC的FPGA学习模块 (eetree.cn))
软件流程图
FPGA资源占用报告
硬件介绍
这部分内容资料来源于硬禾科技在STEP-MXO2:基于Lattice XO2-4000HC的FPGA学习模块 (eetree.cn)中提供的小脚丫STEP-MXO2第二代硬件结构
这个硬件使用Lattice LCMXO2-4000HC-4MG132作为其核心器件,采用132脚BGA封装,引脚间距0.5mm,芯片尺寸为8mm 8mm。这个硬件拥有上电瞬时启动功能,启动时间小于1毫秒。同时,它拥有4320个LUT资源,96Kbit用户闪存 以及92Kbit RAM。同时,还支持2+2路PLL+DLL,以及嵌入式功能块(包括一路SPI,一路定时器,2路I2C)。除此之外,这个硬件还支持DDP/DDR2/LPDDR存储器,具备104个可热插拔I/O,并且支持内核电压在2.5-3.3V范围内工作
关于板载资源,硬件有两位7段数码管,两个RGB三色LED,8路用户LED,4路拨码开关,4路按键
在其他方面,硬件具有36个用户可扩展I/O,其中包括一路SPI硬核接口和一路I2C硬核接口。支持开发工具Lattice Diamond,MICO32/8软核处理器。本硬件集成了FPGA编程器,并采用U盘的模式进行操作。同时,配备一路USB Type C接口,可用于给核心板供电、给FPGA下载JED文件以及与上位机通过UART通信。整体板卡尺寸为52mm x 18mm,适用于各类嵌入式系统开发与实验。
实现的功能及图片展示
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。
按压第一个按钮,控制秒表开始以10Hz时钟速率递增(即每0.1秒计数一次),从 0.0 秒计数到 9.9秒,然后翻转(即又重新从0.0秒开始)
按压第二个按钮,使计数器停止递增,但使数码管显示当前计数器值
按压第三个按钮,每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间
按压第四个按钮强制计数器值为零
GPT的使用(这部分中的代码不是最终代码)
GPT帮助我写了每一部分的代码,具体操作就是我先把我自己的代码思路用文字写成方便GPT的形式,给到GPT,它给我提供一个版本的代码,这个版本不是我最终使用的版本,我自己对GPT给我的代码进行了许多修改和完善。以下是我对GPT的提问截图和GPT给我的代码。代码部分太长,截图放不下,所以GPT给的代码复制到了报告上,这个版本的代码和最终版本差距较大!
top模块
module top #(parameter TIME_20MS = 20000000, FCLK = 50000000, FREFRESH = 1000)
(
input wire clk, // Clock input
input wire rst_n, // Reset input
input wire [2:0] key_in, // Key inputs
output reg [6:0] seg_xs, // Output for segment display (x.xxx)
output reg [3:0] dig_xs, // Output for digit select (x.xxx)
output reg [6:0] seg_gw, // Output for segment display (xx.xx)
output reg [3:0] dig_gw // Output for digit select (xx.xx)
);
// Instantiating debounce module for start, pause, and increment buttons
debounce debounce_start (.clk(clk), .rst_n(rst_n), .key_in(key_in[0]));
debounce debounce_pause (.clk(clk), .rst_n(rst_n), .key_in(key_in[1]));
debounce debounce_increment (.clk(clk), .rst_n(rst_n), .key_in(key_in[2]));
// Instantiating stopwatch control and counting module
stopwatch_control stopwatch_ctrl (.clk(clk), .rst_n(rst_n), .start(debounce_start.output), .pause(debounce_pause.output), .increment(debounce_increment.output));
// Instantiating 7-segment decoder module for the fractional part of stopwatch
seg_decoder seg_decoder_xs (.value(stopwatch_ctrl.fraction), .seg_out(seg_xs), .dig_out(dig_xs));
// Instantiating 7-segment decoder module for the units part of stopwatch
seg_decoder seg_decoder_gw (.value(stopwatch_ctrl.units), .seg_out(seg_gw), .dig_out(dig_gw));
endmodule
module debounce
(
input wire clk, // Clock input
input wire rst_n, // Reset input
input wire key_in, // Input from the key
output reg output // Debounced output
);
// Debounce parameters
parameter DEBOUNCE_DELAY = 25; // Debounce delay (25 cycles for 20ms debounce)
// State definitions
typedef enum logic [1:0] {
IDLE,
PRESS_DEBOUNCE,
RELEASE_DEBOUNCE
} state_t;
// Internal signals
state_t state;
reg [DEBOUNCE_DELAY-1:0] debounce_counter;
reg last_key_in;
// Debounce process
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
debounce_counter <= 0;
last_key_in <= 1'b0;
output <= 1'b0;
end
else begin
case(state)
IDLE: begin
if (key_in != last_key_in) begin
state <= PRESS_DEBOUNCE;
debounce_counter <= 0;
end
end
PRESS_DEBOUNCE: begin
if (debounce_counter == DEBOUNCE_DELAY) begin
if (key_in == 1'b1) begin
output <= 1'b1;
end
state <= RELEASE_DEBOUNCE;
debounce_counter <= 0;
end
else begin
debounce_counter <= debounce_counter + 1;
end
end
RELEASE_DEBOUNCE: begin
if (debounce_counter == DEBOUNCE_DELAY) begin
if (key_in == 1'b0) begin
output <= 1'b0;
end
state <= IDLE;
debounce_counter <= 0;
end
else begin
debounce_counter <= debounce_counter + 1;
end
end
default: state <= IDLE;
endcase
last_key_in <= key_in;
end
end
endmodule
module stopwatch_control
(
input wire clk, // Clock input
input wire rst_n, // Reset input
input wire start, // Start input
input wire pause, // Pause input
input wire increment // Increment input
);
// Stopwatch parameters
parameter COUNT_MAX = 50000; // Maximum count for stopwatch
parameter COUNT_MIN = 0; // Minimum count for stopwatch
// Internal signals
reg [15:0] count; // Counter for stopwatch
reg running; // Running flag for stopwatch
// Counting process
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= COUNT_MIN;
running <= 0;
end
else begin
if (start && !running) begin
running <= 1;
end
else if (pause && running) begin
running <= 0;
end
else if (increment && running && (count < COUNT_MAX)) begin
count <= count + 1;
end
end
end
// Outputs
assign fraction = count % 10000; // Fraction part (x.xxx)
assign units = count / 10000; // Units part (xx.xx)
endmodule
module seg_decoder
(
input wire [15:0] value, // Input value to be decoded
output reg [6:0] seg_out, // Output for segment display
output reg [3:0] dig_out // Output for digit select
);
// 7-segment display values for digits 0 to 9
parameter [6:0] SEG_TABLE [10] = {
7'b0000001, // 0
7'b1001111, // 1
7'b0010010, // 2
7'b0000110, // 3
7'b1001100, // 4
7'b0100100, // 5
7'b0100000, // 6
7'b0001111, // 7
7'b0000000, // 8
7'b0000100 // 9
};
// Segment select for each digit
parameter [3:0] DIG_SELECT [4] = {
4'b1110, // Digit 0
4'b1101, // Digit 1
4'b1011, // Digit 2
4'b0111 // Digit 3
};
// Internal signals
reg [3:0] digit_select;
// Decoding process
always @(*) begin
case(value)
16'd0: begin
seg_out = SEG_TABLE[0];
digit_select = DIG_SELECT[0];
end
16'd1: begin
seg_out = SEG_TABLE[1];
digit_select = DIG_SELECT[1];
end
16'd2: begin
seg_out = SEG_TABLE[2];
digit_select = DIG_SELECT[2
key模块
module key #(parameter TIME_20MS = 20000000, FCLK = 50000000)
(
input wire clk, // Clock input
input wire rst_n, // Reset input
input wire key_in, // Input from the key
output reg key_out // Debounced output
);
// Parameter calculation
parameter TIME_20MS_NUM = TIME_20MS * FCLK / 1000000000;
parameter TIME_20MS_NUM_W = $clogb2(TIME_20MS_NUM);
// Register definitions
reg key_in_ff;
reg [TIME_20MS_NUM_W-1:0] cnt;
reg end_cnt_ff;
// Function to calculate the bit width required to represent a value
function integer clogb2;
input integer value;
begin
clogb2 = $clogb2(value);
end
endfunction
// Clock delay
always @(posedge clk) begin
key_in_ff <= key_in;
end
// Counter logic
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 0;
else if (key_in_ff)
cnt <= cnt + 1;
else
cnt <= 0;
end
// Key detection logic
assign end_cnt_ff = (cnt == TIME_20MS_NUM) ? 1'b1 : 1'b0;
// Output logic
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
key_out <= 0;
else if (end_cnt_ff && !key_in_ff)
key_out <= 1;
else
key_out <= 0;
end
endmodule
control模块
module control #(parameter FCLK = 50000000, FREFRESH = 1000)
(
input wire clk, // Clock input
input wire rst_n, // Reset input
input wire [2:0] key, // Input from keys
output reg cnt_xs, // Output for fractional part counter
output reg cnt_gw // Output for units part counter
);
// Parameter definition
parameter TIME = FCLK / FREFRESH; // Dividing factor for controlling stopwatch count
parameter TIME_W = $clogb2(TIME); // Width of TIME in bits
// Intermediate signal definitions
reg [TIME_W-1:0] divider; // Divider for controlling count
reg [3:0] fraction_counter; // Counter for fractional part (x.xxx)
reg [3:0] units_counter; // Counter for units part (xx.xx)
// Function to calculate the bit width required to represent a value
function integer clogb2;
input integer value;
begin
clogb2 = $clogb2(value);
end
endfunction
// Start counting flag based on key input
reg start_counting;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
start_counting <= 0;
else begin
if (key[0] == 1'b1 && !start_counting)
start_counting <= 1;
else if (key[1] == 1'b1)
start_counting <= 0;
end
end
// Frequency divider counter
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
divider <= 0;
else if (start_counting) begin
if (divider == TIME - 1)
divider <= 0;
else
divider <= divider + 1;
end
end
// Fractional part counter
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
fraction_counter <= 0;
else if (divider == TIME - 1) begin
if (fraction_counter == 9)
fraction_counter <= 0;
else
fraction_counter <= fraction_counter + 1;
end
end
// Units part counter
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
units_counter <= 0;
else if (fraction_counter == 9) begin
if (units_counter == 9)
units_counter <= 0;
else
units_counter <= units_counter + 1;
end
end
// Output assignment
assign cnt_xs = fraction_counter;
assign cnt_gw = units_counter;
endmodule
seg_disp模块
module seg_disp
(
input wire clk, // Clock input
input wire rst_n, // Reset input
input wire [3:0] din, // BCD input
input wire decimal_point, // Decimal point input
output reg [6:0] seg, // Output for segment display
output reg [3:0] dig // Output for digit select
);
// Parameter definition
parameter [6:0] SEG_ERROR = 7'b1111111; // Error display for invalid input
// Segment display data register
reg [6:0] segment;
// Segment decoding logic
always @(*) begin
case (din)
4'b0000: segment = 7'b1000000; // 0
4'b0001: segment = 7'b1111001; // 1
4'b0010: segment = 7'b0100100; // 2
4'b0011: segment = 7'b0110000; // 3
4'b0100: segment = 7'b0011001; // 4
4'b0101: segment = 7'b0010010; // 5
4'b0110: segment = 7'b0000010; // 6
4'b0111: segment = 7'b1111000; // 7
4'b1000: segment = 7'b0000000; // 8
4'b1001: segment = 7'b0010000; // 9
default: segment = SEG_ERROR; // Invalid input
endcase
end
// Segment display logic
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
seg <= 0; // Reset all segments
dig <= 0; // Turn off all digits
end
else begin
case (dig)
4'b1110: seg <= segment; // Display on digit 0
4'b1101: seg <= segment; // Display on digit 1
4'b1011: seg <= segment; // Display on digit 2
4'b0111: seg <= segment; // Display on digit 3
default: seg <= 0; // Turn off segments if no digit selected
endcase
// Handle decimal point
if (decimal_point) begin
case (dig)
4'b1110: seg[6] <= 1; // Enable decimal point for digit 0
4'b1101: seg[6] <= 1; // Enable decimal point for digit 1
4'b1011: seg[6] <= 1; // Enable decimal point for digit 2
4'b0111: seg[6] <= 1; // Enable decimal point for digit 3
default: seg[6] <= 0; // Disable decimal point if no digit selected
endcase
end
end
end
endmodule
test模块
module test;
// Parameter definition
parameter CYCLE = 20; // System clock cycle in ns
parameter RST_TIME = 1000; // Reset duration in cycles
parameter TIME_20MS = 20; // Debounce time in ns
parameter FCLK = 50000000; // System clock frequency in Hz
parameter FREFRESH = 1000; // Stopwatch refresh frequency in Hz
// Signal definition
reg clk; // System clock
reg rst_n; // Reset signal
reg [2:0] key_in; // Input from keys
wire [6:0] seg_xs, seg_gw; // Output for segment display (x.xxx and xx.xx)
wire [3:0] dig_xs, dig_gw; // Output for digit select
// Instantiate top-level module
top #(.TIME_20MS(TIME_20MS), .FCLK(FCLK), .FREFRESH(FREFRESH))
top_inst (.clk(clk), .rst_n(rst_n), .key_in(key_in),
.seg_xs(seg_xs), .dig_xs(dig_xs),
.seg_gw(seg_gw), .dig_gw(dig_gw));
// System clock generation
initial begin
clk = 0;
forever #((CYCLE / 2)) clk = ~clk;
end
// Reset signal generation
initial begin
rst_n = 0;
#((RST_TIME * CYCLE)) rst_n = 1;
end
// Task definition for simulating key presses
task press_key;
input integer key;
begin
key_in = key;
#((2 * TIME_20MS)); // Simulate debounce time
key_in = 0;
#((2 * TIME_20MS)); // Simulate debounce time
end
endtask
// Initial block for simulating key operations
initial begin
// Simulate pressing start count key
$display("Pressing start count key...");
press_key(1);
#1000; // Wait for a while
// Simulate pressing pause count key
$display("Pressing pause count key...");
press_key(2);
#1000; // Wait for a while
// Simulate pressing increment count key
$display("Pressing increment count key...");
press_key(3);
// Stop simulation
$stop;
end
endmodule
主要代码片段及说明(这里是最终代码)
top模块
这部分的代码说明都写在注释里了。
module top #(
parameter TIME_20MS = 20_000_000 ,//按键抖动持续的最长时间,默认最长持续时间为20ms。
parameter FCLK = 12 ,//系统时钟频率,单位MHz。
parameter FREFRESH = 10 //秒表刷新频率,单位Hz。
)(
input clk ,//系统时钟信号;
input rst_n ,//系统复位信号,低电平有效;
input [2 : 0] key_in ,//三个按键输入,分别表示开始、停止、增量。
output [7 : 0] seg_xs ,//显示小数的数据管数据信号;
output dig_xs ,//显示小数的数码管位选信号;
output [7 : 0] seg_gw ,//显示个位的数据管数据信号;
output dig_gw //小时个位的数码管位选信号;
);
wire [2 : 0] key_out ;//消抖后的按键信号,高电平表示按键被按下一次。
wire [3 : 0] cnt_xs ;//秒表的小数位数据,取值范围[0,9];
wire [3 : 0] cnt_gw ;//秒表的个位数据,取值范围[0,9];
//例化按键消抖模块,对开始计数按键消抖。
key #(
.TIME_20MS ( TIME_20MS ),//按键抖动持续的最长时间,默认最长持续时间为20ms。
.FCLK ( FCLK ) //系统时钟频率,单位Hz。
)
u_key0 (
.clk ( clk ),//系统时钟,12MHz。
.rst_n ( rst_n ),//系统复位,低电平有效。
.key_in ( key_in[0] ),//待输入的按键输入信号,默认低电平有效;
.key_out ( key_out[0]) //按键消抖后输出信号,当按键按下一次时,输出一个时钟宽度的高电平;
);
//例化按键消抖模块,对暂停计数按键消抖。
key #(
.TIME_20MS ( TIME_20MS ),//按键抖动持续的最长时间,默认最长持续时间为20ms。
.FCLK ( FCLK ) //系统时钟频率,单位Hz。
)
u_key1 (
.clk ( clk ),//系统时钟,12MHz。
.rst_n ( rst_n ),//系统复位,低电平有效。
.key_in ( key_in[1] ),//待输入的按键输入信号,默认低电平有效;
.key_out ( key_out[1]) //按键消抖后输出信号,当按键按下一次时,输出一个时钟宽度的高电平;
);
//例化按键消抖模块,对增量计数按键消抖。
key #(
.TIME_20MS ( TIME_20MS ),//按键抖动持续的最长时间,默认最长持续时间为20ms。
.FCLK ( FCLK ) //系统时钟频率,单位Hz。
)
u_key2 (
.clk ( clk ),//系统时钟,12MHz。
.rst_n ( rst_n ),//系统复位,低电平有效。
.key_in ( key_in[2] ),//待输入的按键输入信号,默认低电平有效;
.key_out ( key_out[2]) //按键消抖后输出信号,当按键按下一次时,输出一个时钟宽度的高电平;
);
//例化秒表的控制和计数模块,实现对秒表的控制。
control #(
.FCLK ( FCLK ),//系统时钟频率,单位Hz。
.FREFRESH ( FREFRESH ) //秒表刷新频率,单位Hz。
)
u_control (
.clk ( clk ),//系统时钟信号;
.rst_n ( rst_n ),//系统复位信号,低电平有效;
.key ( key_out ),//三个消抖后的按键输入信号;
.cnt_xs ( cnt_xs ),//秒表的小数位数据,取值范围[0,9];
.cnt_gw ( cnt_gw ) //秒表的个位数据,取值范围[0,9];
);
//例化秒表小数位的数码管译码模块;
seg_disp u_seg_disp0 (
.clk ( clk ),//系统时钟信号;
.rst_n ( rst_n ),//系统复位信号,低电平有效;
.din ( cnt_xs ),//对秒表的小数进行译码;
.decimal_point ( 1'b0 ),//小数数码管不需要显示小数点;
.seg ( seg_xs ),//数码管小数位的数据信号;
.dig ( dig_xs ) //数码管小数位的位选信号。
);
//例化秒表个位的数码管译码模块;
seg_disp u_seg_disp1 (
.clk ( clk ),//系统时钟信号;
.rst_n ( rst_n ),//系统复位信号,低电平有效;
.din ( cnt_gw ),//对秒表的个位进行译码;
.decimal_point ( 1'b1 ),//个位数码管需要显示小数点;
.seg ( seg_gw ),//数码管个位的数据信号;
.dig ( dig_gw ) //数码管个位的位选信号。
);
endmodule
key模块
先定义三个输入端口和一个输出端口,以及一些参数和信号,key_in_ff 是延迟两个时钟周期的按键输入信号,cnt 是按键有效时间计数器,end_cnt_ff 是用来检测计数器结束条件的寄存器。add_cnt 和 end_cnt 是用来计数器的控制信号。剩下的就是根据计数器的状态判断按键是否被按下,并且声称对应的信号,然后再在两个时钟周期之后检测计数器结束条件,然后将结果存储到end_cnt_ff寄存器里,根据end_cnt_ff的状态产生输出
//用于按键消抖,并且检测按键是否被按下,当按下时,key_out输出一个时钟宽度的高电平,表示按键被按下一次;
module key#(
parameter TIME_20MS = 20_000_000 ,//按键抖动持续的最长时间,默认最长持续时间为20ms。
parameter FCLK = 12 //系统时钟频率,单位MHz。
)(
input clk ,//系统时钟,12MHz。
input rst_n ,//系统复位,低电平有效。
input key_in ,//待输入的按键输入信号,默认低电平有效;
output reg key_out //按键消抖后输出信号,当按键按下一次时,输出一个时钟宽度的高电平;
);
localparam TIME_20MS_NUM = ((TIME_20MS * FCLK) / 1_000);//按键抖动时间对应的系统时钟数;
localparam TIME_20MS_NUM_W = clogb2(TIME_20MS_NUM-1);//利用自动计算位宽函数,自动计算出TIME_20MS_NUM对应的数据位宽;
reg [1 : 0] key_in_ff;
reg [TIME_20MS_NUM_W - 1 : 0] cnt ;
reg [1 : 0] end_cnt_ff;
wire add_cnt ;
wire end_cnt ;
//自动计算位宽的函数;
function integer clogb2(input integer depth);begin
if(depth==0)
clogb2=1;
else if(depth!=0)
for(clogb2=0; depth>0;clogb2=clogb2+1)
depth=depth>>1;
end
endfunction
//将按键输入信号延迟两个时钟,减小亚稳态出现的机率。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_in_ff <= 2'b11;
end
else begin
key_in_ff <= {key_in_ff[0],key_in};
end
end
//按键按下有效时间的计数器;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin//初始值为0;
cnt <= 0;
end
else if(key_in_ff[1])begin//当按键没有被按下时,计数器清零;
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)//当计数器计数到最大值时,计数器保持不变;
cnt <= cnt;
else//当按键没有被按下,并且没有计数到最大值时,计数器加一;
cnt <= cnt + 1;
end
end
assign add_cnt = ~key_in_ff[1];
assign end_cnt = add_cnt && cnt == TIME_20MS_NUM - 1;
//检测计数器结束条件,满足的上升沿,表示按键被按下一次;
//将按键计数器cnt结束条件延迟两个时钟。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
end_cnt_ff <= 2'b0;
end
else begin
end_cnt_ff <= {end_cnt_ff[0],end_cnt};
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_out <= 1'b0;
end
else begin//检测按键计数器结束条件的上升沿,表示按键被按下一次,将key_out拉高一次;
key_out <= (~end_cnt_ff[1] & end_cnt_ff[0]);
end
end
endmodule
control模块:
这段代码实现秒表的开始,停止,增量,复位功能,并输出秒表的小数位和个位数据
module control #(
parameter FCLK = 12 ,// 系统时钟频率,单位MHz。
parameter FREFRESH = 10 //秒表刷新频率,单位Hz。
)(
input clk ,//系统时钟信号;
input rst_n ,//系统复位信号,低电平有效;
input [2 : 0] key ,//三个消抖后的按键输入信号;
output reg [3 : 0] cnt_xs ,//秒表的小数位数据,取值范围[0,9];
output reg [3 : 0] cnt_gw //秒表的个位数据,取值范围[0,9];
);
//参数定义
localparam TIME = (FCLK*1_000_000 / FREFRESH);//计算分频系数;
localparam TIME_W = clogb2(TIME-1) ;//计算分频系数对应的数据位宽;
//中间信号定义
reg start_flag ;//
reg [TIME_W - 1 : 0] cnt_div ;//
wire add_cnt_div ;
wire end_cnt_div ;
wire add_cnt_xs ;
wire end_cnt_xs ;
wire add_cnt_gw ;
wire end_cnt_gw ;
//自动计算位宽的函数;
function integer clogb2(input integer depth);
begin
if(depth==0)
clogb2=1;
else if(depth!=0)
for(clogb2=0; depth>0;clogb2=clogb2+1)
depth=depth>>1;
end
endfunction
//开始计数的标志信号,该信号为高电平时,秒表就一直对时钟进行计数。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//初始值为0;
start_flag <= 1'b0;
end
else if(key[1])begin//按键1是停止按键,当按键1被按下后,计数标志信号拉低,停止计数。
start_flag <= 1'b0;
end
else if(key[0])begin//按键0是开始计数标志,当按键0按下后,计数标志信号拉高,一直对时钟进行计数。
start_flag <= 1'b1;
end
end
//分频计数器,该计数器在start_flag为高电平时,一直对时钟进行计数。
//当计数到TIME时,表示已经到了0.1s,此时计数器清零,重新计数。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//初始值为0;
cnt_div <= 0;
end
else if(add_cnt_div)begin//计数器加一条件满足;
if(end_cnt_div)//分频计数器结束条件满足;
cnt_div <= 0;
else
cnt_div <= cnt_div + 1;
end
end
assign add_cnt_div = start_flag;//分频计数器加一条件,当计数标志信号有效时加一。
assign end_cnt_div = add_cnt_div && cnt_div == TIME - 1;//计数到0.1s时结束。
//小数计数器,当分频计数器计数结束或者按键2被按下时,小数计数器加1。
//由于小数计数器最大值为9,所以计数到9后清零。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//初始值为0;
cnt_xs <= 0;
end
else if(add_cnt_xs)begin
if(end_cnt_xs)
cnt_xs <= 0;
else
cnt_xs <= cnt_xs + 1;
end
end
assign add_cnt_xs = end_cnt_div || key[2];//每当分频计数器计数到0.1S,或按键2被按下时,小数计数器加1。
assign end_cnt_xs = add_cnt_xs && cnt_xs == 10 - 1;//如果小数计数器计数到9,则清零。
//个位计数器,初始值为0,当小数位计数到9,计数器加1。
//个位最大值为9,所以计数到9后清零。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//初始值为0。
cnt_gw <= 0;
end
else if(add_cnt_gw)begin
if(end_cnt_gw)
cnt_gw <= 0;
else
cnt_gw <= cnt_gw + 1;
end
end
assign add_cnt_gw = end_cnt_xs;//当小数计数器计数到9时,个位加1.
assign end_cnt_gw = add_cnt_gw && cnt_gw == 10 - 1;//当个位计数到9之后,清零。
endmodule
seg_disp模块
这部分代码用于实现数码管显示,这部分的代码说明写在注释里了
module seg_disp (
//输入信号定义
input clk ,//系统时钟,50MHz。
input rst_n ,//系统复位,低电平有效。
input [3 : 0] din ,//需要数码管显示的BCD码数码;
input decimal_point ,//小数点,为1表示显示小数点。
//输出信号定义
output reg [7 : 0] seg ,//数码管的数据线;
output dig //数码管的位选信号;
);
localparam ZERO = 8'h3F ; //8'hC0;前面的数据是共阴数码管使用的,后面数据是共阳数码管使用的;
localparam ONE = 8'h06 ; //8'hF9;
localparam TWO = 8'h5B ; //8'hA4;
localparam THREE = 8'h4F ; //8'hB0;
localparam FOUR = 8'h66 ; //8'h99;
localparam FIVE = 8'h6D ; //8'h92;
localparam SIX = 8'h7D ; //8'h82;
localparam SEVEN = 8'h07 ; //8'hF8;
localparam EIGHT = 8'h7F ; //8'h80;
localparam NINE = 8'h6F ; //8'h90;
localparam ERR = 8'h77 ; //8'h86;
reg [7 : 0] segment ;
assign dig = 1'b0;//该数码管的位选信号一直为低电平,一直有效;
//译码器部分,将输入的十进制信号din译码成数码管显示该数字对应的八位数据信号;
//segment默认是不显示小数点的。
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//初始上电时,所有数码管显示数据0;
segment <= ZERO;
end
else begin
case(din[3:0])//将din译码成对应的segment数据,segment数据驱动数码管才能显示din代表的数字;
4'd0 : segment <= ZERO ;//想要数码管显示0,就要给数码管数据信号segment输入ZERO数据,其余类似;
4'd1 : segment <= ONE ;
4'd2 : segment <= TWO ;
4'd3 : segment <= THREE;
4'd4 : segment <= FOUR ;
4'd5 : segment <= FIVE ;
4'd6 : segment <= SIX ;
4'd7 : segment <= SEVEN;
4'd8 : segment <= EIGHT;
4'd9 : segment <= NINE ;
default: segment <= ERR;
endcase
end
end
//如果需要显示小数点,则把segment的最高位取反输出,不需要显示小数点时直接输出。
always@(posedge clk)begin
seg <= decimal_point ? {~segment[7],segment[6:0]} : segment[7:0];
end
endmodule
仿真波形图
u_key0
u_key1
u_key2
u_control
u_sep_disp0
u_sep_disp1
遇到的主要困难和解决办法
在这个项目中,我第一次尝试进行按键消抖。按键抖动本质上是在我们按下按键的时候,输入信号会在短短时间内多次切换高低电平,导致系统误判按键操作。
解决办法是如果检测到按键被按下,就启动一个计数器来进行计数,计数器的值由参数 TIME_20MS 和 FCLK 决定(按键抖动持续的最长时间和系统时钟频率)。当计数器的值达到设定的20毫秒时,表示按键信号已经稳定,将按键有效信号输出。如果在计数过程中检测到按键被释放,则重新开始计数。
未来的计划或建议
未来会继续练习verilog语言,练习FPGA设计,争取用更复杂的硬件制作出更多内容