FPGA数字时钟(可暂停调数,含代码)

前言

前段时间刚刚开始初步学习FPGA相关知识,在学习了一段时间后,利用前面所学知识,写了一个数字时钟,顺便在这里写下总结,方便理解。

(本人小白一名,有错欢迎指出,欢迎探讨)

(我使用的开发板是Cyclone IV的EP4CE6F17C8,如有想测试实现效果的同学,可以把后面3-1到3-5对应代码 建文件,修改一下开发板型号以及引脚位置,进行测试。)

一.数码管原理

我使用的开发板拥有6个数码管(由位选信号控制每一位的亮灭),每个数码管都有8个小段(由段选信号控制对应小段显示,得到目标数字,其中7个控制数码管小段,还有1个控制小数点),由于时钟显示时,每个位置数字都变化不同,而我们的段选信号是控制6个数码管的8个小段同时呈现什么效果(意思就是6个数码管上显示的数字是一样的),要想实现每个位置数字看起来不一样,我们就需要利用视觉暂留现象,显示时候只能让其中一个位显示,其他位不显示,让数码管位选信号切换的足够快,也就是说数码管对应位由亮到灭需要的时候很短,人眼就无法分清此时此刻数码管状态,使人感觉数码管一直都亮着,且亮着的那一位是正确的数字,这样就能达到看起来,不同位置数字不同且都亮着的效果(也就是我们需要的数字时钟效果)。

FPGA数字时钟(可暂停调数,含代码)_第1张图片

二.模块原理图

FPGA数字时钟(可暂停调数,含代码)_第2张图片

有三个子模块和一个总的处理模块,其中三个子模块分别是key_debounce(消抖模块),count(计数时钟沿,计数1s模块),count2(计数20ms,控制位选信号跳转时间)。总模块count_clk负责处理输入的信号,有6个计数器,分别计数秒,分,时的个位,十位,还有对位选信号的处理和译码部分。

三.代码

这里我使用的计数方法是6个计数器分别计数时,分,秒的个位和十位,计数满了则使进位符置1,给到下一位,这个方法不需要进行取余取整操作,使用触发器资源较多,但节省组合逻辑资源。

3.1计数1s模块代码

module count(
	input wire clk,
	input wire rst_n,
	input wire ok,
	output reg flag1
);

reg[25:0] cnt;
//parameter MAX=24'd31250;//飞驰版54秒24小时 (上板时也可以使用这个,可以看到数码管显示很快)
parameter MAX=26'd50_000_000;//1s   (上板时使用这个)
//parameter MAX=20'd1000_000;//测试    (modelsim时使用这个)
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			cnt<=26'd0;
			flag1<=1'd0;
		end
	else if(cnt==MAX-1'd1)
		begin
			cnt<=26'd0;
			flag1<=1'd1;
		end
	else if(ok)	
		begin
			cnt<=cnt;
			flag1<=1'd0;
		end
	else
		begin
			cnt<=cnt+1'd1;
			flag1<=1'd0;
		end
end

endmodule

	

3.2计数20ms模块代码

module count2(
	input wire clk,
	input wire rst_n,
	output reg box
);

reg[19:0] cnt;
parameter MAX=20'd1_000_0;//20ms

always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			cnt<=20'd0;
			box<=1'd0;
		end
	else if(cnt==MAX-1'd1)
		begin
			cnt<=20'd0;
			box<=1'd1;
		end
	else
		begin
			cnt<=cnt+1'd1;
			box<=1'd0;
		end
end

endmodule

	

3.3消抖模块代码(消抖方式是等待一定时间未动,则消抖成功)

module key_debounce(
	input clk,
	input rst_n,
	input wire [2:0] key,
	
	output reg ok,
	output reg flag9,//改变暂停时数字

	output reg flag10//控制数码管调整数字的位置

);
parameter MAX_TEN=21'd1000_000;//20ms


reg [20:0] delay_cnt;
reg [20:0] delay_cnt2;
reg [20:0] delay_cnt3;

reg value_key;
reg value_key2;
reg value_key3;
reg[0:0] flag8;
//reg[0:0] flag9;

//消抖模块
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			value_key<=1'd1;
			delay_cnt<=MAX_TEN;
		end
	else
		begin
		value_key<=key[0];//取得一瞬间key值
		if(value_key==!key[0])//如果抖动
			begin
				delay_cnt<=MAX_TEN;
			end
		else if(delay_cnt>1'd0&&value_key==1'd0)//当没有抖动时候,开始计数
			begin
				delay_cnt<=delay_cnt-1'd1;
			end
		else
			begin
				delay_cnt<=21'd0;
			end
		end
end

//消抖符号的处理
always@(posedge clk or negedge rst_n)
begin	
	if(!rst_n)
		begin
			flag8<=1'd0;
		end
	else if(delay_cnt==21'd1&&value_key==1'd0)
		begin
			flag8<=1'd1;
		end
	else 
		begin
			flag8<=1'd0;		
		end
end

//暂停标识符的转化
always@(posedge flag8)
begin	
	if(flag8 && !value_key)//按下消除抖动后才置1(没有松开)
		begin
			ok<=~ok;
		end
end



//消抖模块(按键三的消抖)
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			value_key2<=1'd1;
			delay_cnt2<=MAX_TEN;
		end
	else
		begin
		value_key2<=key[1];//取得一瞬间key值(寄存器存储key值)
		if(value_key2==!key[1])//如果抖动
			begin
				delay_cnt2<=MAX_TEN;
			end
		else if(delay_cnt2>1'd0&&value_key2==1'd0)//当没有抖动时候,开始计数
			begin
				delay_cnt2<=delay_cnt2-1'd1;
			end
		else
			begin
				delay_cnt2<=21'd0;
			end
		end
end

//消抖符号的处理
always@(posedge clk or negedge rst_n)
begin	
	if(!rst_n)
		begin
			flag9<=1'd0;
		end
	else if(delay_cnt2==21'd1&&value_key2==1'd0)
		begin
			flag9<=1'd1;//表示按键按下了一次并未松开,且抖动已经消除了
		end
	else 
		begin
			flag9<=1'd0;		
		end
end

//消抖模块(按键四的消抖)
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			value_key3<=1'd1;
			delay_cnt3<=MAX_TEN;//这里其实可以去掉,就不用给后面加东西了
		end
	else
		begin
		value_key3<=key[2];//取得一瞬间key值
		if(value_key3==!key[2])//如果抖动
			begin
				delay_cnt3<=MAX_TEN;
			end
		else if(delay_cnt3>1'd0&&value_key3==1'd0)//当没有抖动时候,开始计数
			begin
				delay_cnt3<=delay_cnt3-1'd1;
			end
		else
			begin
				delay_cnt3<=21'd0;
			end
		end
end

//消抖符号的处理
always@(posedge clk or negedge rst_n)
begin	
	if(!rst_n)
		begin
			flag10<=1'd0;
		end
		else if(delay_cnt3==21'd1&&value_key3==1'd0)//注意(当复位后,置了MAX值,所以会导致计数一次(此时没有抖动),会造成flag10跟flag9置1一次,进而导致暂停时候复位,会让调数的位置默认加一和数字默认加一)
		begin
			flag10<=1'd1;//表示按键按下了一次并松开,且抖动已经消除了
		end
	else 
		begin
			flag10<=1'd0;		
		end
end


//加一标识符的转变
//always@(posedge flag9)
//begin	
//	if(flag9 && !value_key2)
//		begin
//			ok2<=~ok2;
//		end
//end

endmodule

3.4数码管计数显示模块

module count_clk(
	input wire clk,
	input wire rst_n,
	input wire flag1,//计数满1s
	//input wire key,//为1时,表示无效,没被按下
	input wire box,//控制位选跳转(流水灯)
	input wire flag9,
	input wire flag10,
	input wire ok,
	output reg[5:0] sel,
	output reg[7:0] seg
	
);
reg [2:0] sum=3'd0;//计数控制调哪个位置的数字
reg [4:0] s_a;//秒的个位
reg [3:0] s_b;//秒的十位
reg [4:0] m_a;//分的个位
reg [3:0] m_b;//分的十位
reg [4:0] t_a;//时的个位
reg [2:0] t_b;//时的十位
reg [0:0] flag2 = 0;//秒的个位进位标识符
reg [0:0] flag3 = 0;//秒的十位进位标识符
reg [0:0] flag4 = 0;//分的个位进位标识符
reg [0:0] flag5 = 0;//分的十位进位标识符
reg [0:0] flag6 = 0;//时的个位进位标识符
reg [0:0] flag7 = 0;//时的十位进位标识符
reg [0:0] clear =0;//计数满了清零符



always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		sum<=3'd0;
	else if(flag10)
	begin
		if(sum<3'd5)
			begin
				sum<=sum+1'd1;
			end
		else 
			begin
				sum<=3'd0;
			end
	end
	else
		sum<=sum;
end


//秒的个位计数(暂停采用的止住flag进位,而不是采用禁用另一个计数模块的clk)	(已经改成clk暂停了)	
always@(posedge clk or negedge rst_n )
begin
	if(!rst_n)
		begin
			s_a<=5'd0;
			flag2<=1'd0;
		end
	else if(flag1)
		begin
			if(s_a<5'd9)
				begin
					s_a<=s_a+1'd1;
					flag2<=1'd0;
				end
			else
				begin
					s_a<=5'd0;
					flag2<=1'd1;
				end
		end
	else if(flag9)
		begin
			if(ok==1'd1&&sum==3'd0)
				begin
					if(s_a<5'd9)
						begin
							s_a<=s_a+1'd1;
						end
					else
						begin
							s_a<=5'd0;
						end
				end
		end
	else
		begin
			s_a<=s_a;
			flag2<=1'd0;//因为改成clk触发了,触发频率变大,如果不是设置置零,则会在下一个always语句中,多次触发flag2为1 的情况,疯狂计数(最开始写法为flagx触发时候,没有考虑到这种情况,现在改为clk,则需要考虑到不进位时clk上升沿来了触发的情况)
		end
end

//秒的十位计数
always@(posedge clk or negedge rst_n )
begin
	if(!rst_n)
		begin
			s_b<=3'd0;
			flag3<=1'd0;
		end
	else if(flag2)
		begin
			if(s_b<3'd5)
				begin
					s_b<=s_b+1'd1;
					flag3<=1'd0;
				end
			else
				begin
					s_b<=3'd0;
					flag3<=1'd1;
				end
		end
	else if(flag9)
		begin
			if(ok==1'd1&&sum==3'd1)
				begin
					if(s_b<3'd5)
						begin
							s_b<=s_b+1'd1;
						end
					else
						begin
							s_b<=3'd0;
						end
				end
		end
	else
		begin
			flag3<=1'd0;
			s_b<=s_b;
		end
end


//分的个位计数
always@(posedge clk or negedge rst_n )
begin
	if(!rst_n)
		begin
			m_a<=4'd0;
			flag4<=1'd0;
		end
	else if(flag3)
		begin
			if(m_a<4'd9)
				begin
					m_a<=m_a+1'd1;
					flag4<=1'd0;
				end
			else
				begin
				   m_a<=4'd0;
					flag4<=1'd1;
				end
		end
	else if(flag9)
		begin
			if(ok==1'd1&&sum==3'd2)
				begin
					if(m_a<4'd9)
						begin
							m_a<=m_a+1'd1;
						end
					else
						begin
							m_a<=4'd0;
						end
				end
		end
	else
		begin
			m_a<=m_a;
			flag4<=1'd0;
		end
end


//分的十位计数
always@(posedge clk or negedge rst_n )
begin
	if(!rst_n)
		begin
			m_b<=3'd0;
			flag5<=1'd0;
		end
	else if(flag4)
		begin
			if(m_b<3'd5)
				begin
					m_b<=m_b+1'd1;
					flag5<=1'd0;
				end
			else
				begin
				   m_b<=3'd0;
					flag5<=1'd1;
				end
		end
	else if(flag9)
		begin
			if(ok==1'd1&&sum==3'd3)
				begin
					if(m_b<3'd5)
						begin
							m_b<=m_b+1'd1;
						end
					else
						begin
							m_b<=3'd0;
						end
				end
		end
	else
		begin
			m_b<=m_b;
			flag5<=1'd0;
		end
end

//时的个位计数
always@(posedge clk or negedge rst_n )
begin
	if(!rst_n)
		begin
			t_a<=4'd0;
			flag6<=1'd0;
		end
	else if(flag5)
		begin
			if(t_a==4'd9)
				begin
					t_a<=4'd0;
					flag6<=1'd1;
				end
			else if(t_a==4'd3&&t_b==2'd2)
				begin
					t_a<=4'd0;
					clear<=1'd1;//传递给下一位使其判断清零的符号。
					flag6<=1'd1;
				end
			else
				begin
					t_a<=t_a+1'd1;
					flag6<=1'd0;
					clear<=1'd0; 
				end
		end
	else if(flag9)
		begin
			if(ok==1'd1&&sum==3'd4)
				begin
					if(t_a<4'd9&&t_b!=2'd2)
						begin
							t_a<=t_a+1'd1;
						end
					else if(t_b==2'd2)
						begin
							if(t_a<4'd3)
								t_a<=t_a+1'd1;
							else
								t_a<=4'd0;
						end
					else
						begin
							t_a<=4'd0;
						end
				end
		end
	else
		begin
			t_a<=t_a;
			flag6<=1'd0;
		end
end

//时的十位计数(已设置满23:59:59自动清零)  
always@(posedge clk  or negedge rst_n )//?不能加第二个(or posedge clear)加了会导致个位跟十位显示相同,小数点都相同
begin
	if(!rst_n)
		begin
			t_b<=2'd0;
		end
	else if(flag6)//flag6表示进位,也可以代表清零。
		begin
			if(clear)
				begin
					t_b<=2'd0;
				end
			else
				t_b<=t_b+1'd1;
		end
	else if(flag9)
		begin
			if(ok==1'd1&&sum==3'd5)
				begin
					if(t_b<2'd2)
						begin
							t_b<=t_b+1'd1;
						end
					else
						begin
							t_b<=2'd0;
						end
				end
		end
	else
		begin
			t_b<=t_b;
		end
end

//位选流水灯(实现快速闪烁每一个数码管形成残影,造成时钟现象)
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
			sel<=6'b111_110;
		end
	else if(box)
		begin
			sel<={sel[4:0],sel[5]};
		end
	else
		begin
			sel<=sel;
		end
end

//译码模块
always@(sel)
begin
	if(sel==6'b111110)
			begin
				case(s_a)
				4'd0:seg=8'b11000000;
				4'd1:seg=8'b11111001;
				4'd2:seg=8'b10100100;
				4'd3:seg=8'b10110000;
				4'd4:seg=8'b10011001;
				4'd5:seg=8'b10010010;
				4'd6:seg=8'b10000010;
				4'd7:seg=8'b11111000;
				4'd8:seg=8'b10000000;
				4'd9:seg=8'b10010000;
				default:;
				endcase
			end
	else if(sel==6'b111101)
			begin
				case(s_b)
				3'd0:seg=8'b11000000;
				3'd1:seg=8'b11111001;
				3'd2:seg=8'b10100100;
				3'd3:seg=8'b10110000;
				3'd4:seg=8'b10011001;
				3'd5:seg=8'b10010010;
				default:;
				endcase
			end
	else if(sel==6'b111011)
			begin
				case(m_a)
				4'd0:seg=8'b01000000;
				4'd1:seg=8'b01111001;
				4'd2:seg=8'b00100100;
				4'd3:seg=8'b00110000;
				4'd4:seg=8'b00011001;
				4'd5:seg=8'b00010010;
				4'd6:seg=8'b00000010;
				4'd7:seg=8'b01111000;
				4'd8:seg=8'b00000000;
				4'd9:seg=8'b00010000;
				default:;
				endcase
			end
	else if(sel==6'b110111)
			begin
				case(m_b)
				3'd0:seg=8'b11000000;
				3'd1:seg=8'b11111001;
				3'd2:seg=8'b10100100;
				3'd3:seg=8'b10110000;
				3'd4:seg=8'b10011001;
				3'd5:seg=8'b10010010;
				default:;
				endcase
			end
	else if(sel==6'b101111)
			begin
				case(t_a)
				4'd0:seg=8'b01000000;
				4'd1:seg=8'b01111001;
				4'd2:seg=8'b00100100;
				4'd3:seg=8'b00110000;
				4'd4:seg=8'b00011001;
				4'd5:seg=8'b00010010;
				4'd6:seg=8'b00000010;
				4'd7:seg=8'b01111000;
				4'd8:seg=8'b00000000;
				4'd9:seg=8'b00010000;
				default:;
				endcase
			end
	else if(sel==6'b011111)
			begin
				case(t_b)
				2'd0:seg=8'b11000000;
				2'd1:seg=8'b11111001;
				2'd2:seg=8'b10100100;
				default:;
				endcase
			end
		
end

endmodule	
			

3.5顶层模块

module top_count_clk(
	input clk,
	input rst_n, 
	input [2:0] key,
	output [5:0] sel,
	output [7:0] seg
);
wire flag1;
wire box;
wire ok;
wire flag9;
wire flag10;

count u_count(
.clk(clk),
.rst_n(rst_n),
.flag1(flag1),
.ok(ok)
);

count2 u_count2(
.clk(clk),
.rst_n(rst_n),
.box(box)
);

count_clk u_count_clk(
.clk(clk),
.rst_n(rst_n),
.sel(sel),
.seg(seg),
.flag1(flag1),
.box(box),//位选信号例化
.flag9(flag9),
.flag10(flag10),
.ok(ok)
);

key_debounce u_key_debounce(
.clk(clk),
.rst_n(rst_n),
.key(key),
.ok(ok),
.flag9(flag9),
.flag10(flag10)
);

endmodule

3.6测试文件(做仿真时候,需要改一下对应模块参数)

`timescale 1ns/1ps
module top_count_clk_tb();
reg clk;
reg rst_n;
wire [5:0] sel;
wire [7:0] seg;

parameter CYCLE=20;
always#(CYCLE/2) clk=~clk;
initial begin
	clk=1'd0;
	rst_n=1'd0;
	#(CYCLE);
	rst_n=1'd1;
	//#(CYCLE*20'd1000_000*17'd86400);    //对应上面1s计数模块中的仿真参数
	#(CYCLE*17'd31250*17'd86400);        //对应1s计数中的飞驰人生版本parameter
	$stop;
end
top_count_clk u_top_count_clk(
.clk(clk),
.rst_n(rst_n),
.sel(sel),
.seg(seg)
);
endmodule

四.理解总结

上板使用说明:这个实验设置了四个按键,分别是key0(复位按键),key1(暂停按键),key2控制我们进行调数的位数(也就是让我们选择调数字时钟的哪一位),key3则是直接的控制我们调的位的数(按一下加一)。

注:调数功能只能在暂停状态下进行,可在暂停时候进行复位功能。

做实验过程中遇到的问题:

1. 在做好时钟后,想要加入调数功能,结果发现多一个按键需要加入always语句中,因为不能在多个地方给同一个参数赋值,所以这个地方卡了很久,最后只能把posedge flag改为了时钟上升沿触发,但因此在调试过程中,忽略了一个东西,clk上升沿触发的频率比flag触发的次数多非常多,最初的flag一层一层触发,没出现这个问题,是因为对应flag5置零(在上一个flag4来之前),不会进入always语句,影响flag5进位符号对应的位置显示,所以我在每一个always语句后面加了一个else来置零对应flag(这样每次clk触发进入,就能让flag发挥用处后及时置零,不会持续让下一位进位)。

2. 遇到的第二个问题是,消抖模块没写完整,忽略了复位情况下,把cnt置MAX了,所以会默认触发一次flag9和flag10(不知道为什么暂停消抖部分没受影响),进而导致了暂停时候复位,会看到00:00:10的效果,解决方法也很简单,要么把复位的cnt置MAX删掉,要么后面加上判断,使之不能把复位判断成一次按键按下,这样就能解决这个问题了)

后来我看了下:重新梳理一遍,当按下第二个键,再次取消暂停时,其他键也抖动了,且按下时,cnt又置MAX了,

 FPGA数字时钟(可暂停调数,含代码)_第3张图片

结果时钟沿到来,没判定第一个if语句,直接第二个语句,value_key 直接取了key的瞬时值

FPGA数字时钟(可暂停调数,含代码)_第4张图片

又因为按下暂停时,其他键很可能也抖动了,所以就会导致某位计数(?),直到计数为21’d1,然后看下一个图:

FPGA数字时钟(可暂停调数,含代码)_第5张图片

此时clk来了又判断key的瞬时值,就阴差阳错把flag9置1了,flag10同理,结果就导致了我暂停时候复位了,在取消暂停,结果数码管显示的00:00:10。

(注:代码截图是已经改过了的代码,错误已经纠正,前面出现问题的时候忘记截图了,时间久了想不起来具体怎么改的,下次写复位注意多考虑清楚时序关系!)

3. 有机会可以加个谱子,加个到时间自动放音乐。

你可能感兴趣的:(intel,verilog,fpga开发,硬件工程)