简介:本资源是一套基于Xilinx EGO1开发板实现的FPGA密码锁完整工程,面向数字电路与FPGA初学者、课程设计学生及Verilog实践者,解决基础状态机设计、数码管动态扫描显示、按键消抖与密码验证逻辑等典型教学难点。压缩包共126个文件,涵盖27个XML约束与配置文件、16个PB与RST综合/实现报告、12个RPT时序分析日志、7个核心Verilog源文件(.v),以及BIT位流、DCP中间网表、XDC引脚约束、TCL自动化脚本等关键工程文件,总大小仅1.11MB,结构完整、开箱即用。已有1136人学习下载,适合快速上手Vivado全流程开发。读者可直接加载XPR工程,一键综合、实现并烧录至EGO1板卡,通过拨码开关或按钮输入三位密码,数码管实时显示输入状态与验证结果,配套脚本(runme.bat)和二进制文件(coded_lock.bit)已就绪,无需额外配置即可运行验证。
1. FPGA密码锁不是“烧进去就完事”的黑盒——EGO1开发板上跑通Verilog密码逻辑,关键在时序约束、按键消抖与状态机边界控制
你拿到一个标着“下载即可使用”的.rar包,解压后是Vivado工程和几份Verilog文件,双击password_lock.xpr打开,点击Generate Bitstream——结果卡在[DRC MDRV-1]或[Timing 38-282]报错;或者比特流生成成功,但按下按键毫无反应,LED不亮、数码管不刷新。这不是工程“不能用”,而是FPGA开发中典型的行为仿真通过、综合后功能异常、上板后时序违例三重陷阱。本项目本质是一个基于有限状态机(FSM)的同步数字系统:三位十进制密码输入(0–9)、按键确认/清除、错误计数锁定、数码管动态扫描显示。它不涉及加密算法,但对时钟域划分、异步信号同步化、组合逻辑毛刺抑制有刚性要求。适合刚学完Verilog语法、正尝试从仿真走向真实硬件的新手,也适合需要快速验证状态机建模规范性的中级开发者。文中所有操作均基于Xilinx EGO1(Artix-7 XC7A35T)开发板+Vivado 2022.2(兼容2021.2/2023.1),不依赖任何IP核或SDK,纯RTL实现。
2. 用Vivado在EGO1上跑通密码锁的最小闭环:从创建工程到烧录bitstream的6步实操
2.1 创建空工程并正确导入EGO1板级约束(.xdc文件不可省略)
Vivado新建工程时必须选择“RTL Project”,勾选“Do not specify sources at this time”。完成向导后,在Flow Navigator中点击“Add Sources” → “Add or create constraints”,选择“Create File”,命名为ego1_constraints.xdc。此文件必须明确定义以下4类物理约束,缺一不可:
# ego1_constraints.xdc —— 必须逐行复制,不可遗漏 set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { clk_100mhz }]; set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { rst_n }]; set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { key[0] }]; set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { key[1] }]; set_property -dict { PACKAGE_PIN W17 IOSTANDARD LVCMOS33 } [get_ports { key[2] }]; set_property -dict { PACKAGE_PIN W16 IOSTANDARD LVCMOS33 } [get_ports { key[3] }]; set_property -dict { PACKAGE_PIN U15 IOSTANDARD LVCMOS33 } [get_ports { seg[0] }]; set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { seg[1] }]; set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { seg[2] }]; set_property -dict { PACKAGE_PIN V13 IOSTANDARD LVCMOS33 } [get_ports { seg[3] }]; set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { seg[4] }]; set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { seg[5] }]; set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { seg[6] }]; set_property -dict { PACKAGE_PIN V10 IOSTANDARD LVCMOS33 } [get_ports { seg[7] }]; set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { an[0] }]; set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { an[1] }]; set_property -dict { PACKAGE_PIN U10 IOSTANDARD LVCMOS33 } [get_ports { an[2] }]; set_property -dict { PACKAGE_PIN U9 IOSTANDARD LVCMOS33 } [get_ports { an[3] }];注意:EGO1的
clk_100mhz引脚为T10(非常见开发板的E3),rst_n为低电平复位,key[0:3]对应4个独立按键(0=确认,1=清除,2=加1,3=减1),seg[0:7]为共阴极数码管段码(a~g+dp),an[0:3]为4位位选信号。若直接套用Basys3或Nexys4的约束文件,必然导致引脚冲突或功能错乱。
2.2 Verilog顶层模块必须显式声明时钟与复位域,禁止隐式推断
密码锁核心是同步时序电路,顶层模块password_lock_top.v必须严格遵循Xilinx推荐的同步复位风格,且所有寄存器必须由同一时钟驱动:
// password_lock_top.v —— 关键结构不可简化 module password_lock_top ( input logic clk_100mhz, input logic rst_n, input logic [3:0] key, output logic [7:0] seg, output logic [3:0] an ); // 所有子模块实例化前,先定义全局时钟使能信号 logic clk_1mhz; // 1MHz用于数码管扫描和按键采样 logic clk_10khz; // 10kHz用于密码输入状态机 logic rst_sync; // 同步化后的复位信号 // 时钟分频器(必须用计数器,禁用PLL IP除非明确需要) always_ff @(posedge clk_100mhz or negedge rst_n) begin if (!rst_n) begin clk_1mhz <= 1'b0; clk_10khz <= 1'b0; end else begin // 分频系数 = 100_000_000 / 1_000_000 = 50 → 计数50次翻转 static logic [5:0] cnt_1mhz; cnt_1mhz <= cnt_1mhz + 1; if (cnt_1mhz == 6'd49) begin clk_1mhz <= ~clk_1mhz; cnt_1mhz <= 0; end // 10kHz分频:100MHz / 10kHz = 10000 → 计数10000次 static logic [13:0] cnt_10khz; cnt_10khz <= cnt_10khz + 1; if (cnt_10khz == 14'd9999) begin clk_10khz <= ~clk_10khz; cnt_10khz <= 0; end end end // 复位同步化(两级触发器,消除亚稳态) logic rst_meta; always_ff @(posedge clk_100mhz) begin rst_meta <= !rst_n; rst_sync <= rst_meta; end // 实例化子模块(此处省略具体实例化代码,见2.3节) password_fsm uut_fsm ( .clk(clk_10khz), .rst(rst_sync), .key(key), .seg(seg), .an(an) ); endmodule提示:
clk_10khz是密码输入状态机的主时钟,避免直接用100MHz高频时钟驱动按键检测——否则单次按键可能被采样数十次,导致密码位被重复累加。rst_sync必须经两级寄存器同步,否则上电瞬间状态机可能进入非法状态(如IDLE→UNLOCKED跳变)。
2.3 密码输入状态机(FSM)的3个必调参数与2处易错边界
密码锁功能由password_fsm.v实现,其状态转移图包含IDLE、INPUT_DIGIT、VERIFY、LOCKED、UNLOCKED五种状态。关键参数必须可配置,而非硬编码:
| 参数名 | 默认值 | 作用 | 修改建议 |
|---|---|---|---|
MAX_ATTEMPTS | 3 | 连续输错次数上限 | 调试阶段设为1,验证锁定逻辑 |
PASSWORD_LEN | 3 | 密码位数 | 若扩展为4位,需同步修改digit_cnt位宽 |
DEBOUNCE_CYCLES | 20_000 | 按键消抖计数阈值(对应200μs) | EGO1 100MHz下,20_000 ≈ 200μs,低于此值易误触发 |
状态机核心代码中,两处边界必须显式处理:
// password_fsm.v 片段:输入状态下的关键判断 always_ff @(posedge clk or negedge rst) begin if (!rst) begin digit_cnt <= 0; input_digits <= 0; state <= IDLE; end else begin case (state) IDLE: begin if (key_valid) begin // key_valid由消抖模块输出 if (key == 4'b0001) state <= INPUT_DIGIT; // 按下"确认"键进入输入 end end INPUT_DIGIT: begin if (key_valid && key == 4'b0010) begin // "加1"键 input_digits[digit_cnt] <= input_digits[digit_cnt] + 1; // ▶️ 边界1:必须限制0–9循环,避免溢出导致显示异常 if (input_digits[digit_cnt] == 4'd9) input_digits[digit_cnt] <= 4'd0; end else if (key_valid && key == 4'b0100) begin // "减1"键 // ▶️ 边界2:减法必须防负数,否则4位寄存器会回绕成15 if (input_digits[digit_cnt] > 0) input_digits[digit_cnt] <= input_digits[digit_cnt] - 1; else input_digits[digit_cnt] <= 4'd9; end else if (key_valid && key == 4'b0001) begin // "确认"键 digit_cnt <= digit_cnt + 1; if (digit_cnt == PASSWORD_LEN-1) state <= VERIFY; end end // ... 其他状态 endcase end end提示:
key_valid信号必须由独立的按键消抖模块(key_debounce.v)生成,该模块对原始key信号进行边沿检测+计数延时,输出稳定电平。若将消抖逻辑写入状态机内部,会导致状态转移条件受毛刺干扰,出现“按一次键触发两次输入”。
3. 数码管动态扫描与段码译码:避免“鬼影”和“闪烁”的3层驱动设计
3.1 为什么EGO1的4位数码管必须用动态扫描而非静态驱动?
EGO1开发板采用共阴极4位数码管,每位8段(a~g+dp),若用静态驱动需32个IO口(4位×8段),但实际只引出8段码线+4位选线(共12根)。因此必须采用动态扫描:以1kHz以上频率轮流通电每一位,利用人眼视觉暂留形成“同时点亮”假象。扫描频率过低(<50Hz)会导致肉眼可见闪烁;过高(>5kHz)则每位点亮时间过短,亮度不足。
3.2 三层驱动架构:扫描控制器 → 位选译码 → 段码查表
完整驱动链路分为三级,每级职责清晰:
- 扫描控制器(
scan_ctrl.v):生成4位轮流使能信号an[3:0],周期固定为1ms(即1kHz扫描率) - 位选译码器(组合逻辑):将当前扫描位索引
scan_idx[1:0]转换为an信号(如scan_idx=2'b00 → an=4'b1110) - 段码查表(
seg_decode.v):根据当前要显示的数字digit_val[3:0],查表输出seg[7:0](共阴极真值表)
// seg_decode.v —— 共阴极段码表(a~g+dp顺序,低电平点亮) module seg_decode ( input logic [3:0] digit, output logic [7:0] seg ); always_comb begin unique case (digit) 4'h0: seg = 8'b00000011; // a~g+dp, dp=1表示小数点灭 4'h1: seg = 8'b10011111; 4'h2: seg = 8'b00100101; 4'h3: seg = 8'b00001101; 4'h4: seg = 8'b10011001; 4'h5: seg = 8'b01001001; 4'h6: seg = 8'b01000001; 4'h7: seg = 8'b00011111; 4'h8: seg = 8'b00000001; 4'h9: seg = 8'b00001001; default: seg = 8'b11111111; // 全灭 endcase end endmodule注意:段码表必须与硬件实物一致。EGO1数码管为共阴极,故段码为“低电平点亮”,
seg=8'b00000011表示a~g全灭、dp灭(即全黑),而8'b00000001表示仅dp点亮。若误用共阳极表,所有数字将反相显示(如0显示为8)。
3.3 防“鬼影”关键:位选与段码必须严格同步更新
动态扫描最大风险是“鬼影”(crosstalk):当an[0]关闭、an[1]开启的切换瞬间,若seg信号尚未稳定,会导致两位同时微弱发光。解决方案是在扫描时钟上升沿同时更新an和seg:
// scan_ctrl.v 中的关键同步逻辑 always_ff @(posedge clk_1mhz or negedge rst) begin if (!rst) begin scan_idx <= 2'b00; seg_data <= 8'hFF; end else begin scan_idx <= scan_idx + 1; // ▶️ 在scan_idx更新后,立即查表获取对应位的段码 case (scan_idx) 2'b00: seg_data <= decode_digit(digit_0); // digit_0为第0位密码值 2'b01: seg_data <= decode_digit(digit_1); 2'b10: seg_data <= decode_digit(digit_2); 2'b11: seg_data <= decode_digit(digit_3); endcase end end // 输出端口赋值(在同一个always块内完成) assign an = ~{1'b0, 1'b0, 1'b0, 1'b1} << scan_idx; // 位选信号左移 assign seg = seg_data; // 段码与位选严格同步提示:
an信号必须用~{...}<<scan_idx方式生成,确保每次只有一位为低电平(共阴极有效),且切换无中间态。若用case语句分别赋值,综合工具可能插入组合逻辑延迟,破坏同步性。
4. 上板调试的4个必查点与2个典型故障定位方法
4.1 烧录后无任何反应?按顺序检查这4个物理层问题
| 检查项 | 操作方法 | 正常现象 | 异常表现 |
|---|---|---|---|
| 电源指示灯 | 观察EGO1板右上角红色LED(PWR) | 常亮红色 | 不亮 → 检查USB供电或JTAG线是否插牢 |
| 配置完成灯 | 观察板载绿色LED(DONE) | 烧录完成后常亮 | 闪烁或不亮 → 比特流生成失败或配置时序错误 |
| 时钟输入 | 用示波器测T10引脚 | 100MHz方波,峰峰值3.3V | 无信号 → 约束文件中clk_100mhz引脚写错 |
| 复位按键 | 按下板载SW1(标有RST) | DONE灯短暂熄灭后恢复 | 无反应 →rst_n约束引脚错误或原理图未连接 |
注意:EGO1的
DONE灯不亮是最高频问题,根源90%在于约束文件缺失或引脚写错。务必确认ego1_constraints.xdc已添加至工程Sources → Constraints节点下,且文件状态为“Used in Implementation”。
4.2 功能异常时的2个精准定位法:ILA抓取与LED辅助观测
当密码输入无响应或数码管显示错乱,不要盲目改代码。先用Xilinx ILA(Integrated Logic Analyzer)抓取关键信号:
- 添加ILA核:在Vivado中右键Design Sources → “Set as Top”,然后Tools → Debug → “Set Up Debug”,勾选
key[3:0]、state、digit_cnt、input_digits等信号,采样深度设为1024 - 重新生成比特流并烧录,打开Hardware Manager → 点击“Open Target” → “Auto Connect”,双击ILA核启动波形窗口
- 触发条件设置:将Trigger Setup设为
key_valid == 1,这样每次按键都会捕获前后100周期波形,直观看到消抖效果与状态跳变时机
若无ILA许可或想快速验证,用板载LED做辅助观测:
// 在top模块中临时添加LED观测 assign led[0] = rst_sync; // 复位信号:常亮表示复位未释放 assign led[1] = clk_10khz; // 10kHz时钟:应为快速闪烁(≈100Hz肉眼可见) assign led[2] = (state == VERIFY); // 验证状态:按下确认键时应闪一下 assign led[3] = (error_cnt > 0); // 错误计数:输错时亮起提示:EGO1的LED引脚为U16(LED0)、E19(LED1)、U19(LED2)、V19(LED3),需在
.xdc中添加对应约束。通过LED状态可5秒内区分是逻辑错误(LED2不闪)还是时序问题(LED1不闪)。
4.3 解决“生成比特流失败:[Synth 8-439]”的Verilog写法避坑清单
该错误通常因Verilog语法违反综合规则导致,常见于密码锁工程:
❌ 错误:在
always_ff块中对同一信号既用阻塞赋值又用非阻塞赋值
✅ 正确:全部使用<=,且不混用=(阻塞赋值仅用于always_comb)❌ 错误:
case语句未覆盖所有分支且未写default,导致锁存器推断
✅ 正确:所有case必须含default,且default分支给所有输出赋确定值❌ 错误:用
real或time类型声明变量(综合工具不支持)
✅ 正确:全部使用logic、reg、wire,计数器用logic [N-1:0]❌ 错误:在
assign语句右侧调用未定义函数(如$display)
✅ 正确:$display仅限仿真,综合时必须删除或用ifdef SIMULATION包裹
最后一步:当ILA确认key_valid稳定、state正常跳转、digit_cnt递增,但数码管仍不显示,立即检查seg与an的电平逻辑——共阴极要求an为低电平选中、seg为低电平点亮段,二者必须同时满足。此时用万用表测an[0]引脚,应看到1kHz方波;测seg[0](a段),应随数字变化在0V/3.3V间切换。信号存在但显示异常,唯一可能是段码表与硬件不匹配,更换为本文提供的共阴极表即可。
本文还有配套的精品资源,点击获取