尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA/VHDL 4位电子密码锁:自建时钟替代状态机与左移寄存器

FPGA/VHDL 4位电子密码锁:自建时钟替代状态机与左移寄存器 简介这份基于VHDL的4位电子密码锁设计文档面向数字电路课程设计学生及FPGA初学者围绕DE2开发板给出从需求分析到硬件落地的完整方案。资源以docx格式提供压缩包内仅1个文件体积约153KB内容为大连理工大学数字电路课程设计实验报告涵盖设计要求、系统方案、模块划分、管脚分配、数码管真值表及VHDL源代码。读者可据此了解4位8421BCD密码输入、只取前4位、三次重试与报警锁定、密码重置、7段数码管逐位左移显示、取消键重新输入等功能的实现思路并掌握分频模块、模4与模3计数器、密码比较和重置、LED正确/错误指示等模块设计。文档中给出清晰的模块框图和端口信号说明便于按步骤复现与验证对课设报告撰写与FPGA调试具有直接参考价值。目前已有178人学习下载适合需要课设参考、VHDL入门或综合练习的读者。1. 一个自建 clock 的 4 位密码锁为什么比状态机更值得先学实验室里 4 个按键、一排拨码开关、4 个七段数码管要求做出一个能改密码、错三次报警的锁。多数人第一反应是把所有逻辑塞进一个状态机 process。真到上板才发现最先出问题的不是密码比较而是“每按一次键数码管左移一位”——按键给的是电平不是时钟数码管根本不动作。这套基于 VHDL 的 4 位电子密码锁核心手法是自己造一个 clock拨码开关组合经译码后拉高该信号用它的上升沿驱动左移寄存器同时用模 5000 分频器把 50MHz 系统时钟压到 10000Hz保证人手拨开关的间隔远大于采样周期边沿不会漏掉。密码为 4 位 8421BCD 码超过 4 位只取前 4 位错三次拉高报警管理员用 reset 低电平解除密码正确后可通过 set_psw 改密。目标板是 DE2芯片 EP2C35F672C6。适合学过数字电路、手边有 FPGA 板子、想找完整案例练管脚约束和仿真的人。2. 从拨码开关到数码管模块划分与 DE2 管脚约束先把系统拆成能单独仿真的小块再拼顶层是这个设计最省时间的地方。DE2 上可用按键只有 4 个而密码有 10 个数字要输入所以输入用 switch[9]~switch[0] 的独热编码来模拟数字 9~0一个开关对应一个数字物理上不可能同时拨两个天然避免了编码冲突。整条数据通路是开关 → 输入译码8421BCD→ 左移寄存器 → 密码比较 → LED 指示控制通路则是start 使能、cancel 清零、yes 确认、set_psw 改密、reset 管理员复位。2.1 五个模块的职责边界模块输入输出关键信号输入译码num[9:0]q[3:0]、clock独热码转 BCD产生移位时钟显示驱动out_q[15:0]out0~out3[6:0]共阳极段码低电平点亮左移控制clock、cnt4out_q[15:0]模 4 计数超 4 位丢弃比较与重置out_q、psw、yescorrect、out_error模 3 计数三次报警报警与解锁reset、alarmalarmreset0 时强制清报警这里最容易被忽略的是模 4 计数器的定位。设计需求写的是“多于 4 位密码只取前 4 位”有两种实现一是在比较时只截取高 4 位二是干脆不让第 5 位挤进寄存器。这份代码选了后者用 cnt4 在计数到 4 后停止移位输入第 5 个数字时null掉逻辑更直接也避免了显示上出现“第 5 位把有效位顶出去”的错觉。2.2 顶层端口与管脚映射顶层实体 coded_lock 的端口共 11 个clk、start、reset、yes、cancel、set_psw、num[9:0]、out3~out0、correct、alarm、out_error。在 Quartus 里落地成约束文件写法不是图形界面点出来的而是直接写 qsf# 50MHz 系统时钟 set_location_assignment PIN_N25 -to clk # 拨码开关 num[0]~num[9]对应数字 0~9 set_location_assignment PIN_N25 -to num[0] set_location_assignment PIN_N26 -to num[1] set_location_assignment PIN_P25 -to num[2] set_location_assignment PIN_AE14 -to num[3] set_location_assignment PIN_AF14 -to num[4] set_location_assignment PIN_AD13 -to num[5] set_location_assignment PIN_AC13 -to num[6] set_location_assignment PIN_C13 -to num[7] set_location_assignment PIN_B13 -to num[8] set_location_assignment PIN_A13 -to num[9] # 功能键 set_location_assignment PIN_U4 -to start set_location_assignment PIN_G26 -to yes set_location_assignment PIN_V1 -to cancel set_location_assignment PIN_N23 -to set_psw set_location_assignment PIN_V2 -to reset # LED 指示 set_location_assignment PIN_AE22 -to correct set_location_assignment PIN_AE23 -to out_error set_location_assignment PIN_AF23 -to alarm约束里的每个 PIN 都必须与硬件手册一致写错一个现象往往是“功能都对就是某个灯不亮”很容易被误判成逻辑 bug。数码管侧同理out0[0]~out0[6] 接到 PIN_AF10、PIN_AB12、PIN_AC12、PIN_AD11、PIN_AE11、PIN_V14、PIN_V13out1、out2、out3 依次接 PIN_V20 起的三组、PIN_AB23 起的三组、PIN_Y23 起的七段。四组段码线一共 28 根接线量大建议先只连 out0 调通一位再复制到其余三位。注意 num[0] 与 clk 不能共用同一个引脚原文管脚表里两处都写了 PIN_N25属于表格笔误实际分配时要给 num[0] 另选一个空闲开关引脚否则综合能过、上板时钟直接失效。3. 分频、左移寄存器与密码比较的 VHDL 实现这份源码最值得抄的不是密码比较而是时钟域的处理方式。系统时钟 50MHz人拨一次开关的时间动辄几十毫秒两者差了六个数量级直接拿 50MHz 去采样按键电平会在一次拨动期间采到成百上千个高电平左移寄存器一次按键就移位几十次。所以必须分频把采样节奏降下来。3.1 模 5000 分频50MHz 降到 10000Hz-- 分频模块50MHz - 10000Hz process(clk) variable tem : integer range 0 to 4999; begin if rising_edge(clk) then if tem 4999 then clk_in 1; tem : 0; else tem : tem 1; clk_in 0; end if; end if; end process;计满 4999 个系统时钟周期clk_in 输出一个周期的高电平等效分频比 5000输出 10000Hz周期 0.1ms。这个数值不是随便取的人手拨动开关的最短间隔约几十毫秒10000Hz 的采样周期远小于它既保证不漏采又不会在一个电平内重复移位。tem 用 variable 而非 signal是为了在同一个 process 内立即更新避免引入额外延迟范围约束 0 to 4999 让综合器知道位宽省掉默认 32 位比较器。常见改法是只降一档比如把分频比改成 500输出 100kHz仿真跑得快但上板后按键响应会变“过于灵敏”一次拨动仍可能被采到两次。3.2 开关译码与自建 clock 的边沿构造process(clk_in, start, reset, enable) begin if reset 0 then clock 0; elsif rising_edge(clk_in) then if start 1 and enable 1 then case num is when 1000000000 q 1001; clock 1; -- 开关9 - 9 when 0100000000 q 1000; clock 1; -- 开关8 - 8 when 0010000000 q 0111; clock 1; when 0001000000 q 0110; clock 1; when 0000100000 q 0101; clock 1; when 0000010000 q 0100; clock 1; when 0000001000 q 0011; clock 1; when 0000000100 q 0010; clock 1; when 0000000010 q 0001; clock 1; when 0000000001 q 0000; clock 1; -- 开关0 - 0 when others q 1111; clock 0; end case; end if; end if; end process;这段就是整个设计的“心脏”。开关拨到某一位case 命中q 得到对应的 8421BCD 码同时 clock 拉高松开开关后 num 变成全 0落到when othersclock 回到低电平。下一次拨动clock 再产生一个上升沿。整个过程由 clk_in 同步所以边沿干净、无毛刺。enable 是第三次错密后的封锁位为 0 时 case 不再执行键盘直接失效这就是“报警后无法继续尝试”的实现方式比在比较模块里判断更省资源。需要注意when others里把 q 置成 1111对应七段译码输出全灭正好把松开开关的瞬间隐藏掉不会在数码管上闪出杂码。3.3 左移寄存器与模 4 计数器process(clock, yes, start, reset, cancel) begin if reset 0 then correct 0; out_error 0; counter 0; enable 1; alarm 0; out_q 1111111111111111; elsif cancel 1 then -- 取消本次输入 correct 0; out_error 0; out_q 0000000000000000; cnt4 0; elsif start 0 then -- 未启动寄存器清零 cnt4 0; out_q 0000000000000000; else if rising_edge(clock) then if cnt4 4 then null; -- 已满 4 位第 5 位丢弃 else out_q out_q(11 downto 0) q; -- 左移 4 位新数进最低位 cnt4 cnt4 1; end if; end if; end if; end process;out_q(11 downto 0) q是典型的拼接移位高 12 位整体下移新输入的 4 位 BCD 落到最低 4 位也就是最右边的数码管 u0旧数据依次向 u3 方向移动。cnt4 从 0 计到 4到 4 后锁死实现“只取前 4 位”。cancel 分支把 out_q 和 cnt4 一起清零重新开始一轮输入start0 是待机态同样保持清零。注意这里用rising_edge(clock)而不是clockevent and clock1前者是标准写法综合结果一致但可读性更好。3.4 密码比较、三次机会与模 3 计数器if falling_edge(yes) then if out_q psw then correct 1; out_error 0; alarm 0; elsif counter 2 then -- 第三次仍错 correct 0; out_error 1; alarm 1; enable 0; -- 封锁键盘 else correct 0; out_error 1; alarm 0; counter counter 1; end if; end if;比较用out_q psw直接做 16 位向量相等判断综合出来是一排同或门加与门资源很小。判断沿选的是 yes 的下降沿也就是松开确认键的那一刻才比较避免按住时重复触发。counter 从 0 开始等于 2 时说明前面已经错了两回这是第三次于是拉高 alarm 并把 enable 清 0。默认密码 psw 初值写成1001100001110110四位分别是 9、8、7、6与 8421BCD 一一对应。这里有个容易踩的坑counter 是 integer 类型仿真初值默认是 integerleft必须显式赋 0否则首次比较时counter 2的判断行为不可预期。3.5 密码重设与共阳极段码process(correct, set_psw) begin if falling_edge(set_psw) then if correct 1 then -- 只有验密通过才允许改 psw out_q; end if; end if; end process;改密逻辑只有三行但顺序很关键先输入 9876 按 yescorrect 拉高再输入新密码按 set_psw新值写入 psw。correct 为 0 时按 set_psw 不产生任何效果防止误改。段码侧用共阳极真值表0 表示该段点亮BCD段码BCD段码01000000500100101111100160000010201001007111100030110000800000004001100190010000其余输入统一输出1111111全灭。四位数字分别取自 out_q 的 [15:12]、[11:8]、[7:4]、[3:0]各驱动一个 decoder 元件所以数码管的排布与移位方向严格对应调显示时只要看 out_q 的哪一段不对就能定位是移位还是译码的问题。4. Quartus II 下的编译、仿真与典型报错定位工程建好后先综合再看仿真顺序反了会浪费大量时间。综合阶段的报错大多是类型和端口问题仿真阶段的波形异常则多半是初值或边沿问题两类问题的排查手法完全不同。下面按实际操作顺序走一遍并把这份代码最容易踩的几个点挑出来。4.1 顶层实体与 buffer 端口的处理原文把 correct 和 out_error 声明成buffer因为它们既做输出又参与内部判断。buffer 在 VHDL-93 里合法但对端口映射有限制某些工具链会在层次化调用时报错。常见做法是改成out加一个内部 signal内部用sig_correct参与比较输出端correct sig_correct这样既能被读数又不会被综合器抱怨。若坚持用 buffer必须保证该端口不被其他模块驱动否则报“multiple drivers”。4.2 用 Testbench 复现三次错误报警signal clk_tb : std_logic : 0; signal num_tb : std_logic_vector(9 downto 0) : (others 0); -- 50MHz 时钟周期 20ns clk_tb not clk_tb after 10 ns; stim : process begin start 1; -- 第一轮8765 num_tb 0100000000; wait for 1 ms; num_tb (others 0); wait for 1 ms; num_tb 0000100000; wait for 1 ms; num_tb (others 0); wait for 1 ms; num_tb 1111000 000; wait for 1 ms; num_tb (others 0); wait for 1 ms; yes 1; wait for 1 ms; yes 0; wait for 1 ms; -- 下降沿触发比较 -- 第二、三轮换成 9776、5432重复上述拨码动作 wait; end process;Testbench 的关键是把每次拨码的高电平保持时间设成毫秒级远大于 0.1ms 的采样周期模拟真实手速yes 用一个高脉冲再拉低制造下降沿。观察 alarm、out_error、correct 三条波形就能验证“前两次错只亮红灯、第三次错红灯加报警、reset 拉低后 alarm 归零”是否符合需求。如果第三轮结束 alarm 始终不拉高先查 counter 的初值再查 enable 是否在第一次就误清 0。4.3 上板阶段的两类现象按键抖动是第一个坑。cancel 和 yes 接的是按键而非开关机械抖动会在一两个毫秒内产生多个边沿导致“按一次确认被判成两次比较”。这类问题的处理办法是在按键后加一级同步加计数滤波连续采到 N 个相同的 clk_in 周期才认账。引脚写错是第二个坑。前面提到的 num[0] 与 clk 冲突就属于这一类现象是烧录成功、数码管全灭、任何开关操作都无反应很容易误判成复位信号没拉高。排查时先确认 clk 引脚上确实有 50MHz 输入可以直接在顶层把 clk 引到一个 LED 上做分频闪烁测试灯在闪说明时钟没问题再回头查开关。5. 把密码位数与尝试次数参数化这套代码把“4 位密码”和“3 次机会”写死在了 out_q 位宽和 integer range 里想改成 6 位密码就得动多处。工程化做法是用 generic 把它们提出来顶层实例化时再定值。移位部分改成按 4 比特步进推进计数器上限与握手信号统一由常数推导entity coded_lock is generic( PWD_LEN : integer : 4; -- 密码位数 TRY_MAX : integer : 3 -- 允许尝试次数 ); port( ... ); end; architecture behave of coded_lock is signal cnt4 : integer range 0 to PWD_LEN : 0; begin -- 输入满 PWD_LEN 位后停止移位 if rising_edge(clock) then if cnt4 PWD_LEN then null; else out_q out_q(15 - 4 downto 0) q; cnt4 cnt4 1; end if; end if; -- 尝试次数改为比较 counter TRY_MAX - 1 if falling_edge(yes) then if counter TRY_MAX - 1 then alarm 1; enable 0; else counter counter 1; end if; end if; end process; end behave;参数化的收益不只是少改几行。密码位数变化时out_q 的位宽、译码器的实例个数、数码管的接线数量必须同步改generic 至少能把逻辑侧的一致性保证住。若位数超过 4 位显示资源会不够这时应该把静态四位数码管改成动态扫描用 clk_in 分频出一路几百赫兹的扫描时钟轮流选通各位人眼利用余辉看到整体显示代价是引入位选信号和更高频率的刷新时序。验证仍按老三样先只改 PWD_LEN 跑一遍综合看 out_q 位宽和译码实例是否匹配再用 Testbench 把密码填满并多输一位确认第 5 位被丢弃且显示不乱最后上板时把 TRY_MAX 临时改成 1一秒内就能触发报警比连按三次确认键省事得多。需要留神的是counter TRY_MAX - 1这类表达式在 integer range 边界上容易越界range 至少要留出 TRY_MAX 个取值否则第三次比较时综合器会报范围溢出警告仿真波形也会在报警前意外回绕。本文还有配套的精品资源点击获取
返回列表