本次实验分四步走
第一步(完成)
本步骤的目标如下:
1.让FPGA上的数码管每隔1秒钟全部点亮或熄灭
2.应该看到4个“8”在闪烁
3.验证了FPGA的管脚和数码管的连接是好的
4.本阶段使用原理图的完成设计
首先需要需要设计一个模块,此模块的功能为利用50M的时钟。经过分频产生1Hz的时钟然后,利用1Hz的信号不断翻转高低电平,然后把此信号输出给七段数码管的全部引脚,实现上述功能的代码如下
module LED(
CLK_50,
LED_ON_OFF
);
input CLK_50;
output LED_ON_OFF;
reg [25:0]couter_val;
reg LED_ON_OFF;
always @(posedge CLK_50)begin
if(couter_val<50000000)begin
couter_val<=couter_val+1'b1;
LED_ON_OFF<=LED_ON_OFF;
end
else begin
couter_val<=0;
LED_ON_OFF<=~LED_ON_OFF;
end
end
endmodule
然后把此代码综合为一个器件,接着创建一个顶层文件BDF,设计顶层文件如下图所示
接着设计管教约束如下图所示
此步骤的RTL图如下图所示
最后编译下载代码第一步功能实现
第二步 (完成)
目标
1.全部的电路功能使用Verilog代码实现
2.使用“Assignments — Import Assignments”的管脚指派方法
意思就是与第一步相同,但是要全部用代码实现。
实现此功能的代码如下:
module step2(
output reg HEX0_D0,
output reg HEX0_D1,
output reg HEX0_D2,
output reg HEX0_D3,
output reg HEX0_D4,
output reg HEX0_D5,
output reg HEX0_D6,
output reg HEX0_DP,
output reg HEX1_D0,
output reg HEX1_D1,
output reg HEX1_D2,
output reg HEX1_D3,
output reg HEX1_D4,
output reg HEX1_D5,
output reg HEX1_D6,
output reg HEX1_DP,
output reg HEX2_D0,
output reg HEX2_D1,
output reg HEX2_D2,
output reg HEX2_D3,
output reg HEX2_D4,
output reg HEX2_D5,
output reg HEX2_D6,
output reg HEX2_DP,
output reg HEX3_D0,
output reg HEX3_D1,
output reg HEX3_D2,
output reg HEX3_D3,
output reg HEX3_D4,
output reg HEX3_D5,
output reg HEX3_D6,
output reg HEX3_DP,
input CLOCK_50
);reg led_ON_off;
reg [25:0]cnt;
always @(posedge CLOCK_50)
begin
if (cnt<=50_000_000)begin
led_ON_off<=led_ON_off;
cnt=cnt +1'b1;
end
else begin
led_ON_off<=~led_ON_off;
cnt=0;
end
end
always @(*)begin
HEX0_D0<=led_ON_off;
HEX0_D1<=led_ON_off;
HEX0_D2<=led_ON_off;
HEX0_D3<=led_ON_off;
HEX0_D4<=led_ON_off;
HEX0_D5<=led_ON_off;
HEX0_D6<=led_ON_off;
HEX0_DP<=led_ON_off;
HEX1_D0<=led_ON_off;
HEX1_D1<=led_ON_off;
HEX1_D2<=led_ON_off;
HEX1_D3<=led_ON_off;
HEX1_D4<=led_ON_off;
HEX1_D5<=led_ON_off;
HEX1_D6<=led_ON_off;
HEX1_DP<=led_ON_off;
HEX2_D0<=led_ON_off;
HEX2_D1<=led_ON_off;
HEX2_D2<=led_ON_off;
HEX2_D3<=led_ON_off;
HEX2_D4<=led_ON_off;
HEX2_D5<=led_ON_off;
HEX2_D6<=led_ON_off;
HEX2_DP<=led_ON_off;
HEX3_D0<=led_ON_off;
HEX3_D1<=led_ON_off;
HEX3_D2<=led_ON_off;
HEX3_D3<=led_ON_off;
HEX3_D4<=led_ON_off;
HEX3_D5<=led_ON_off;
HEX3_D6<=led_ON_off;
HEX3_DP<=led_ON_off;
end
endmodule
RTL视图如下
第三步 (完成)
目标:本阶段的设计目标为设计一个可以从0.01秒计数到99.99秒的计时器电路。
思路如下:
- 复位按键使用拨码开关,不需要去抖
- 脉冲生成模块:带工作使能的计数器
- 对输入时钟信号计数,生成四种尺度的时间脉冲信号
- 所有的时间脉冲信号都是单周期高电平有效的
- 该模块内部为4个计数器,并且有一条串联的计数器溢出进位链
- 小时间尺度计数器的进位信号被后级大时间尺度计数器作为计数使能信号
- 脉冲计数模块:带工作使能的计数器
- 对输入的高电平脉冲0~9回绕计数
- 模块内部是一个计数器
- 输入的高电平信号作为计数器的计数使能
- 计数器的计数值0~9作为模块的输出
- BCD译码模块:3-8译码器的变体
- 该模块是一个纯粹的组合逻辑
- 该模块把0~9的数字译码成为LED的段显示码
- 该模块使用CASE语句实现
代码如下图所示:
module step3(
CLK,
RST,
LED0,
LED1,
LED2,
LED3,
LED0DP,
LED1DP,
LED2DP,
LED3DP
);
output [6:0]LED0,LED1,LED2,LED3;//每一位数码管的显示
output LED0DP,LED1DP,LED2DP,LED3DP;//每一位数码管的小数点的显示
input CLK,RST;//时钟信号和复位信号
wire P0_w,P1_w,P2_w,P3_w;//进位信号
wire [3:0]cnt0_w,cnt1_w,cnt2_w,cnt3_w;//每一位数码管的计数值
pluse_gen U_PG(
.CLK(CLK),
.RST(RST),
.P0(P0_w),
.P1(P1_w),
.P2(P2_w),
.P3(P3_w)
);
pulse_counter_0to9 PC0(
.RST (RST),
.CLK (CLK),
.P_IN (P0_w),
.CNT (cnt0_w)
);
pulse_counter_0to9 PC1(
.RST (RST),
.CLK (CLK),
.P_IN (P1_w),
.CNT (cnt1_w)
);
pulse_counter_0to9 PC2(
.RST (RST),
.CLK (CLK),
.P_IN (P2_w),
.CNT (cnt2_w)
);
pulse_counter_0to9 PC3(
.RST (RST),
.CLK (CLK),
.P_IN (P3_w),
.CNT (cnt3_w)
);
BCD2LED GUAN0(
.BCD(cnt0_w),
.IN_DP(1'b0),//不显示小数点
.LED7(LED0),
.LED_DP(LED0DP)
);
BCD2LED GUAN1(
.BCD(cnt1_w),
.IN_DP(1'b0),//不显示小数点
.LED7(LED1),
.LED_DP(LED1DP)
);
BCD2LED GUAN2(
.BCD(cnt2_w),
.IN_DP(1'b1),//显示小数点
.LED7(LED2),
.LED_DP(LED2DP)
);
BCD2LED GUAN3(
.BCD(cnt3_w),
.IN_DP(1'b0),//小数点不显示
.LED7(LED3),
.LED_DP(LED3DP)
);
endmodule
//四位数码管对应的时钟产生部分
module pluse_gen(
CLK,
RST,
P0,
P1,
P2,
P3
);
input CLK,RST;
output P0,P1,P2,P3;
reg P0,P1,P2,P3;
reg [19:0]couter_1_100_R;
reg [19:0]couter_1_100_d1R;
reg [3:0]couter_1_10_R;
reg [3:0]couter_1_10_d1R;
reg [3:0]couter_1_R;
reg [3:0]couter_1_d1R;
reg [3:0]couter_10_R;
reg [3:0]couter_10_d1R;
parameter cntmax=500000-1;
//更新1/100秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1_100_R<=0;
couter_1_100_d1R<=0;
end
else begin
couter_1_100_d1R<=couter_1_100_R;//延时一个周期
if(couter_1_100_R<cntmax)
begin
couter_1_100_R<=couter_1_100_R+1'b1;
end
else begin
couter_1_100_R<=0;
end
end
end
always @(couter_1_100_R or couter_1_100_d1R)begin
if((couter_1_100_d1R==cntmax)&&(couter_1_100_R==0))
P0=1;
else
P0=0;
end
//更新1/10秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1_10_R<=0;
couter_1_10_d1R<=0;
end
else begin
couter_1_10_d1R<=couter_1_10_R;
if(P0)begin
if(couter_1_10_R<9)begin
couter_1_10_R<=couter_1_10_R+1'b1;
end
else begin
couter_1_10_R<=0;
end
end
else begin
couter_1_10_R<=couter_1_10_R;
end
end
end
always @(couter_1_10_R or couter_1_10_d1R)begin
if((couter_1_10_d1R==9)&&(couter_1_10_R==0))
P1=1;
else
P1=0;
end
//更新1秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1_R<=0;
couter_1_d1R<=0;
end
else begin
couter_1_d1R<=couter_1_R;
if(P1)begin
if(couter_1_R<9)begin
couter_1_R<=couter_1_R+1'b1;
end
else begin
couter_1_R<=0;
end
end
else begin
couter_1_R<=couter_1_R;
end
end
end
always @(couter_1_R or couter_1_d1R)begin
if((couter_1_d1R==9)&&(couter_1_R==0))
P2=1;
else
P2=0;
end
//更新10秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_10_R<=0;
couter_10_d1R<=0;
end
else begin
couter_10_d1R<=couter_10_R;
if(P2)begin
if(couter_10_R<9)begin
couter_10_R<=couter_10_R+1'b1;
end
else begin
couter_10_R<=0;
end
end
else begin
couter_10_R<=couter_10_R;
end
end
end
always @(couter_10_R or couter_10_d1R)begin
if((couter_10_d1R==9)&&(couter_10_R==0))
P3=1;
else
P3=0;
end
endmodule
//对输入脉冲0到1的跳变进行0到9的计数
module pulse_counter_0to9(
RST,
CLK,
P_IN,
CNT
);
input RST;
input CLK;
input P_IN;
output [3:0] CNT;
reg P_in_d1R,P_in_d2R;
reg [3:0]CNT;
always @(posedge CLK or posedge RST)begin
if(RST)begin
P_in_d1R<=0;
P_in_d2R<=0;
CNT<=0;
end
else begin
P_in_d1R<=P_IN;
P_in_d2R<=P_in_d1R;
if((P_in_d1R==1)&&(P_in_d2R==0))begin
if(CNT<9)
CNT<=CNT+1'b1;
else
CNT<=0;
end
else begin
CNT<=CNT ;
end
end
end
endmodule
//数码管显示部分
module BCD2LED(
BCD,
IN_DP,
LED7,
LED_DP
);
input [3:0]BCD;
input IN_DP;
output [7:0]LED7;
output LED_DP;
reg [6:0]LED7;
always @(BCD)begin
case(BCD)
0:LED7=7'b1_000_000;
1:LED7=7'b1_111_001;
2:LED7=7'b0_100_100;
3:LED7=7'b0_110_000;
4:LED7=7'b0_011_001;
5:LED7=7'b0_010_010;
6:LED7=7'b0_000_010;
7:LED7=7'b1_111_000;
8:LED7=7'b0_000_000;
9:LED7=7'b0_010_000;
default: LED7=7'b1_111_111;
endcase
end
assign LED_DP=~IN_DP;
endmodule
管角配置如下图所示:
生成的RTL视图如下图所示:
第四步(完成)
在3的基础上实现:
- 上电的自动复位,上电后自动生成复位信号,把所有模块复位
- 添加一个暂停/继续的功能按钮,需要去抖
- 添加一个计数清零后暂停的功能按钮,需要去抖
思路:
- 该信号状态为运行时,正常生成计时脉冲
- 该信号状态为暂停时,内部电路进入保持状态,不生成计时脉冲
- 本模块不生成计时脉冲,则后级各个脉冲计数器会进入计数保持状态
- 该信号实际上是各个计数器的工作使能信号
-
给脉冲生成模块和各个LED的计数模块添加清零信号
- 清零信号有效时,模块内部的计数器的D触发器归零
按键去抖模块的的使用方法:
- 使用一个溢出时间为10ms左右的计数器,溢出后再从0计数
- 在一次计数周期的过程中对按键输入值进行多次检测
- 若检测值均相同则说明是稳定键值并输出检测值,否则保持原输出值
利用状态机实现控制功能
- 控制模块根据按键的输入值来生成2个信号
- 运行/暂停信号: 信号位宽1比特,标识运行和暂停2种状态
- 运行态:使后级电路正常工作
- 暂停态:使后级电路进入HOLD状态
- 清零信号 :信号位宽1比特,标识清零有效和无效2种状态
- 清零有效:让后级电路计数信息归零
最终实现代码如下所示
module step3(
CLK,
BTN_CZ,//BUTTON按钮用于,用于启动暂停的控制
BTN_RS,//buton每次按下后计数清零并暂停
P0,
P1,
P2,
P3,
LED0,
LED1,
LED2,
LED3,
LED0DP,
LED1DP,
LED2DP,
LED3DP
);
output [6:0]LED0,LED1,LED2,LED3;//数码管
output LED0DP,LED1DP,LED2DP,LED3DP;//小数点
output P0,P1,P2,P3;//进位信号
input CLK,BTN_RS,BTN_CZ;//设置两个按键,一个按键用来控制暂停和启动,另外一个按键用来控制清零,此按键信号未经过消抖
wire P0_w,P1_w,P2_w,P3_w;//使能信号
wire rst_W;//复位
wire btn_rs_out_W,r1s0_W;
wire btn_cz_out_W,cz_W;
wire [3:0]cnt0_w,cnt1_w,cnt2_w,cnt3_w;//每位数码管的计数值
assign P0=P0_w;
assign P1=P1_w;
assign P2=P2_w;
assign P3=P3_w;
//上电自动复位模块
powerup_reset PUP_RST(
.CLK (CLK),
.RSTOUT (rst_W),
);
//按键消抖模块
button_in_out U_BTN_RS(
.CLK(CLK),
.IN(BTN_RS),
.OUT(btn_rs_out_W),//输出经过消抖的暂停启动·信号
);
//按键去抖模块
button_in_out U_BTN_CZ(
.CLK(CLK),
.IN(BTN_CZ),
.OUT(btn_cz_out_W),//输出经过消抖的清零信号
);
//生成控制模块
control U_CTL(
.CLK(CLK),
.RST(rst_W),
.BTN_RS(btn_rs_out_W),
.BTN_CZ(btn_cz_out_W),
.R1S0(r1s0_W),
.CZ(cz_W)
);
pluse_gen U_PG(
.CLK(CLK),
.RST(rst_W),
.CZ(cz_W),
.R1S0(r1s0_W),
.P0(P0_w),
.P1(P1_w),
.P2(P2_w),
.P3(P3_w)
);
pulse_counter_0to9 PC0(
.RST (rst_W),
.CLK (CLK),
.CZ(cz_W),
.P_IN (P0_w),
.CNT (cnt0_w)
);
pulse_counter_0to9 PC1(
.RST (rst_W),
.CLK (CLK),
.CZ(cz_W),
.P_IN (P1_w),
.CNT (cnt1_w)
);
pulse_counter_0to9 PC2(
.RST (rst_W),
.CLK (CLK),
.CZ(cz_W),
.P_IN (P2_w),
.CNT (cnt2_w)
);
pulse_counter_0to9 PC3(
.RST (rst_W),
.CLK (CLK),
.CZ(cz_W),
.P_IN (P3_w),
.CNT (cnt3_w)
);
BCD2LED GUAN0(
.BCD(cnt0_w),
.IN_DP(1'b0),
.LED7(LED0),
.LED_DP(LED0DP)
);
BCD2LED GUAN1(
.BCD(cnt1_w),
.IN_DP(1'b0),
.LED7(LED1),
.LED_DP(LED1DP)
);
BCD2LED GUAN2(
.BCD(cnt2_w),
.IN_DP(1'b1),
.LED7(LED2),
.LED_DP(LED2DP)
);
BCD2LED GUAN3(
.BCD(cnt3_w),
.IN_DP(1'b0),
.LED7(LED3),
.LED_DP(LED3DP)
);
endmodule
module pluse_gen(
CLK,
RST,
R1S0,
CZ,
P0,
P1,
P2,
P3
);
input CLK,RST;
input R1S0,CZ;
output P0,P1,P2,P3;
reg P0,P1,P2,P3;
reg [19:0]couter_1_100_R;
reg [19:0]couter_1_100_d1R;
reg [3:0]couter_1_10_R;
reg [3:0]couter_1_10_d1R;
reg [3:0]couter_1_R;
reg [3:0]couter_1_d1R;
reg [3:0]couter_10_R;
reg [3:0]couter_10_d1R;
parameter cntmax=500000-1;
//更新1/100秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1_100_R<=0;
couter_1_100_d1R<=0;
end
else begin
if(CZ)begin// 同步清零有效
couter_1_100_R<=0;
couter_1_100_d1R<=0;
end
else begin// CZ 同步清零无效
if(R1S0)begin
couter_1_100_d1R<=couter_1_100_R;
if(couter_1_100_R<cntmax)
begin
couter_1_100_R<=couter_1_100_R+1'b1;
end
else begin
couter_1_100_R<=0;
end// else counter_1_100_R
end// if R1S0
else // R1S0
couter_1_100_R<=0;
end// else CZ
end// else RST
end// always
// 组合逻辑 根据计数值生成 1/100秒脉冲, 脉冲宽度1个时钟周期 20ns
always @(couter_1_100_R or couter_1_100_d1R)begin
// 判断计数值的跳变,从最大计数值变成0的周期,输出高脉冲
if((couter_1_100_d1R==cntmax)&&(couter_1_100_R==0))
P0=1;
else
P0=0;
end
//更新1/10秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1_10_R<=0;
couter_1_10_d1R<=0;
end
else begin
if(CZ)begin
couter_1_10_R<=0;
couter_1_10_d1R<=0;
end
else begin
couter_1_10_d1R<=couter_1_10_R;
if(P0)begin
if(couter_1_10_R<9)begin
couter_1_10_R<=couter_1_10_R+1'b1;
end
else begin
couter_1_10_R<=0;
end
end
else begin
couter_1_10_R<=couter_1_10_R;
end
end
end
end
always @(couter_1_10_R or couter_1_10_d1R)begin
if((couter_1_10_d1R==9)&&(couter_1_10_R==0))
P1=1;
else
P1=0;
end
//更新1秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1_R<=0;
couter_1_d1R<=0;
end
else begin
if(CZ)begin
couter_1_R<=0;
couter_1_d1R<=0;
end
else begin
couter_1_d1R<=couter_1_R;
if(P1)begin
if(couter_1_R<9)begin
couter_1_R<=couter_1_R+1'b1;
end
else begin
couter_1_R<=0;
end
end
else begin
couter_1_R<=couter_1_R;
end
end
end
end
always @(couter_1_R or couter_1_d1R)begin
if((couter_1_d1R==9)&&(couter_1_R==0))
P2=1;
else
P2=0;
end
//更新10秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_10_R<=0;
couter_10_d1R<=0;
end
else begin
if(CZ)begin
couter_10_R<=0;
couter_10_d1R<=0;
end
else begin
couter_10_d1R<=couter_10_R;
if(P2)begin
if(couter_10_R<9)begin
couter_10_R<=couter_10_R+1'b1;
end
else begin
couter_10_R<=0;
end
end
else begin
couter_10_R<=couter_10_R;
end
end
end
end
always @(couter_10_R or couter_10_d1R)begin
if((couter_10_d1R==9)&&(couter_10_R==0))
P3=1;
else
P3=0;
end
endmodule
//对输入脉冲0到1的跳变进行0到9的计数
module pulse_counter_0to9(
RST,
CLK,
CZ,
P_IN,
CNT
);
input RST;
input CLK;
input P_IN,CZ;
output [3:0] CNT;
reg P_in_d1R,P_in_d2R;
reg [3:0]CNT;
always @(posedge CLK or posedge RST)begin
if(RST)begin
P_in_d1R<=0;
P_in_d2R<=0;
CNT<=0;
end
else begin
P_in_d1R<=P_IN;
P_in_d2R<=P_in_d1R;
if(CZ)begin
CNT<=0;
end
else begin
if((P_in_d1R==1)&&(P_in_d2R==0))begin
if(CNT<9)
CNT<=CNT+1'b1;
else
CNT<=0;
end
else begin
CNT<=CNT ;
end
end
end
end
endmodule
module BCD2LED(
BCD,
IN_DP,
LED7,
LED_DP
);
input [3:0]BCD;
input IN_DP;
output [7:0]LED7;
output LED_DP;
reg [6:0]LED7;
always @(BCD)begin
case(BCD)
0:LED7=7'b1_000_000;
1:LED7=7'b1_111_001;
2:LED7=7'b0_100_100;
3:LED7=7'b0_110_000;
4:LED7=7'b0_011_001;
5:LED7=7'b0_010_010;
6:LED7=7'b0_000_010;
7:LED7=7'b1_111_000;
8:LED7=7'b0_000_000;
9:LED7=7'b0_010_000;
default: LED7=7'b1_111_111;
endcase
end
assign LED_DP=~IN_DP;
endmodule
//按键消抖模块
module button_in_out(
CLK,
IN,
OUT
);
parameter CNT_WL=20;
parameter CNT_MAX=20'h7A120;
parameter CNT_MAX68=(CNT_MAX>>3)*6;
parameter CNT_MAX78=(CNT_MAX>>3)*7;
input CLK,IN;
output OUT;
reg button_in_d1R;
reg button_in_d2R;
reg [CNT_WL-1:0]counterR;
reg buttonIn0R,buttonIn1R,buttonIn2R,buttonIn3R,buttonOutR;
assign OUT=buttonOutR;
always @ (posedge CLK)begin
button_in_d1R<=IN;
button_in_d2R<=button_in_d1R;
end
always @(posedge CLK)begin
if(counterR==0)
buttonIn0R<=button_in_d2R;
else
buttonIn0R<=buttonIn0R;
if(counterR==CNT_MAX68)
buttonIn1R<=button_in_d2R;
else
buttonIn1R<=buttonIn1R;
if(counterR==CNT_MAX78)
buttonIn2R<=button_in_d2R;
else
buttonIn2R<=buttonIn2R;
if(counterR==CNT_MAX)
buttonIn3R<=button_in_d2R;
else
buttonIn3R<=buttonIn3R;
end
always@(posedge CLK)begin
if(counterR<CNT_MAX)
counterR<=counterR+1;
else
counterR<=0;
end
always @(posedge CLK)begin
if(buttonIn0R==buttonIn1R==buttonIn2R==buttonIn3R)
buttonOutR<=buttonIn3R;
else
buttonOutR<=buttonOutR;
end
endmodule
//上电生成自动复位模块,用于复位其他电路模块
module powerup_reset(
CLK,
RSTOUT,
);
input CLK;
output RSTOUT;
reg RSTOUT;
//时钟周期50ns,经过实验测定,复位时间为1/10;如果时间周期过短不起作用,猜测和FPGA的加载时间以及电路板的设计有关
parameter CNTMAX=5000000 - 1;
reg [31:0]counterR;
always @(posedge CLK)begin
if(counterR<CNTMAX)begin
counterR<=counterR+1'b1;
end
else begin
counterR<=CNTMAX;
end
end
always @(counterR)begin
if(counterR<CNTMAX)
RSTOUT=1'b1;
else
RSTOUT=1'b0;
end
endmodule
module control(
CLK,
RST,
BTN_CZ, // 输入按键信号,控制清零后STOP
BTN_RS,// 输入按键信号,控制RUN和STOP
R1S0, // 输出控制信号 1 RUN 0 STOP
CZ// 输出控制信号 1 计数器清零
);
input CLK,RST;
input BTN_RS,BTN_CZ;
output R1S0,CZ;
reg btn_rs_d1R,btn_rs_d2R;
reg btn_rs_tick; // 每次按键按下抬起,该信号上出现一个单时钟周期高电平
reg btn_cz_d1R,btn_cz_d2R;
reg btn_cz_tick; // 每次按键按下抬起,该信号上出现一个单时钟周期高电平
reg [3:0]state_R,next_state;
reg R1S0,CZ;
parameter BTN_PUSH=0;// 按键按下的信号值
parameter BTN_RELEASE=1; // 按键松开的信号值
// 时序逻辑,更新按键输入的两级触发器
always @(posedge CLK or posedge RST )begin
if(RST)begin
btn_rs_d1R<=BTN_RELEASE;
btn_rs_d2R<=BTN_RELEASE;
btn_cz_d1R<=BTN_RELEASE;
btn_cz_d2R<=BTN_RELEASE;
end
else begin
btn_rs_d1R<=BTN_RS;
btn_rs_d2R<=btn_rs_d1R;
btn_cz_d1R<=BTN_CZ;
btn_cz_d2R<=btn_cz_d1R;
end
end
// 组合逻辑,捕捉按键信号的按下和抬起,生成 btn_rs_tick
always @ (btn_rs_d1R or btn_rs_d2R)begin
if((btn_rs_d2R==BTN_RELEASE)&&(btn_rs_d1R==BTN_PUSH))begin
btn_rs_tick=1;
end
else begin
btn_rs_tick=0;
end
if((btn_cz_d2R==BTN_RELEASE)&&(btn_cz_d1R==BTN_PUSH))begin
btn_cz_tick=1;
end
else begin
btn_cz_tick=0;
end
end
parameter STATE_RUN=2'b00;
parameter STATE_STOP_HOLD=2'b01;
parameter STATE_STOP_ZERO=2'b11;
// 3段状态机,用于更新状态,生成控制信号
// 输入信号 btn_rs_tick,输出信号R1S0
// 段1,根据输入信号和现状态,生成次状态
always @(state_R or btn_rs_tick or btn_cz_tick)begin
case(state_R)
STATE_RUN:
if(btn_cz_tick)
next_state=STATE_STOP_ZERO;
else
if(btn_rs_tick)
next_state=STATE_STOP_HOLD;
else
next_state=STATE_RUN;
STATE_STOP_HOLD:
if(btn_cz_tick)
next_state=STATE_STOP_ZERO;
else
if(btn_rs_tick)
next_state=STATE_RUN;
else
next_state=STATE_STOP_HOLD;
STATE_STOP_ZERO:
if(btn_cz_tick)
next_state=STATE_STOP_ZERO;
else
if(btn_rs_tick)
next_state=STATE_RUN;
else
next_state=STATE_STOP_ZERO;
default:
next_state=STATE_STOP_HOLD;//非法状态说明电路出现故障
endcase
end
// 复位后直接运行计时
always @(posedge CLK or posedge RST)begin
if(RST)
state_R<=STATE_RUN;
else
state_R<=next_state;
end
// 段3 根据现状态生成输出控制信号
always @(state_R)begin
case(state_R)
STATE_RUN:begin
R1S0=1'b1;
CZ=1'b0;
end
STATE_STOP_HOLD :begin
R1S0=1'b0;
CZ=1'b0;
end
STATE_STOP_ZERO :begin
R1S0=1'b0;
CZ=1'b1;
end
default:begin
R1S0=1'b0;
CZ=1'b0;
end
endcase
end
endmodule
生成的RTL图如下图所示:
控制模块RTL视图:
状态机的内部结构转移图如下图所示:
升级实验
- 最小显示精度为1秒,60进制,从0计时到59分59秒
- 使用拨码开关和按键,能够设置时间的功能,
- * 采用4个拨码开关,任何一个开关拨到上方时,对应的数码管开始闪烁
- * 闪烁的数码管应当仍能正常更新计时的显示值
- * 采用“增加”和“减少”2个按键 * 每按下“增加”按键一次,闪烁的数码的显示值加1,并且以0-60回绕 * 每按下“减少”按键一次,闪烁的数码的显示值减1,并且以0-60回绕
设计参加代码如下:此代码经过初步调试没有BUG,后期若读者发现存在BUG请在评论区指出,我加以修正
module step3(
CLK,
P0,
P1,
P2,
P3,
LED0,
LED1,
LED2,
LED3,
LED0DP,
LED1DP,
LED2DP,
LED3DP,
buton_add,
buton_down,
en
);
output [6:0]LED0,LED1,LED2,LED3;//数码管
output LED0DP,LED1DP,LED2DP,LED3DP;//小数点
output P0,P1,P2,P3;//进位信号
input buton_add;//设置按键加
input buton_down;//设置按键减
input CLK;//整个系统的所有时钟
input [4-1:0]en;//设置四位拨码开关,对应控制四位数码管闪烁
wire rst_W;//复位
wire P0_w,P1_w,P2_w,P3_w;//使能信号
wire [3:0]cnt0_w,cnt1_w,cnt2_w,cnt3_w;//每位数码管的计数值
wire btn_out_add;//按键加经过消抖输出
wire btn_out_down;//按键减经过消抖输出
wire [4-1:0]en;
assign P0=P0_w;//秒脉冲
assign P1=P1_w;//十秒脉冲
assign P2=P2_w;//分脉冲
assign P3=P3_w;//十分脉冲
button_in_out U_BTN_ADD(
.CLK(CLK),
.IN(buton_add),
.OUT(btn_out_add),//输出经过消抖的信号
);
//按键去抖模块
button_in_out U_BTN_DOWN(
.CLK(CLK),
.IN(buton_down),
.OUT(btn_out_down),//输出经过消抖的信号
);
//复位模块
powerup_reset PUP_RST(
.CLK (CLK),
.RSTOUT (rst_W),
);
//秒冲生成模块
pluse_gen U_PG(
.CLK(CLK),
.RST(rst_W),
.P0(P0_w),
.P1(P1_w),
.P2(P2_w),
.P3(P3_w)
);
//秒计数模块
pulse_counter_0to9 PC0(
.RST (rst_W),
.CLK (CLK),
.P_IN (P0_w),
.BUTON_ADD(btn_out_add),
.BUTON_DOWN(btn_out_down),
.en(en[0]),
.CNT (cnt0_w)
);
//十秒计数模块
pulse_counter_0to6 PC1(
.RST (rst_W),
.CLK (CLK),
.P_IN (P1_w),
.BUTON_ADD(btn_out_add),
.BUTON_DOWN(btn_out_down),
.en(en[1]),
.CNT (cnt1_w)
);
//分计数模块
pulse_counter_0to9 PC2(
.RST (rst_W),
.CLK (CLK),
.P_IN (P2_w),
.BUTON_ADD(btn_out_add),
.BUTON_DOWN(btn_out_down),
.en(en[2]),
.CNT(cnt2_w)
);
//十分计数模块
pulse_counter_0to6 PC3(
.RST (rst_W),
.CLK (CLK),
.P_IN (P3_w),
.BUTON_ADD(btn_out_add),
.BUTON_DOWN(btn_out_down),
.en(en[3]),
.CNT (cnt3_w)
);
//秒位实时显示模块
BCD2LED GUAN0(
.CLK (CLK),
.BCD(cnt0_w),
.IN_DP(1'b0),
.LED7(LED0),
.EN(en[0]),
.LED_DP(LED0DP)
);
//十秒位实时显示
BCD2LED GUAN1(
.CLK (CLK),
.BCD(cnt1_w),
.IN_DP(1'b0),
.LED7(LED1),
.EN(en[1]),
.LED_DP(LED1DP)
);
//分实时显示
BCD2LED GUAN2(
.CLK (CLK),
.BCD(cnt2_w),
.IN_DP(1'b1),
.LED7(LED2),
.EN(en[2]),
.LED_DP(LED2DP)
);
//十分实时显示
BCD2LED GUAN3(
.CLK (CLK),
.BCD(cnt3_w),
.IN_DP(1'b0),
.LED7(LED3),
.EN(en[3]),
.LED_DP(LED3DP)
);
endmodule
//计数脉冲生成模块
module pluse_gen(
CLK,
RST,
P0,
P1,
P2,
P3
);
input CLK,RST;
output P0,P1,P2,P3;
reg P0,P1,P2,P3;
reg [27:0]couter_1_R;
reg [27:0]couter_1_d1R;
reg [3:0]couter_10_R;
reg [3:0]couter_10_d1R;
reg [3:0]couter_1m_R;
reg [3:0]couter_1m_d1R;
reg [3:0]couter_10m_R;
reg [3:0]couter_10m_d1R;
parameter cntmax=50000000-1;
//更新1秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1_R<=0;
couter_1_d1R<=0;
end
else begin
couter_1_d1R<=couter_1_R;
if(couter_1_R<cntmax)
begin
couter_1_R<=couter_1_R+1'b1;
end
else begin
couter_1_R<=0;
end// else counter_1_100_R
end// else RST
end// always
// 组合逻辑 根据计数值生成10秒脉冲, 脉冲宽度1个时钟周期 20ns
always @(couter_1_R or couter_1_d1R)begin
// 判断计数值的跳变,从最大计数值变成0的周期,输出高脉冲
if((couter_1_d1R==cntmax)&&(couter_1_R==0))
P0=1;
else
P0=0;
end
//更新10秒脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_10_R<=0;
couter_10_d1R<=0;
end
else begin
couter_10_d1R<=couter_10_R;
if(P0)begin
if(couter_10_R<9)begin
couter_10_R<=couter_10_R+1'b1;
end
else begin
couter_10_R<=0;
end
end
else begin
couter_10_R<=couter_10_R;
end
end
end
always @(couter_10_R or couter_10_d1R)begin
if((couter_10_d1R==9)&&(couter_10_R==0))
P1=1;
else
P1=0;
end
//更新分脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_1m_R<=0;
couter_1m_d1R<=0;
end
else begin
couter_1m_d1R<=couter_1m_R;
if(P1)begin
if(couter_1m_R<5)begin
couter_1m_R<=couter_1m_R+1'b1;
end
else begin
couter_1m_R<=0;
end
end
else begin
couter_1m_R<=couter_1m_R;
end
end
end
always @(couter_1m_R or couter_1m_d1R)begin
if((couter_1m_d1R==5)&&(couter_1m_R==0))
P2=1;
else
P2=0;
end
//更新10分脉冲
always @(posedge CLK or posedge RST)begin
if(RST)begin
couter_10m_R<=0;
couter_10m_d1R<=0;
end
else begin
couter_10m_d1R<=couter_10m_R;
if(P2)begin
if(couter_10m_R<9)begin
couter_10m_R<=couter_10m_R+1'b1;
end
else begin
couter_10m_R<=0;
end
end
else begin
couter_10m_R<=couter_10m_R;
end
end
end
always @(couter_10m_R or couter_10m_d1R)begin
if((couter_10m_d1R==9)&&(couter_10m_R==0))
P3=1;
else
P3=0;
end
endmodule
//对输入脉冲0到1的跳变进行0到9的计数
module pulse_counter_0to9(
RST,//复位
CLK,//时钟
P_IN,//
en,//拨码开关
BUTON_ADD,//按键加
BUTON_DOWN,//按键减
CNT//计数器
);
input RST;
input CLK;
input P_IN;
input BUTON_ADD;
input BUTON_DOWN;
input en;
output [3:0] CNT;
reg P_in_d1R,P_in_d2R;
reg [3:0] CNT;
reg temp0;
reg temp1;
reg temp2;
reg temp3;
wire BUTON_ADD;
wire BUTON_DOWN;
wire pe_add;
wire pe_down;
assign pe_add=temp0&(~temp1);
assign pe_down=temp2&(~temp3);
always @(posedge CLK)begin
temp0<=BUTON_ADD;
temp1<=temp0;
end
always @(posedge CLK)begin
temp2<=BUTON_DOWN;
temp3<=temp2;
end
always @(posedge CLK or posedge RST)begin
if(RST)begin
P_in_d1R<=0;
P_in_d2R<=0;
CNT<=0;
end
else if((pe_add==1)&&(en==1))begin//捕捉到加按键的下降沿,并且对应位的拨码开关有效,则对应的加操作
if(CNT<9)begin
CNT<=CNT+1;
end
else begin
CNT<=0;
end
end
else if((pe_down==1)&&(en==1))begin//捕捉减按键的下降沿,并且有对应位的拨码开关有效则执行相应的减操作
if((CNT<=9)&&(CNT>0))begin
CNT<=CNT-1;
end
else if(CNT==0)begin
CNT<=9;
end
else begin
CNT<=0;
end
end
else begin//如果都没有捕捉到有按键按下则进行计数显示
P_in_d1R<=P_IN;
P_in_d2R<=P_in_d1R;
if((P_in_d1R==1)&&(P_in_d2R==0))begin
if(CNT<9)
CNT<=CNT+1;
else
CNT<=0;
end
else begin
CNT<=CNT;
end
end
end
endmodule
//对输入进行0-6的计数
module pulse_counter_0to6(
RST,//复位
CLK,//时钟
P_IN,//
BUTON_ADD,//按键加
BUTON_DOWN,//按键减
en,//使能
CNT//计数值
);
input RST;
input CLK;
input P_IN;
input BUTON_ADD;
input BUTON_DOWN;
output [3:0] CNT;
input en;
reg P_in_d1R,P_in_d2R;
reg [3:0]CNT;
reg temp0;
reg temp1;
reg temp2;
reg temp3;
wire BUTON_ADD;
wire BUTON_DOWN;
wire pe_add;
wire pe_down;
assign pe_add=temp0&(~temp1);//捕捉加按键的下降沿
assign pe_down=temp2&(~temp3);//捕捉减按键的下降沿
//缓存按键加的两个状态
always @(posedge CLK)begin
temp0<=BUTON_ADD;
temp1<=temp0;
end
//缓存按键减的两个状态
always @(posedge CLK)begin
temp2<=BUTON_DOWN;
temp3<=temp2;
end
always @(posedge CLK or posedge RST)begin
if(RST)begin
P_in_d1R<=0;
P_in_d2R<=0;
CNT<=0;
end
else if((pe_add==1)&&(en==1))begin//捕捉到加按键的下降沿,并且对应位的拨码开关有效,则对应的加操作
if(CNT<5)begin
CNT<=CNT+1;
end
else begin
CNT<=0;
end
end
else if((pe_down==1)&&(en==1))begin//捕捉减按键的下降沿,并且有对应位的拨码开关有效则执行相应的减操作
if((CNT<=5)&&(CNT>0))begin
CNT<=CNT-1;
end
else if(CNT==0)begin
CNT<=5;
end
else begin
CNT<=0;
end
end
else begin//如果都没有捕捉到有按键按下则进行计数显示
P_in_d1R<=P_IN;
P_in_d2R<=P_in_d1R;
if((P_in_d1R==1)&&(P_in_d2R==0))begin
if(CNT<5)
CNT<=CNT+1'b1;
else
CNT<=0;
end
else begin
CNT<=CNT;
end
end
end
endmodule
// 对表示十进制数据的BCD码进行译码,生成LED的段显示信息
// DE0 电路板的 LED 段,该LED已经连接了VCC,FPGA管脚的逻辑0
// 将会电路对应的LED段
// HEX0_D0
// ########
// # #
// HEX0_D5 # # HEX0_D1
// # HEX0_D6#
// ########
// # #
// HEX0_D4 # # HEX0_D2
// # #
// ######## # HEX0_DP
// HEX0_D3
module BCD2LED(
CLK,
BCD, //输入的BCD码
IN_DP, //输入小数点控制, 1 表示点亮小数点
LED7,//输出的LED 7段显示
LED_DP,//输出的LED 小数点, 0 会点亮小数点
EN
);
input CLK;
input [3:0]BCD;
input IN_DP;
input EN;
output [7:0]LED7;
output LED_DP;
reg [6:0]LED7;
reg [30:0]cnt=0;
parameter sex=25000000;
reg flag=0;
wire EN;
always @(posedge CLK)begin//
if(cnt<sex)begin
cnt<=cnt+1'b1;
flag<=flag;
end
else begin
cnt=0;
flag<=~flag;
end
end
always @(BCD)begin
if(EN&&flag)begin
LED7=7'b1_111_111;//只有按按键使能和标志位为1才每隔一秒闪烁一次
end
else begin//其余时间正常显示
case(BCD)
0:LED7=7'b1_000_000;
1:LED7=7'b1_111_001;
2:LED7=7'b0_100_100;
3:LED7=7'b0_110_000;
4:LED7=7'b0_011_001;
5:LED7=7'b0_010_010;
6:LED7=7'b0_000_010;
7:LED7=7'b1_111_000;
8:LED7=7'b0_000_000;
9:LED7=7'b0_010_000;
default: LED7=7'b1_111_111;
endcase
end
end
assign LED_DP=~IN_DP;
endmodule
// 按键去抖原理:使用计数器,计数器每隔20毫秒会达到最大值然后溢出,在计数器达到
// 溢出值的 8/8, 7/8, 6/8, 0/8 时刻对按键的值进行采样,如果全部采样值相同则说明
// 没有抖动发生输出采样值,否则认为发生了抖动,输出值保持不变
// 时间轴方向 ---------------------------------------------->
// 按键采样值 :v0, v1, v2 v3
// 计数器值 :0, 1, 2, .... 6/8 max, 7/8 max, max
// |<------计数器溢出时间 10ms------->|
//
// 模块的重用,根据时钟速率,合理配置计数器的溢出值,使得溢出时间为10毫秒
module button_in_out(
CLK,
IN,
OUT,
);
// 合理配置计数器溢出值,10到20毫秒溢出一次
// 当前时钟周期,20ns,溢出值为 500,000 = 0x7A120,每10毫秒溢出一次
parameter CNT_WL=20;
parameter CNT_MAX=20'h7A120;
parameter CNT_MAX68=(CNT_MAX>>3)*6;
parameter CNT_MAX78=(CNT_MAX>>3)*7;
input CLK,IN;
output OUT;
reg button_in_d1R;
reg button_in_d2R;
reg [CNT_WL-1:0]counterR;
reg buttonIn0R,buttonIn1R,buttonIn2R,buttonIn3R,buttonOutR;
assign OUT=buttonOutR;
// 输入信号跨时钟域,使用2个级联的D触发器把按键信号引导进入
// FPGA的时钟域,避免采样到按键信号的跳变边沿
always @ (posedge CLK)begin
button_in_d1R<=IN;
button_in_d2R<=button_in_d1R;
end
always @(posedge CLK)begin
if(counterR==0)
buttonIn0R<=button_in_d2R;
else
buttonIn0R<=buttonIn0R;
if(counterR==CNT_MAX68)
buttonIn1R<=button_in_d2R;
else
buttonIn1R<=buttonIn1R;
if(counterR==CNT_MAX78)
buttonIn2R<=button_in_d2R;
else
buttonIn2R<=buttonIn2R;
if(counterR==CNT_MAX)
buttonIn3R<=button_in_d2R;
else
buttonIn3R<=buttonIn3R;
end
always@(posedge CLK)begin
if(counterR<CNT_MAX)
counterR<=counterR+1;
else
counterR<=0;
end
always @(posedge CLK)begin
if(buttonIn0R==buttonIn1R==buttonIn2R==buttonIn3R)
buttonOutR<=buttonIn3R;
else
buttonOutR<=buttonOutR;
end
endmodule
//上电生成自动复位模块,用于复位其他电路模块
module powerup_reset(
CLK,
RSTOUT,
);
input CLK;
output RSTOUT;
reg RSTOUT;
//时钟周期50ns,经过实验测定,复位时间为1/10;如果时间周期过短不起作用,猜测和FPGA的加载时间以及电路板的设计有关
parameter CNTMAX=5000000 - 1;//0.1秒内完成复位
reg [31:0]counterR;
always @(posedge CLK)begin
if(counterR<CNTMAX)begin
counterR<=counterR+1'b1;
end
else begin
counterR<=CNTMAX;
end
end
always @(counterR)begin
if(counterR<CNTMAX)
RSTOUT=1'b1; // 输出复位有效
else
RSTOUT=1'b0; // 输出复位无效
end
endmodule
引脚约束如下图所示:
RTL视图如下图所示:
声明:由于作者水平有限,如果代码出现BUG请在评论区及时指出,作者将及时修正,大家一起学习,共同进步。