【Verilog】硬件密码锁
问题定义
-
密码锁共有12个键, 0-9的数字键, *为取消键, #为确定键
-
开锁时,需要输入4位正确密码后,按#号键确定,密码锁可以打开,注意这里只要最后按#键前4位正确即可,密码门打开后30秒回到初始态。
-
如果连续3次输错密码,密码门自动死锁3分钟。
-
密码门有一个六位超级密码230419,输入后可以用户重置并且设置四位开锁密码, 用于设置新的开锁密码时,需要连续输入两次并按#确认,两次必须相同。否则设置失败。
-
无论是开锁还是设置密码可以按*号来取消。
思路推导与代码实现
输入/输出定义如下:
module lock(
input wire clk,
input wire clr,
input wire [3:0] din, //数字键0-9,需要4bit
input wire confirm, //确定键'#'
input wire cancel, //取消键'*'
output reg unlock_ok, //成功开锁状态时,输出1
output reg reset_ok, //重设密码成功时,输出1
output reg locking //输错密码3次进入锁定状态时,输出1
);
此外,定义一些变量用于保存密码/计时等
//初始密码1234
reg [3:0] passwd0 = 1;
reg [3:0] passwd1 = 2;
reg [3:0] passwd2 = 3;
reg [3:0] passwd3 = 4;
//超级密码230419
reg [3:0] superwd0 = 2;
reg [3:0] superwd1 = 3;
reg [3:0] superwd2 = 0;
reg [3:0] superwd3 = 4;
reg [3:0] superwd4 = 1;
reg [3:0] superwd5 = 9;
//新密码
reg [3:0] newpasswd0 = 0, newpasswd1 = 0, newpasswd2 = 0, newpasswd3 = 0;
//开启状态保持时间
reg [5:0] open_time = 0;
//连续输错密码次数
reg [5:0] wrong_count = 0;
//锁定状态保持时间
reg [5:0] lock_time = 0;
具体功能使用两个状态机实现。
对于"输入密码——判定密码——开锁/死锁"部分功能,划分一个状态机 。
对于"输出超级密码——重设密码"功能,划分一个状态机 。
状态机 共有 个状态:
- S0 - S3: 当前已输入了正确密码的前0/1/2/3位。
- S4: 已输入正确密码,等待确认。
- Open: 已开启,开启状态保持30秒。
- Lock: 已死锁,死锁状态保持3分钟。
状态机S定义部分代码如下:
//状态定义
reg[3:0] present_state_s, next_state_s;//当前状态,下一状态
parameter S0 = 3'b0, S1 = 3'b1, S2 = 3'b10, S3 = 3'b11;
parameter S4 = 3'b100; //等待键入确定键'#'
parameter Open = 3'b101; //开启状态
parameter Lock = 3'b110; //输错3次密码,锁定状态
S0-S3的状态转移为常见的序列检测思路,根据当前输入的数字din是否与保存在passwd0/1/2/3中的正确密码一致,决定下一个状态为 还是 S0。此外,在每个状态中检测确定键和取消键,确定键和取消键都会使状态跳转到S0,而此时按下确定键会使得wrong_count
(记录的连续输错次数)加1。
如果正确输入了四位密码,进入S4,此时按下确认键即可跳转到Open状态,否则回到S0。
在Open状态开始时,输出unlock_ok设为1,将reg [5:0] open_time 赋值为保持开启状态的时钟周期数,这里为方便仿真验证设置为3个周期。每个时钟上升沿open_time递减,直到3个周期后为0,回到S0状态。
如果连续输错次数wrong_count达到3,则跳转至Lock状态,输出locking设为1,将reg [5:0] lock_time 赋值为保持死锁状态的时钟周期数,这里为方便仿真验证设置为3个周期。每个时钟上升沿lock_time递减,直到3个周期后为0,回到S0状态。
状态机 的具体实现如下
//状态机S
always @(*) begin
case (present_state_s)
S0:
begin
if (din == passwd0) next_state_s <= S1;
else next_state_s <= S0;
if (confirm == 1)
begin
wrong_count = wrong_count + 1;
if (wrong_count >= 3)
begin
next_state_s <= Lock;
end
else
next_state_s <= S0;
end
if (cancel == 1) next_state_s <= S0;
end
S1:
begin
if (din == passwd1) next_state_s <= S2;
else next_state_s <= S0;
if (confirm == 1)
begin
wrong_count <= wrong_count + 1;
if (wrong_count >= 3)
begin
next_state_s <= Lock;
end
else
next_state_s <= S0;
end
if (cancel == 1) next_state_s <= S0;
end
S2:
begin
if (din == passwd2) next_state_s <= S3;
else next_state_s <= S0;
if (confirm == 1)
begin
wrong_count <= wrong_count + 1;
if (wrong_count >= 3)
begin
next_state_s <= Lock;
end
else
next_state_s <= S0;
end
if (cancel == 1) next_state_s <= S0;
end
S3:
begin
if (din == passwd3) next_state_s <= S4;
else next_state_s <= S0;
if (confirm == 1)
begin
wrong_count <= wrong_count + 1;
if (wrong_count >= 3)
begin
next_state_s <= Lock;
end
else
next_state_s <= S0;
end
if (cancel == 1) next_state_s <= S0;
end
S4:
begin
if (confirm == 1) begin
next_state_s <= Open;
wrong_count <= 0;
end
else
next_state_s <= S0;
end
Open:
begin
if (open_time > 0)
next_state_s <= Open;
else
next_state_s = S0;
end
Lock:
begin
if (lock_time > 0)
begin
next_state_s <= Lock;
end
else next_state_s = S0;
end
endcase
end
//状态切换与输出
always @(posedge clk or posedge clr) begin
if (open_time > 0) begin
if (open_time == 1) unlock_ok <= 0;
open_time <= open_time - 1;
end
if (lock_time > 0) begin
if (lock_time == 1) begin
locking <= 0;
wrong_count <= 0;
end
lock_time <= lock_time - 1;
end
if (clr == 1) begin
present_state_s <= S0;
unlock_ok <= 0;
locking <= 0;
end
else
begin
if (present_state_s != Open && next_state_s == Open) begin
unlock_ok <= 1;
open_time <= 3;
end
if (present_state_s != Lock && next_state_s == Lock) begin
locking <= 1;
lock_time <= 3;
end
present_state_s <= next_state_s;
end
end
状态机 共有 个状态:
- T0 - T11: 检测12位序列230419230419已经正确输入到哪一位
- T11 - T15: 输入新密码第1/2/3/4位
- T16 - T19: 重复新密码的第1/2/3/4位
- T20: 等待确认。
- OK: 重设密码成功。
T0-T11的状态转移为常见的序列检测思路,T11-T15将输入的数字暂存在reg [3:0] newpasswd0/1/2/3
中,T16-T19分别检测第二次输入的密码是否与寄存器中的密码相同,如果相同,进入T20等待按下确认键。
以上各状态中,超级密码出错/再次输出密码不一致/按下取消键都会回到T0状态。
T20时按下确认键进入OK状态,将newpasswd
中的密码复制到passwd中,并重置连续输错次数wrong_count为0。
状态机 的具体实现如下:
reg[5:0] present_state_t, next_state_t;
parameter T0 = 5'b0, T1 = 5'b1, T2 = 5'b10, T3 = 5'b11, T4 = 5'b100, T5 = 5'b101,
T6 = 5'b110, T7 = 5'b111, T8 = 5'b1000, T9 = 5'b1001, T10 = 5'b1010, T11 = 5'b1011,
T12 = 5'b1100, T13 = 5'b1101, T14 = 5'b1110, T15 = 5'b1111,
T16 = 5'b10000, T17 = 5'b10001, T18 = 5'b10010, T19 = 5'b10011,
T20 = 5'b10100, //等待键入确定键'#'
OK = 5'b10101;
always @(posedge clk or posedge clr) begin
if (clr == 1)
present_state_t <= T0;
else
present_state_t <= next_state_t;
end
//状态机T
always @(*) begin
case (present_state_t)
T0: if (din == superwd0) next_state_t <= T1;
else next_state_t <= T0;
T1: if (din == superwd1) next_state_t <= T2;
else next_state_t <= T0;
T2: if (din == superwd2) next_state_t <= T3;
else next_state_t <= T0;
T3: if (din == superwd3) next_state_t <= T4;
else next_state_t <= T0;
T4: if (din == superwd4) next_state_t <= T5;
else next_state_t <= T0;
T5: if (din == superwd5) next_state_t <= T6;
else next_state_t <= T0;
T6: if (din == superwd0) next_state_t <= T7;
else next_state_t <= T0;
T7: if (din == superwd1) next_state_t <= T8;
else next_state_t <= T0;
T8: if (din == superwd2) next_state_t <= T9;
else next_state_t <= T0;
T9: if (din == superwd3) next_state_t <= T10;
else next_state_t <= T0;
T10: if (din == superwd4) next_state_t <= T11;
else next_state_t <= T0;
T11: if (din == superwd5) next_state_t <= T12;
else next_state_t <= T0;
T12:
begin
newpasswd0 <= din; next_state_t <= T13;
end
T13:
begin
newpasswd1 <= din; next_state_t <= T14;
end
T14:
begin
newpasswd2 <= din; next_state_t <= T15;
end
T15:
begin
newpasswd3 <= din; next_state_t <= T16;
end
T16:
if (din == newpasswd0) next_state_t <= T17;
else next_state_t <= T0;
T17:
if (din == newpasswd1) next_state_t <= T18;
else next_state_t <= T0;
T18:
if (din == newpasswd2) next_state_t <= T19;
else next_state_t <= T0;
T19:
if (din == newpasswd3) next_state_t <= T20;
else next_state_t <= T0;
T20:
if (confirm == 1) begin
next_state_t <= OK;
end
else next_state_t <= T0;
OK:
begin
passwd0 <= newpasswd0;
passwd1 <= newpasswd1;
passwd2 <= newpasswd2;
passwd3 <= newpasswd3;
next_state_t <= T0;
wrong_count <= 0;//重设密码后,重新计算连续输错的次数
end
default: next_state_t <= T0;
endcase
if (cancel == 1) next_state_t <= T0;
end
always @(posedge clk or posedge clr) begin
if (clr == 1) reset_ok <= 0;
else
if (present_state_t == OK)
reset_ok <= 1;
else
reset_ok <= 0;
end
仿真验证
编写仿真文件进行功能验证。
`timescale 1ns / 1ps
module tb_lock();
reg clk;
reg clr;
reg cancel; //取消键'*'
reg [3:0] din; //数字键
reg confirm; //确定键'#'
wire unlock_ok; //开锁,输出1
wire reset_ok; //成功重设密码,输出1
wire locking; //输错密码锁定状态,输出1
initial begin
clk <= 0;
confirm <= 0;
din <= 0;
cancel <= 0;
clr <= 1;
#30
clr <= 0;
//依次输入 1 2 3 4 确认,成功开锁
din <= 1;
#20
din <= 2;
#20
din <= 3;
#20
din <= 4;
#20
din <= 0;
confirm <= 1;
#20
confirm <= 0;
#20
#20
#20
#20
//依次输入 1 2 3 5 确认,无法开锁
din <= 1;
#20
din <= 2;
#20
din <= 3;
#20
din <= 5;
#20
din <= 0;
confirm <= 1;
#20
confirm <= 0;
din <= 1;
#20
din <= 0;
#20
#20
#20
#20
//依次输入 230419 230419 6789 6789 确认
//密码从默认的1234 修改为 6789
din <= 2; #20
din <= 3; #20
din <= 0; #20
din <= 4; #20
din <= 1; #20
din <= 9; #20
din <= 2; #20
din <= 3; #20
din <= 0; #20
din <= 4; #20
din <= 1; #20
din <= 9; #20
din <= 6; #20
din <= 7; #20
din <= 8; #20
din <= 9; #20
din <= 6; #20
din <= 7; #20
din <= 8; #20
din <= 9; #20
confirm <= 1; #20
confirm <= 0; din <= 0; #20
//连续输入3次错误密码:
//1234 # 1234 # 1234 #
din <= 1; confirm <= 0; #20
din <= 2; #20
din <= 3; #20
din <= 4; #20
din <= 0; confirm <= 1; #20
din <= 1; confirm <= 0; #20
din <= 2; #20
din <= 3; #20
din <= 4; #20
din <= 0; confirm <= 1; #20
din <= 1; confirm <= 0; #20
din <= 2; #20
din <= 3; #20
din <= 4; #20
din <= 0; confirm <= 1; #20
//连续输错三次后,进入lock状态,无法开锁
din <= 6; confirm <= 0; #20
din <= 7; #20
din <= 8; #20
din <= 9; #20
din <= 0; confirm <= 1; #20
//中途取消test
din <= 6; confirm <= 0; #20
din <= 7; #20
din <= 8; #20
cancel <= 1; din <= 0; #20
din <= 9; cancel <= 0; #20
din <= 0; confirm <= 1; #20
confirm <= 0;
end
always #10 clk = ~clk; //晶振周期20ns
lock test_lock(
.clk(clk),
.clr(clr),
.din(din),
.confirm(confirm),
.cancel(cancel),
.unlock_ok(unlock_ok),
.reset_ok(reset_ok),
.locking(locking)
);
endmodule
在Vivado中进行行为级仿真,结果符合预期
第一部分:依次输入1、2、3、4、Confirm,输出unlock_ok, 保持三个周期。
第二部分:依次输入1、2、3、5、Confirm,回到状态S0, wrong_count = 1
第三部分:依次输入230419 230419 6789 6789、Confirm,密码从1234修改为6789。
此时不关心状态机S的状态。按下确认键后,重设密码成功,则wrong_count会清零。
第四部分:依次输入1234 confirm 1234 confirm 1234 confirm
wrong_count达到3,进入死锁状态,持续三个周期,在此期间输入6789Confirm,也无法开锁。
第五部分:依次按下6 7 8 取消 9 确认。可见按下取消键后回到了S0状态。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器