
简介本资源是一套基于Xilinx EGO1开发板实现的FPGA密码锁完整工程面向数字电路与Verilog初学者及嵌入式硬件实践者解决基础状态机设计、数码管动态扫描显示、按键消抖与密码验证逻辑等典型FPGA开发问题。压缩包共126个文件涵盖27个XML约束与配置文件、16个PB与RST综合/实现报告、12个RPT时序分析结果、7个Verilog源码.v、4个DCP网表文件及BIT位流文件等完整覆盖Vivado工程从设计、综合、布局布线到下载调试全流程总大小仅1.11MB轻量易部署。已有1136人学习下载资源开箱即用包含已编译生成的coded_lock.bit可编程文件、runme.bat一键脚本、XDC引脚约束及HTML使用说明无需额外配置即可在EGO1板上运行三位密码设置与验证功能数码管实时显示输入状态与结果适合课程实验、课程设计及FPGA入门项目实战。1. 三位密码锁在EGO1上跑通不是“烧进去就亮”而是要搞懂数码管扫描节拍与按键消抖的时序耦合你拿到这个.rar包解压后双击runme.batVivado 自动打开工程、综合、实现、生成比特流——最后把coded_lock.bit下载进 EGO1 开发板数码管显示---按 0~9 键输入三位数字再按#确认绿灯亮表示解锁成功。表面看是“下载即用”但实际调试中 70% 的失败不是代码写错而是没理清三个关键时序链按键采样周期 vs 数码管刷新周期 vs 密码状态机跳转时机。这个工程不是教学 Demo它用纯组合逻辑同步时序混合建模把密码比较、数码管段码译码、动态扫描控制全塞进一个顶层模块coded_lock.v没有调用 Xilinx IP 核适合想从零理解 FPGA 数码管交互逻辑的工程师。如果你刚学 Verilog它比 LED 流水灯难但比 UART 通信简单如果你已用过 Zynq 或做过多通道 ADC 采集你会立刻注意到它对clk_100MHz的分频策略——只用了两级计数器100MHz → 1kHz → 100Hz却支撑起 4 位共阴数码管的稳定扫描和 20ms 按键防抖这种资源精打细算的写法在 EGO1 这类 Spartan-6 12KB Block RAM 的小资源板上非常典型。2. 从顶层模块到状态机Verilog 实现密码锁的三重时序约束解析2.1 顶层模块coded_lock.v的信号拓扑与资源映射逻辑该工程未使用 XDC 约束文件显式声明引脚而是依赖 Vivado 自动映射见runme.bat中vivado -mode batch -source scripts/synth.tcl调用逻辑。但实际引脚绑定必须人工核对否则数码管不亮或按键无响应。核心信号映射如下信号名方向物理引脚EGO1 Base Board功能说明clkinputJ18 (100MHz)主时钟源所有逻辑以此为基准rst_ninputT15 (SW0)低电平复位对应拨码开关 SW0key_in[3:0]inputU18, U17, T17, T18 (KEY0~KEY3)四位独立按键其中 KEY00, KEY11, KEY22, KEY3#确认键seg_sel[3:0]outputU13, U14, T14, T13 (AN0~AN3)数码管位选信号低电平有效共阴seg_data[7:0]outputT12, U12, U11, V10, U9, U8, V8, V7 (CA~CGDP)段码输出CA 对应最高位注意key_in并非矩阵键盘而是 4 个独立按键直连 FPGA因此无需行列扫描逻辑seg_sel采用静态驱动非 PWM 调光靠 100Hz 扫描频率实现视觉暂留。该模块实例化了三个子模块key_debounce按键消抖、display_ctrl数码管动态扫描、password_fsm密码状态机。它们共享同一clk但各自运行在不同分频时钟域——这是理解整个系统时序的关键。2.2 按键消抖模块key_debounce.v的两级同步与计数器设计消抖不是简单延时 20ms而是用两级寄存器同步 计数器锁定。源码关键片段如下// key_debounce.v module key_debounce ( input clk, input rst_n, input [3:0] key_raw, output reg [3:0] key_valid ); reg [3:0] key_sync1, key_sync2; reg [19:0] cnt; // 20-bit counter for ~20ms 100MHz always (posedge clk or negedge rst_n) begin if (!rst_n) begin key_sync1 4b1111; key_sync2 4b1111; cnt 0; key_valid 4b1111; end else begin key_sync1 key_raw; key_sync2 key_sync1; if (key_sync1 ! key_sync2) begin // detect edge cnt 0; end else if (cnt 20_000_000 - 1) begin // 100MHz * 20ms 2e6 cycles cnt cnt 1; end else begin key_valid key_sync2; // stable value latched end end end endmodule这段代码实现的是「边沿触发式消抖」当key_raw变化时重置计数器仅当连续20_000_000个时钟周期即 20ms内值不变才将key_sync2锁存为key_valid。这里有两个易错点计数器位宽必须 ≥21 位20_000_000的二进制是100110001001011010000000025 位但工程中用19:020 位能覆盖0到2^20-1 1,048,575远小于所需值 —— 实际运行会提前溢出导致消抖时间不足 1ms。正确做法是改用reg [24:0] cnt并设上限20_000_000-1。key_valid是电平信号非脉冲它持续反映当前稳定按键状态因此password_fsm必须检测key_valid的下降沿松开或上升沿按下不能直接判断key_valid 4b0111KEY0 按下——因为只要按键保持按下该值就一直为真会导致重复录入。2.3 数码管动态扫描模块display_ctrl.v的 100Hz 刷新率与段码查表逻辑EGO1 板载 4 位共阴数码管需以 ≥50Hz 频率轮询每位否则出现闪烁。本工程采用100Hz周期 10ms由clk_100MHz经两级分频得到// display_ctrl.v reg [16:0] scan_cnt; // 100MHz / 100000 1000Hz → 再 /10 100Hz always (posedge clk or negedge rst_n) begin if (!rst_n) scan_cnt 0; else scan_cnt scan_cnt 1; end assign scan_clk scan_cnt[16]; // 100Hz enable signal reg [1:0] digit_sel; always (posedge clk or negedge rst_n) begin if (!rst_n) digit_sel 0; else if (scan_clk) digit_sel digit_sel 1; end // 查表digit_data[3:0] 为当前要显示的数字0~9,A~F,- // seg_data 输出 CA~CGDP 共 8 位段码 always (*) begin case (digit_data[digit_sel]) 4h0: seg_data 8b11000000; // CA1, CB1, CC0... 4h1: seg_data 8b11110011; ... 4hf: seg_data 8b10001000; default: seg_data 8b11111111; // all off endcase end这里scan_cnt[16]是关键100MHz时钟下2^17 131072131072 ≈ 100000所以scan_cnt[16]翻转周期约为131072 / 100e6 1.31ms再经digit_sel计数器分频为 4 位 × 1.31ms ≈ 5.24ms 周期即190Hz高于 100Hz 要求。但实际digit_sel是 2 位计数器0→1→2→3→0每scan_clk上升沿加 1因此每位显示时间为1/100Hz ÷ 4 2.5ms完全满足人眼余晖要求。提示段码表必须与硬件数码管类型严格匹配。EGO1 使用共阴极seg_data[7:0]中1表示点亮0表示熄灭若误用共阳极段码表如8b00111111则显示全黑或乱码。2.4 密码状态机password_fsm.v的四态迁移与比较逻辑状态机采用 Moore 型输出仅取决于当前状态避免毛刺。四个状态定义如下状态编码触发条件输出行为IDLE2b00复位或解锁成功后显示---等待输入INPUT2b01检测到任意key_valid变化将按键值存入input_buf[2:0]显示当前输入位如输第一位显示-0-CHECK2b10key_valid[3]KEY3/#拉低比较input_buf与password_reg结果驱动led_greenUNLOCK2b11input_buf password_reg且key_valid[3]释放保持led_green15 秒后自动返回 IDLE比较逻辑在CHECK状态中完成// password_fsm.v reg [11:0] password_reg 12h123; // 默认密码 123 reg [11:0] input_buf; always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else case (state) IDLE: if (key_valid ! 4b1111) state INPUT; INPUT: if (key_valid[3] 0) state CHECK; // # key pressed CHECK: if (key_valid[3] 1) begin // # key released if (input_buf password_reg) state UNLOCK; else state IDLE; end UNLOCK: if (timer_expired) state IDLE; endcase end注意input_buf的更新方式不是每次按键都覆盖而是按顺序填入input_buf[3:0]的低三位。例如输入1→2→3input_buf依次变为4h1,4h12,4h123最终与12h123比较。这种设计避免了移位寄存器带来的额外时序路径但要求用户必须严格按三位输入多按或少按均视为错误。3. Vivado 工程构建与比特流生成从 Tcl 脚本到 bit 文件的全流程实操3.1runme.bat脚本的执行链与关键 Tcl 命令解析runme.bat并非简单启动 Vivado GUI而是通过批处理调用 Tcl 脚本完成自动化流程。其核心内容如下echo off set VIVADO_PATHC:\Xilinx\Vivado\2022.2\bin\vivado.bat %VIVADO_PATH% -mode batch -source scripts/synth.tcl -notrace pausescripts/synth.tcl是真正干活的脚本关键命令分解# synth.tcl create_project coded_lock ./project -part xc6slx9-2-tqg144 set_property target_language Verilog [current_project] add_files -fileset sources_1 ./src/coded_lock.v ./src/key_debounce.v ./src/display_ctrl.v ./src/password_fsm.v import_files -fileset constrs_1 ./constrs/ego1.xdc ;# 注意原始包中无此文件需手动创建 update_compile_order -fileset sources_1 launch_runs synth_1 wait_on_run synth_1 launch_runs impl_1 wait_on_run impl_1 write_bitstream -force ./output/coded_lock.bit注意原始.rar包中不含 XDC 约束文件import_files行会报错。必须手动创建./constrs/ego1.xdc内容需严格匹配 EGO1 开发板原理图。常见错误是将AN0~AN3位选与CA~CGDP段码引脚写反导致数码管全亮或全暗。3.2 手动补全 EGO1 引脚约束文件ego1.xdc根据 Digilent 官方 EGO1 Reference Manual Rev1.0关键引脚定义如下仅列出本工程必需部分# ego1.xdc set_property PACKAGE_PIN J18 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property PACKAGE_PIN T15 [get_ports rst_n] set_property IOSTANDARD LVCMOS33 [get_ports rst_n] # Key inputs (active low) set_property PACKAGE_PIN U18 [get_ports key_in[0]] set_property PACKAGE_PIN U17 [get_ports key_in[1]] set_property PACKAGE_PIN T17 [get_ports key_in[2]] set_property PACKAGE_PIN T18 [get_ports key_in[3]] set_property IOSTANDARD LVCMOS33 [get_ports key_in[*]] # Digit select (active low, common cathode) set_property PACKAGE_PIN U13 [get_ports seg_sel[0]] # AN0 set_property PACKAGE_PIN U14 [get_ports seg_sel[1]] # AN1 set_property PACKAGE_PIN T14 [get_ports seg_sel[2]] # AN2 set_property PACKAGE_PIN T13 [get_ports seg_sel[3]] # AN3 set_property IOSTANDARD LVCMOS33 [get_ports seg_sel[*]] # Segment data (CA~CGDP) set_property PACKAGE_PIN T12 [get_ports seg_data[0]] # CA set_property PACKAGE_PIN U12 [get_ports seg_data[1]] # CB set_property PACKAGE_PIN U11 [get_ports seg_data[2]] # CC set_property PACKAGE_PIN V10 [get_ports seg_data[3]] # CD set_property PACKAGE_PIN U9 [get_ports seg_data[4]] # CE set_property PACKAGE_PIN U8 [get_ports seg_data[5]] # CF set_property PACKAGE_PIN V8 [get_ports seg_data[6]] # CG set_property PACKAGE_PIN V7 [get_ports seg_data[7]] # DP set_property IOSTANDARD LVCMOS33 [get_ports seg_data[*]]提示set_property IOSTANDARD LVCMOS33必须为所有 I/O 添加否则 Vivado 默认DEFAULT电平标准可能导致输入识别错误。key_in和seg_sel均为低电平有效但 Verilog 代码中已按此逻辑设计如key_valid[3]0表示#键按下因此 XDC 中无需额外取反。3.3 比特流生成失败的三大高频原因与定位方法生成coded_lock.bit失败时Vivado 报错通常指向以下三类问题错误类型典型报错信息定位方法解决方案引脚冲突ERROR: [DRC 23-20] Rule violation (IOSTANDARDS-1)查Vivado → Flow Navigator → Open Synthesized Design → I/O Planning检查黄色警告图标在I/O Planning视图中右键引脚 →Edit Pin Planning→ 修改IOSTANDARD或PACKAGE_PIN时序违例WARNING: [Timing 38-282] The design failed to meet the timing requirement.查Reports → Timing → Report Timing Summary看WNS (ns)是否为负降低clk频率如改用clk_50MHz分频、插入(* keep true *)保留关键路径寄存器、或重写关键逻辑为流水线结构资源超限ERROR: [Place 30-639] IO Clock Placer failed...查Reports → Utilization → Report Utilization看Slice LUTs使用率是否 100%删除未用信号、将password_reg改为parameter编译期常量、或合并display_ctrl与password_fsm的部分逻辑特别提醒Spartan-6 的BUFG全局时钟缓冲器仅支持 8 个若工程中意外例化多个BUFG如每个模块都BUFG clk_out会导致布线失败。本工程未显式调用BUFG但若后续添加 PLL 或 DDR 控制器需手动检查Report DRC中ROUTE-3类错误。4. 密码修改与功能扩展从硬编码密码到可配置 EEPROM 存储4.1 修改默认密码的两种安全方式原始代码中密码12h123写死在password_fsm.v的reg [11:0] password_reg 12h123;。直接修改此处虽快但存在两个隐患版本管理困难每次改密码都要重新综合无法 runtime 更新安全性低bit 文件反编译可轻易提取明文密码。推荐两种升级方案方案一参数化密码推荐新手将密码改为parameter在顶层模块传入// coded_lock.v module coded_lock #( parameter PASSWORD 12h123 ) ( input clk, input rst_n, input [3:0] key_in, output [3:0] seg_sel, output [7:0] seg_data, output led_green ); // ... 实例化子模块时传入 PASSWORD password_fsm #(.PASSWORD(PASSWORD)) uut_fsm ( .clk(clk), .rst_n(rst_n), .key_valid(key_valid), .led_green(led_green) ); endmodule然后在synth.tcl中设置set_property generic {PASSWORD12h456} [get_cells coded_lock]这样修改密码只需改 Tcl 参数无需动 RTL且综合后密码固化在网表中比寄存器存储更难逆向。方案二SPI 接口读取外部 EEPROM进阶EGO1 板载 SPI FlashW25Q80可用于存储密码。需添加spi_master模块通过spi_sclk/spi_mosi/spi_miso/spi_cs读取地址0x0000处的 12-bit 密码。关键代码// spi_read.v reg [11:0] eeprom_password; always (posedge spi_clk) begin if (spi_done) eeprom_password {spi_rx_data[15:4]}; // W25Q80 读状态寄存器返回 2B密码存于前 12bit end注意W25Q80 的spi_cs必须由 FPGA 主动拉低且spi_clk频率 ≤20MHz。需在ego1.xdc中添加set_property PACKAGE_PIN V15 [get_ports spi_cs] set_property PACKAGE_PIN U16 [get_ports spi_sclk] set_property PACKAGE_PIN V16 [get_ports spi_mosi] set_property PACKAGE_PIN U15 [get_ports spi_miso]4.2 数码管显示增强支持错误次数计数与倒计时锁定原工程无防暴力破解机制。可在password_fsm.v中添加error_cnt计数器reg [2:0] error_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) error_cnt 0; else if (state CHECK input_buf ! password_reg) error_cnt error_cnt 1; else if (state UNLOCK) error_cnt 0; end // 当 error_cnt 3 时进入 LOCKED 状态显示 ERR 并禁用输入 30 秒 always (posedge clk) begin if (error_cnt 3 lock_timer 30_000_000) begin // 30s 100MHz state IDLE; lock_timer 0; end end对应数码管显示需扩展段码表增加ERR的8b00000100E、8b00100000R、8b00000000R——注意共阴极下0为灭1为亮因此ERR实际段码为8b11111011E、8b11011111R、8b11011111R。4.3 验证密码锁功能的三种实测方法不要只依赖下载后看数码管必须用以下方法交叉验证Vivado Simulator 波形验证创建 testbench注入key_in序列{4b1110, 4b1101, 4b1011, 4b0111}即按 0→1→2→#观察led_green在第 4 个周期后拉高且seg_data正确输出0、1、2的段码。ILAIntegrated Logic Analyzer在线抓取在password_fsm.v中添加(* mark_debug true *) reg [11:0] debug_input_buf input_buf; (* mark_debug true *) reg [1:0] debug_state state;综合后在Flow Navigator → Program and Debug → Open Hardware Manager中添加 ILA 核触发条件设为debug_state 2b10CHECK 状态即可实时查看input_buf与password_reg的值。物理按键压力测试连续快速按0#10 次观察是否出现led_green误触发。若发生说明key_debounce的计数器位宽不足或key_valid边沿检测逻辑有缺陷——此时应将key_debounce改为检测key_valid的下降沿按键释放瞬间而非电平持续为低。最终当你看到 EGO1 板上数码管稳定显示123按下#后绿灯亮起且用 ILA 抓到input_bufpassword_reg12h123才算真正吃透这个 FPGA 密码锁工程的全部技术细节。本文还有配套的精品资源点击获取