Lesson 4

🔐 密码锁

🏆 4位密码验证+开锁
✅ Verilator仿真验证通过

📖 实验描述

实现4位数字密码锁,支持密码输入、验证和开锁/锁定控制。

密码锁是序列检测FSM的典型应用。 4位密码锁需要按正确顺序输入4个数字,每输入一位与预设密码比较,全部正确则开锁,任一位错误则锁定并报警。 设计关键:状态记录当前已输入正确位数。输入正确则前进,输入错误则复位到初始态并触发锁定计数器。 安全特性:3次错误后锁定一段时间,防止暴力破解。 密码锁广泛应用于门禁系统、保险箱、电子设备等。全球有超过10亿个电子锁在使用。

🧠 核心概念

📐 电路结构

电路与状态图
密码锁FSM:

  输入数字d → 比较器 → 与pwd[i]匹配?
                           │
  S0 ──yes──→ S1 ──yes──→ S2 ──yes──→ S3 ──yes──→ UNLOCK
   │            │            │            │
   └──────no────┴──────no────┴──────no────┘
                           │
                       LOCK_CNT++
                       3次→LOCKOUT

📝 设计步骤

  1. 1定义4位预设密码(参数化)
  2. 2设计状态:S0/S1/S2/S3/UNLOCK/LOCKOUT
  3. 3逐位比较:输入digit与password[state_idx]比较
  4. 4正确则推进状态,错误则复位+错误计数+1
  5. 54位全对:输出unlock信号
  6. 63次错误:进入LOCKOUT,延迟后解锁

💻 Verilog实现

password.svSystemVerilog · Verilator 5.020
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
// Password Lock - 4位密码锁
// 预设密码 1-2-3-4,3次错误后锁定
module password_lock(
    input  wire       clk,
    input  wire       rst,
    input  wire [3:0] digit,    // 输入数字0-9
    input  wire       enter,   // 确认输入
    output reg        unlock,  // 开锁信号
    output reg        locked,  // 锁定报警
    output reg  [1:0] err_cnt, // 错误计数
    output reg  [1:0] progress // 已正确输入位数
);

localparam PWD0 = 4'd1;
localparam PWD1 = 4'd2;
localparam PWD2 = 4'd3;
localparam PWD3 = 4'd4;

typedef enum logic [2:0] {
    S_IDLE  = 3'd0,
    S_D1    = 3'd1,
    S_D2    = 3'd2,
    S_D3    = 3'd3,
    S_OPEN  = 3'd4,
    S_LOCK  = 3'd5
} state_t;

state_t state;
reg [7:0] lock_timer;

always @(posedge clk or posedge rst) begin
    if (rst) begin
        state      <= S_IDLE;
        unlock     <= 1'b0;
        locked     <= 1'b0;
        err_cnt    <= 2'd0;
        progress   <= 2'd0;
        lock_timer <= 8'd0;
    end else begin
        unlock <= 1'b0;

        case (state)
            S_IDLE: begin
                locked   <= 1'b0;
                progress <= 2'd0;
                if (enter && digit == PWD0) begin
                    state    <= S_D1;
                    progress <= 2'd1;
                end else if (enter && digit != PWD0) begin
                    err_cnt <= err_cnt + 2'd1;
                    if (err_cnt >= 2'd2) state <= S_LOCK;
                end
            end
            S_D1: begin
                progress <= 2'd1;
                if (enter && digit == PWD1) begin
                    state    <= S_D2;
                    progress <= 2'd2;
                end else if (enter && digit != PWD1) begin
                    err_cnt <= err_cnt + 2'd1;
                    state   <= S_IDLE;
                    if (err_cnt >= 2'd2) state <= S_LOCK;
                end
            end
            S_D2: begin
                progress <= 2'd2;
                if (enter && digit == PWD2) begin
                    state    <= S_D3;
                    progress <= 2'd3;
                end else if (enter && digit != PWD2) begin
                    err_cnt <= err_cnt + 2'd1;
                    state   <= S_IDLE;
                    if (err_cnt >= 2'd2) state <= S_LOCK;
                end
            end
            S_D3: begin
                progress <= 2'd3;
                if (enter && digit == PWD3) begin
                    state    <= S_OPEN;
                    unlock   <= 1'b1;
                    err_cnt  <= 2'd0;
                    progress <= 2'd0;
                end else if (enter && digit != PWD3) begin
                    err_cnt <= err_cnt + 2'd1;
                    state   <= S_IDLE;
                    if (err_cnt >= 2'd2) state <= S_LOCK;
                end
            end
            S_OPEN: begin
                unlock <= 1'b1;
                // Auto relock after some cycles
                lock_timer <= lock_timer + 8'd1;
                if (lock_timer >= 8'd100) begin
                    state      <= S_IDLE;
                    unlock     <= 1'b0;
                    lock_timer <= 8'd0;
                end
            end
            S_LOCK: begin
                locked <= 1'b1;
                lock_timer <= lock_timer + 8'd1;
                if (lock_timer >= 8'd200) begin
                    state      <= S_IDLE;
                    locked     <= 1'b0;
                    err_cnt    <= 2'd0;
                    lock_timer <= 8'd0;
                end
            end
            default: state <= S_IDLE;
        endcase
    end
end

endmodule

🔬 仿真说明

仿真环境与策略

测试:输入1-2-3-4→开锁;输入1-2-3-5→错误计数+1,回到S0;连续3次错误→进入LOCKOUT;锁定超时后→恢复可用。

✅ 验证结果

Verilator 5.020 仿真通过

本实验所有Verilog代码已通过Verilator编译验证,功能行为正确。测试用例覆盖核心功能路径,确保设计满足规格要求。

⚠️ 常见错误

🌍 真实世界

工业应用

现代电子锁使用加密算法替代简单比较,支持指纹、RFID、蓝牙等多种开锁方式。但序列检测FSM仍是底层验证逻辑的基础。智能门锁市场2024年规模超500亿元。

💡 扩展挑战

🚀 自己动手

# 编译并运行
verilator --cc password.sv --exe password_tb.cpp --build -j 0
./obj_dir/Vpassword

🎯 试一试

修改参数

尝试修改代码中的关键参数,观察仿真结果变化:

  • 调整位宽,观察不同数据范围
  • 修改初始值/种子,观察不同起始条件
  • 改变时钟分频,测试不同速度

💡 Verilator会在位宽不匹配时给出Warning,这是学习的好机会

添加功能

在现有基础上增加新功能:

  • 添加新的输入信号和控制逻辑
  • 增加状态或修改状态转移条件
  • 扩展输出,增加更多显示信息

🔧 增量开发:每次只改一个地方,验证通过后再改下一个

📋 Verilog速查

语法说明示例
reg [7:0]8位寄存器reg [7:0] data;
wire组合逻辑连线wire valid = cnt > 5;
always @(posedge clk)时序逻辑上升沿触发
always @(*)组合逻辑敏感列表自动推导
localparam局部常量localparam DIV = 50000000;
case多分支选择注意default分支
$display仿真打印不可综合,仅仿真用

📊 性能指标

资源估算(FPGA参考)

本设计在典型FPGA上的资源占用估算:LUT约20-120个,FF约30-100个,无BRAM/DSP依赖(特殊模块除外)。时钟频率可达50-100MHz+。Verilator仿真速度约5-10M周期/秒。

← 上一课 📚 目录 下一课 →