1 项目需求
- 通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。秒
- 表使用四个按钮输入:开始、停止、增量和清除(重置)。开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。
小脚丫STEP-MXO2-LPC介绍
硬件规范
- 核心器件:Lattice LCMXO2-4000HC-4MG132
- 板载资源:
- 两位7段数码管;
- 两个RGB三色LED;
- 8路用户LED;
- 4路拨码开关;
- 4路按键;
- 36个用户可扩展I/O(其中包括一路SPI硬核接口和一路I2C硬核接口)
- 支持的开发工具思德普开发的Web IDE以及Lattice官方提供的Diamond
- 支持MICO32/8软核处理器以及RISC-V软核
- 板上集成FPGA编程器,采用U盘的模式
- 一路USB Type C接口,可用于给核心板供电、给FPGA下载JED文件以及同上位机通过UART通信
- 板卡尺寸52mm x 18mm
引脚定义
2 完成的功能
2.1.秒表显示
通过数码管实时显示计数值,从0.0秒递增到9.9秒后翻转。
2.2 按键控制
通过四个轻触按键分别实现开始、停止、增量和清除(重置)功能。
2.3.定时器
使用10Hz定时器源,每0.1秒更新一次计数值。
2.4.中断处理
利用中断处理函数精确控制计数值的更新频率。
3 实现思路
- 首先,我们需要一个时钟转换模块 Clock,将输入的12MHz时钟信号转换为10Hz的时钟信号,用于秒表的计时。
- 接下来是设计一个计数器模块 Counter,根据10Hz的时钟信号进行计数。该模块应该包含一个计数器,能够实现递增功能,并在达到99时归零。实现这个模块后,根据题目要求还需要根据添加启动、停止功能的逻辑。
- 然后需要将计数值显示出来,所以设计了将计数器的值转换为七段显示器的输出的模块 SevenSegmentDisplay 和 SevenSegmentDisplay1,将计数器的值转换为七段显示器的编码输出,用于显示秒表的时间。
- 最后是秒表模块 Stopwatch,将以上各个模块整合在一起。在这个模块中,将时钟模块、计数器模块和七段显示模块连接在一起,并根据输入的信号实现启动、停止、递增和清除功能的逻辑。
利用webide实现设计。
webide无需下载即可在网页设计,并且可以非常方便的分配管脚,一键下载jed文件,非常值得推荐
利用电子森林AI助手,可以生成简单Verilog代码,大大减少设计时间,提高效率。
4 实现过程
4.1 程序流程图
4.2 时钟转换模块 Clock
功能:
- 将输入的12MHz时钟信号转换为10Hz的时钟信号,用于秒表的计时。
输入:
- clk_12MHz:12MHz的输入时钟信号
输出:
- clk_10Hz:10Hz的输出时钟信号
工作原理:
- 接收来自外部的12MHz时钟信号 clk_12MHz。利用适当的逻辑或计数器,将12MHz时钟信号转换为10Hz的时钟信号 clk_10Hz。输出10Hz的时钟信号,用于驱动秒表的计时功能。
时钟转换模块:
module Clock
(
input wire clk_12MHz,
output reg clk_10Hz
);
parameter WIDTH = 24;
parameter N = 1200000;
reg [WIDTH-1:0] cnt_p;
always @ (posedge clk_12MHz)
begin
if(cnt_p==N-1)
cnt_p<=0;
else
cnt_p<=cnt_p+1;
if(cnt_p<(N>>1))
clk_10Hz<=1;
else
clk_10Hz<=0;
end
endmodule
4.3 计数器模块 Counter
功能:
- 根据10Hz的时钟信号进行计数。该模块包含一个计数器,能够实现递增功能,并在达到99时归零。
输入:
clk_10Hz
:10Hz的时钟信号reset
:重置信号,当接收到重置信号时,计数器归零
输出:
count
:当前计数器的值,范围从0到99
工作原理:
- 每次接收到10Hz的时钟信号
clk_10Hz
,计数器递增一个单位。 - 当计数器的值达到99时,自动归零。
- 如果接收到重置信号
reset
,计数器立即归零。 - 输出当前计数器的值
count
,范围在0到99之间。
启动、停止功能逻辑:
- 在秒表模块中,根据启动、停止信号的控制,可以通过控制时钟信号的传递来实现启动和停止功能。当接收到启动信号时,允许时钟信号传递到计数器模块,使计数器开始计数;当接收到停止信号时,停止时钟信号的传递,计数器停止计数,保持当前值不变。
module Counter(
input wire clk,
input wire reset,
input wire increment,
input wire start,
input wire stop,
output wire [3:0] ones_digit,
output wire [3:0] tenths_digit
);
reg [3:0] ones_counter;
reg [3:0] tenths_counter;
reg [1:0] state;
reg [7:0] counter;
wire [11:0] counter1;
reg increment_triggered;
always @(posedge clk or posedge reset )
begin
if(!start)
state<=1;
if(!stop)
state<=0;
if (reset)
begin
counter <= 0;
state<=0;
end
else if (state)
begin
if(counter==99) // cnt 等于 9 则归零
counter <=8'd0;
else
counter <= counter + 1'b1; // cnt 小于 9 则累加
end
if (increment)
increment_triggered = 1;
if ((!increment)&increment_triggered )
begin
increment_triggered = 0;
if(counter==99) // cnt 等于 9 则归零
counter <=8'd0;
else
counter <= counter + 1'b1; // cnt 小于 9 则累加
end
end
Bin2bcd bin2bcd(.bitcode(counter),.bcdcode(counter1));
assign tenths_digit = counter1[7:4];
assign ones_digit = counter1[3:0];
endmodule
4.4 数码管显示模块
功能:
- 将计数器的值转换为七段显示器的输出,带小数点。
输入:
- count:计数器的值,范围从0到99
输出:
- segment_output:七段显示器的编码输出,包括小数点,用于显示计数器的值
工作原理:
- 根据计数器的值 count,将其转换为对应的带小数点的七段显示器的编码输出。
- 输出转换后的七段显示器编码 segment_output,包括小数点,用于显示计数器的值。
module SevenSegmentDisplay(
input wire [3:0] value,
output wire [8:0] segment_output
);
// 七段显示器的编码表
reg [8:0] segment_encoding [0:9];
initial begin
segment_encoding[0] = 9'h3f; //对存储器中数赋值9'b00_0011_1111,共阴极接地,DP点变低不亮,7段显示数字 0
segment_encoding[1] = 9'h06; //7段显示数字 1
segment_encoding[2] = 9'h5b; //7段显示数字 2
segment_encoding[3] = 9'h4f; //7段显示数字 3
segment_encoding[4] = 9'h66; //7段显示数字 4
segment_encoding[5] = 9'h6d; //7段显示数字 5
segment_encoding[6] = 9'h7d; //7段显示数字 6
segment_encoding[7] = 9'h07; //7段显示数字 7
segment_encoding[8] = 9'h7f; //7段显示数字 8
segment_encoding[9] = 9'h6f; //7段显示数字 9
end
assign segment_output = segment_encoding[value];
endmodule
module SevenSegmentDisplay1(
input wire [3:0] value,
output wire [8:0] segment_output
);
// 七段显示器的编码表
reg [8:0] segment_encoding [0:9];
initial begin
segment_encoding[0] = 9'hbf; //7段显示数字 0.
segment_encoding[1] = 9'h86; //7段显示数字 1.
segment_encoding[2] = 9'hdb; //7段显示数字 2.
segment_encoding[3] = 9'hcf; //7段显示数字 3.
segment_encoding[4] = 9'he6; //7段显示数字 4.
segment_encoding[5] = 9'hed; //7段显示数字 5.
segment_encoding[6] = 9'hfd; //7段显示数字 6.
segment_encoding[7] = 9'h87; //7段显示数字 7.
segment_encoding[8] = 9'hff; //7段显示数字 8.
segment_encoding[9] = 9'hef; //7段显示数字 9.
end
assign segment_output = segment_encoding[value];
endmodule
4.5 秒表模块 Stopwatch
功能:
- 将时钟模块、计数器模块和七段显示器输出模块整合在一起,实现启动、停止、递增和清除功能的逻辑。
输入:
clk_10Hz
:10Hz的时钟信号start
:启动信号,用于启动秒表stop
:停止信号,用于停止秒表clear
:清除信号,用于清除秒表- 其他可能的输入信号,如重置信号等(根据需要添加)
输出:
- 七段显示器的编码输出,用于显示秒表的时间
工作原理:
- 接收来自时钟模块的时钟信号
clk_10Hz
。 - 根据输入的信号
start
、stop
、clear
等,控制计数器模块的启动、停止、清除操作。 - 将计数器模块的计数值传递给七段显示器输出模块,实现时间的显示。
- 根据输入信号的控制,实现秒表的启动、停止、递增和清除功能的逻辑。
启动、停止功能逻辑:
- 当接收到启动信号
start
时,允许时钟信号传递到计数器模块,启动计数。 - 当接收到停止信号
stop
时,停止时钟信号的传递,停止计数,保持当前值不变。
清除功能逻辑:
- 当接收到清除信号
clear
时,将计数器模块归零,并清空七段显示器的显示。
module Stopwatch(
input wire clk_12MHz,
input wire increment,
input wire clear,
input wire start,
input wire stop,
output wire [8:0] ones_digit,
output wire [8:0] tenths_digit
);
wire [3:0] ones_counter_value;
wire [3:0] tenths_counter_value;
wire [8:0] ones_segment_output;
wire [8:0] tenths_segment_output;
Clock clock(.clk_12MHz(clk_12MHz), .clk_10Hz(clk_10Hz));
Counter counter(.clk(clk_10Hz), .reset(!clear),.start(start),.stop(stop), .increment(increment ), .ones_digit(ones_counter_value), .tenths_digit(tenths_counter_value));
SevenSegmentDisplay ones_display(.value(ones_counter_value), .segment_output(ones_segment_output));
SevenSegmentDisplay1 tenths_display(.value(tenths_counter_value), .segment_output(tenths_segment_output));
assign ones_digit = ones_segment_output;
assign tenths_digit = tenths_segment_output;
endmodule
4.6资源利用率
- 寄存器数量:总共4635个寄存器中使用了54个(1%),这些寄存器可以用于存储数据或状态信息。
- PFU(Programmable Function Unit)寄存器:在4320个PFU寄存器中使用了54个(1%),PFU通常用于实现逻辑功能。
- SLICE数量:总共2160个SLICE中使用了85个(4%),SLICE是FPGA中的基本逻辑单元。
- LUT4数量:在4320个LUT4中使用了160个(4%),LUT4是FPGA中的Look-Up Table单元,用于实现逻辑功能。
- PIO(Parallel Input/Output)寄存器:在315个PIO寄存器中没有被使用,PIO通常用于处理输入输出。
- 块RAM数量:在10个块RAM中没有被使用
资源占用较少。
5 遇到的主要难题
5.1 always语句赋值问题
在Verilog中,确保敏感信号具有相同的触发方式,并且在always
块中只使用reg
类型的变量进行赋值操作,并且这些赋值操作需要保持一致,即要么都是阻塞赋值,要么都是非阻塞赋值。这些规则有助于确保时序逻辑的正确性和避免潜在的问题,最重要的是可以减少大量调试时间
建议:
- 相同触发方式:
- 在
always
块中,确保所有敏感信号具有相同的触发方式,例如都是在时钟的上升沿或下降沿触发。混合使用不同的触发方式可能会导致意外的行为和时序问题。
- 在
- 只使用
reg
类型变量: - 在
always
块中,应该只使用reg
类型的变量进行赋值操作。reg
类型的变量用于存储时序逻辑的状态,并且在时钟信号触发时更新。
- 在
- 保持一致的赋值方式:
- 在
always
块中,确保所有的赋值操作要么都是阻塞赋值(=
),要么都是非阻塞赋值(<=
)。混合使用这两种赋值方式可能会导致意外的逻辑错误和时序问题。
- 在
5.2 时序逻辑和时钟域交叉问题
在数字电路设计中,时钟信号的稳定性和时序逻辑的正确性至关重要。确保各模块在正确的时钟域中操作,避免时钟域交叉可能是一个巨大的工程。
5.3 注意webide的run.log文件报错
在进行fpga设计时,难免会出现因代码问题导致的映射错误。但是在使用webide设计时,如果有映射错误,只会在run.log文件的最后一行报错,并且报错后可以正常下载上一次jed文件,这是难以发现的,这会使得
6 未来的计划建议
该项目已经成功实现了秒表的功能,并达到了预期指标。然而,还有许多可以提升与扩展的地方:
功能扩展与优化:
- 考虑增加更多功能,如计次功能、倒计时功能、闹钟功能等,以提升秒表的实用性和功能性,当然这需要增加数码管数量。
性能优化:
- 对代码进行优化,提高秒表的响应速度和稳定性。