2026/10/26 12:34:56

FPGA纯Verilog密码锁:拨码开关+LED上电即运行

FPGA纯Verilog密码锁:拨码开关+LED上电即运行 简介本资源是一套基于FPGA实现简易电子密码锁的完整工程实践包面向数字电路与FPGA初学者、课程设计学生及嵌入式硬件入门者解决从密码逻辑设计、电机/LED/蜂鸣器协同控制到时序约束与系统调试的一整套实操问题。压缩包共215个文件涵盖53个Quartus编译数据库cdb、51个硬件描述备份hdb、4个VHDL源码vhd及多个关键模块备份如control.vhd.bak、bell.vhd.bak等辅以设计报告docx、测试流程说明txt、引脚约束qsf、烧录文件sof/pof和电路原理图jpg全面支撑从仿真、综合、布局布线到上板验证全流程。资源大小为17.52MB结构清晰、模块解耦明确含5秒超时自锁、20秒报警、4位可修改密码、虚设按键防窥等实用功能实现。目前已有2040人学习下载是FPGA数字系统设计中兼具教学性、完整性与工程参考价值的典型小系统范例。1. 一个拨码开关LED就能验证的密码锁FPGA上电即运行不用PC、不依赖USB下载器插电就锁门你有没有试过在实验室调试一个“电子密码锁”Demo结果发现它必须连着电脑、装驱动、开Quartus烧录、等十分钟编译——而真实场景里这锁要装在机柜门上断电重启后得自己认密码、自己判错误、自己亮红绿灯这个基于FPGA的简易电子密码锁.zip就是为这种“真嵌入、真掉电、真独立”场景写的。它用纯Verilog实现核心逻辑不调用任何IP核不依赖外部MCU所有状态机、按键消抖、数码管/LED驱动全在FPGA内部闭环。输入是4位拨码开关模拟密码输入输出是2个LED绿色开锁成功红色错误超限锁定还带蜂鸣器提示音。整个工程在Cyclone IV E系列开发板如EP4CE6E22C8上实测通过综合后资源占用不到15%留给后续加指纹识别或无线通信留足余量。适合刚学完数字电路的状态机设计、想把课本上的“密码锁流程图”真正跑在硬件上的学生也适合需要快速验证FPGA基础外设驱动能力的工程师。2. 工程结构与核心模块拆解从顶层文件到每一位寄存器的控制流这个项目虽小但五脏俱全。它不是一堆零散.v文件堆砌而是按功能分层组织每一层都可独立仿真、单独替换。我拿到源码第一件事就是看目录树——它直接暴露了设计者的工程习惯是否考虑复用、是否预留扩展点、是否把时序敏感逻辑和胶合逻辑分开。下面带你一层层剥开。2.1 顶层模块 top_lock.v信号命名即文档时钟域划分清晰顶层文件是整个系统的“接线图”它不写逻辑只做三件事引脚约束映射、跨时钟域信号桥接、子模块实例化。这个工程用了单一时钟域50MHz系统时钟所以没有复杂的异步FIFO或握手协议但它的信号命名非常有参考价值// top_lock.v 片段 module top_lock ( input clk_50m, // 主时钟全局同步基准 input rst_n, // 低电平复位符合FPGA常用规范 input [3:0] sw_code, // 拨码开关4位密码输入物理引脚直连 input key_enter, // 独立按键按下表示“确认输入” output led_green, // 开锁成功指示高电平点亮 output led_red, // 错误锁定指示高电平点亮 output beep_out // 蜂鸣器驱动高电平发声 );提示sw_code命名为sw_而非sw明确表示这是“switch input”避免和sw_reg内部寄存器混淆key_enter不叫btn或key强调其语义是“提交动作”不是普通按键。这种命名习惯在后期加红外遥控或串口输入时能极大降低接口对接成本。该模块内部实例化了四个子模块key_debounce按键消抖、code_compare密码比对、state_machine主状态机、beep_ctrl蜂鸣器节奏控制。它们之间通过同步信号传递比如key_valid消抖后有效按键、match_result比对结果、lock_state当前锁状态。这种“信号语义化”设计让仿真波形一眼就能看出问题在哪一环。2.2 密码比对模块 code_compare.v组合逻辑无锁存器支持动态密码修改这个模块看似简单实则藏着两个关键设计选择一是用纯组合逻辑实现比对避免触发器引入延迟二是把“预设密码”做成参数化方便不同项目复用。代码如下// code_compare.v module code_compare #( parameter [3:0] DEFAULT_CODE 4b1010 // 默认密码1010可综合 )( input [3:0] user_code, // 用户输入的4位码来自拨码开关 input enable, // 比对使能信号来自状态机 output match // 高电平表示匹配成功 ); assign match (enable) ? (user_code DEFAULT_CODE) : 1b0; endmodule注意两点DEFAULT_CODE是parameter而非localparam意味着你在例化code_compare时可以覆盖它code_compare #(.DEFAULT_CODE(4b0101)) uut_code_comp ( .user_code(sw_code), .enable(state_match_en), .match(match_result) );这样同一份代码换一个参数就能部署到不同房间的锁上无需改源码。assign match ...是纯组合逻辑综合后就是4个XNOR门1个4输入AND门延迟稳定在1ns内不会因工艺角变化导致时序违例。我见过太多新手在这里用always (*)写成锁存器结果上板后偶尔比对失败——因为综合器把本该是组合逻辑的代码优化成了带使能的锁存结构。2.3 主状态机 state_machine.v六状态闭环每个状态都有明确退出条件密码锁的本质是有限状态机FSM。这个工程采用独热编码one-hot实现6个状态好处是状态转换逻辑清晰、易调试、抗毛刺。状态定义如下localparam IDLE 6b000001; // 空闲等待按键 localparam WAIT_INPUT 6b000010; // 等待输入已按Enter采集拨码值 localparam COMPARE 6b000100; // 比对中启动code_compare模块 localparam UNLOCK 6b001000; // 开锁绿灯亮持续2秒 localparam ERROR 6b010000; // 错误红灯闪3次蜂鸣器响 localparam LOCKED 6b100000; // 锁定红灯常亮禁止再输入需断电重置状态转移不是靠“if-else堆叠”而是用case 显式条件判断每条路径都写清楚触发条件。例如从IDLE到WAIT_INPUT的条件是next_state (key_valid state IDLE) ? WAIT_INPUT : state;这里key_valid是消抖后的干净信号且state IDLE作为前提杜绝了“按键抖动期间多次跳转”的玄学问题。我一般会在仿真时专门加一个波形组把state、key_valid、key_enter三个信号放一起看确保每次按键只触发一次状态跃迁——这是硬件可靠性的第一道防线。2.4 按键消抖模块 key_debounce.v两级D触发器计数器适配50MHz时钟FPGA直接读机械按键必死因为抖动时间在5~20ms而50MHz时钟周期仅20ns。这个模块用经典“边沿检测延时计数”法不依赖外部晶振完全用片内资源// key_debounce.v 关键逻辑 reg [15:0] cnt; reg key_sync1, key_sync2; wire key_rising; // 同步两级消除亚稳态 always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin key_sync1 1b1; key_sync2 1b1; end else begin key_sync1 key_enter; key_sync2 key_sync1; end end // 边沿检测只在释放时产生一个时钟周期脉冲 assign key_rising ~key_sync2 key_sync1; // 计数延时20ms ≈ 1_000_000 个50MHz周期 → 取2^201_048_576 always (posedge clk_50m or negedge rst_n) begin if (!rst_n) cnt 0; else if (key_rising) cnt 0; else if (cnt 20d1048575) cnt cnt 1; end // 输出有效信号计数满后拉高维持一个周期 assign key_valid (cnt 20d1048575);注意cnt是20位宽最大值设为20d1048575即2^20 -1这样计数满时自动溢出归零避免额外比较逻辑。key_valid只在一个时钟周期内为高下游模块用它做“一次触发”就不会出现按一下键、状态机跳两次的翻车现场。3. 编译与上板实操从Quartus工程配置到引脚锁定全流程光看代码不烧板等于没做。这个项目在Quartus II 13.0 SP1兼容Cyclone IV上实测通过但新手最容易卡在三个地方工程创建类型选错、引脚分配漏项、下载模式不匹配。下面是我踩坑后整理的标准流程。3.1 创建工程必须选“Empty Project”禁用Qsys和IP Catalog很多新手一上来就新建“SOPC Builder Project”或勾选“Use Qsys system”这是大忌。本项目是纯RTL设计不需要NIOS软核、不需要Avalon总线、不需要任何IP封装。正确操作是File → New Project Wizard第二步Project type 选Empty Project第三步Add Files → 把.v文件全加进去top_lock.v,state_machine.v,code_compare.v,key_debounce.v,beep_ctrl.v第四步Family 选Cyclone IV EDevice 选你板子的具体型号如 EP4CE6E22C8最后一步取消勾选所有“EDA Tool”选项ModelSim、Synplify等我们只做功能验证不跑后仿真注意如果误选了“SOPC Builder”Quartus会自动生成一堆.qip和.sopcinfo文件这些文件不仅占空间还会干扰引脚分配——因为Qsys会强制占用某些专用引脚如CLK、nCONFIG导致你的LED引脚分配失败。3.2 引脚分配Pin Planner拨码开关必须设为“asynchronous”LED要加电流限制引脚分配不是随便填它直接决定硬件能否工作。本工程关键信号分配规则如下Signal推荐引脚以DE2-115为例I/O StandardSpecial Settingsclk_50mPIN_Y23.3-V LVTTLClock → 必须接专用时钟引脚rst_nPIN_W163.3-V LVTTLWeak Pull-Up → 确保上电默认高sw_code[3:0]PIN_AB12, AB13, AB14, AB153.3-V LVTTLAsynchronous→ 关键拨码开关是异步输入不打拍会引发亚稳态key_enterPIN_W173.3-V LVTTLWeak Pull-Up → 按下接地释放靠上拉led_greenPIN_AE233.3-V LVTTLCurrent Limit: 8mA → 防止LED烧毁led_redPIN_AF233.3-V LVTTLCurrent Limit: 8mAbeep_outPIN_AG243.3-V LVTTL—提示“Asynchronous”设置在Pin Planner右键菜单 →I/O Pin Properties→Advanced标签页里。不设这个sw_code信号直接进状态机综合器会报Found 4-bit asynchronous input警告上板后密码输不对——因为亚稳态导致某一位采样错误。3.3 综合与布局布线关闭“Smart Compilation”手动指定Fitter SeedQuartus默认开启“Smart Compilation”它会自动跳过未修改的模块听起来省时间但在FPGA工程里是隐患。比如你改了state_machine.v但code_compare.v因为没动就被跳过结果新状态机逻辑和旧比对模块时序不匹配上板后行为诡异。必须关掉Assignments → Settings → Compiler →Uncheck “Enable Smart Compilation”Tools → Options → EDA Tool Options → 取消勾选所有EDA工具路径避免意外调用在Fitter设置里手动指定Seed值如12345保证每次编译布局布线结果一致便于对比时序报告编译完成后重点看两个报告Fitter Report → Resource Usage Summary确认Logic utilization≤ 15%本工程应为12~14%留足余量TimeQuest Timing Analyzer → Setup Summary检查Slack是否全部为正如1.23 ns负值意味着时序违例必须优化。3.4 下载与验证用USB-Blaster禁用“Program Configuration Device”下载前务必确认板子供电正常5V/3.3V LED亮USB-Blaster驱动已安装设备管理器里显示“Altera USB-Blaster”Quartus Programmer 中Hardware name 选USB-BlasterFile 选生成的.sof文件不是.pof注意绝对不要勾选 “Program Configuration Device”。.sof是SRAM配置文件掉电即失适合调试.pof是Flash配置文件写入后永久保存但一旦写错会导致板子变砖需JTAG恢复。新手阶段一律用.sof验证无误后再考虑固化。下载成功后立即做三步验证拨动sw_code到1010按key_enter→ 绿灯亮2秒拨到0000按key_enter→ 红灯闪3次蜂鸣器响连续输错3次 → 红灯常亮再按任何键无反应锁定生效。4. 常见问题排查5个真实翻车现场及血泪解决方案这个项目虽小但新手上手时90%的问题都集中在以下五个点。我整理了实验室里A同学、B同学反复踩过的坑按“现象→原因→解决”结构给出可立即执行的方案。4.1 现象按下按键绿灯不亮红灯也不闪蜂鸣器无声原因rst_n复位信号未正确接入或引脚配置为“weak pull-down”导致上电即被拉低解决用万用表测rst_n引脚电压正常应为3.3V高电平若为0V检查Pin Planner里是否误设为Weak Pull-Down在top_lock.v顶层加一句调试assign led_red ~rst_n;上板后若红灯常亮说明复位一直有效状态机卡在初始态修改Pin Planner右键rst_n→ I/O Pin Properties → Electrical → Pull Resistor →Weak Pull-Up。4.2 现象密码输对了绿灯只闪一下远小于2秒原因UNLOCK状态的计时器未用clk_50m分频而是直接用原始时钟计数导致定时精度错误解决检查state_machine.v中UNLOCK状态的计数逻辑确认是否用了分频后的clk_1hz或类似信号正确做法在beep_ctrl.v或单独timer_2s.v模块中用50MHz时钟计数100_000_000次2秒输出一个timer_done脉冲在状态机中UNLOCK状态只在timer_done为高时跳转到IDLE否则保持。4.3 现象拨码开关拨到1010但状态机始终不进入COMPARE状态原因sw_code信号未做同步处理亚稳态导致state_machine.v采样到错误值解决在top_lock.v中sw_code输入后必须经过两级D触发器同步reg [3:0] sw_sync1, sw_sync2; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin sw_sync1 4b1111; sw_sync2 4b1111; end else begin sw_sync1 sw_code; sw_sync2 sw_sync1; end end // 后续所有逻辑使用 sw_sync2而非直接用 sw_code修改state_machine.v中所有sw_code引用为sw_sync2。4.4 现象连续输错3次后红灯常亮但再次拨码按键状态机仍响应原因LOCKED状态的退出条件缺失未强制阻断所有输入路径解决在state_machine.v的主case语句最外层加一个LOCKED分支并在其中屏蔽所有输入LOCKED: begin next_state LOCKED; // 锁定态不响应任何输入 led_green 1b0; led_red 1b1; // 红灯常亮 beep_out 1b0; end同时检查key_valid和sw_code是否在LOCKED态下仍被送入比对逻辑——必须用enable信号门控确保LOCKED时enable 0。4.5 现象Quartus编译报错Error (10821): Net led_green has multiple drivers原因led_green在多个always块中被赋值Verilog语法不允许同一信号由多个过程块驱动解决全局搜索led_green确认它只在top_lock.v的顶层assign或一个always块中出现常见错误在state_machine.v里写了led_green 1b1;又在top_lock.v里写了assign led_green ...;二者冲突统一方案所有LED/蜂鸣器输出均由top_lock.v的assign语句驱动子模块只输出内部信号如green_on,red_on顶层做最终仲裁。5. 二次开发实战从4位密码升级到6位液晶屏显示的改造路径现在你已经能跑通基础版下一步就是把它变成一个真正可用的工程。我以“升级为6位密码1602液晶屏显示”为例讲清楚每一步怎么改、为什么这么改、改完如何验证。这不是理论推演而是我在某高校实验室带学生做的真实改造记录。5.1 扩展密码位宽从4位到6位只需改3个地方原工程用4位拨码开关但实际应用中6位更安全。扩展不是简单把sw_code[3:0]改成[5:0]就完事必须同步更新三处顶层端口top_lock.v中input [5:0] sw_code;并重新分配2个新引脚如 PIN_AC12, AC13比对模块code_compare.v的parameter和端口宽度同步改为6位module code_compare #( parameter [5:0] DEFAULT_CODE 6b101010 )( input [5:0] user_code, input enable, output match );状态机逻辑state_machine.v中所有sw_code相关操作从[3:0]改为[5:0]特别注意case语句里的default分支——原来4位有16种组合6位有64种default触发概率更高必须确保它导向安全态如ERROR。血泪经验改完必须重新跑仿真用Testbench给sw_code注入6b101010、6b000000、6b111111三组值观察match_result是否正确。我曾见过学生只改了端口忘了改code_compare.v结果6位密码永远比对失败debug两小时才发现参数还是4位。5.2 接入1602液晶屏用并行接口状态机驱动不增加CPU负担1602液晶屏用并行接口DB0~DB7 RS/RW/E共11根线。FPGA驱动它不能像MCU那样用延时函数必须用状态机精确控制时序。我推荐复用现有state_machine.v新增一个LCD_INIT和LCD_WRITE状态状态功能关键时序要求LCD_INIT发送初始化指令0x38,0x0C等每条指令后需延时40μsLCD_WRITE写入字符如LOCK、OPEN字符写入后延时37μs实现要点用clk_50m分频出clk_100k周期10μs作为LCD时钟基准每个LCD指令用一个子状态机例如LCD_CMD_38→WAIT_40US→LCD_CMD_0C→WAIT_40USWAIT_40US状态用计数器40μs / 10μs 4计数4次即够屏幕内容存在ROM中initial $readmemh(lcd_data.hex, lcd_rom);状态机按地址读取并输出到DB总线。这样改造后屏幕显示完全由FPGA自主完成不占用任何外部资源且显示内容可随状态实时更新如IDLE显示 INPUT CODELOCKED显示 LOCKED!。5.3 加入错误计数持久化用EEPROM存储错误次数断电不丢失基础版错误计数存在寄存器里断电清零。要实现“3次输错永久锁定”需外挂I2C EEPROM如AT24C02。改造分三步硬件连接SCL接FPGA任意IO如 PIN_AD12SDA接另一IOPIN_AD13加上拉电阻软件模块新增i2c_master.v标准I2C主机控制器和eeprom_ctrl.v封装读写API状态机集成在ERROR状态末尾调用eeprom_ctrl写入地址0x00的计数值在IDLE状态开始时先读取该地址值判断是否 ≥3。关键细节I2C通信必须用clk_50m分频出clk_100k100kHz标准速率且起始/停止条件需严格满足tSU:STA ≥ 4.7μs、tHD:STA ≥ 4.0μs。我一般会把I2C时序参数写成parameter方便不同EEPROM型号切换。5.4 验证方法论从波形仿真到实物联调的三级验证链改完代码不能直接上板必须走完三级验证RTL级仿真用ModelSim跑Testbench注入边界值如全0、全1、密码临界值检查match_result、lock_state波形是否符合预期门级仿真用Quartus生成.vo文件用ModelSim做Post-Fit仿真验证布局布线后时序是否仍满足尤其关注sw_code同步链路的建立/保持时间实物联调用逻辑分析仪抓sw_code、key_valid、state三信号确认按键与状态跃迁严格一一对应再用万用表测led_green对地电压确认高电平为3.3V非2.8V排除驱动不足。从那以后我每次做FPGA外设扩展都强制走一遍这三级验证——哪怕多花两小时也比上板后对着不亮的LED干瞪眼强。希望帮到你。本文还有配套的精品资源点击获取