数字时钟
一、功能描述:
设计一个多功能数字时钟,有基本的数字时钟、秒表、闹钟三个功能。其中,数字时钟可设置,秒表可以暂停和清零,闹钟可设置,到达设定时间后LED灯亮。
二、具体说明
1,数字时钟:
当拨码开关为“00”状态时,对应功能为数字时钟状态。当按下一次设置键时,可以使用加减键设置小时,再次按下设置键时,可以使用加减键设置分钟。第三次按下设置键,返回正常数字时钟状态。
2,秒表功能:
当拨码开关为“01”状态时,对应功能为秒表。对应显示状态为“SS.0D”,这里,SS代表秒,范围0~59。D代表毫秒,范围0~9。两个按键,按键一代表暂停与继续,按键二代表清零。
3,闹钟功能:
当拨码开关为“10”状态时,对应功能为闹钟。默认进入闹钟状态时,不可以设置闹钟时间。当按下一次设置键时,可以使用加减键设置闹钟小时,再次按下设置键时,可以使用加减键设置闹钟分钟。第三次按下设置键,返回正常闹钟状态。
三、框图
还没有总线的概念,因此各个模块和状态机的连线比较复杂。
主要模块:数字时钟、秒表、闹钟设置、按键驱动、拨码开关驱动、数码管显示以及转换电路、LED显示、状态机。
顶层模块:拨码开关选择功能,对应数码管显示不同内容。实际上就是动态地连接输入输出。
四、各个模块的IO说明
1,按键扫描模块
module key_scan(
input clk,
input rst_n,
input [4:0]button, //Buttons
output button_flag, //press button flag
output [4:0]db_button //debounced button,High valid
);
借鉴了CrazyBingo设计的按键消抖模块,这里button flag为指示有按键按下的标志,当db_button之中有任何一个按下时,button_flag维持一个时钟的高电平。
2,拨码开关消抖模块
module debounce_slide_switch(
input clk,
input rst_n,
input [1:0]slide_switch,//slide switch
output [1:0]db_slide_switch //debounced slide switch
);
利用《FPGA prototyping by verilog examples》一书中P146页的拨码开关消抖例子来实现的,主要思路就是间隔一段时间去读取相应电平高低。
3,数码管显示模块
module data_disp_convert(
input clk,
input rst_n,
input [5:0] bin_data1, //input data to display in binary
input [5:0] bin_data2, //
input [3:0]dp_in, //4 decimal points
input start, //start to convet binary to BCD
output [7:0]seg_display,//7 segment display
output [3:0]an_seg //anode 7 segment display select
);
主要就是将输入的二进制数据转换为BCD码,然后将十位和个位分别显示在数码管上,dp_in为各个位的小数点。bin_data1和bin_data2为要显示的两个二进制数据。start为启动bin2bcd转换的信号。输出则是数码管8位段选信号和4个片选信号。
下面来看一下内部构成,主要由‘bin2bcd’的数据格式转换模块和‘four_segment_display’十六进制数码管显示电路组成。
bin2bcd模块的接口如下,主要完成二进制编码到BCD编码的转换。
module bin2bcd(
input clk,
input rst_n,
input start,
input [12:0] bin, //0000-8191
output reg ready, //ready to receive data to display
output reg done_tick,//transform done trick signal
output [3:0] bcd3,bcd2,bcd1,bcd0 //binary coded decimal
);
这部分利用《FPGA prototyping by Verilog examples》一书中P160页的Binary-to-BCD conversion的例子来实现的,需要注意的是start为启动转换信号,ready为可以转换信号(模块不忙),done_tick为转换结束标志位,当转换完成时,持续一个时钟周期,提醒显示模块读取数据。
four_segment_display模块的接口如下:
module four_segment_display(
input clk,
input rst_n,
input load_data, //load input signal
input [3:0]data1,data2,data3,data4, //input data to display, in binary mode
input [3:0]dp_in, //4 decimal points
output reg[7:0]seg_display,//7 segment display
output reg[3:0]an_seg //anode 7 segment display select
);
这部分很简单,就是将输入的四个数据按照预先编定的查找表,然后将显示数据依次送到数码管的段选上。这部分代码参考《FPGA prototyping by Verilog examples》一书中P99页的LED time-multiplesing circuit的例子。
显示部分需要注意的就是,只有当bin2bcd模块的done_tick为高电平时,four_segment_display模块的load_data信号才为高,这时候才载入新的显示数据,以防在bin2bcd模块进行数据转换时,错误的数据被送给four_segment_display。
4,正常时钟模块
module normal_clock(
input clk,
input rst_n,
input button_flag, //button pressed flag
input [4:0]db_button,
output reg [3:0]dp_in, //data points
output [5:0]hh_data,mm_data //hour,minute in binary of 6 bits
);
输入按键信号,输出小时分钟(二进制格式)和小数点。这部分后面单独一个标题来说明。
5,秒表模块
module stopwatch(
input clk,
input rst_n,
input button_flag, //button pressed flag
input [4:0]db_button, //debounced button
output reg [3:0]dp_in, //data points
output reg [5:0]stopwatch_ss,stopwatch_ms //stopwatch's second and millisecond
);
这部分代码参考《FPGA prototyping by Verilog examples》一书中P107页的stopwatch的例子。
6,闹钟模块
module alarm_clock(
input clk,
input rst_n,
input button_flag,
input [4:0]db_button, //debounced button
input [5:0]hh_data,mm_data, //get the current time
output reg led_alarm, //led for alarming
output reg [3:0]dp_in, //data points
output [5:0]alarm_hh_data, // the setted alarm's hour
output [5:0]alarm_mm_data // the setted alarm's minute
);
主要就是获取当前的的时钟数据,以便和设置的闹钟数据进行对比。
五,正常时钟模块
这里主要讲一下正常时钟模块的设计思路,其他的闹钟或者秒表实际思路差不多。
先来看一下需要实现的功能:
1)显示格式为HH.MM,秒数利用小数点来显示。随着时钟运行,小数点闪烁。
2)key[0]:在正常运行、设置HH、设置MM三个状态之间进行切换。利用小数点指示当前是设置小时还是分钟。
3)Key[2] “++”。
4)Key[4]“–”。
状态转换图如下:
在这里设置三个状态,分别为normal、set_hh、set_mm,对应正常运行、设置小时、设置分钟三个状态。
1) Normal状态,ss_reg正常累加,hh_reg和mm_reg根据进位累加。dp_in[2]跟随秒嘀嗒闪烁。
2)Set_hh状态,ss_reg清零,hh_reg根据按键相应加减。Dp_in[3]亮起。
3)Set_mm状态,ss_reg清零,mm_reg根据按键相应加减。Dp_in[1]亮起。
需要操纵的数据主要是小时数,分钟数,秒数。数据路径需要执行的操作:
1)秒数的累加与清零。
2)hh_reg和mm_reg的进位累加和累减。
为了状态机写起来直观,将控制路径和数据路径分开。根据以上的抽象,FSM对data path的control signal利用以下几个:
1)ss_go来实现秒数的运行和暂停,ss_clr实现秒数的清零。
2)hh_up_down来实现小时数的加减。
3) mm_up_down来实现分钟的加减。
部分代码如下:
信号声明:
//symbolic state declaration
localparam [1:0]
NORMAL = 2'b00,
SET_HH = 2'b01,
SET_MM = 2'b10;
//signal declaration
//clk=100Mhz,(100^6/100_000_000=1Hz)
localparam FREQ_DV=32'd100_000_000;
//localparam FREQ_DV=32'd20; //only for test
//signal declaration
reg [1:0] state_reg,state_next;
reg [31:0] ss_tick_reg; //second tick register
wire [31:0]ss_tick_next;
wire ss_tick; //second tick
reg [7:0] ss_reg,mm_reg,hh_reg; //second,minute,hour register
wire [7:0] ss_next,mm_next,hh_next;
reg ss_go; //second go or stop signal
//1:go
//0:stay at current value
reg ss_clr; //second claer signal
//1:clear
//0:other
reg ss_tick_clr;
reg [1:0]hh_up_down; //hour up or down signal
//00:keep current data;
//01:counter up
//10:counter down
//11:clear to zero
reg [1:0]mm_up_down; //minute up or down signal
状态转换:
//fsmd state & data register
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
state_reg<=NORMAL; // reset
ss_tick_reg<=0;
ss_reg<=0;
mm_reg<=0;
hh_reg<=0;
end
else
begin
state_reg<=state_next;
ss_tick_reg<=ss_tick_next;
ss_reg<=ss_next;
mm_reg<=mm_next;
hh_reg<=hh_next;
end
end
数据流路径:
//generate the second tick
assign ss_tick_next = (ss_tick_clr || (ss_tick_reg==FREQ_DV-1'b1)) ? 31'd0 : ss_tick_reg + 1'b1;
assign ss_tick = (ss_tick_reg==FREQ_DV-1'b1) ? 1'b1 : 1'b0;
//FSMD data path next-state logic
assign ss_next = (ss_clr)? 0 :
(ss_go)? (ss_reg + 1'b1) :
ss_reg;
//assign ss_next = (ss_go_clr)? (ss_reg + 1'b1) : 0;
assign hh_next = (hh_up_down == 2'b00)? hh_reg :
(hh_up_down == 2'b01)? hh_reg + 1'b1 :
(hh_up_down == 2'b10)? hh_reg - 1'b1 : 0;
assign mm_next = (mm_up_down == 2'b00)? mm_reg :
(mm_up_down == 2'b01)? mm_reg + 1'b1 :
(mm_up_down == 2'b10)? mm_reg - 1'b1 : 0;
状态转换机:
//FSMD control path next-state logic
always @*
begin
state_next=state_reg; //default state: the same
ss_clr=1'b1; //ss_next=0
ss_go=1'b0;
ss_tick_clr=1'b1; //second tick clear
hh_up_down=2'b00; //hh_next=hh_reg
mm_up_down=2'b00; //mm_next=mm_reg
case(state_reg)
NORMAL:
begin
ss_tick_clr = 1'b0;
ss_clr = 1'b0;
if(button_flag)
begin
if(db_button[0])
state_next=SET_HH;
end
else
begin
if(ss_tick)
begin
if(ss_reg!=8'd59)
ss_go=1'b1;////////////////////////////////////
else
begin
ss_clr = 1'b1;/////////////////////////////
if(mm_reg!=8'd59)
mm_up_down = 2'b01;
else
begin
mm_up_down = 2'b11;
if(hh_reg !=8'd23)
hh_up_down = 2'b01;
else
hh_up_down = 2'b11;
end
end
end
end
end
SET_HH:
begin
ss_tick_clr=1'b1; //second tick clear
ss_clr=1'b1; //ss_next=0
if(button_flag)
begin
if(db_button[0])
state_next=SET_MM;
else if(db_button[2]) //hour ++
begin
if(hh_reg !=8'd23)
hh_up_down = 2'b01;
else
hh_up_down = 2'b11;
end
else if(db_button[4]) //hour --
begin
if(hh_reg !=8'd0)
hh_up_down = 2'b10;
else
hh_up_down = 2'b11;
end
end
end
SET_MM:
begin
ss_tick_clr=1'b1; //second tick clear
ss_clr=1'b1; //ss_next=0
if(button_flag)
begin
if(db_button[0])
state_next=NORMAL;
else if(db_button[2]) //minute ++
begin
if(mm_reg !=8'd59)
mm_up_down = 2'b01;
else
mm_up_down = 2'b11;
end
else if(db_button[4]) //minute --
begin
if(mm_reg !=8'd0)
mm_up_down = 2'b10;
else
mm_up_down = 2'b11;
end
end
end
default: state_next=NORMAL;
endcase
end
同步化输出:
//output data point signal
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
dp_in <=4'b1111; //reset,data point off
end
else
begin
case(state_reg)
NORMAL:
if(ss_tick)
dp_in <={1'b1,~dp_in[2],1'b1,1'b1};
SET_HH:
dp_in <= 4'b0111;
SET_MM:
dp_in <= 4'b1101;
default:
dp_in <= 4'b1111;
endcase
end
end
assign hh_data = hh_reg[5:0];
assign mm_data = mm_reg[5:0];
七,总结
学到了什么,
1)学到了状态机写法,control path和data path的抽象分立和通信。
2)模块化划分,各个模块之间的通信。
待学习:
1)四段式状态机写法。
2)常用的模块间通信控制信号。
相关阅读
目录 移位寄存器简介 分类 4位右移位寄存器工作原理 1、 16位右移位寄存器 2、 16位左移寄存器 3、 串行输入并行输出寄存器 4
Logo“撞衫”?logo设计网让你找到属于自己的 企业 logo
标志是企业与社会公众之间的视觉桥梁,它必须要向公众传达出企业的行业特性。--奥利弗·沃克Logo是树立品牌的基础,小到应用软
随着项目的积累,我们的文件目录会变得十分的盘大,如果不好好管理,将会变得一团糟,有时会影响到我们工作的效率与心情,好的文档管理方式
无论是2B产品,还是2C产品,用户系统都是基础。对于非互联网产品从业者,2C用户系统的场景和功能通过日常各类APP的使用,大家都非常熟悉
作为新版本表情的主设计师,来回答这个问题似乎有点让人 o(*////▽////*)q首先,QQ 默认表情其实已经经历了许多次的更新换代,每一次的