在此特别感谢哔站up主甘第发布的FPGA企业实训课(基于FPGA的数字钟设计)教学视频,让一个FPGA小白开始了第一个FPGA设计开发流程。本设计参考了这个教学视频,在此基础上添加并修改了一些代码,完成了这个小小的不带任何功能的数字时钟。
初次学习FPGA,初次学习发布博客,如有错误,请指正!!!
一、设计功能
本设计主要实现可调的数字时钟。具体功能如下:
(1)首先实现的功能是:秒计时到59后,分钟加1;分钟计时到59后,小时加1;小时计时到23后,复位,秒从0开始计时。这样循环计时,完成时钟的计时功能。
(2)在(1)的基础上添加小时和分钟的校准/调整功能,实现切换式调节数字时钟。通过按键切换至小时并闪烁,此时可通过按键加减小时的数值;然后切换至分钟,调节分钟的数值,以达到实时的准确时间。
二、设计方案
本设计包含按键消抖模块、边缘检测模块,数字钟逻辑控制模块和数码管显示模块。系统设计架构如图1所示。各个模块的具体功能如下:
(1)按键消抖模块:通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。抖动时间的长短由按键的机械特性决定,一般为5ms~10ms,所以软件消抖的时间大于按键自身抖动时间即可。
图1 系统架构
(2)边缘检测模块:一般在项目设计的过程中,经常需要检测信号由高到低或由低到高的跳变。即:检测输入信号,如果输入信号从0~1,检测信号对应的上升沿;如果输入信号从1~0,检测信号对应的下降沿。
(3)数码管显示模块:该模块主要是在开发板的数码管上显示小时、分钟、秒的数值。
(4)数字钟逻辑控制模块:此模块内部比较复杂,包含七个子模块。数字钟逻辑控制模块的内部架构如图2所示。
图2 数字钟逻辑控制模块的内部架构
三、设计操作
1、设计开发流程
(1)首先新建一个文件夹,并为文件夹重新命名。本设计为数字钟,所以以digital_clock命名。然后打开digital_clock文件夹,并在该文件夹中新建4个子文件夹,分别为doc、prj、rtl、sim,其中doc用于存放本次设计的逻辑架构文档,prj为工程文件夹,sim用于保存仿真测试文件,rtl用于保存源代码。
(2)启动Quartus ii软件,首新建工程(注意选择芯片型号为EP4CE6E22C8),然后新建文本编辑器,开始编写代码。本设计需要四个主模块以及各个主模块中的子模块,所以需要编写很多个.v文件,比如key_filter.v、 edge_check.v、 clk_logic_ctrl.v、seven_tube等.v文件。编写完成后将这些.v文件另存(Save as)到rtl文件夹中。
( 3)在代码编写完后,需要对其进行编译,以便检查是否存在语法错误。点击按钮或按组合键“Ctrl+K”对代码进行编译。程序代码主界面和编译界面如图3所示。
图3 程序代码主界面和编译界面
(4)编译正确后,通过Modelsim仿真软件对程序运行情况进行仿真。Modelsim仿真有两种途径,既可以直接在Quartus ii中启动Modelsim进行仿真,也可以在Modelsim仿真软件中独立仿真。本次设计我们直接在Quartus ii中启动Modelsim进行仿真。
正在上传…重新上传取消正在上传…重新上传取消 首先在仿真之前需要建立Testbench仿真文件,一般以“模块名_tb”命名,由于本次设计的模块较多,所以只需对顶层模块进行仿真即可。新建digital_clock_tb文件夹,编写仿真代码。编写完成后,按组合键“Ctrl+K”对代码进行编译,确认代码正确后,点击菜单栏Tools → Run Simulation Tool → RTL Simulation,启动Modelsim进行仿真。启动成功后,点击进入Modelsim界面。先在Wave选项卡中安“Ctrl+A” “Ctrl+G”组合键实行自动分组。然后点击restart按钮,并设置运行时间,再点击run all按钮,在Wave选项卡查看仿真效果即可。顶层模块的部分仿真波形如图4所示。
图4顶层模块的部分仿真波形
(5)下载验证
仿真成功后,点击Assignments菜单中的Device,弹出芯片选择界面,选择相应的下载芯片,如图5所示。
图5 下载芯片选择
然后点击Assignments菜单中的Pin Planner,进行引脚分配,如图6所示。配置完成后关闭此界面。引脚配置完成后,点击Tools菜单栏中的Programmer,弹出如图7所示的界面。点击Hardware Setup,选择USB-Blaster [USB-0],然后点击Start,开始下载运行。
图6 芯片引脚配置
图7 下载界面
综上所述的步骤过程为本次设计的一般开发流程。下面将逐个分析每个模块的逻辑架构及每个模块之间的联系。
2、各个模块的逻辑设计
(1)按键消抖模块
一般在项目设计的过程中,都会用到按键开关,而通常使用的按键开关为机械弹性开关。当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上被稳定地接通,在断开时也不会马上被断开,因此,在闭合及断开的瞬间均伴有一连串的抖动。
按键抖动的时间长短由按键的机械特性决定,一般为5至10ms;按键稳定闭合时间的长短则由操作人员的按键动作决定,一般为零点几秒至数秒。按键抖动会引起一次按键被误读为多次的错误。为了确保智能单元针对按键的一次闭合仅做一次处理,必须执行按键消抖操作:在按键闭合稳定时读取按键的状态,并且在按键释放稳定后再做处理。按键的消抖操作可用硬件或软件两种方法实现。本次设计通过软件方法实现按键消抖操作。
按键消抖模块的输入信号有系统时钟clk,系统复位rst_n和按键输入key_in,输出信号为尖峰脉冲信号flag(输出只占一个时钟周期),其时序分析如图8所示,设计流程如图9所示。
图8 按键消抖时序分析
图9 按键消抖设计流程
根据设计流程分析,可以采用状态机实现这部分的代码描述。Modelsim 的测试结果如图10所示。
图10 按键消抖的仿真波形
(2)边沿检测模块
一般在项目设计的过程中,经常需要检测信号由高到低或由低到高的跳变。经典的边沿检测电路有1级D触发器对应的边沿检测和2级D触发器对应的边沿检测。通过该电路,可以在信号出现跳变时产生尖峰脉冲,从而驱动其他电路模块执行相关的动作。最常用的是两级寄存器,第二级寄存器锁存住某个时钟上升沿到来时的输入电平,第一级寄存器锁存住下一个时钟沿到来时的输入电平,如果这两个寄存器锁存住的电平信号不同,就说明检测到了边沿,具体是上升沿还是下降沿可以通过组合逻辑来实现。边沿检测电路的结构图如图11所示。
图11 边沿检测电路的结构图
当检测到上升沿时, pos_edge信号输出一个时钟周期的高电平; 检测到下降沿时,neg_edge输出一个时钟周期的高电平。Modelsim 的测试结果如图12所示。
图12 边沿检测仿真波形
(3)数码管显示模块
LED数码管(LED Segment Displays)是由8个发光二极管构成,并按照一定的图形及排列封装在一起的显示器件。 其中7个LED构成7笔字形,1个LED构成小数点(也被称为为八段数码管)。分为两大类:共阳极数码管和共阴极数码管。对于共阴极数码管来说,其8个发光二极管的阴极在数码管内部全部连接在一起,所以称为“共阴”,而阳极独立。对于共阳极数码管来说,其8个发光二极管的阳极在数码管内部全部连接在一起,所以称为“共阳”,而阴极独立。
本次设计所用开发板上的数码管为共阳极数码管。系统输入的时钟为50MHz(周期为20ns),而人眼可分辨的时间需要几十ms以上,即数码管之间切换的频率需要1KHz,所以要进行分频,得到设计所需要的经验频率。数码管显示模块的设计架构如图13所示。Modelsim测试结果如图14所示。
图13 数码管显示模块的设计架构
图14 数码管显示的仿真波形
(4) 数字钟逻辑控制模块
如前面所述,数字钟逻辑控制模块比较复杂,包括逻辑控制模块、秒控制模块、分钟控制模块、小时控制模块、二进制转BCD模块、小时闪烁模块、分钟闪烁模块。其中二进制转BCD模块是一般设计中都要用到的基础模块,下面我们采用“大四加三”算法来实现二进制转BCD。
以8位输入二进制(8’1010_1001)为例,那么BCD码部分则需要12位,分别为BCD高位(4位二进制),BCD中间位(4位二进制),BCD低位(4位二进制)。首先准备一个20位全新序列(高12位为BCD部分(初始全为0),低8位为输入的二进制数)20’b0000_0000_0000_1010_1001。然后完成以下两个步骤:
a.BCD部分进行“大四加三”判断;
b.全新序列整体左移一次;
然后将a,b步骤重复8次,最后取第8次完成的数据高12位即可得到BCD码。具体过程如下表所示:
操作步骤 a步骤:大四加三判断 b步骤:全新序列整体左移一次 |
BCD高位 |
BCD中间位 |
BCD低位 |
输入的二进制 部分 |
说明 |
0000 |
0000 |
0000 |
1010_1001 |
准备20位全新序列 |
|
1a:BCD部分进行 大四加三 判断 |
0000 |
0000 |
0000 |
1010_1001 |
大四加三 判断 |
1b:整体左移 |
0000 |
0000 |
0001 |
0101_0010 |
左移一次 |
2a:BCD部分进行 大四加三 判断 |
0000 |
0000 |
0001 |
0101_0010 |
大四加三 判断 |
2b:整体左移 |
0000 |
0000 |
0010 |
1010_0100 |
左移一次 |
3a:BCD部分进行 大四加三 判断 |
0000 |
0000 |
0010 |
1010_0100 |
大四加三 判断 |
3b:整体左移 |
0000 |
0000 |
0101 |
0100_1000 |
左移一次 |
4a:BCD部分进行 大四加三 判断 |
0000 |
0000 |
1000 |
0100_1000 |
大四加三 判断 |
4b:整体左移 |
0000 |
0001 |
0000 |
1001_0000 |
左移一次 |
5a:BCD部分进行 大四加三 判断 |
0000 |
0001 |
0000 |
1001_0000 |
大四加三 判断 |
5b:整体左移 |
0000 |
0010 |
0001 |
0010_0000 |
左移一次 |
6a:BCD部分进行 大四加三 判断 |
0000 |
0010 |
0001 |
0010_0000 |
大四加三 判断 |
6b:整体左移 |
0000 |
0100 |
0010 |
0100_0000 |
左移一次 |
7a:BCD部分进行 大四加三 判断 |
0000 |
0100 |
0010 |
0100_0000 |
大四加三 判断 |
7b:整体左移 |
0000 |
1000 |
0100 |
1000_0000 |
左移一次 |
8a:BCD部分进行 大四加三 判断 |
0000 |
1011 |
0100 |
1000_0000 |
大四加三 判断 |
8b:整体左移 |
0001(1) |
0110(6) |
1001(9) |
0000_0000 |
左移一次 |
从表中可以清楚的看出二进制转BCD的设计思想,然后编写源代码和测试代码,进行仿真,得仿真结果如图15所示,注意:仿真时将bin设置为十进制(unsigned),bcd设置为十六进制。
图15 二进制转BCD仿真结果
逻辑控制模块( logic_ctrl )采用状态机(FSM)实现。设置3个状态,分别显示正常状态(s0),调节小时状态(s1),调节分钟状态(s2)。小时控制模块,分钟控制模块,秒控制模块满足数字钟逻辑即可。
根据数字钟的逻辑编写逻辑控制模块的源代码,进行编译。最后完成数字钟逻辑控制顶层模块的连线。
3、顶层模块的搭建
顶层模块的作用是连接每个模块,实现信号传输。
五、硬件设计效果及说明
设计成果展示如图16所示。
图16 成果展示
本次设计只是一个简单的数字时钟,没有额外加其他功能,比如闹钟。因为是初次学习FPGA,所以在学习设计这个数字钟的过程中,也遇到了很多问题,同时也学到了很多新的知识。
六、程序附录
//顶层模块
//`define RUN_SIM
module digital_clock(
input clk,
input rst_n,
input key_adjust, //按键切换
input key_add, //按键加
input key_sub, //按键减
output [5:0] sel,
output [7:0] seg
);
wire key_adjust_out;
wire key_add_out;
wire key_sub_out;
wire flag_adjust;
wire flag_add;
wire flag_sub;
wire [23:0] display_data;
//按键消抖:切换按键进行消抖
key_filter key_filter_adjust(
.clk(clk),
.rst_n(rst_n),
.key_in(key_adjust),
.key_out(key_adjust_out)
);
//按键消抖:加按键进行消抖
key_filter key_filter_add(
.clk(clk),
.rst_n(rst_n),
.key_in(key_add),
.key_out(key_add_out)
);
//按键消抖:减按键进行消抖
key_filter key_filter_sub(
.clk(clk),
.rst_n(rst_n),
.key_in(key_sub),
.key_out(key_sub_out)
);
//边沿检测:切换按键消抖后的边沿
edge_check edge_check_adjust(
.clk(clk),
.rst_n(rst_n),
.signal(key_adjust_out),
.pos_edge(flag_adjust),
.neg_edge()
);
//边沿检测:加按键消抖后的边沿
edge_check edge_check_add(
.clk(clk),
.rst_n(rst_n),
.signal(key_add_out),
.pos_edge(flag_add),
.neg_edge()
);
//边沿检测:减按键消抖后的边沿
edge_check edge_check_sub(
.clk(clk),
.rst_n(rst_n),
.signal(key_sub_out),
.pos_edge(flag_sub),
.neg_edge()
);
//数字钟的逻辑控制模块
clk_logic_ctrl clk_logic_ctrl_dut (
.clk(clk),
.rst_n(rst_n),
.flag_adjust(flag_adjust),
.flag_add(flag_add),
.flag_sub(flag_sub),
.display_data(display_data)
);
//数码管显示模块
seven_tube seven_tube_dut(
.clk(clk),
.rst_n(rst_n),
.data_in(display_data),
.seg(seg),
.sel(sel)
);
endmodule
//按键消抖
//按键消抖
1. module key_filter(
2. input clk,
3. input rst_n,
4. input key_in, //开发板的独立按键输入
5.
6. output reg key_out //输出按键消抖后的动作
7. );
8.
9. reg [31:0] cnt; //延时10ms的计数器
10. reg state; //状态寄存器
11.
12. parameter S0 = 1'b0; //按键按下状态
13. parameter S1 = 1'b1; //按键抬起状态
14.
15. parameter T = 10_000_000/20 - 1; //10ms所用计数次数499_999
16.
17. always @(posedge clk or negedge rst_n) begin
18. if(!rst_n) begin
19. state <= S0;
20. key_out <= 1'b1;
21. cnt <= 32'd0;
22. end
23. else begin
24. case(state)
25. S0 : if(key_in == 1'b0) //按键按下
26. if(cnt < T) begin
27. cnt <= cnt +1'd1;
28. end
29. else begin
30. cnt <= 32'd0;
31. key_out <= 1'b0;
32. state <= S1;
33. end
34. else
35. state <= S0;
36. S1 : begin
37. if(key_in == 1'b1) //按键抬起
38. if(cnt < T) begin
39. cnt <= cnt +1'd1;
40. key_out <= 1'b0;
41. end
42. else begin
43. cnt <= 32'd0;
44. key_out <= 1'b1;
45. state <= S0;
46. end
47. else
48. state <= S1;
49. end
50. default : state <= S0; //安全行为
51. endcase
52. end
53. end
54. endmodule
//边沿检测
//边沿检测
1. module edge_check(
2. input clk,
3. input rst_n,
4. input signal, //待检测信号
5.
6. output pos_edge, //检测上升沿
7. output neg_edge //检测下降沿
8.
9. );
10. reg q1; //寄存一级D触发器输出
11. reg q2; //寄存二级级D触发器输出
12.
13. //二级D触发器的描述
14. always @(posedge clk or negedge rst_n) begin
15. if(!rst_n) begin
16. q1 <= signal;
17. q2 <= signal;
18. end
19. else begin
20. q1 <= signal;
21. q2 <= q1;
22. end
23. end
24. assign pos_edge = q1 && (~q2); //上升沿
25. assign neg_edge = (~q1) && q2; //下降沿
26. endmodule
//二进制转BCD
//二进制转BCD
1. module bin_bcd(
2. input [7:0] bin, //输入二进制数
3.
4. output [11:0] bcd //输出BCD码
5. );
6.
7. wire [19:0] bcd_reg0,bcd_reg1,bcd_reg2,bcd_reg3,bcd_reg4;
8. wire [19:0] bcd_reg5,bcd_reg6,bcd_reg7,bcd_reg8;
9.
10. assign bcd_reg0 = {12'b0 , bin}; //20位全新序列
11.
12. //移位第1次
13. bcd_modify bcd_modify_m1(.bcd_in(bcd_reg0),.bcd_out(bcd_reg1));
14.
15. //移位第2次
16. bcd_modify bcd_modify_m2(.bcd_in(bcd_reg1),.bcd_out(bcd_reg2));
17.
18. //移位第3次
19. bcd_modify bcd_modify_m3(.bcd_in(bcd_reg2),.bcd_out(bcd_reg3));
20.
21. //移位第4次
22. bcd_modify bcd_modify_m4(.bcd_in(bcd_reg3),.bcd_out(bcd_reg4));
23.
24. //移位第5次
25. bcd_modify bcd_modify_m5(.bcd_in(bcd_reg4),.bcd_out(bcd_reg5));
26.
27. //移位第6次
28. bcd_modify bcd_modify_m6(.bcd_in(bcd_reg5),.bcd_out(bcd_reg6));
29.
30. //移位第7次
31. bcd_modify bcd_modify_m7(.bcd_in(bcd_reg6),.bcd_out(bcd_reg7));
32.
33. //移位第8次
34. bcd_modify bcd_modify_m8(.bcd_in(bcd_reg7),.bcd_out(bcd_reg8));
35. assign bcd = {bcd_reg8[19:8]}; //取高12位作为输出结果
36. Endmodule
37. module bcd_modify(
38. input [19:0] bcd_in, //移位前的数据
39.
40. output [19:0] bcd_out //移位后的数据
41. );
42. wire [3:0] data_bcd1;
43. wire [3:0] data_bcd2;
44. wire [3:0] data_bcd3;
45.
46. //比较BCD高位
47. cmp cmp_high(.data_in(bcd_in[19:16]), .data_out(data_bcd1));
48.
49. //比较BCD中间位
50. cmp cmp_mid(.data_in(bcd_in[15:12]), .data_out(data_bcd2));
51.
52. //比较BCD低位
53. cmp cmp_low(.data_in(bcd_in[11:8]), .data_out(data_bcd3));
54.
55. assign bcd_out = {data_bcd1[2:0], data_bcd2[3:0], data_bcd3[3:0],bcd_in[7:0], 1'b0}; //整体左移一次
56.
57. endmodule
58.
59. module cmp(
60. input [3:0] data_in, //BCD位待比较的数据
61.
62. output [3:0] data_out //比较后大四加三的数据
63. );
64.
65. assign data_out = (data_in > 4'd4) ?(data_in + 4'd3) : data_in ;
66. endmodule
//数码管驱动模块
注意我用到的开发板型号是ep4ce6e22c8n,数码管的位选是低电平有效,小伙伴们要注意自己开发板的原理哦!!!
//数码管驱动模块
1. module seven_tube(
2. input clk,
3. input rst_n,
4. input [23:0] data_in,
5.
6. output [7:0] seg , //数码管段选信号线
7. output [5:0] sel //数码管位选信号线
8. );
9.
10. wire clk_1k; //定义 中间连线信号
11.
12. //分频模块50MHz--->1kHz
13. freq freq_dut(
14. .clk(clk),
15. .rst_n(rst_n),
16.
17. .clk_1k(clk_1k)
18. );
19.
20. //数码管驱动模块
21. seg_ctrl seg_ctrl_dut(
22. .clk_1k(clk_1k),
23. .rst_n(rst_n),
24. .data_in(data_in),
25.
26. .seg(seg),
27. .sel(sel)
28. );
29. endmodule
30. module freq(
31. input clk,
32. input rst_n,
33.
34. output reg clk_1k
35.
36. );
37. reg [31:0] count;
38.
39. parameter cnt_num = 50_000/2 - 1; //1kHz 是 1ms ,所以只需计数一半,然后取反即可 0.5ms
40.
41. always @(posedge clk or negedge rst_n) begin
42. if(!rst_n) begin
43. clk_1k <= 1'b0;
44. count <= 32'd0;
45. end
46. else if(count < cnt_num)
47. count <= count + 1'd1;
48. else begin
49. count <= 32'd0;
50. clk_1k <= ~clk_1k; //周期1ms(1KHz)
51. end
52. end
53.
54. endmodule
55. module seg_ctrl(
56. input clk_1k,
57. input rst_n,
58. input [23:0] data_in,
59.
60. output reg [7:0] seg ,
61. output reg [5:0] sel
62.
63. );
64.
65. //******数码管切换:1KHz******//
66. //parameter [5:0] IDLE = 6'b000000;
67. parameter [5:0] S0 = 6'b011111; //位选端低电平有效
68. parameter [5:0] S1 = 6'b101111;
69. parameter [5:0] S2 = 6'b110111;
70. parameter [5:0] S3 = 6'b111011;
71. parameter [5:0] S4 = 6'b111101;
72. parameter [5:0] S5 = 6'b111110;
73.
74. reg [5:0] current_state,next_state;
75.
76. //****** 每一位数码管对应的四位数据******//
77. reg [3:0] data_temp; //寄存输入24位数据的某四位
78.
79. always@(posedge clk_1k or negedge rst_n) begin
80. if(!rst_n)
81. current_state <= S0;
82. else
83. current_state <= next_state;
84. end
85.
86. always@(posedge clk_1k or negedge rst_n) begin
87. if(!rst_n)
88. next_state <= S0;
89. else
90. case(current_state)
91. S0: next_state <= S1;
92. S1: next_state <= S2;
93. S2: next_state <= S3;
94. S3: next_state <= S4;
95. S4: next_state <= S5;
96. S5: next_state <= S0;
97. default:next_state <= S0;
98. endcase
99. end
100.
101. always@(posedge clk_1k or negedge rst_n) begin
102. if(!rst_n) begin
103. sel <= 6'b111111;
104. data_temp <= 4'b0;
105. end
106. else begin
107. case(current_state)
108. S0: begin sel <= S0; data_temp <= data_in[23:20]; end
109. S1: begin sel <= S1; data_temp <= data_in[19:16]; end
110. S2: begin sel <= S2; data_temp <= data_in[15:12]; end
111. S3: begin sel <= S3; data_temp <= data_in[11:8]; end
112. S4: begin sel <= S4; data_temp <= data_in[7:4]; end
113. S5: begin sel <= S5; data_temp <= data_in[3:0]; end
114. default : begin sel <= S0;data_temp <= data_in[23:20]; end
115. endcase
116. end
117. end
118.
119. //****** 数码管译码******//
120. always @(*) begin
121. if(!rst_n)
122. seg = 8'h0;
123. else
124. case(data_temp)
125. 4'h0 : seg = 8'b1100_0000;
126. 4'h1 : seg = 8'b1111_1001;
127. 4'h2 : seg = 8'b1010_0100;
128. 4'h3 : seg = 8'b1011_0000;
129.
130. 4'h4 : seg = 8'b1001_1001;
131. 4'h5 : seg = 8'b1001_0010;
132. 4'h6 : seg = 8'b1000_0010;
133. 4'h7 : seg = 8'b1111_1000;
134.
135. 4'h8 : seg = 8'b1000_0000;
136. 4'h9 : seg = 8'b1001_0000;
137. 4'hA : seg = 8'b1000_1000; //A
138. 4'hb : seg = 8'b1000_0011; //B
139.
140. 4'hC : seg = 8'b1100_0110; //C
141. 4'hd : seg = 8'b1010_0001; //D
142. 4'hE : seg = 8'b1000_0110; //E
143. 4'hF : seg = 8'b1000_1110; //F
144. default : seg = 8'b1100_0000;
145. endcase
146. end
147. endmodule
//数字钟逻辑控制模块(顶层模块)文章来源:https://www.toymoban.com/news/detail-739055.html
//数字钟逻辑控制模块
1. module clk_logic_ctrl(
2. input clk,
3. input rst_n,
4. input flag_adjust, //按键切换的标志信号
5. input flag_add, //按键加的标志信号
6. input flag_sub, //按键减的标志信号
7.
8. output [23:0] display_data //输出显示的数据
9. );
10. /******* logic_ctrl ******/
11. wire min_en;
12. wire hour_en;
13. wire flag_hour_add;
14. wire flag_hour_sub;
15. wire flag_min_add;
16. wire flag_min_sub;
17.
18. /******* sec_ctrl ******/
19. wire flag_min;
20. wire [5:0] sec;
21.
22. /******* min_ctrl ******/
23. wire flag_hour;
24. wire [5:0] min;
25.
26. /******* hour_ctrl ******/
27. wire [5:0] hour;
28.
29. /******* bin_bcd ******/
30. wire [11:0] bcd_s;
31. wire [11:0] bcd_m;
32. wire [11:0] bcd_h;
33.
34. /******* min_adjust/hour_adjust ******/
35. wire [7:0] data_h;
36. wire [7:0] data_m;
37.
38. //逻辑控制模块
39. logic_ctrl logic_ctrl_dut(
40. .clk(clk),
41. .rst_n(rst_n),
42. .flag_adjust(flag_adjust),
43. .flag_add(flag_add),
44. .flag_sub(flag_sub),
45.
46. .flag_hour_add(flag_hour_add),
47. .flag_hour_sub(flag_hour_sub),
48. .flag_min_add(flag_min_add),
49. .flag_min_sub(flag_min_sub),
50. .hour_en(hour_en),
51. .min_en(min_en)
52. );
53.
54. //秒的控制模块:#(.T1s(4)) //用于仿真
55. sec_ctrl sec_ctrl_dut(
56. .clk(clk),
57. .rst_n(rst_n),
58.
59. .flag_min(flag_min),
60. .sec(sec)
61. );
62.
63. //分钟的控制模块
64. min_ctrl min_ctrl_dut(
65. .clk(clk),
66. .rst_n(rst_n),
67. .flag_min(flag_min),
68. .flag_min_add(flag_min_add),
69. .flag_min_sub(flag_min_sub),
70.
71. .flag_hour(flag_hour),
72. .min(min)
73. );
74.
75. //小时的控制模块
76. hour_ctrl hour_ctrl_dut(
77. .clk(clk),
78. .rst_n(rst_n),
79. .flag_hour(flag_hour),
80. .flag_hour_add(flag_hour_add),
81. .flag_hour_sub(flag_hour_sub),
82.
83. .hour(hour)
84. );
85.
86. //秒转码
87. bin_bcd bin_bcd_sec(
88. .bin({2'b0,sec}),
89.
90. .bcd(bcd_s)
91. );
92.
93. //分钟转码
94. bin_bcd bin_bcd_min(
95. .bin({2'b0,min}),
96.
97. .bcd(bcd_m)
98. );
99.
100. //小时转码
101. bin_bcd bin_bcd_hour(
102. .bin({2'b0,hour}),
103.
104. .bcd(bcd_h)
105. );
106.
107. //小时闪烁模块
108. hour_adjust hour_adjust_dut (
109. .clk(clk),
110. .rst_n(rst_n),
111. .hour_en(hour_en),
112. .bcd_h(bcd_h[7:0]),
113.
114. .data_h(data_h)
115. );
116.
117. //分钟闪烁模块
118. min_adjust min_adjust_dut(
119. .clk(clk),
120. .rst_n(rst_n),
121. .min_en(min_en),
122. .bcd_m(bcd_m[7:0]),
123. .data_m(data_m)
124. );
125. assign display_data = {data_h, data_m, bcd_s[7:0]};
126.
127. endmodule
//数字钟逻辑控制模块的各个底层模块文章来源地址https://www.toymoban.com/news/detail-739055.html
//逻辑控制模块
1. module logic_ctrl(
2. input clk,
3. input rst_n,
4. input flag_adjust, //切换按键标志
5. input flag_add, //加按键标志
6. input flag_sub, //减按键标志
7.
8. output reg flag_hour_add, //小时加标志
9. output reg flag_hour_sub, //小时减标志
10. output reg flag_min_add, //分钟加标志
11. output reg flag_min_sub, //分钟减标志
12. output reg hour_en, //小时的闪烁使能
13. output reg min_en //分钟的闪烁使能
14. );
15.
16. reg [1:0] state; //状态变量
17.
18. parameter s0 = 2'b00; //正常显示状态:切换到小时状态
19. parameter s1 = 2'b01; //小时状态(可以对小时进行调节):切换到分钟状态
20. parameter s2 = 2'b10; //分钟状态(可以对分钟进行调节):切换完成
21.
22. always@(posedge clk or negedge rst_n) begin
23. if(!rst_n)
24. state <= s0;
25. else
26. case(state) //状态机结构
27. s0 : if(flag_adjust == 1'b1) //第一次按下切换按键,切换到小时
28. state <= s1;
29. else
30. state <= s0;
31. s1 : if(flag_adjust == 1'b1) //第二次按下切换按键,切换到分钟
32. state <= s2;
33. else
34. state <= s1;
35. s2 : if(flag_adjust == 1'b1) //第一次按下切换按键,切换完成
36. state <= s0;
37. else
38. state <= s2;
39. default : state <= s0; //安全行为
40. endcase
41.
42. end
43.
44.
45. //小时加控制
46. always@(posedge clk or negedge rst_n) begin
47. if(!rst_n)
48. flag_hour_add <= 1'b0;
49. else if(state == s1 && flag_add == 1'b1) //切换到小时状态并且按下加按键
50. flag_hour_add <= 1'b1;
51. else
52. flag_hour_add <= 1'b0;
53. end
54.
55. //小时减控制
56. always@(posedge clk or negedge rst_n) begin
57. if(!rst_n)
58. flag_hour_sub <= 1'b0;
59. else if(state == s1 && flag_sub == 1'b1) //切换到小时状态并且按下减按键
60. flag_hour_sub <= 1'b1;
61. else
62. flag_hour_sub <= 1'b0;
63. end
64.
65. //分钟加控制
66. always@(posedge clk or negedge rst_n) begin
67. if(!rst_n)
68. flag_min_add <= 1'b0;
69. else if(state == s2 && flag_add == 1'b1) //切换到分钟状态并且按下加按键
70. flag_min_add <= 1'b1;
71. else
72. flag_min_add <= 1'b0;
73. end
74.
75. //分钟减控制
76. always@(posedge clk or negedge rst_n) begin
77. if(!rst_n)
78. flag_min_sub <= 1'b0;
79. else if(state == s2 && flag_sub == 1'b1) //切换到分钟状态并且按下减按键
80. flag_min_sub <= 1'b1;
81. else
82. flag_min_sub <= 1'b0;
83. end
84.
85.
86. //小时的闪烁
87. always@(posedge clk or negedge rst_n) begin
88. if(!rst_n)
89. hour_en <= 1'b0;
90. else if(state == s1 ) //切换到小时状态
91. hour_en <= 1'b1; //小时闪烁
92. else
93. hour_en <= 1'b0;
94. end
95.
96.
97. //分钟的闪烁
98. always@(posedge clk or negedge rst_n) begin
99. if(!rst_n)
100. min_en <= 1'b0;
101. else if(state == s2 ) //切换到分钟状态
102. min_en <= 1'b1; //分钟闪烁
103. else
104. min_en <= 1'b0;
105. end
106.
107.
108. endmodule
1. //秒的控制器
2.
3. module sec_ctrl(
4. input clk,
5. input rst_n,
6. output flag_min,
7. Output reg [5:0] sec
8. );
9. //产生1秒周期
10. reg [31:0] cnt; //计数器计数1s
11.
12. parameter T1s = 50_000_000 - 1; //1s
13.
14. always @(posedge clk or negedge rst_n) begin
15. if(!rst_n)
16. cnt <= 32'd0;
17. else if(cnt < T1s)
18. cnt <= cnt + 1'd1;
19. else
20. cnt <= 32'd0;
21.
22. end
23.
24. wire flag_1s; //1s标志信号
25.
26. assign flag_1s = (cnt == T1s) ? 1'b1 : 1'b0; //1s标志
27.
28.
29. //完成秒控制
30. always @(posedge clk or negedge rst_n) begin
31. if(!rst_n)
32. sec <= 6'd0;
33. else if(flag_1s == 1'b1 && sec < 6'd59)
34. sec <= sec + 1'd1;
35. else if(flag_1s)
36. sec <= 6'd0;
37. else
38. sec <= sec;
39.
40. end
41.
42. //产生分钟标志
43. assign flag_min = (sec == 6'd59 && flag_1s == 1'b1) ? 1'b1 : 1'b0; //1min
44. endmodule
1. //分钟的控制器
2.
3. module min_ctrl(
4. input clk,
5. input rst_n,
6. input flag_min, //分钟标志,是由秒产生的
7. input flag_min_add, //分钟加标志
8. input flag_min_sub, //分钟减标志
9.
10. output flag_hour, //小时标志
11. output reg [5:0] min //分钟输出
12. );
13.
14. //控制分钟加减
15. always@(posedge clk or negedge rst_n) begin
16. if(!rst_n)
17. min <= 6'd0;
18. else if(flag_min == 1'b1 || flag_min_add == 1'b1) //分钟都可以加
19. begin
20. if(min < 6'd59) //分钟小于59
21. min <= min + 1'b1; //分钟加1
22. else //分钟大于59
23. min <= 6'd0; //分钟清零
24. end
25. else if(flag_min_sub == 1'b1 && min > 6'd0) //减按键按下且分钟大于0
26. min <= min - 1'd1; //分钟减1
27. else if(flag_min_sub == 1'b1 && min == 6'd0) //分钟减到0
28. min <= 6'd59; //分钟回到最大值
29. else
30. min <= min; //分钟保持当前值
31.
32. end
33.
34. //产生小时的标志
35. assign flag_hour = (min <= 6'd59 && flag_min == 1'b1) ? 1'b1 : 1'b0; //1h
36.
37. endmodule
1. //小时的控制器
2.
3. module hour_ctrl(
4. input clk,
5. input rst_n,
6. input flag_hour, //小时标志,是由分钟产生的
7. input flag_hour_add, //小时加标志
8. input flag_hour_sub, //小时减标志
9.
10. output reg [5:0] hour //小时输出
11.
12. );
13.
14. //控制小时加减
15. always@(posedge clk or negedge rst_n) begin
16. if(!rst_n)
17. hour <= 6'd0;
18. else if(flag_hour == 1'b1 || flag_hour_add == 1'b1) //小时都可以加
19. begin
20. if(hour < 6'd23) //小时小于59
21. hour <= hour + 1'b1; //小时加1
22. else //小时大于59
23. hour <= 6'd0; //小时清零
24. end
25. else if(flag_hour_sub == 1'b1 && hour > 6'd0) //减按键按下且小时大于0
26. hour <= hour - 1'd1; //小时减1
27. else if(flag_hour_sub == 1'b1 && hour == 6'd0) //小时减到0
28. hour <= 6'd23; //小时回到最大值
29. else
30. hour <= hour; //小时保持当前值
31. end
32. endmodule
1. //小时的闪烁
2. module hour_adjust(
3. input clk,
4. input rst_n,
5. input hour_en, //小时闪烁使能
6. input [7:0] bcd_h, //转码后的数据
7.
8. output reg [7:0] data_h //输出数据
9.
10. );
11.
12. //产生0.5秒高,0.5秒低
13. reg [31:0] cnt; //计数器计数1s
14. reg flag_1s_half; //0.5秒闪烁使能信号
15.
16. parameter T1s_half = 50_000_000 / 2 - 1; //0.5s
17.
18. always @(posedge clk or negedge rst_n) begin
19. if(!rst_n) begin
20. cnt <= 32'd0;
21. flag_1s_half <= 1'b0;
22. end
23. else if(cnt < T1s_half)
24. cnt <= cnt + 1'd1;
25. else begin
26. cnt <= 32'd0;
27. flag_1s_half <= ~flag_1s_half; //0.5秒高,0.5秒低
28. end
29.
30. end
31.
32. //0.5秒数码管亮,0.5秒数码管灭
33. always @(posedge clk or negedge rst_n) begin
34. if(!rst_n)
35. data_h <= 8'h0;
36. else if(hour_en == 1'b1) //切换到小时
37. begin
38. if(flag_1s_half == 1'b1)
39. data_h <= bcd_h; //0.5s
40. else
41. data_h <= 8'hff; //数码管译码中f为8'b1111_1111
42. end
43. else
44. data_h <= bcd_h; //正常显示
45. end
46. endmodule
//分钟的闪烁
1. module min_adjust(
2. input clk,
3. input rst_n,
4. input min_en, //分钟闪烁使能
5. input [7:0] bcd_m, //转码后的数据
6.
7. output reg [7:0] data_m //输出数据
8.
9. );
10.
11. //产生0.5秒高,0.5秒低
12. reg [31:0] cnt; //计数器计数1s
13. reg flag_1s_half; //0.5秒闪烁使能信号
14.
15. parameter T1s_half = 50_000_000 / 2 - 1; //0.5s
16.
17. always @(posedge clk or negedge rst_n) begin
18. if(!rst_n) begin
19. cnt <= 32'd0;
20. flag_1s_half <= 1'b0;
21. end
22. else if(cnt < T1s_half)
23. cnt <= cnt + 1'd1;
24. else begin
25. cnt <= 32'd0;
26. flag_1s_half <= ~flag_1s_half; //0.5秒高,0.5秒低
27. end
28.
29. end
30.
31. //0.5秒数码管亮,0.5秒数码管灭
32. always @(posedge clk or negedge rst_n) begin
33. if(!rst_n)
34. data_m <= 8'h0;
35. else if(min_en == 1'b1) //切换到扥中
36. begin
37. if(flag_1s_half == 1'b1)
38. data_m <= bcd_m; //0.5s
39. else
40. data_m <= 8'hff; //数码管译码中f为8'b1111_1111
41. end
42. else
43. data_m <= bcd_m; //正常显示
44. end
45.
46. endmodule
到了这里,关于基于FPGA的可调数字钟设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!