目录
项目设计按键点灯。要求:(14分)
方法一(推荐)
绘制模块框图及波形图
编写模块代码
上板验证
方法二
绘制模块框图及波形图
编写模块代码
上板验证
总结
项目设计按键点灯。要求:(14分)
(注小灯均为4个灯,共有A、B、C三个按键,有效按下不分先后。小灯效果可以达成条件立即执行也可3s后统一执行,不做强制要求。3s有效区间不会随着有按键按下重新计时。最终改卷以实际上板效果为主,需完成上板前的所有准备工作)。
方法一(推荐)
绘制模块框图及波形图

按键消抖、关灯、开灯、流水灯、闪光灯与4选1的多路选择器,六个模块比较简单,这里波形图就不在绘制了,想看波形图的可以去看我之前的文章。下面绘制的是fsm模块的波形图。

这里的flag_A、flag_B、flag_C代表在3s内,A、B、C按下时为高电平,3s到了均为低电平。
在此基础上我加了一个静态数码管显示,循环显示1、2、3,每隔1s跳一次,为了方便按按键。


编写模块代码
顶层模块key_led
module key_led(
input wire clk ,
input wire rst_n ,
input wire A ,
input wire B ,
input wire C ,
output wire [3:0] led ,
output wire ds ,
output wire shcp ,
output wire stcp ,
output wire oe
);
wire key_flag_A;
wire key_flag_B;
wire key_flag_C;
wire [1:0] sel;
wire [3:0] in1;
wire [3:0] in2;
wire [3:0] in3;
wire [3:0] in4;
key_XD
#(
.CNT_MAX ( 32'd999_999 )//20ms
)
key_XD_A
(
.clk (clk ),
.rst_n (rst_n ),
.key_in (A ),
.key_flag(key_flag_A)
);
key_XD
#(
.CNT_MAX ( 32'd999_999 )//20ms
)
key_XD_B
(
.clk (clk ),
.rst_n (rst_n ),
.key_in (B ),
.key_flag(key_flag_B)
);
key_XD
#(
.CNT_MAX ( 32'd999_999 )//20ms
)
key_XD_C
(
.clk (clk ),
.rst_n (rst_n ),
.key_in (C ),
.key_flag(key_flag_C)
);
fsm
#(
.TIME_3S ( 32'd2 ),
.TIME_1S ( 32'd1 ),
.TIME_500MS ( 32'd24_999_999)
)
fsm_0
(
.clk (clk ) ,
.rst_n (rst_n) ,
.A (key_flag_A) ,
.B (key_flag_B) ,
.C (key_flag_C) ,
.sel (sel)
);
SGD
#(
.CNT_MAX ( 32'd24_999_999 )
)
SGD_0
(
.clk (clk ) ,
.rst_n (rst_n) ,
.led (in4)
);
water_led
#(
.CNT_MAX_1S ( 32'd49_999_999)//1s
)
water_led_0
(
.clk (clk ) ,
.rst_n (rst_n) ,
.led (in3)
);
open_led open_led_0(
.clk (clk ) ,
.rst_n (rst_n) ,
.led (in2)
);
close_led close_led_0(
.clk (clk ) ,
.rst_n (rst_n) ,
.led (in1)
);
mux_4 mux_4_inst(
.in_1 (in1) ,
.in_2 (in2) ,
.in_3 (in3) ,
.in_4 (in4) ,
.sel (sel) ,
.led (led)
);
seg_595_static seg_595_static_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.ds (ds ) ,
.shcp (shcp ) ,
.stcp (stcp ) ,
.oe (oe )
);
endmodule
功能模块key_XD
module key_XD
#(
parameter CNT_MAX = 32'd999_999
)
(
input wire clk ,
input wire rst_n ,
input wire key_in ,
output reg key_flag
);
reg [31:0] cnt;
//cnt:低电平计数器
always @ (posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 32'd0;
else if(key_in)//高电平
cnt <= 32'd0;
else if(cnt == CNT_MAX)//低电平且计数满
cnt <= cnt;
else//低电平且计数未满
cnt <= cnt + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_998时拉高,维持一个时钟的高电平
always @ (posedge clk or negedge rst_n)
if(!rst_n)
key_flag <= 1'b0;
else if(cnt == CNT_MAX – 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
功能模块fsm
module fsm
#(
parameter TIME_3S = 32'd2 ,
parameter TIME_1S = 32'd1 ,
parameter TIME_500MS = 32'd24_999_999
)
(
input wire clk ,
input wire rst_n ,
input wire A ,
input wire B ,
input wire C ,
output reg [1:0] sel
);
reg [31:0] cnt_3s;
reg [31:0] cnt_1s;
reg [31:0] cnt_500ms;
reg flag_A;
reg flag_B;
reg flag_C;
//cnt_500ms
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_500ms <= 32'd0;
else if(cnt_500ms == TIME_500MS)
cnt_500ms <= 32'd0;
else
cnt_500ms <= cnt_500ms + 1'b1;
end
//cnt_1s
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_1s <= 32'd0;
else if(cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
cnt_1s <= 32'd0;
else if(cnt_500ms == TIME_500MS)
cnt_1s <= cnt_1s + 1'b1;
else
cnt_1s <= cnt_1s;
end
//cnt_3s
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_3s <= 32'd0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
cnt_3s <= 32'd0;
else if(cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
cnt_3s <= cnt_3s + 1'b1;
else
cnt_3s <= cnt_3s;
end
// flag_A:3s内,A按下为高,没按下或者3s到了均为低
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_A <= 1'b0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
flag_A <= 1'b0;
else if(A)
flag_A <= 1'b1;
else
flag_A <= flag_A;
end
//flag_B
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_B <= 1'b0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
flag_B <= 1'b0;
else if(B)
flag_B <= 1'b1;
else
flag_B <= flag_B;
end
//flag_C
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_C <= 1'b0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
flag_C <= 1'b0;
else if(C)
flag_C <= 1'b1;
else
flag_C <= flag_C;
end
//sel
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
sel <= 2'b00;//熄灭
else if(flag_A && flag_B && flag_C)//保持1s的闪光灯效果(0.5s亮,0.5s灭)
sel <= 2'b11;
else if(flag_A && flag_C)//保持1s的流水灯效果(左右都行)
sel <= 2'b10;
else if(flag_A && flag_B)//保持被点亮
sel <= 2'b01;
else if(flag_A || flag_B || flag_C)//保持熄灭
sel <= 2'b00;
else
sel <= sel;
end
endmodule
功能模块close_led
module close_led(
input wire clk ,
input wire rst_n ,
output wire [3:0] led
);
assign led = 4'hf;
endmodule
功能模块open_led
module open_led(
input wire clk ,
input wire rst_n ,
output wire [3:0] led
);
assign led = 4'b0000;
endmodule
功能模块water_led
module water_led
#(
parameter CNT_MAX_1S = 32'd49_999_999//1s
)
(
input wire clk ,
input wire rst_n ,
output reg [3:0] led
);
reg [31:0] cnt_1s;
//cnt_1s
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_1s <= 32'd0;
else if(cnt_1s == CNT_MAX_1S)
cnt_1s <= 32'd0;
else
cnt_1s <= cnt_1s + 1'b1;
end
//led
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
led <= 4'b0111;
else if(cnt_1s == CNT_MAX_1S)
led <= {led[0],led[3:1]};
else
led <= led;
end
endmodule
功能模块SGD
module SGD
#(
parameter CNT_MAX = 32'd24_999_999
)
(
input wire clk ,
input wire rst_n ,
output reg [3:0] led
);
reg [31:0] cnt;
//cnt
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= 32'd0;
else if(cnt == CNT_MAX)
cnt <= 32'd0;
else
cnt <= cnt + 1'b1;
end
//led
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
led <= 4'h0;
else if(cnt == CNT_MAX)
led <= ~led;
else
led <= led;
end
endmodule
功能模块mux_4
module mux_4(
input wire [3:0] in_1 ,
input wire [3:0] in_2 ,
input wire [3:0] in_3 ,
input wire [3:0] in_4 ,
input wire [1:0] sel ,
output reg [3:0] led
);
always@(*)
begin
case(sel)
2'b00 : led = in_1;
2'b01 : led = in_2;
2'b10 : led = in_3;
2'b11 : led = in_4;
default:led = in_1;
endcase
end
endmodule
功能模块seg_595_static
module seg_595_static(
input wire clk ,
input wire rst_n ,
output wire ds ,
output wire shcp ,
output wire stcp ,
output wire oe
);
wire [7:0] seg;
wire [5:0] sel;
seg_static seg_static_1
(
.clk (clk ) ,
.rst_n (rst_n) ,
.seg (seg ) ,
.sel (sel )
);
hc595_ctrl hc595_ctrl_1
(
.clk (clk ) ,
.rst_n(rst_n) ,
.seg (seg ) ,
.sel (sel ) ,
.ds (ds ) ,
.shcp (shcp ) ,
.stcp (stcp ) ,
.oe (oe )
);
endmodule
功能模块seg_static
module seg_static
#(
parameter CNT_MAX_1S = 32'd49_999_999//1s
)
(
input wire clk ,
input wire rst_n ,
output reg [7:0] seg ,
output reg [5:0] sel
);
reg [31:0] cnt_1s;
reg [3:0] cnt_data ;
//cnt_1s
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_1s <= 32'd0;
else if(cnt_1s == CNT_MAX_1S)
cnt_1s <= 32'd0;
else
cnt_1s <= cnt_1s + 1'b1;
end
//cnt_data
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_data <= 4'h1;
else if(cnt_data == 4'h3 && cnt_1s == CNT_MAX_1S)
cnt_data <= 4'h1;
else if(cnt_1s == CNT_MAX_1S)
cnt_data <= cnt_data + 1'b1;
else
cnt_data <= cnt_data;
end
//seg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
seg <= 8'hff;//数码管8段全灭
else case(cnt_data)
4'h0 : seg <= 8'hc0;
4'h1 : seg <= 8'hf9;
4'h2 : seg <= 8'ha4;
4'h3 : seg <= 8'hb0;
4'h4 : seg <= 8'h99;
4'h5 : seg <= 8'h92;
4'h6 : seg <= 8'h82;
4'h7 : seg <= 8'hf8;
4'h8 : seg <= 8'h80;
4'h9 : seg <= 8'h90;
4'ha : seg <= 8'h88;
4'hb : seg <= 8'h83;
4'hc : seg <= 8'hc6;
4'hd : seg <= 8'ha1;
4'he : seg <= 8'h86;
4'hf : seg <= 8'h8e;
default:seg <= 8'hff;//数码管8段全灭
endcase
end
//sel
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
sel <= 6'd0;
else
sel <= 6'b111_111;
end
endmodule
功能模块hc595_ctrl
module hc595_ctrl
#(
parameter CNT_MAX_DIV4 = 2'd3 ,
parameter CNT_MAX_BIT = 4'd13
)
(
input wire clk ,
input wire rst_n ,
input wire [7:0] seg ,
input wire [5:0] sel ,
output reg ds ,
output reg shcp ,
output reg stcp ,
output reg oe
);
reg [13:0] data ;
reg [1:0] cnt_div4;
reg [3:0] cnt_bit ;
//data
always@(*)
begin
if(!rst_n)
data = 14'd0;
else
data = {seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7],sel[5:0]};
end
//cnt_div4
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_div4 <= 2'd0;
else if(cnt_div4 == CNT_MAX_DIV4)
cnt_div4 <= 2'd0;
else
cnt_div4 <= cnt_div4 + 1'b1;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_bit <= 4'd0;
else if(cnt_bit == CNT_MAX_BIT && cnt_div4 == CNT_MAX_DIV4)
cnt_bit <= 4'd0;
else if(cnt_div4 == CNT_MAX_DIV4)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
// ds
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
ds <= 1'd0;
else
ds <= data[cnt_bit];
end
// shcp
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
shcp <= 1'b0;
else if(cnt_div4 == CNT_MAX_DIV4 – 1'b1 || cnt_div4 == CNT_MAX_DIV4)
shcp <= 1'b1;
else
shcp <= 1'b0;
end
// stcp
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
stcp <= 1'b0;
else if(cnt_bit == CNT_MAX_BIT && cnt_div4 == CNT_MAX_DIV4)
stcp <= 1'b1;
else
stcp <= 1'b0;
end
// oe
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
oe <= 1'b1;
else
oe <= 1'b0;
end
endmodule
上板验证
绑定引脚
使用signaltap查看波形并验证功能
方法二
绘制模块框图及波形图

下面只需要绘制fsm模块的波形图即可


这里的flag_A、flag_B、flag_C代表在3s内,A、B、C按下时为高电平,3s到了均为低电平。
state_flag:用于提前知晓是切换到流水灯还是闪光灯,方便更新led初值
编写模块代码
顶层模块key_fsm_led
module key_fsm_led(
input wire clk ,
input wire rst_n ,
input wire A ,
input wire B ,
input wire C ,
output wire [3:0] led ,
output wire ds ,
output wire shcp ,
output wire stcp ,
output wire oe
);
wire key_flag_A;
wire key_flag_B;
wire key_flag_C;
key_XD
#(
.CNT_MAX ( 32'd999_999 )//20ms
)
key_XD_A
(
.clk (clk ),
.rst_n (rst_n ),
.key_in (A ),
.key_flag(key_flag_A)
);
key_XD
#(
.CNT_MAX ( 32'd999_999 )//20ms
)
key_XD_B
(
.clk (clk ),
.rst_n (rst_n ),
.key_in (B ),
.key_flag(key_flag_B)
);
key_XD
#(
.CNT_MAX ( 32'd999_999 )//20ms
)
key_XD_C
(
.clk (clk ),
.rst_n (rst_n ),
.key_in (C ),
.key_flag(key_flag_C)
);
fsm
#(
.TIME_3S ( 32'd2 ),
.TIME_1S ( 32'd1 ),
.TIME_500MS ( 32'd24_999_999)
)
fsm_0
(
.clk (clk ) ,
.rst_n (rst_n) ,
.A (key_flag_A) ,
.B (key_flag_B) ,
.C (key_flag_C) ,
.led (led)
);
seg_595_static seg_595_static_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.ds (ds ) ,
.shcp (shcp ) ,
.stcp (stcp ) ,
.oe (oe )
);
endmodule
功能模块key_XD和方法一的一样
功能模块fsm
module fsm
#(
parameter TIME_3S = 32'd2 ,
parameter TIME_1S = 32'd1 ,
parameter TIME_500MS = 32'd24_999_999 ,
parameter s0 = 2'd0,//熄灭
parameter s1 = 2'd1,//常亮
parameter s2 = 2'd2,//1s流水灯
parameter s3 = 2'd3 //1s闪光灯(0.5s亮,0.5s灭)
)
(
input wire clk ,
input wire rst_n ,
input wire A ,
input wire B ,
input wire C ,
output reg [3:0] led
);
reg [31:0] cnt_3s;
reg [31:0] cnt_1s;
reg [31:0] cnt_500ms;
reg flag_A;
reg flag_B;
reg flag_C;
reg [2:0] state;
reg state_flag;
//cnt_500ms
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_500ms <= 32'd0;
else if(cnt_500ms == TIME_500MS)
cnt_500ms <= 32'd0;
else
cnt_500ms <= cnt_500ms + 1'b1;
end
//cnt_1s
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_1s <= 32'd0;
else if(cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
cnt_1s <= 32'd0;
else if(cnt_500ms == TIME_500MS)
cnt_1s <= cnt_1s + 1'b1;
else
cnt_1s <= cnt_1s;
end
//cnt_3s
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_3s <= 32'd0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
cnt_3s <= 32'd0;
else if(cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
cnt_3s <= cnt_3s + 1'b1;
else
cnt_3s <= cnt_3s;
end
// flag_A:3s内,A按下为高,没按下或者3s到了均为低
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_A <= 1'b0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
flag_A <= 1'b0;
else if(A)
flag_A <= 1'b1;
else
flag_A <= flag_A;
end
//flag_B
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_B <= 1'b0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
flag_B <= 1'b0;
else if(B)
flag_B <= 1'b1;
else
flag_B <= flag_B;
end
//flag_C
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_C <= 1'b0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
flag_C <= 1'b0;
else if(C)
flag_C <= 1'b1;
else
flag_C <= flag_C;
end
//state
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
state <= s0;
else if(cnt_3s == TIME_3S && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)//3s后判断按键状态
if(flag_A && flag_B && flag_C)//保持1s的闪光灯效果(0.5s亮,0.5s灭)
state <= s3;
else if(flag_A && flag_C)//保持1s的流水灯效果(左右都行)
state <= s2;
else if(flag_A && flag_B)//保持被点亮
state <= s1;
else if(flag_A || flag_B || flag_C)//保持熄灭
state <= s0;
else
state <= state;
else
state <= state;
end
//state_flag
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
state_flag <= 1'b0;
else if(cnt_3s == TIME_3S – 1'b1 && cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)
state_flag <= 1'b1;
else
state_flag <= 1'b0;
end
//led
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
led <= 4'hf;
else if(state_flag && flag_A && flag_B && flag_C)//赋初始值
led <= 4'h0;
else if(state_flag && flag_A && flag_C)
led <= 4'b0111;
else case(state)
s0 : led <= 4'hf;//熄灭
s1 : led <= 4'h0;//常亮
s2 : //1s流水灯
if(cnt_1s == TIME_1S && cnt_500ms == TIME_500MS)//1s流水
led <= {led[0],led[3:1]};
else
led <= led;
s3 : //1s闪光灯(0.5s亮,0.5s灭)
if(cnt_500ms == TIME_500MS)
led <= ~led;
else
led <= led;
default:led <= 4'hf;
endcase
end
endmodule
功能模块静态数码管的代码都和方法一的一样
上板验证
绑定引脚和方法一绑定的引脚一致,下载代码验证功能即可,这里不再编写仿真代码,进行仿真验证。
总结
设计思路:首先需要进行按键消抖,那麽如何在3s内去记录按键A/B/C有没有按下呢?我们需要先设计一个3s的循环计数器,那用什么去记录按键有没有按下呢?假如使用三个计数器,cnt_AB,cnt_AC,cnt_ABC。先分析cnt_AB,A按下时,cnt_AB=1;B按下时,cnt_AB=2;3s到了清零,这样确实可以记录AB顺序按下,然后执行led常量,但按照BA按下就不行了,所以使用计数器记录按键有木有按下是不行的。假如使用序列检测,按键的排列组合太多,此方案不行。假如使用flag信号,当A按下过,让flag_A一直为高电平,3s到了才拉低,B和C一样这样设计。每当clk的上升沿到来时,去判断一次flag_A、flag_B、flag_C三个信号,当前处于什么状态,然后去执行相应的led效果,所以又想到了使用状态机,但是这里需要注意优先级,感觉这个方案可行。然后写代码写到给led赋值时,我发现如果当前led是常亮状态,然后跳转到下一个流水灯状态(使用位拼接的方法),那么在实现流水时,需要让led=4'b0111才行;我寻思着,是否可以在状态跳转时,去更新led的值。如果是一段式的话,我感觉应该可行;二段式和三段式不太行,因为不可以在两个always语句去给同一个信号赋值。这里卡住之后,我就把close_led,open_led,water_led,SGD模块框图画了出来,发现输出都是led[3:0],而顶层也是led[3:0],而这四路信号不可以同时接到输出信号,那如何选取一路输出呢?这时就想到了4选1多路选择器,选通信号就相当于是当前需要执行的状态。这样就不会出现前面在状态跳转时,无法更新led的值。所以方法一是最稳妥的,最直接的方案。
接下来,我介绍一下方法二的想法吧。前面不是在状态跳转时,无法更新led的值,那我就想去解决这个问题,不就可以了嘛。出现这个问题主要是无法提前判断下一时刻的状态是什么,所以我就让其在cnt_3s计数到最大值时,再去判断led改执行什么功能。这样的话,就是每3s就去判断下一个3s的状态。然后设置一个state_flag信号,当cnt_3s=MAX-1时拉高一个时钟周期;当这个信号有效时,再根据flag_A、flag_B、flag_C信号,我们可以提前一个时钟周期知晓下一个led状态是什么,那么在这个时钟周期内,我们就可以去更新led的值,解决了这个问题,那么方案二就有了。



