文章目录
一、本次实验简介
本次是实验是为了利用状态机模拟电子锁,相关要求如下:
- 顺序输入4位密码,密码为1234,用按键来键入密码
- 用led灯指示键入第几位密码,(博主IDLE状态亮1个LED,输入第一位密码后亮2个,以此类推),若密码输入正确,让4个led闪烁(每间隔0.3s)
- 用3个按键,按下key1,对应位的数值加1
- 按下key2,对应位的数值减1
- 按下按键key3,表示确认键入该数值
二、源码及分析
fsm_lock模块:
源码分析:
- 博主为状态机设计了五个状态并采用独热码编码的方式(一般均建议状态机状态编码方式采用独热码):
IDLE 空闲状态
P1密码1输入正确状态
P2 密码2输入正确状态
P3密码3输入正确状态
P4密码4输入正确后LED闪烁状态 - 有题目可以看出,我们有四位的密码,博主的想法是设置四个密码寄存器,KEY1按下后计数值加1,KEY2按下时计数值减一,以此来存储键入密码值(当然每个密码寄存器的加1条件有些许不同,详细请看代码)。
- 而除了最后一个状态,其他状态博主设置的跳转下一状态的条件均为状态机处于当前状态并且按下了KEY3(确认键)并且此时键入的值为密码值。
- 对于最后一个状态,由于需要在P4闪烁LED灯,因此跳转状态设置为了LED闪烁结束。至于如何控制LED闪烁结束,博主设置了一个八位的次数寄存器,LED每完成一次闪烁,该计数器左移一位(初始值为8'd1),当闪烁完成时,该计数器的最高位为1,此时便可以将最高位作为跳转的控制条件。
c
/****
状态机模拟电子锁
- 顺序输入4位密码,密码为1234,用按键来键入密码
- 用led灯指示键入第几位密码,(博主IDLE状态亮1个LED,输入第一位密码后亮2个,以此类推),若密码输入正确,让4个led闪烁(每间隔0.3s)
- 用3个按键,按下key1,对应位的数值加1
- 按下key2,对应位的数值减1
- 按下按键key3,表示确认键入该数值
****/
module fsm_lock (
input wire clk ,
input wire rst_n ,
input wire [2:0] key_in , //按键输入信号
output reg [3:0] led //输出led
);
//参数定义
parameter MAX = 15_000_000 ;//300ms计数器
parameter IDLE = 5'b00001 ,//空闲状态
P1 = 5'b00010 ,//密码1输入正确状态
P2 = 5'b00100 ,//密码2输入正确状态
P3 = 5'b01000 ,//密码3输入正确状态
P4 = 5'b10000 ;//密码4输入正确LED闪烁状态
//状态转移条件定义
wire idle2p1 ;
wire p12p2 ;
wire p22p3 ;
wire p32p4 ;
wire p42idle ;
//内部信号定义
reg [23:0] cnt ;//300ms计数寄存器
reg [7:0] cnt_8 ;//解锁成功后让LED闪烁4次
reg [4:0] cstate ;//现态
reg [4:0] nstate ;//次态
reg [3:0] cnt_key1 ;//密码计数器 计数当前按下的密码值
reg [3:0] cnt_key2 ;
reg [3:0] cnt_key3 ;
reg [3:0] cnt_key4 ;
reg led_light;
//三段式状态机
//第一段时序逻辑,描述状态转移
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段组合逻辑,描述状态转移规律及状态转移条件
always@(*)begin
case(cstate)
IDLE : begin
if(idle2p1)begin
nstate = P1;
end
else begin
nstate = cstate;
end
end
P1 : begin
if(p12p2)begin
nstate = P2;
end
else begin
nstate = cstate;
end
end
P2 : begin
if(p22p3)begin
nstate = P3;
end
else begin
nstate = cstate;
end
end
P3 : begin
if(p32p4)begin
nstate = P4;
end
else begin
nstate = cstate;
end
end
P4 : begin
if(p42idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
assign idle2p1 = (cstate == IDLE) && (key_in[2]) && (cnt_key1 == 4'd1);
assign p12p2 = (cstate == P1 ) && (key_in[2]) && (cnt_key2 == 4'd2);
assign p22p3 = (cstate == P2 ) && (key_in[2]) && (cnt_key3 == 4'd3);
assign p32p4 = (cstate == P3 ) && (key_in[2]) && (cnt_key4 == 4'd4);
assign p42idle = (cstate == P4 ) && (cnt_8[7]);//led闪烁完成后跳转
//第三段,时序逻辑,描述不同状态下的输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 4'b0000;
end
else begin
case(cstate)
IDLE : led <= 4'b0001 ;
P1 : led <= 4'b0011 ;
P2 : led <= 4'b0111 ;
P3 : led <= 4'b1111 ;
P4 : led <= led_light ;
default : led <= 4'b0000 ;
endcase
end
end
//密码计数器1
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_key1 <= 1'b0;
end
else if(key_in[0] && cstate == IDLE)begin
cnt_key1 <= cnt_key1 + 1'b1;
end
else if(key_in[1] && (cnt_key1 != 1'b0))begin
cnt_key1 <= cnt_key1 - 1'b1;
end
else if(cnt_8[7])begin
cnt_key1 <= 1'b0;
end
else begin
cnt_key1 <= cnt_key1;
end
end
//密码计数器2
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_key2 <= 1'b0;
end
else if(key_in[0] && cstate == P1)begin
cnt_key2 <= cnt_key2 + 1'b1;
end
else if(key_in[1] && (cnt_key2 != 1'b0))begin
cnt_key2 <= cnt_key2 - 1'b1;
end
else if(cnt_8[7])begin
cnt_key2 <= 1'b0;
end
else begin
cnt_key2 <= cnt_key2;
end
end
//密码计数器3
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_key3 <= 1'b0;
end
else if(key_in[0] && cstate == P2)begin
cnt_key3 <= cnt_key3 + 1'b1;
end
else if(key_in[1] && (cnt_key3 != 1'b0))begin
cnt_key3 <= cnt_key3 - 1'b1;
end
else if(cnt_8[7])begin
cnt_key3 <= 1'b0;
end
else begin
cnt_key3 <= cnt_key3;
end
end
//密码计数器4
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_key4 <= 1'b0;
end
else if(key_in[0] && cstate == P3)begin
cnt_key4 <= cnt_key4 + 1'b1;
end
else if(key_in[1] && (cnt_key4 != 1'b0))begin
cnt_key4 <= cnt_key4 - 1'b1;
end
else if(cnt_8[7])begin
cnt_key4 <= 1'b0;
end
else begin
cnt_key4 <= cnt_key4;
end
end
//300ms计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 1'b0;
end
else if(cnt == MAX - 1'b1)begin
cnt <= 1'b0;
end
else if(cstate == P4)begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 1'b0;
end
end
//闪烁次数寄存器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_8 <= 8'b00000001;
end
else if(cnt == MAX -1'd1)begin
cnt_8 <= {cnt_8[6:0],cnt_8[7]};
end
else if(cstate == IDLE)begin
cnt_8 <= 8'b00000001;
end
else begin
cnt_8 <= cnt_8;
end
end
//led闪烁
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led_light <= 4'b1111;
end
else if(cstate == P4 && cnt == MAX - 1'b1)begin
led_light <= ~led_light;
end
else begin
led_light <= led_light;
end
end
endmodule
fsm_key模块(按键消抖模块):
如果有读者不了解按键消抖原理与代码细节的可以查看博主此篇博文,博主在此就不再赘述了
c
module fsm_key#(parameter WIDTH = 3) (
input wire clk ,
input wire rst_n ,
input wire [WIDTH - 1:0] key_in ,//按键信号输入
output reg [WIDTH - 1:0] key_out //消抖后稳定的脉冲信号输出
);
//参数定义
parameter IDLE = 4'b0001;//空闲状态
parameter FILTER_DOWN = 4'b0010;//按键按下抖动状态
parameter HOLD_DOWN = 4'b0100;//按键稳定状态
parameter FILTER_UP = 4'b1000;//按键释放抖动状态
parameter MAX = 20'd1_000_000 ;//延时20ms采样
//内部信号定义
reg [19:0] cnt_20ms ;//20ms计数寄存器
reg [WIDTH - 1:0] key_r0 ;//同步
reg [WIDTH - 1:0] key_r1 ;//打一拍
reg [WIDTH - 1:0] key_r2 ;//打两拍
reg [3:0] cstate ;//现态寄存器
reg [3:0] nstate ;//次态寄存器
//状态转移条件定义
wire idle2down ;
wire down2hold ;
wire hold2up ;
wire up2idle ;
//计数器信号定义
wire add_cnt_20ms;//开始计时的标志
wire end_cnt_20ms;//结束计时的标志
wire [WIDTH - 1:0] nedge ;//下降沿寄存器
wire [WIDTH - 1:0] pedge ;//上升沿寄存器
//同步打拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
//20ms计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 1'b0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 1'b0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
else begin
cnt_20ms <= 1'b0;
end
end
assign add_cnt_20ms = cstate == FILTER_DOWN || cstate == FILTER_UP;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX - 1'b1;
//下降沿 上升沿检测
assign nedge = ~key_r1 & key_r2;
assign pedge = key_r1 & ~key_r2;
//三段式状态机
//第一段,时序逻辑,描述状态转移
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段,组合逻辑,状态转移规律及状态转移条件
always @(*)begin
case (cstate)
IDLE : begin
if(idle2down)begin
nstate <= FILTER_DOWN;
end
else begin
nstate <= cstate;
end
end
FILTER_DOWN : begin
if(down2hold)begin
nstate <= HOLD_DOWN;
end
else begin
nstate <= cstate;
end
end
HOLD_DOWN : begin
if(hold2up)begin
nstate <= FILTER_UP;
end
else begin
nstate <= cstate;
end
end
FILTER_UP : begin
if(up2idle)begin
nstate <= IDLE;
end
else begin
nstate <= cstate;
end
end
default : nstate <= IDLE;
endcase
end
assign idle2down = cstate == IDLE && nedge ;
assign down2hold = cstate == FILTER_DOWN && end_cnt_20ms ;
assign hold2up = cstate == HOLD_DOWN && pedge ;
assign up2idle = cstate == FILTER_UP && end_cnt_20ms ;
//第三段 时序逻辑或组合逻辑 描述输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out <= {WIDTH{1'b0}};
end
else if(down2hold)begin
key_out <= ~key_in;
end
else begin
key_out <= {WIDTH{1'b0}};
end
end
endmodule
顶层模块:
c
module top_lock(
input wire clk ,
input wire rst_n ,
input wire [2:0] key_in ,
output wire [3:0] led
);
wire [2:0] key_out;
fsm_lock u_fsm_lock(
.clk (clk ) ,
.rst_n (rst_n ) ,
.key_in(key_out) ,
.led (led)
);
fsm_key u_fsm_key(
.clk (clk ) ,
.rst_n (rst_n ) ,
.key_in (key_in ) ,
.key_out(key_out)
);
endmodule
三、总结
本次实验较为简单,博主没有编写仿真文件跑仿真(其实是因为懒毕竟我觉得TB文件写起来真的很麻烦),功能实现也较为简单,感兴趣的读者可以自主添加其他功能,如:输入错误蜂鸣器鸣叫并自动跳转回到IDLE状态,或者利用数码管显示正在输入的密码位数与密码值等。