Verilog状态机实战:从零构建自动售货机核心逻辑

发布时间:2026/5/16 4:32:21

Verilog状态机实战:从零构建自动售货机核心逻辑 1. 自动售货机与状态机的完美结合第一次接触自动售货机状态机设计时我盯着那个铁盒子看了足足十分钟。硬币掉进去的咔嗒声饮料掉出来的闷响还有找零时硬币滚落的清脆声音这些看似简单的动作背后隐藏着一个精密的逻辑控制系统。用Verilog实现这个系统就像是给这个铁盒子注入灵魂。自动售货机作为状态机的经典案例特别适合FPGA初学者练手。它包含了状态机设计的三大核心要素明确的状态划分、清晰的状态转移条件以及具体的输出动作。我在实际教学中发现很多同学第一次用Verilog写状态机时最容易犯的错误就是把所有逻辑都塞进一个always块里最后代码变成一团乱麻。而自动售货机这个案例恰好能教会我们如何优雅地组织代码。这个设计案例来自哈工大MOOC课程经过多年教学验证确实能帮助新手快速掌握状态机设计精髓。我们将要实现的售货机功能很明确每次只能投入五角或一元硬币投入一元五角时出货投入两元时出货并找零五角。看似简单但要把这些业务逻辑转化为Verilog代码需要经过严谨的状态分析和设计。2. 需求分析与状态定义2.1 信号定义与功能拆解先来看看这个自动售货机的接口信号。时钟clk和复位rst是每个FPGA设计都有的基本信号这个不用多说。重点在于这几个输入输出信号half_yuan五角硬币投入信号高电平有效one_yuan一元硬币投入信号同样高电平有效half_out找零信号输出五角硬币dispense出货信号控制饮料掉落collect取货确认信号这里有个设计细节值得注意为什么需要collect信号在实际售货机中用户取走饮料需要一定时间这个信号就是用来确认完成取货的。但在我们的简化版设计中可以暂时忽略这个信号等基础功能实现后再考虑添加。2.2 状态划分的艺术状态划分是状态机设计最关键的一步。我见过不少初学者要么状态划分太少导致逻辑混乱要么划分太多让代码变得臃肿。对于这个自动售货机经过多次实践验证三种状态最为合适IDLE初始状态等待投币HALF已投入五角硬币ONE已投入一元硬币为什么不是更多状态比如为什么不设一个ONE_AND_HALF状态这是因为我们的售货机价格设置很巧妙最高价格两元通过组合这三种基础状态就能覆盖所有情况。这种状态划分方式既简洁又完整是经过精心设计的。3. Verilog实现详解3.1 状态机编码风格在Verilog中实现状态机我强烈推荐使用三段式写法。虽然原始代码中使用的是单always块实现但对于初学者来说三段式更易读易维护// 第一部分状态寄存器 always (posedge clk or posedge rst) begin if(rst) ST IDLE; else ST NEXT_STATE; end // 第二部分状态转移逻辑 always (*) begin case(ST) IDLE: if(half_yuan) NEXT_STATE HALF; else if(one_yuan) NEXT_STATE ONE; else NEXT_STATE IDLE; // 其他状态转移... endcase end // 第三部分输出逻辑 always (posedge clk) begin if(rst) {dispense, collect, half_out} 3b0; else begin case(ST) // 输出逻辑... endcase end end这种写法将时序逻辑和组合逻辑分离调试时特别方便。我在实际项目中踩过的坑告诉我千万不要把所有逻辑都塞进一个always块后期维护简直是噩梦。3.2 完整代码解析让我们仔细分析原始代码中的状态转移逻辑。以HALF状态为例half: if(half_yuan) begin dispense 0; collect 0; half_out 0; ST one; end else if(one_yuan) begin dispense 1; collect 1; half_out 0; ST idle; end else begin dispense 0; collect 0; half_out 0; ST half; end这段代码处理的是已投入五角后的情况如果再投入五角转移到ONE状态如果投入一元则满足一元五角的出货条件触发dispense信号并回到IDLE状态。这里有个细节collect信号也被置为1虽然我们的简化设计暂时用不到它。4. 测试与调试技巧4.1 Testbench设计要点原始代码中的testbench使用了随机投币的方式测试这对验证基本功能很有帮助always(posedge clk) begin one_yuan {$random}%2; half_yuan ~one_yuan; // 每次只投一枚 end但在实际项目中我建议先编写确定性测试用例再配合随机测试。比如可以设计以下测试序列投五角-投一元-检查是否出货投一元-投一元-检查是否出货并找零连续投三次五角-检查状态变化4.2 常见问题排查原始代码末尾提到的重复捕获问题确实值得关注。在实际硬件中投币信号可能保持多个时钟周期如果不处理会导致状态机多次响应。解决方法有两种在状态机中检测信号边沿reg half_yuan_dly; always (posedge clk) half_yuan_dly half_yuan; wire half_yuan_rise ~half_yuan_dly half_yuan;在外部模块中生成单周期脉冲// 硬币检测模块 always (posedge clk) begin if(coin_inserted) begin one_yuan_pulse (coin_type1); half_yuan_pulse (coin_type0); end else begin one_yuan_pulse 0; half_yuan_pulse 0; end end我在实际项目中发现第二种方法更可靠特别适合处理机械开关的抖动问题。5. 功能扩展与优化5.1 多商品支持基础版本完成后可以考虑扩展为支持多种商品的选择。这需要增加商品选择按钮输入不同商品的价格存储显示当前投入金额和还需金额状态机状态也会相应增加比如parameter SEL_A3b000, SEL_B3b001; reg [2:0] product_sel;5.2 库存管理更完善的售货机还需要库存管理功能。可以添加每种商品的库存计数器缺货显示信号补货接口reg [7:0] stock_A, stock_B; always (posedge clk) begin if(restock) begin stock_A 8d10; stock_B 8d10; end else if(dispense_A) begin stock_A stock_A - 1; end // 其他商品类似 end5.3 状态机优化技巧随着功能增加状态机可能变得复杂。这时可以采用以下优化方法分级状态机将投币逻辑和出货逻辑分离使用独热编码(one-hot)简化状态判断添加超时返回功能避免卡在某个状态// 超时计数器示例 reg [31:0] timeout_cnt; always (posedge clk) begin if(ST ! NEXT_STATE) timeout_cnt 0; else timeout_cnt timeout_cnt 1; if(timeout_cnt 32d1000000) begin NEXT_STATE IDLE; // 1秒无操作返回初始 end end6. 工程实践建议在实际FPGA实现时有几点需要特别注意信号同步投币信号来自机械开关需要进行消抖和跨时钟域处理输出驱动dispense信号可能需要驱动电磁铁要添加适当驱动电路资源利用简单状态机消耗资源很少但扩展功能后要注意查找表(LUT)使用情况时序约束即使低速设计也应添加基本约束// 消抖模块示例 module debounce ( input clk, input btn_in, output reg btn_out ); reg [19:0] cnt; always (posedge clk) begin if(btn_in ! btn_out) begin if(cnt 20hFFFFF) btn_out btn_in; else cnt cnt 1; end else cnt 0; end endmodule在Xilinx Vivado中实现时建议使用ILA(Integrated Logic Analyzer)调试状态机。可以设置触发条件捕获特定状态转移非常方便。我在教学实验中发现这种可视化调试方式能帮助学生快速理解状态机工作原理。7. 从仿真到硬件完成仿真验证后将设计下载到FPGA开发板时还需要考虑输入输出管脚约束正确分配按钮和LED对应的FPGA管脚时钟分频开发板通常提供高频时钟需要分频到合适频率物理接口连接真正的硬币识别模块和出货机构// 时钟分频示例 reg [24:0] clk_div; always (posedge clk_100m) begin clk_div clk_div 1; end wire sys_clk clk_div[24]; // 约3Hz如果使用Xilinx Zynq系列FPGA还可以考虑将状态机作为PL(Programmable Logic)部分通过AXI接口与PS(Processing System)交互实现更复杂的功能。比如用ARM处理器管理库存和销售数据用FPGA实现实时控制。这个自动售货机状态机设计虽然看似简单但涵盖了数字系统设计的核心思想。通过逐步扩展功能可以演变出各种实用的控制系统。我建议初学者在完成基础版本后尝试添加新功能比如支持移动支付接口或者加入销售统计功能这样的实践最能提升Verilog设计能力。

相关新闻