一、项目需求
使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。
二、完成的功能
秒表使用四个按钮输入:开始、停止、增量和清除(重置)。
1.开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次)。
2.停止输入使计数器停止递增,但使数码管显示当前计数器值。
3.每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间。
4. 复位/清除输入强制计数器值为零。
三、设计思路
1.时钟分频
调用小脚丫示例实验17分频器。
2.计时初始化
设置显示器,整数位、小数位数字分别为0-9时七段显示数码管显示对应数字,并设置显示初值为0.0。为了显示小数点,整数位数值比小数位数值增加二进制数1000 0000。
3.清零重置按键
对重置信号rst进行判断,若有效,则保持显示数字0.0并循环。若重置信号无效,进入计数功能。
4.状态量设置
设置开始、暂停、增量状态量。当相应按键信号有效时,状态量改为相应状态量。特别的是,对增量状态量的判断,需要增加使用增量前状态,用以判断增量状态有效时间位于时钟的位置,确保在时钟下降沿后有效再将状态量设为增量状态量,以对齐时钟,保证在0.1秒计数完成后再增加计数。若增量状态未有效,增量前状态重置(也不能有效);增量状态有效,对时钟判断决定增量前状态是否有效,两个状态均为有效时小数位计数加1。
5.开始按键
重置信号无效时,按下开始按键,状态量设为开始按键对应状态量,按10Hz时钟周期进行计数。每过一个时钟周期,小数位计数从0开始加1;每次计数增加前对小数位和整数位进行判断:若小数位计数增加前数字不为9,则小数位计数+1,整数位不变;若小数位计数增加前数字为9整数位计数增加前数字不是9,则小数位置为0,整数位计数加1;若小数位计数增加前数字为9且整数位计数增加前数字也为9,则小数位和整数位均置为0,即计数满9.9清0重新开始。
6.暂停按键
重置信号无效时,按下暂停按键,状态量设为暂停按键对应状态量,计数不变。
7.增量按键
重置信号无效时,按下增量按键,状态量设为增量按键对应状态量,并对增量前状态进行判断。若增量前状态有效,对小数位和整数位数字进行判断,若小数位计数增加前数字不为9,则小数位计数+1,整数位不变;若小数位计数增加前数字为9整数位计数增加前数字不是9,则小数位置为0,整数位计数加1;若小数位计数增加前数字为9且整数位计数增加前数字也为9,则小数位和整数位均置为0,即计数满9.9清0重新开始。若增量前状态无效,重置增量状态。
8.计时完成后翻转
技术增加时自动判断计数是否达到9.9,达到9.9时自动清零进行下一轮计数。
四、设计过程
1.功能框图
2.代码实现
·初始状态设置
设置显示器,整数位、小数位数字分别为0-9时七段显示数码管显示对应数字,并设置显示初值为0.0。为了显示小数点,整数位数值比小数位数值增加二进制数1000 0000。
input clk,rst;
input start;
input stop;
input plus;
output [8:0] seg_led_1,seg_led_2;
reg cnt_flag=1'b0; //计数标志
reg plus_flag=1'b0; //增量标志
reg pre_plus=1'b1; //增量前标志
reg [9:0] seg1 [9:0];
reg [9:0] seg2 [9:0];
reg [3:0] cnt_ge; //个位
reg [3:0] cnt_shi; //十位
initial
begin
seg1[0] = 9'hbf; //对存储器中第一个数赋值9'b00_1011_1111,相当于共阴极接地,DP点变高亮,7段显示数字 0
seg1[1] = 9'h86; //7段显示数字 1
seg1[2] = 9'hdb; //7段显示数字 2
seg1[3] = 9'hcf; //7段显示数字 3
seg1[4] = 9'he6; //7段显示数字 4
seg1[5] = 9'hed; //7段显示数字 5
seg1[6] = 9'hfd; //7段显示数字 6
seg1[7] = 9'h87; //7段显示数字 7
seg1[8] = 9'hff; //7段显示数字 8
seg1[9] = 9'hef; //7段显示数字 9
end
initial
begin
seg2[0] = 9'h3f; //对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮,7段显示数字 0
seg2[1] = 9'h06; //7段显示数字 1
seg2[2] = 9'h5b; //7段显示数字 2
seg2[3] = 9'h4f; //7段显示数字 3
seg2[4] = 9'h66; //7段显示数字 4
seg2[5] = 9'h6d; //7段显示数字 5
seg2[6] = 9'h7d; //7段显示数字 6
seg2[7] = 9'h07; //7段显示数字 7
seg2[8] = 9'h7f; //7段显示数字 8
seg2[9] = 9'h6f; //7段显示数字 9
end
·时钟分频
分出10Hz频率(0.1s)时钟。
//用于分出一个10Hz的频率
divide #(.WIDTH(32),.N(1200000)) U1 (
.clk(clk),
.rst_n(rst),
.clkout(clk10h)
);
分频模块(调用小脚丫网站示例17)
// ********************************************************************
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************
// File name : divide.v
// Module name : divide
// Author : STEP
// Description : clock divider
// Web : www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2017/03/02 |Initial ver
// --------------------------------------------------------------------
// Module Function:任意整数时钟分频
module divide ( clk,rst_n,clkout);
input clk,rst_n; //输入信号,其中clk连接到FPGA的C1脚,频率为12MHz
output clkout; //输出信号,可以连接到LED观察分频的时钟
//parameter是verilog里常数语句
parameter WIDTH = 3; //计数器的位数,计数的最大值为2**WIDTH-1
parameter N = 5; //分频系数,请确保N < 2**WIDTH-1,否则计数会溢出
reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器
reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟
//上升沿触发时计数器的控制
always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信号上升沿和下降沿
//当clk上升沿来临或者rst_n变低的时候执行一次always里的语句
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
end
//上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<(N>>1)) //N>>1表示右移一位,相当于除以2去掉余数
clk_p<=0;
else
clk_p<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
//下降沿触发时计数器的控制
always @ (negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
//下降沿触发的分频时钟输出,和clk_p相差半个时钟
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //条件判断表达式
//当N=1时,直接输出clk
//当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p
//当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与
endmodule
·判断重置信号
对重置信号rst进行判断,若有效,则保持显示数字0.0并循环。
always @(posedge clk10h or negedge rst)begin
if (!rst == 1)
begin
cnt_ge <= 4'd0;
cnt_shi <= 4'd0;
end
若重置信号无效,进入计数功能。
·状态量设置
设置开始、暂停、增量状态量。当相应按键信号有效时,状态量改为相应状态量。特别的是,对增量状态量的判断,需要增加使用增量前状态,用以判断增量状态有效时间位于时钟的位置,确保在时钟下降沿后有效再将状态量设为增量状态量,以对齐时钟,保证在0.1秒计数完成后再增加计数。若增量状态未有效,增量前状态重置(也不能有效);增量状态有效,对时钟判断决定增量前状态是否有效,两个状态均为有效时小数位计数加1。
else
begin
//开始状态设置
if (start==1'b0)
begin
cnt_flag<=1'b1;
end
//暂停状态设置
else if (stop==1'b0)
begin
cnt_flag<=1'b0;
end
//增量状态设置
else if (plus==1'b0)
begin
//确保是时钟下降沿
if(pre_plus==1'b1)
begin
plus_flag<=1'b1;
end
pre_plus<=1'b0;
end
//如果没有进行增量功能,重置增量前标志
else if (plus==1'b1)
begin
if(pre_plus==1'b0)
begin
pre_plus<=1'b1;
end
end
·计数功能
开始按键对应状态量有效时,按10Hz时钟周期进行计数。每过一个时钟周期,小数位计数从0开始加1;每次计数增加前对小数位和整数位进行判断:若小数位计数增加前数字不为9,则小数位计数+1,整数位不变;若小数位计数增加前数字为9整数位计数增加前数字不是9,则小数位置为0,整数位计数加1;若小数位计数增加前数字为9且整数位计数增加前数字也为9,则小数位和整数位均置为0,即计数满9.9清0重新开始。
//计数
if (cnt_flag==1'b1)
begin
if(cnt_ge==4'd9)
begin
cnt_ge<=4'd0;
if(cnt_shi==4'd9)
begin
cnt_shi<=4'd0;
cnt_ge<=4'd0;
end
else
begin
cnt_shi<=cnt_shi+4'd1;
end
end
else
begin
cnt_ge<=cnt_ge+4'd1;
end
end
·暂停功能
暂停按键对应状态量有效时,计数不变。暂停状态中可启用增量功能。
·增量功能
增量按键对应状态量有效时,对小数位和整数位数字进行判断,若小数位计数增加前数字不为9,则小数位计数+1,整数位不变;若小数位计数增加前数字为9整数位计数增加前数字不是9,则小数位置为0,整数位计数加1;若小数位计数增加前数字为9且整数位计数增加前数字也为9,则小数位和整数位均置为0,即计数满9.9清0重新开始。若增量前状态无效,重置增量状态。
//暂停
else
begin
//暂停时使用增量功能
if (plus_flag==1'b1)
begin
plus_flag<=1'b0;
if(cnt_ge==4'd9)
begin
cnt_ge<=4'd0;
if(cnt_shi==4'd9)
begin
cnt_shi<=4'd0;
cnt_ge<=4'd0;
end
else
begin
cnt_shi<=cnt_shi+4'd1;
end
end
else
begin
cnt_ge<=cnt_ge+4'd1;
end
end
end
end
end
·输出数字至七段显示数码管,显示相应数字。
assign seg_led_1[8:0] = {2'b00,seg2[cnt_ge]};
assign seg_led_2[8:0] = {2'b00,seg1[cnt_shi]};
五、gpt设计模块参考
module stopwatch_display(
input clk,
input start,
input pause,
input reset,
input inc,
output reg [3:0] sec_high = 0,
output reg [3:0] sec_low = 0,
output reg [6:0] display1, // 显示高位的数码管
output reg [6:0] display2 // 显示低位的数码管
);
// 数码管显示从0到9的逻辑段编码(假定共阳极配置,0为亮,1为灭)
// 顺序是 gfedcba
wire [6:0] segment_map[9:0] = {
7'b1000000, // 0
7'b1111001, // 1
7'b0100100, // 2
7'b0110000, // 3
7'b0011001, // 4
7'b0010010, // 5
7'b0000010, // 6
7'b1111000, // 7
7'b0000000, // 8
7'b0010000 // 9
};
reg [25:0] counter;
reg running = 0;
reg inc_pressed_pre = 0;
always @(posedge clk) begin
if (reset) begin
sec_high <= 0;
sec_low <= 0;
counter <= 0;
running <= 0;
end else if (start) begin
running <= 1;
end else if (pause) begin
running <= 0;
end else if (inc && !inc_pressed_pre) begin
if (sec_low < 9) begin
sec_low <= sec_low + 1;
end else begin
sec_low <= 0;
if (sec_high < 9) begin
sec_high <= sec_high + 1;
end else begin
sec_high <= 0;
end
end
end
inc_pressed_pre <= inc;
if (running) begin
if (counter >= 5000000 - 1) begin
counter <= 0;
if (sec_low < 9) begin
sec_low <= sec_low + 1;
end else begin
sec_low <= 0;
if (sec_high < 9) begin
sec_high <= sec_high + 1;
end else begin
sec_high <= 0;
end
end
end else begin
counter <= counter + 1;
end
end
// 更新数码管显示
display1 <= segment_map[sec_high];
display2 <= segment_map[sec_low];
end
endmodule
六、仿真波形图
七、FPGA资源利用说明
八、遇到的困难及解决方法
1.增量功能
起初我并未使用状态量,判断按键有效后直接进行计数/暂停/重置,但是无法实现增量功能。经与同学讨论后设置状态量,增加了状态判断分支,每次执行功能前根据状态量来确定功能。
2.如何使得不论按住增量按钮多久计数都只增加一次
设置增量前状态,在一个时钟有效沿结束并完成一次增量后增量前状态重置,增量状态也不再置为有效。
九、未来的计划
1.可以增加至更多的计数上限
2.不仅有增量功能,还有减量功能,以便增加计数加多了可以取消
3.增加倒计时功能,由拨动开关决定启用正计时还是倒计时功能