【Verilog】硬件密码锁

问题定义

  1. 密码锁共有12个键, 0-9的数字键, *为取消键, #为确定键

  2. 开锁时,需要输入4位正确密码后,按#号键确定,密码锁可以打开,注意这里只要最后按#键前4位正确即可,密码门打开后30秒回到初始态。

  3. 如果连续3次输错密码,密码门自动死锁3分钟。

  4. 密码门有一个六位超级密码230419,输入后可以用户重置并且设置四位开锁密码, 用于设置新的开锁密码时,需要连续输入两次并按#确认,两次必须相同。否则设置失败。

  5. 无论是开锁还是设置密码可以按*号来取消。

思路推导与代码实现

输入/输出定义如下:

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;

具体功能使用两个状态机实现。

对于"输入密码——判定密码——开锁/死锁"部分功能,划分一个状态机 \(S\)

对于"输出超级密码——重设密码"功能,划分一个状态机 \(T\)

状态机 \(S\) 共有 \(6\) 个状态:

  • 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中的正确密码一致,决定下一个状态为 \(S_{i+1}\) 还是 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\) 的具体实现如下

    //状态机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

状态机 \(T\) 共有 \(22\) 个状态:

  • 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。

状态机 \(T\)的具体实现如下:

    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中进行行为级仿真,结果符合预期

image-20230527022018272

image-20230527020234165

第一部分:依次输入1、2、3、4、Confirm,输出unlock_ok, 保持三个周期。

image-20230527013610676

第二部分:依次输入1、2、3、5、Confirm,回到状态S0, wrong_count = 1

image-20230527013817931

第三部分:依次输入230419 230419 6789 6789、Confirm,密码从1234修改为6789。

image-20230527014148450

此时不关心状态机S的状态。按下确认键后,重设密码成功,则wrong_count会清零。

第四部分:依次输入1234 confirm 1234 confirm 1234 confirm

wrong_count达到3,进入死锁状态,持续三个周期,在此期间输入6789Confirm,也无法开锁。

image-20230527014629619

第五部分:依次按下6 7 8 取消 9 确认。可见按下取消键后回到了S0状态。

image-20230527015942777

posted @ 2023-05-27 02:22  _vv123  阅读(565)  评论(0编辑  收藏  举报