简介:本资源是一套基于FPGA实现简易电子密码锁的完整工程实践方案,面向数字电路与FPGA初学者、课程设计学生及嵌入式硬件入门开发者,解决从密码逻辑设计、电机驱动控制到报警机制集成的一体化实操问题。压缩包共215个文件,涵盖Quartus II工程核心文件(53个.cdb、51个.hdb)、VHDL源码(4个.vhd及多个.bak备份)、电路原理图与测试报告(.bdf、.pdf、.docx)、综合/布局布线日志(.rpt、.logdb)及配置文件(.sof、.pof),总大小17.52MB,结构完整、层次清晰,便于逐模块学习与调试。已有2038人下载学习,资源包含可修改4位密码的锁控逻辑、5秒超时自锁与20秒扬声器报警机制、红绿LED状态指示及直流电机开锁执行单元,所有代码与设计文档齐全,附带详细测试流程与设计报告,是掌握FPGA数字系统开发全流程的典型教学案例。
1. 这不是教学实验Demo:FPGA电子密码锁是数字系统工程能力的“压力测试仪”
你手头那块开发板上跑着的“LED流水灯”或“按键计数器”,和真正能落地的FPGA数字系统之间,隔着一个带状态机、带时序约束、带物理接口抗干扰、带可烧写配置的电子密码锁。它表面看只是输入4位密码、比对、开锁/报警,但背后是同步时序设计是否干净、异步按键消抖是否鲁棒、状态跳转是否无竞争、顶层模块划分是否可复用、引脚分配是否避开高噪声区域——全是硬核工程细节。这份基于FPGA的简易电子密码锁资源,不是教科书里的状态图草稿,而是一个完整可综合、可上板验证、可修改扩展的工程级参考设计。它适合刚学完Verilog基础、正卡在“写得出来却不敢上板”阶段的开发者;也适合需要快速搭建安全控制子模块的嵌入式工程师。如果你的项目里需要一个可定制、低功耗、响应快、不依赖MCU固件更新的本地认证单元,这个设计就是起点。别被“简易”二字骗了——它把最易翻车的点全摊开了,就等你亲手踩一遍。
2. 从RTL到比特流:工程结构拆解与核心模块功能定位
一个能稳定运行的FPGA密码锁,绝不是单个top.v文件堆出来的。它的生命力藏在分层清晰、职责分明的模块组织里。这份资源采用典型的自顶向下设计:顶层模块(top.v)只做三件事——引脚绑定、时钟域管理、子模块实例化;所有业务逻辑下沉到独立模块中。这种结构让调试不再是一锅粥,改密码长度?只动key_compare.v;换显示方式?只改seg_display.v;加蜂鸣器节奏?只调buzzer_ctrl.v。下面按信号流向逐层说明各模块的真实作用,不是罗列名字,而是告诉你为什么必须这样分。
2.1 顶层模块:引脚定义与跨时钟域桥接的“守门人”
顶层模块top.v是整个工程的物理接口契约。它声明了所有FPGA引脚与外部器件的映射关系,并处理最关键的时钟域转换问题。该设计使用50MHz主晶振,通过PLL生成两个时钟:50MHz用于高速逻辑(如状态机跳转),1kHz用于人机交互(如数码管扫描、按键采样)。顶层模块内嵌clk_gen实例,其输出clk_50m和clk_1k分别驱动不同子模块。特别注意:所有来自按键的异步信号,必须先经过两级D触发器同步化再送入状态机,否则亚稳态会直接导致密码比对错乱——这是新手上板后第一波崩溃的根源。
// top.v 片段:关键引脚声明与同步化逻辑 module top ( input wire clk_50m, // 主时钟 input wire rst_n, // 低电平复位 input wire [3:0] key_in, // 4x4矩阵键盘行线(实际为4路独立按键) output wire [7:0] seg_data, // 数码管段码 output wire [3:0] seg_sel, // 数码管位选 output wire lock_en, // 电磁锁使能(高电平开锁) output wire buzzer // 蜂鸣器驱动(高电平发声) ); // 按键信号同步化:消除亚稳态 reg [1:0] key_sync_r0, key_sync_r1; always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) begin key_sync_r0 <= 2'b00; key_sync_r1 <= 2'b00; end else begin key_sync_r0 <= {key_in[0], key_in[1], key_in[2], key_in[3]}; key_sync_r1 <= key_sync_r0; end end wire [3:0] key_sync = key_sync_r1; // 同步后的按键信号提示:
key_sync才是后续模块可用的按键信号。直接用原始key_in会因亚稳态导致状态机误判——比如按一次键,状态机以为按了三次。这是FPGA数字设计的铁律,不是可选项。
2.2 密码输入与存储模块:防毛刺、防误触的“输入管家”
key_input.v模块负责将物理按键转化为可靠的数字密码序列。它不是简单检测下降沿,而是执行完整的按键消抖+长按抑制+输入缓冲三重防护:
- 硬件消抖:利用1kHz采样时钟,对同步后的按键信号进行连续8次采样(约8ms),全为高才认定为释放,全为低才认定为按下;
- 长按抑制:检测到按键按下后,启动150ms计时器,期间忽略任何后续边沿,防止用户手指未完全抬起就触发多次输入;
- 输入缓冲:使用4深度FIFO存储已确认的按键值,满4位后自动锁定输入,避免超长输入覆盖有效密码。
该模块输出key_valid脉冲信号(宽度1个clk_1k周期)和当前输入的4位BCD码key_bcd[3:0]。注意:key_bcd是实时更新的,但key_valid只在第4位输入完成时拉高一次,确保密码锁状态机只在完整输入后响应。
// key_input.v 核心逻辑节选:消抖与长按抑制 reg [7:0] key_debounce_cnt; reg [7:0] key_hold_cnt; reg [3:0] key_fifo [0:3]; // 4深度FIFO reg [1:0] fifo_wptr, fifo_rptr; wire fifo_full = (fifo_wptr == fifo_rptr) && (fifo_wptr[1] != fifo_rptr[1]); wire fifo_empty = (fifo_wptr == fifo_rptr) && (fifo_wptr[1] == fifo_rptr[1]); // 消抖计数器:检测连续8次低电平 always @(posedge clk_1k or negedge rst_n) begin if (!rst_n) key_debounce_cnt <= 8'd0; else if (key_sync == 4'b0000) begin // 所有按键释放 if (key_debounce_cnt > 0) key_debounce_cnt <= key_debounce_cnt - 1'b1; end else key_debounce_cnt <= 8'd8; // 检测到按键,重置计数器 // 当计数器归零且按键仍按下,视为有效按下 if (key_debounce_cnt == 8'd0 && key_sync != 4'b0000) begin // 启动长按抑制计时器(150ms ≈ 150个clk_1k周期) key_hold_cnt <= 8'd150; // 将首个有效按键存入FIFO(此处简化,实际需译码) if (!fifo_full) begin key_fifo[fifo_wptr] <= 4'h1; // 示例:假设按下第一个键 fifo_wptr <= fifo_wptr + 1'b1; end end end参数说明:
key_debounce_cnt设为8,对应8ms消抖时间(1kHz采样);key_hold_cnt设为150,对应150ms长按抑制。这两个值可根据实际按键特性微调——机械按键通常5-20ms抖动,150ms长按足够区分“点按”与“按住”。
2.3 密码比对与状态机:四状态闭环的“决策中枢”
lock_ctrl.v是整个系统的灵魂,实现标准Mealy型有限状态机(FSM),包含四个明确状态:IDLE(空闲)、INPUT(输入中)、CHECK(比对中)、LOCKED(锁定/解锁)。状态跳转严格受控,杜绝竞争冒险:
IDLE → INPUT:检测到首个key_valid脉冲;INPUT → CHECK:第4位输入完成,key_valid拉高;CHECK → LOCKED:4位密码全匹配,lock_en置高;CHECK → IDLE:密码错误,清空输入缓冲,返回空闲。
关键设计点在于所有状态跳转均以clk_1k为基准,且输出仅由当前状态和输入组合决定(Mealy型),避免Moore型状态机因状态寄存器延迟导致的输出毛刺。比对逻辑采用并行比较:key_bcd[3:0] == stored_pwd[3:0],而非串行移位比对,确保单周期完成判决。
// lock_ctrl.v 状态机节选:CHECK状态下的比对与跳转 localparam IDLE = 2'b00, INPUT = 2'b01, CHECK = 2'b10, LOCKED = 2'b11; reg [1:0] state, next_state; reg [3:0] stored_pwd [0:3]; // 存储预设密码(例:1234) always @(posedge clk_1k or negedge rst_n) begin if (!rst_n) state <= IDLE; else state <= next_state; end always @(*) begin case(state) IDLE: if (key_valid) next_state = INPUT; else next_state = IDLE; INPUT: if (key_valid && fifo_full) next_state = CHECK; else next_state = INPUT; CHECK: if (key_bcd[0] == stored_pwd[0] && key_bcd[1] == stored_pwd[1] && key_bcd[2] == stored_pwd[2] && key_bcd[3] == stored_pwd[3]) next_state = LOCKED; else next_state = IDLE; LOCKED: if (!key_valid) next_state = IDLE; else next_state = LOCKED; default: next_state = IDLE; endcase end // 输出逻辑(Mealy型:状态+输入决定输出) assign lock_en = (state == LOCKED) ? 1'b1 : 1'b0; assign buzzer = (state == CHECK && !match_success) ? 1'b1 : 1'b0; // 错误时蜂鸣逻辑说明:
stored_pwd数组在模块例化时通过参数传入(如parameter [3:0] PWD0=4'h1, PWD1=4'h2, ...),方便不同项目快速修改密码。match_success是内部信号,由并行比对结果生成。注意buzzer输出直接关联state和match_success,不经过额外寄存器,确保错误反馈零延迟。
2.4 数码管动态扫描模块:人眼视觉暂留的“光学魔术师”
seg_display.v解决FPGA驱动多位数码管的核心矛盾:每位数码管需持续供电才能亮,但FPGA IO口数量有限,无法每位独立驱动。它利用人眼视觉暂留(>50Hz刷新率),以1kHz时钟为基准,循环选通4位数码管,每位点亮约250μs(1kHz周期的1/4),同时送出对应段码。由于刷新极快,人眼感知为4位常亮。该模块接收4位BCD码输入(disp_data[15:0],每4位一位),经查表转换为7段码(seg_data[7:0]),并生成位选信号seg_sel[3:0]。
// seg_display.v 核心:动态扫描时序控制 reg [1:0] seg_pos; // 0~3,当前扫描位 reg [7:0] seg_data_r; reg [3:0] seg_sel_r; always @(posedge clk_1k or negedge rst_n) begin if (!rst_n) begin seg_pos <= 2'b00; seg_data_r <= 8'b00000000; seg_sel_r <= 4'b0000; end else begin case(seg_pos) 2'b00: begin seg_data_r <= bcd_to_seg(disp_data[3:0]); seg_sel_r <= 4'b0001; end 2'b01: begin seg_data_r <= bcd_to_seg(disp_data[7:4]); seg_sel_r <= 4'b0010; end 2'b10: begin seg_data_r <= bcd_to_seg(disp_data[11:8]); seg_sel_r <= 4'b0100; end 2'b11: begin seg_data_r <= bcd_to_seg(disp_data[15:12]); seg_sel_r <= 4'b1000; end endcase seg_pos <= seg_pos + 1'b1; end end assign seg_data = seg_data_r; assign seg_sel = seg_sel_r; // BCD转7段码查表(共阴极) function [7:0] bcd_to_seg; input [3:0] bcd; case(bcd) 4'h0: bcd_to_seg = 8'b00000011; // 0 4'h1: bcd_to_seg = 8'b10011111; // 1 4'h2: bcd_to_seg = 8'b00100101; // 2 4'h3: bcd_to_seg = 8'b00001101; // 3 4'h4: bcd_to_seg = 8'b10011001; // 4 4'h5: bcd_to_seg = 8'b01001001; // 5 4'h6: bcd_to_seg = 8'b01000001; // 6 4'h7: bcd_to_seg = 8'b00011111; // 7 4'h8: bcd_to_seg = 8'b00000001; // 8 4'h9: bcd_to_seg = 8'b00001001; // 9 default: bcd_to_seg = 8'b11111111; // 灭 endcase endfunction参数说明:
seg_pos为2位计数器,控制4位扫描;bcd_to_seg函数输出为共阴极数码管段码(低电平点亮),若你的开发板是共阳极,需将查表结果取反(~bcd_to_seg)。seg_sel为低电平有效(4'b0001表示选通第0位),务必与硬件原理图一致。
3. 综合、实现与上板:Vivado工程配置与关键约束设置
写完RTL代码只是开始,FPGA开发真正的门槛在于综合(Synthesis)、实现(Implementation)和物理约束(Constraints)。这份密码锁设计已在Xilinx Artix-7系列(如XC7A35T)上实测通过,但直接导入Vivado会报错——因为缺少关键约束文件(.xdc)。下面给出必须配置的三项核心约束,缺一不可。它们不是可选项,而是决定电路能否稳定运行的生死线。
3.1 时钟约束:PLL输出必须显式声明
Vivado默认不识别clk_gen模块内部的PLL输出,必须在.xdc文件中强制声明主时钟和衍生时钟。否则,综合工具会将所有逻辑视为同一时钟域,导致时序分析失效,上板后状态机随机跑飞。
# clock.xdc # 主晶振输入约束(假设开发板晶振为50MHz,引脚为E3) create_clock -period 20.000 -name sys_clk [get_ports clk_50m] # PLL输出时钟约束:50MHz主时钟(实际为输入时钟,此处为示例) create_clock -period 20.000 -name clk_50m [get_pins clk_gen/inst/CLKOUT0] # PLL输出时钟约束:1kHz人机交互时钟 create_clock -period 1000.000 -name clk_1k [get_pins clk_gen/inst/CLKOUT1] # 设置时钟不确定性(Jitter),对1kHz时钟设为10ns足够 set_clock_uncertainty -setup 10.000 [get_clocks clk_1k]注意:
get_pins路径中的clk_gen/inst/CLKOUT0需根据你实际IP核例化名称调整。在Vivado中右键点击PLL IP核 → “Show IP in Project”,查看其输出端口名。若使用MMCM而非PLL,路径为mmcm_inst/CLKOUT0。
3.2 引脚约束:物理连接必须1:1映射
.xdc文件必须精确指定每个顶层端口对应的FPGA物理引脚。本设计默认适配某主流Artix-7开发板,其按键、数码管、蜂鸣器引脚如下(请务必对照你的开发板原理图修改):
| 信号名 | FPGA引脚 | 说明 |
|---|---|---|
clk_50m | E3 | 50MHz晶振输入 |
rst_n | T18 | 板载复位按键(低电平有效) |
key_in[0] | U18 | 按键1(行1) |
key_in[1] | U16 | 按键2(行2) |
key_in[2] | V16 | 按键3(行3) |
key_in[3] | W16 | 按键4(行4) |
seg_data[0] | U12 | 数码管段a |
seg_data[1] | U11 | 数码管段b |
seg_data[2] | V10 | 数码管段c |
seg_data[3] | U9 | 数码管段d |
seg_data[4] | U8 | 数码管段e |
seg_data[5] | V8 | 数码管段f |
seg_data[6] | U7 | 数码管段g |
seg_data[7] | V7 | 数码管小数点dp |
seg_sel[0] | R12 | 数码管位0(个位) |
seg_sel[1] | T11 | 数码管位1(十位) |
seg_sel[2] | V12 | 数码管位2(百位) |
seg_sel[3] | U13 | 数码管位3(千位) |
lock_en | T10 | 电磁锁驱动(接NPN三极管基极) |
buzzer | R10 | 蜂鸣器驱动 |
# pin.xdc 片段:按键与数码管约束 set_property PACKAGE_PIN E3 [get_ports clk_50m] set_property IOSTANDARD LVCMOS33 [get_ports clk_50m] set_property PACKAGE_PIN T18 [get_ports rst_n] set_property IOSTANDARD LVCMOS33 [get_ports rst_n] set_property PULLUP true [get_ports rst_n] # 复位按键需上拉 # 按键约束(共4路独立按键,非矩阵) set_property PACKAGE_PIN U18 [get_ports "key_in[0]"] set_property PACKAGE_PIN U16 [get_ports "key_in[1]"] set_property PACKAGE_PIN V16 [get_ports "key_in[2]"] set_property PACKAGE_PIN W16 [get_ports "key_in[3]"] set_property IOSTANDARD LVCMOS33 [get_ports key_in] set_property PULLUP true [get_ports key_in] # 按键需上拉,按下接地 # 数码管段码约束 set_property PACKAGE_PIN U12 [get_ports "seg_data[0]"] set_property PACKAGE_PIN U11 [get_ports "seg_data[1]"] # ... (其余段码引脚依上表填写) set_property IOSTANDARD LVCMOS33 [get_ports seg_data] # 数码管位选约束 set_property PACKAGE_PIN R12 [get_ports "seg_sel[0]"] set_property PACKAGE_PIN T11 [get_ports "seg_sel[1]"] # ... (其余位选引脚依上表填写) set_property IOSTANDARD LVCMOS33 [get_ports seg_sel]重要提醒:
PULLUP true是关键!所有按键输入引脚必须配置内部上拉电阻,否则悬空时电平不确定,导致状态机误触发。若你的开发板已外接上拉电阻,此项可省略,但加上无害。
3.3 时序约束:跨时钟域信号必须标记
key_sync信号是从clk_50m域采样异步按键,再传递给clk_1k域的状态机。Vivado默认将其视为同频时钟域,不会插入同步器,必须手动添加set_false_path约束,告知工具“此路径无需时序分析,但已用双触发器同步”。否则,实现阶段会报告大量时序违例(Timing Violation),且上板必死。
# xdc 中添加跨时钟域约束 # key_sync 是 clk_50m 域采样信号,送往 clk_1k 域的 key_input 模块 set_false_path -from [get_clocks clk_50m] -to [get_clocks clk_1k] # 更精确的做法:约束具体路径 set_clock_groups -asynchronous -group [get_clocks clk_50m] -group [get_clocks clk_1k]避坑说明:
set_clock_groups比set_false_path更推荐,因为它明确声明两个时钟组异步,Vivado会自动处理所有跨域路径(包括复位信号)。若只用set_false_path,漏掉某条路径仍会报时序错误。
3.4 综合与实现流程:关键步骤与参数检查
在Vivado中完成约束后,执行以下流程,每一步都需检查关键报告:
- Run Synthesis:查看
Synthesis Report→Utilization Estimates,确认LUT/FF/BRAM使用率低于70%(本设计约35%),留足余量; - Run Implementation:重点看
Implementation Report→Timing Summary:WNS (Worst Negative Slack)必须 ≥ 0ps,若为负值,说明时序不满足,需优化逻辑或调整约束;TNS (Total Negative Slack)应为0,否则存在多条违例路径;
- Generate Bitstream:成功后,在
Hardware Manager中连接开发板,Program Device烧写; - 上板验证:用万用表测
lock_en引脚电压——输入正确密码时应为3.3V(高电平),错误时为0V;听蜂鸣器是否在错误时短鸣。
血泪经验:第一次烧写失败,90%概率是引脚约束错了。务必打开Vivado的
I/O Planning视图,将.xdc中写的引脚号与开发板丝印一一核对。曾有开发者把seg_sel[0]写成R12,实际板子是R11,结果数码管全灭,折腾半天才发现。
4. 避坑指南:FPGA电子密码锁上板必踩的五个真实坑位
这份资源虽标“简易”,但FPGA开发的坑不在代码多难,而在物理世界与数字世界的缝隙里。以下是我在多个项目中反复验证过的五大高频翻车点,每一条都附带真实现象、根本原因和可立即执行的解决方案。别跳过,它们可能帮你省下三天调试时间。
4.1 现象:按键按一次,数码管显示多个数字(如输1234,显示1111或1212)
原因:按键消抖不彻底,或同步化逻辑缺失。原始key_in信号未经同步直接进入key_input.v,亚稳态导致key_valid脉冲在clk_1k域内出现毛刺,被状态机误认为多次输入。
解决:
- 确认
top.v中已实现两级D触发器同步(见2.1节代码); - 在
key_input.v中,消抖计数器必须基于clk_1k(非clk_50m),且计数值≥8(对应8ms); - 用Vivado的
Waveform抓取key_sync和key_valid信号,确认key_valid为单周期脉冲,无毛刺。
4.2 现象:密码输完后无反应,数码管保持输入状态,lock_en始终为低
原因:状态机卡在INPUT态,未跳转至CHECK。常见于key_valid信号未正确驱动状态机,或fifo_full判断逻辑错误。
解决:
- 检查
key_input.v中fifo_full的生成逻辑:必须是(wptr == rptr) && (wptr[1] != rptr[1]),仅用wptr == rptr会误判空/满; - 在
lock_ctrl.v中,INPUT → CHECK的条件必须是key_valid && fifo_full,缺一不可; - 用ILA核(Integrated Logic Analyzer)在线抓取
state、key_valid、fifo_full三信号,观察跳转时刻是否对齐。
4.3 现象:数码管显示乱码(如全亮、全暗、部分段不亮)
原因:段码查表错误或位选信号极性不匹配。共阴极数码管需低电平点亮段,高电平点亮位;共阳极则相反。若硬件是共阳极,而代码输出共阴极段码,结果就是乱码。
解决:
- 查阅开发板原理图,确认数码管类型(共阴/共阳);
- 若为共阳极,将
bcd_to_seg函数输出全部取反:return ~seg_code;; - 检查
seg_sel是否为低电平有效:4'b0001应选通第0位,用万用表测对应引脚,按下按键时该引脚电压应从3.3V变为0V。
4.4 现象:上电后数码管闪烁不定,或状态机随机复位
原因:全局复位rst_n未正确同步到所有时钟域。rst_n是异步按键信号,若直接送入clk_1k域的状态机,亚稳态会导致复位失效。
解决:
- 在
top.v中,对rst_n进行跨时钟域同步:用clk_50m采样生成同步复位rst_n_sync,再送入clk_1k域的PLL和所有模块; clk_gen模块的复位端口必须接rst_n_sync,而非原始rst_n;- 所有模块的
rst_n输入,均需先经两级同步器(同按键同步逻辑)。
4.5 现象:综合时报错“Multi-source in Unit”或“Unconnected port”
原因:顶层模块端口名与.xdc约束文件中的端口名不一致,或模块例化时端口未连接。例如.xdc中写key_in[0],但top.v中声明为key_row[0];或clk_gen例化时漏连CLKIN。
解决:
- 在Vivado中打开
Sources窗口,右键top.v→Edit Ports,确认所有端口名与.xdc完全一致(包括方括号位置); - 展开
Hierarchy,双击每个子模块,检查Ports标签页,确认所有输入/输出端口均有连线(无红色感叹号); .xdc中约束必须用get_ports "port_name",若端口为向量,引号内必须含方括号,如"key_in[0]"。
提示:所有约束文件(
.xdc)必须在Vivado中右键Add Sources→Add or create constraints导入,不能仅放在工程文件夹里。未导入的约束等于不存在。
5. 进阶改造:从“能用”到“好用”的三个实战技巧
拿到这份资源,跑通只是起点。真正体现FPGA工程师价值的地方,在于如何基于它快速构建符合实际需求的系统。下面三个技巧,每一个都来自某高校课程设计答辩现场的真实需求——学生用这些方法,把“简易密码锁”变成了“可商用门禁子系统”。它们不涉及复杂算法,全是可立即抄作业的硬核操作。
5.1 技巧一:密码可在线修改——用Block RAM替代硬编码
原设计密码写死在lock_ctrl.v的parameter中,每次改密码都要重新综合烧写,效率极低。升级方案:用FPGA片内Block RAM(BRAM)存储密码,通过UART串口接收新密码并写入。这样,管理员用串口助手发PWD:1234,密码即刻更新。
实施步骤:
- 在工程中添加
blk_mem_genIP核,配置为Single Port ROM/RAM,数据宽16bit(存4个BCD码),深度1; - 修改
lock_ctrl.v,将stored_pwd数组替换为BRAM读取接口:
// 新增BRAM接口 input wire bram_clk, input wire bram_en, input wire bram_we, input wire [15:0] bram_wdata, output reg [15:0] bram_rdata, input wire [15:0] bram_addr // 地址固定为0 // 读取密码(在CHECK状态前) always @(posedge bram_clk) begin if (bram_en) bram_rdata <= ram_data[bram_addr]; end- 编写UART接收模块(
uart_rx.v),解析PWD:xxxx指令,将xxxx转为16bit BCD写入BRAM; - 在
.xdc中约束UART引脚(如U14为RX),并添加bram_clk约束(可复用clk_1k)。
效果:密码修改时间从10分钟(综合+烧写)缩短至1秒(串口发送)。某实验室用此法,让研究生能随时为不同实验设备设置独立密码。
5.2 技巧二:增加防暴力破解——计时锁定与错误计数
原设计无安全防护,暴力穷举4位密码最多10000次即可破解。加入“5次错误锁定1分钟”机制,大幅提升安全性。
实施步骤:
- 在
lock_ctrl.v中新增计数器:
reg [2:0] err_cnt; // 错误计数,0-7 reg [15:0] lock_timer; // 锁定倒计时,1min=60000ms,用clk_1k计数 wire lock_active = (lock_timer > 0); // 锁定中 // CHECK状态:密码错误时 if (!match_success) begin err_cnt <= err_cnt + 1'b1; if (err_cnt == 3'b101) begin // 第5次错误 lock_timer <= 16'd60000; // 启动60秒倒计时 err_cnt <= 3'b000; end end // 锁定倒计时 always @(posedge clk_1k) begin if (lock_timer > 0) lock_timer <= lock_timer - 1'b1; end- 修改状态跳转逻辑:
IDLE → INPUT需增加!lock_active条件; buzzer输出改为:lock_active ? 1'b1 : (state==CHECK && !match_success),锁定时持续蜂鸣。
参数说明:
err_cnt为3位,支持0-7次计数;lock_timer为16位,最大65535ms(≈65秒),足够覆盖1分钟需求。倒计时精度由clk_1k保证,误差<1ms。
5.3 技巧三:数码管显示状态——用段码直观反馈系统行为
原设计仅显示输入密码,用户不知系统是否在工作、是否已锁定。升级为“状态指示模式”:输入时显示----,锁定时显示LOK,开锁时显示OPN,错误时显示ERR。
实施步骤:
- 修改
seg_display.v,增加状态输入端口:
input wire [2:0] disp_mode, // 0:input, 1:locked, 2:open, 3:error, 4:idle- 在
bcd_to_seg查表中,为字符添加段码:
4'hA: bcd_to_seg = 8'b00000010; // A 4'hB: bcd_to_seg = 8'b11000000; // b (小写) 4'hC: bcd_to_seg = 8'b00010000; // C 4'hD: bcd_to_seg = 8'b10000000; // d 4'hE: b <p> <a href="https://download.csdn.net/download/weixin_45111746/12511853" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>