ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FPGA自动售货机课设:从状态机设计到上板调试的完整实践指南

FPGA自动售货机课设:从状态机设计到上板调试的完整实践指南 简介这是东南大学信息学院短学期数字系统设计课程的自动售货机项目以FPGA为硬件平台完整展示从输入信号解析硬币检测、按键选择、核心逻辑控制金额累计、商品选择、找零退款到输出驱动显示、货道电机的闭环设计。面向正在修读数字电路与硬件描述语言课程的高年级本科生也适合准备FPGA综合实践的开发者。包内共160个文件整体仅1.46MB主要源码为VHDL文件另有xdc约束、xpr工程、rpt报告、log日志以及bat/tcl脚本便于在Vivado中直接打开工程并复现综合、实现与仿真流程。目前已有532人学习。压缩包还保留了四次dcp网表和多份jou备份可对比不同设计阶段的资源占用与时序结果顶层模块划分清晰便于拆解状态机、按键消抖和分频逻辑也可借鉴其模块化思路迁移到其他数字系统设计任务中是深入理解FPGA开发流程和短学期综合实践的参考范本。 东南大学信息学院的数字系统设计课设自动售货机算是出镜率最高的题目之一了。每年到了课程后期实验室里讨论最多的就是它——有人觉得状态机绕有人觉得显示和按键配合起来麻烦还有人仿真全通过、上板就黑屏。我当年做这个题目的时候也折腾了不少时间后来帮学弟学妹调过几版踩过的坑和总结出来的经验都有不少。这篇就围绕自动售货机这个课设从方案设计、模块划分、状态机实现到上板调试把关键环节捋一遍。1. 选题门道为什么自动售货机是数字系统设计的“黄金课设”1.1 一个课设题目背后的知识体系先说结论自动售货机这题几乎把数字系统设计课程里的核心知识点全串起来了。它不是一个纯逻辑题而是典型的“数据通路 控制通路”结合的题目。你既要处理货币输入、商品价格、找零金额这些数据又要设计一套状态机来管理整个交易流程。说得直白点数据怎么走、什么时候走、走到哪全是状态机说了算。具体来说这个题目覆盖了这些内容有限状态机FSM的设计与编码包括状态转移条件、输出逻辑时序逻辑设计寄存器的使用、同步与异步处理分频电路设计板载时钟一般几十MHz而数码管扫描、按键采样根本用不了这么高的频率按键消抖机械按键的抖动是硬件问题处理不好状态机直接就乱了数码管动态扫描显示涉及刷新率、位选段选时序简单的运算电路比如累计金额、找零计算有些要求高的版本还会让你加“最多投币金额限制”“售罄指示”“取消退币”这些功能难度直接再上一个台阶。1.2 从需求文档到数字系统的常规拆解路径课设拿到手第一件事绝对不要打开 Vivado 或者 Quartus 写代码先看需求然后把需求翻译成状态和条件。拿最常见的规格来说售货机卖三种饮料价格分别是 2 元、3 元、4 元支持投 1 元、5 元、10 元选货后判断金额是否足够够则出饮料并找零不够则提示继续投币或取消退币。翻译成数字系统需求就是投币输入需要编码比如两位二进制区分 1、5、10 三种面额累计金额寄存器需要做加法上限可能需要限制防止一直投下去溢出选货信号对应三种商品每种的单价不同金额足够时触发“出货”“找零”两个动作找零金额 累计金额 - 单价状态机管理的核心是“空闲 → 投币 → 选货 → 出货找零 → 回到空闲”这条主流程这一步做完整个系统的框架就有了。急着写代码的人后面大概率要反复删改。2. 系统方案与模块划分先画图再写代码2.1 总体架构数据通路与控制通路我的习惯是画一张模块框图把顶层模块拆成几个子模块再动手。自动售货机系统的经典拆分方式如下。顶层模块负责实例化所有子模块并连线按键消抖模块处理投币按键、选货按键、确认/取消按键分频模块生成数码管扫描时钟、按键采样时钟状态机模块这是控制通路的核心金额计算模块负责累计投币金额和找零计算数码管显示模块显示当前金额、商品价格或提示信息数据通路的流向是按键面额 → 金额寄存器 → 比较器与单价比较→ 找零计算器 → 数码管显示。控制通路的流向是按键事件 → 状态机状态转移 → 控制信号出货、找零、金额清零。这两个通路之间是相互配合的关系状态机给出控制信号数据通路执行运算运算结果比如金额够不够又作为状态转移的条件。很多人写代码的时候把这两个东西混在一起状态寄存器里面既存状态又存金额最后状态转移逻辑又长又乱排错的时候特别痛苦。2.2 核心模块划分与接口设计模块划分之后每个模块的接口先定义清楚再各写各的。这里给一份我常用的接口清单可以作为参考。模块主要接口作用key_debounceclk, rst_n, key_in, key_pulse消抖并输出单周期脉冲clk_divclk, rst_n, clk_ms, clk_scan分频输出毫秒级和扫描级时钟vending_fsmclk, rst_n, coin_pulse, sel_pulse, price, amount_enough状态转移与输出控制money_calcclk, rst_n, coin_value, add_en, clear, change_value金额累计与找零输出seg_displayclk, rst_n, data_in, seg, sel数码管动态扫描显示每个子模块的功能尽量单一接口信号尽量少。一个常见的问题是模块之间通信信号命名混乱比如金额模块的输出有的叫 total有的叫 sum到了顶层连线的时候两个人各写各的对不上。建议信号命名统一例如金额累计用amount_sum找零用change_out清一色用小写加下划线顶层连线时一眼就能看出意思。2.3 开发环境与目标板卡选型东南大学信息学院的数字系统设计课一般用的是 FPGA 开发板主流是 Xilinx 的 Artix-7 系列EDA 工具是 Vivado。也有部分往年用的是 Altera 的 Cyclone IV 配合 Quartus核心流程大同小异但语法细节和引脚约束文件的写法有些区别。我建议这个项目用 Verilog 实现原因很简单课设代码量不大Verilog 更灵活而且 Vivado 对 Verilog 的支持非常成熟。VHDL 在类型系统上更严谨但对这个体量的项目来说优势体现不出来写起来还啰嗦。板卡上的外设要提前确认有几个按键、几位数码管、时钟频率是多少、是高电平有效还是低电平有效。这些信息决定了你的代码细节。比如按键按下时输入是高电平还是低电平直接决定了消抖模块里采样后取反还是不变。3. 状态机设计自动售货机的“大脑”3.1 需求状态与状态转移条件状态机是这个项目的核心设计好了整个系统就稳了。我习惯把状态画成状态转移图虽然课设不强制交这个图但画一遍之后写代码的速度会快很多。以一个带“取消退币”的版本为例状态可以这样设计IDLE空闲状态等待投币。有投币脉冲进入COIN_IN无操作则一直停留COIN_IN投币累计状态。每次投币累加金额投币结束可以选货进入SELECTSELECT选货状态。按选货键之后比较金额是否足够。足够则进入DISPENSE不够则停留在SELECT并给出“金额不足”提示DISPENSE出货找零状态。输出出货信号、计算找零执行完成后回到IDLE如果要求支持“取消退币”则COIN_IN状态下还有一个取消按键的转移条件触发后退币并回到IDLE。有些实现还会加一个WAIT_CHANGE状态把“出货”和“找零”拆开执行这样控制逻辑更清晰也方便后续加“找零不足”提示。我个人建议状态宁可多一个也不要在一个状态里做太多事。3.2 状态编码方式选择状态编码有三种常见方式二进制编码、格雷码、独热码。课设规模下我推荐独热码。原因很直接状态数量少一般 4 到 6 个状态独热码每个状态只有一个 bit 为 1状态转移逻辑就是很简单的比较和置位不容易出错而且综合出来的电路时序也干净。二进制编码虽然寄存器用得少但译码逻辑复杂排查状态乱跳的时候很难一眼看出当前是哪个状态。独热码的代价是多用几个触发器这在 FPGA 上根本不是事儿资源完全够。课设评审老师问起来还可以顺势答一条独热码以寄存器资源换取组合逻辑的简洁这在 FPGA 设计中很常见。3.3 三段式状态机模板状态机的写法我强烈推荐三段式这是业界标准实践也可以说是数字设计里最成熟的状态机模板。第一段是时序逻辑完成状态寄存器的更新。第二段是组合逻辑完成次态判断也就是根据当前状态和输入信号决定下一个状态。第三段是输出逻辑根据当前状态和输入信号产生输出控制信号。用 Verilog 写出来大致是这个样子// 第一段状态寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) current_state IDLE; else current_state next_state; end // 第二段次态组合逻辑 always (*) begin case (current_state) IDLE: begin if (coin_pulse) next_state COIN_IN; else next_state IDLE; end // 其他状态同理 endcase end // 第三段输出逻辑 always (*) begin case (current_state) DISPENSE: dispense 1b1; default: dispense 1b0; endcase end这里有个特别要注意的细节次态逻辑和输出逻辑都用组合逻辑但为什么输出逻辑不直接跟状态寄存器绑定成时序逻辑这样可以避免输出比状态晚一个周期从而在仿真的时候出现输出滞后实际用的时候第三段输出如果用在时钟沿打一拍输出天然会比状态慢一拍。有些场景允许但如果你是控制出货信号晚一拍可能就导致出货只维持了一个周期而不是一个完整周期上板后现象非常奇怪。所以第三段输出我统一用组合逻辑控制信号随状态立即变化。注意复位信号的极性、同步复位还是异步复位必须和板卡的复位按键一致。很多板上复位按键按下去是低电平代码里写negedge rst_n这没问题。写反了的表现是上电后系统直接不在初始状态状态机随机乱跑。4. 关键外设模块的实现细节4.1 按键消抖别让抖动毁掉状态机机械按键按下和释放的时候触点会来回弹跳几毫秒到十几毫秒。如果不做消抖状态机一次投币动作可能被触发十几次金额直接爆表这是上板后最常遇到的诡异现象之一。消抖的思路是检测到按键电平变化后等一段时间再确认电平是否稳定。常用的方法是延时采样检测到按下沿之后延时 10ms 到 20ms在稳定期再采样一次如果仍是按下电平就认为按键有效。这个延时从哪里来有的同学直接用一个计数器在状态机里延时while循环等 20ms。这在单片机里可以在 FPGA 里不行组合逻辑里用while是不可综合的如果目标是竞赛就得认真考虑。正确做法是独立做一个消抖模块用计数器打拍延时。我用的模板是在时钟驱动下当按键输入变化时计数器清零重新计数当计数器计到设定值且按键电平稳定不变就输出一个单周期脉冲。always (posedge clk or negedge rst_n) begin if (!rst_n) begin key_reg 1b1; cnt 0; end else if (key_in ! key_reg) begin key_reg key_in; cnt 0; end else if (cnt DEBOUNCE_CNT) begin // 稳定后根据边沿产生脉冲 key_pulse (key_in ! key_reg); // 注意这里需要额外处理 end else begin cnt cnt 1; end end实际实现里建议用两级寄存器先把key_in同步到时钟域再做边沿检测和延时消抖避免亚稳态传播。这是仿真看不出来只有上板后偶发乱跳才会发现的问题。4.2 分频与时钟处理怎样生成扫描时钟板载时钟一般很高我当年用的板子是 50MHz即 20ns 周期。这个频率直接给数码管扫描用太高了需要分频。分频的目的是生成两个不同频率的时钟按键采样用的 1kHz 左右的时钟以及数码管扫描用的 1kHz 到 2kHz 左右的时钟。数码管动态扫描频率太快则切换太快肉眼看到的是亮度不均太慢则能看到闪烁一般 1kHz 左右比较合适也就是每一位点亮 1ms 左右。分频的做法是用计数器到达计数值就翻转输出或者产生一个使能脉冲。例如 50MHz 分频得到 1kHz计数值就是 50000需要注意计数起点是 0所以计到 49999 时清零。reg [15:0] cnt; reg clk_1k; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 16d0; clk_1k 1b0; end else if (cnt 16d49999) begin cnt 16d0; clk_1k ~clk_1k; end else begin cnt cnt 1b1; end end还有一个做法是产生时钟使能信号而非翻转时钟在建模中叫作 clock enable这是一种更好的做法。分频后的时钟再用posedge clk_1k去驱动多个模块很容易出现时钟偏移和时序问题。更稳妥的方案是始终保持系统主时钟分频模块只生成一个使能脉冲比如每 1ms 产生一个高电平脉冲en_1k所有时序逻辑仍然用posedge clk而用en_1k作为条件判断。这种做法的好处是整个设计只有一个时钟域没有跨时钟域问题综合出来的时序报告也干净得多。我就是被一个时钟不同步的问题教育过之后改过来的。4.3 数码管显示加分项往往在这里自动售货机至少要显示当前累计金额好的实现还会显示商品价格、找零金额、提示信息。不要把显示功能堆到状态机里独立成模块最好。动态扫描的原理是某一时刻只点亮一位数码管通过位选信号选择当前点亮的是第几位段选信号输出这位要显示的内容然后快速切换到下一位。利用人眼的视觉暂留效应看起来像是同时点亮了所有位。扫描频率、位选切换节奏是重点。比如 4 位数码管1ms 切换一位4ms 完成一个轮回刷新率 250Hz不会有闪烁感显示亮度也够。一个常见的问题是扫描频率和状态机时钟冲突。如果你用同一个时钟既做状态机又做扫描状态机每个周期都在变而扫描也要每个周期切换位选两者会产生奇怪的干扰。我最后是在系统时钟下先用分频使能产生一个scan_switch信号只有这个信号的脉冲到来时才切换位选状态机其他时间照常跑。4.4 找零算法用减法器还是查表找零金额的计算在多数课设规格下只是一个组合逻辑减法change amount_sum - price。但如果你要处理“找零硬币面额组合”比如 1 元、5 角、1 角几种币值分别出多少个就需要一个简单的贪心算法或者查表。贪心算法的思路是优先用大面额找零依次减下去。在数字逻辑里实现就是一组减法器和比较器串起来。另一种更省资源的做法是查表。因为单价和投币组合是有限的把所有可能的找零方案提前算好存进 ROM 或 case 语句里输出直接查表。查表的好处是逻辑简单、时序好坏处是需求变化时表的改动比较麻烦。我建议如果课设只要求显示找零金额用组合减法就够了。如果要求“退回相应数量的硬币”用查表逻辑一目了然评审老师也容易看懂。always (*) begin case (change_value) 4d1: change_coin 4b0001; // 找回1元硬币 4d2: change_coin 4b0010; // 找回2元硬币 // 根据实际币值设计编码 default: change_coin 4b0000; endcase end提示找零计算的结果在状态机里一定要锁存住不能在回到 IDLE 状态以后因为金额累加寄存器清零而跟着清零。一个简单的做法是在DISPENSE状态把找零结果打一拍到独立的寄存器显示模块只读这个寄存器的值。5. 仿真验证与板上调试实录5.1 Testbench 怎么搭才有效率仿真不是走过场这个项目的调试效率很大程度上取决于 Testbench 写得好不好。我的建议是不要只测一条“投币-选货-出货”的快乐路径要把异常路径也覆盖到。比如投币金额不足就选货、投币过程中按取消、连续按两次同一商品按键、投币超过上限。这些情况在状态机设计时会直接影响转移条件而转移条件恰恰是bug重灾区。一个简单有效的 Testbench 写法是分段激励用initial begin按时间顺序给不同的激励组合。比如initial begin rst_n 0; #100 rst_n 1; // 投1元 coin 2b01; #20 coin 2b00; // 再投1元 #100 coin 2b01; #20 coin 2b00; // 选3元商品此时金额只有2元应提示不足 sel 2b10; #20 sel 2b00; // 取消退币 cancel 1b1; #20 cancel 1b0; end仿真时重点看两个方面一是状态机状态是否按预期跳转二是输出信号出货、找零的时序是否在正确的状态和正确的周期出现。我看波形的时候习惯把状态信号导出来观察各状态停留的周期数是否符合预期。如果状态在某个输入下没有跳转或者跳了一个不该去的状态先查次态组合逻辑的case分支有没有遗漏。5.2 常见问题与排查技巧速查表上板调试是另一个世界很多仿真里永远不会出现的问题一上板全冒出来了。我这里整理了一个问题速查表都是我实际遇到过或者帮别人排查过的。现象可能原因排查方向投币一次金额加好几次按键没有消抖或消抖参数不对检查消抖模块延时是否足够是否输出单周期脉冲上电后状态机不工作复位极性不对或初始状态不对检查复位按键电平、上电复位信号数码管显示乱跳或闪烁扫描频率不合适或时钟使能处理不当检查扫描分频参数尝试改用时钟使能方式显示亮度不均扫描每一位停留时间不一致检查位选切换逻辑是否均匀找零金额经常少1元组合电平瞬间变化被采样到找零结果锁存到独立寄存器用状态机时序控制选货后不出货状态机输出逻辑用的是时序逻辑输出晚了一拍输出逻辑改用组合逻辑或提前一拍输出个别按键时灵时不灵按键信号没有同步有亚稳态打两拍同步再做消抖上板调试最有效的手段是看板上的 LED 指示。我在状态机里预留了一组调试 LED把当前状态用二进制编码输出到 LED 上。状态跳转对不上看 LED 就知道状态卡在哪了。这个小技巧在评审答辩的时候讲出来老师会觉得你的调试思路非常清晰。如果遇到仿真正常、上板不正常的疑难问题还有一个经典排查法用 Vivado 的 ILA集成逻辑分析仪抓内部信号。把状态、按键脉冲、金额寄存器抓进去上板后触发条件设好就能看到内部真实的波形。这比对着现象瞎猜效率高得多。6. 扩展思路从一个课设到完整系统自动售货机这个题目做完之后很多同学就交差结束了其实这个题目完全可以作为课程设计的一个起点往多个方向延伸。如果你对 SoC 方向感兴趣可以把控制逻辑做成一个 AXI 外设挂到 MicroBlaze 软核上用 C 语言控制出货逻辑FPGA 做显示和按键接口。这个方案直接把数字系统设计课程和嵌入式课程的内容打通了做出来的东西复杂度和完整度都不在一个量级。如果你对低功耗设计感兴趣可以尝试给不同模块做时钟门控统计各状态的翻转率对比优化前后的功耗差异。这个方向在课设里很少见如果你做出来绝对比同组的同学多一个亮点。如果你对验证方法学感兴趣可以用 SystemVerilog 给这个自动售货机搭一个 UVM 验证环境把所有功能覆盖点写全。这在数字系统设计课里算是降维打击但真的有人这样做过而且效果很好。我自己做这个题目最大的体会是数字系统设计和软件编程最大的不同在于所有的事情都是并行的、时序的状态机的状态转移不是顺序执行的 if-else而是每一个时钟沿大家都在动。理解了这个区别后面学计算机组成、操作系统、体系结构都会顺很多。自动售货机这个题目就是帮你建立这种感觉的绝佳载体。本文还有配套的精品资源点击获取
返回列表