在此特别感谢哔站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

//数字钟逻辑控制模块(顶层模块)

//数字钟逻辑控制模块

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

//数字钟逻辑控制模块的各个底层模块

//逻辑控制模块

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

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: