基于FPGA的可调数字钟设计

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

版权声明:本文为博主作者:以安_wjf原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/mc_012345678/article/details/130525689

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2024年1月11日
下一篇 2024年1月11日

相关推荐