Lesson 3

🥤 售货机

🏆 投币→出货→找零
✅ Verilator仿真验证通过

📖 实验描述

实现售货机控制器,支持投币累计、商品选择、出货和找零。

售货机是状态机与数据路径结合的经典案例。 售货机需要:累计投入金额、判断是否足够购买、出货、计算找零。这涉及状态控制和数据运算两个维度。 简化模型:2种商品(A=3元,B=5元),接受1元和2元硬币,投入金额累计,选择商品后判断余额是否足够。 状态:IDLE → COIN_INSERT → DISPENSE → CHANGE → IDLE 售货机是全球最早的大规模自动化设备之一,日本平均每23人就有一台售货机。

🧠 核心概念

📐 电路结构

电路与状态图
售货机结构:

  投币输入 ─→ 累计寄存器
                  │
           ┌──────▼──────┐
           │  金额比较    │
           │ balance≥price?│
           └──────┬──────┘
                  │足够
           ┌──────▼──────┐
           │  出货+找零   │
           │ change=bal-price│
           └─────────────┘

  商品A: 3元  商品B: 5元
  硬币: 1元/2元

📝 设计步骤

  1. 1定义状态:IDLE/COIN_INSERT/DISPENSE/CHANGE
  2. 2设计金额累计寄存器(支持1元/2元投币)
  3. 3实现商品价格比较:balance ≥ price
  4. 4出货信号生成:选择商品且金额足够
  5. 5找零计算:change = balance - price
  6. 6退款:取消时退回全部余额

💻 Verilog实现

vending.svSystemVerilog · Verilator 5.020
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
// Vending Machine - 售货机控制器
// 商品A=3元, B=5元; 接受1元/2元硬币
module vending(
    input  wire clk,
    input  wire rst,
    input  wire coin1,      // 投入1元
    input  wire coin2,      // 投入2元
    input  wire sel_a,      // 选择商品A(3元)
    input  wire sel_b,      // 选择商品B(5元)
    input  wire cancel,     // 取消/退款
    output reg  dispense_a, // 出货A
    output reg  dispense_b, // 出货B
    output reg  [3:0] change,// 找零金额
    output reg  refund,     // 退款信号
    output reg  [3:0] balance // 当前余额(显示用)
);

localparam PRICE_A = 4'd3;
localparam PRICE_B = 4'd5;

typedef enum logic [1:0] {
    S_IDLE   = 2'd0,
    S_COIN   = 2'd1,
    S_DISP   = 2'd2,
    S_CHANGE = 2'd3
} state_t;

state_t state;

always @(posedge clk or posedge rst) begin
    if (rst) begin
        state      <= S_IDLE;
        balance    <= 4'd0;
        dispense_a <= 1'b0;
        dispense_b <= 1'b0;
        change     <= 4'd0;
        refund     <= 1'b0;
    end else begin
        dispense_a <= 1'b0;
        dispense_b <= 1'b0;
        refund     <= 1'b0;

        case (state)
            S_IDLE: begin
                if (coin1) begin
                    balance <= balance + 4'd1;
                    state   <= S_COIN;
                end else if (coin2) begin
                    balance <= balance + 4'd2;
                    state   <= S_COIN;
                end
            end
            S_COIN: begin
                if (coin1) begin
                    balance <= balance + 4'd1;
                end else if (coin2) begin
                    balance <= balance + 4'd2;
                end else if (sel_a && balance >= PRICE_A) begin
                    dispense_a <= 1'b1;
                    change     <= balance - PRICE_A;
                    balance    <= 4'd0;
                    state      <= S_CHANGE;
                end else if (sel_b && balance >= PRICE_B) begin
                    dispense_b <= 1'b1;
                    change     <= balance - PRICE_B;
                    balance    <= 4'd0;
                    state      <= S_CHANGE;
                end else if (cancel) begin
                    refund  <= 1'b1;
                    change  <= balance;
                    balance <= 4'd0;
                    state   <= S_IDLE;
                end
            end
            S_DISP: begin
                state <= S_CHANGE;
            end
            S_CHANGE: begin
                change <= 4'd0;
                state  <= S_IDLE;
            end
            default: state <= S_IDLE;
        endcase
    end
end

endmodule

🔬 仿真说明

仿真环境与策略

测试:投入2元+2元=4元,选A(3元)→出货A,找零1元;投入2元+2元+1元=5元,选B(5元)→出货B,找零0元;投入2元后取消→退款2元。

✅ 验证结果

Verilator 5.020 仿真通过

本实验所有Verilog代码已通过Verilator编译验证,功能行为正确。测试用例覆盖核心功能路径,确保设计满足规格要求。

⚠️ 常见错误

🌍 真实世界

工业应用

现代售货机支持纸币、移动支付、RFID等多种支付方式,核心仍是金额累计与条件判断。日本售货机还具备地震感知自动免费出货功能,这是FSM与传感器结合的应用。

💡 扩展挑战

🚀 自己动手

# 编译并运行
verilator --cc vending.sv --exe vending_tb.cpp --build -j 0
./obj_dir/Vvending

🎯 试一试

修改参数

尝试修改代码中的关键参数,观察仿真结果变化:

  • 调整位宽,观察不同数据范围
  • 修改初始值/种子,观察不同起始条件
  • 改变时钟分频,测试不同速度

💡 Verilator会在位宽不匹配时给出Warning,这是学习的好机会

添加功能

在现有基础上增加新功能:

  • 添加新的输入信号和控制逻辑
  • 增加状态或修改状态转移条件
  • 扩展输出,增加更多显示信息

🔧 增量开发:每次只改一个地方,验证通过后再改下一个

📋 Verilog速查

语法说明示例
reg [7:0]8位寄存器reg [7:0] data;
wire组合逻辑连线wire valid = cnt > 5;
always @(posedge clk)时序逻辑上升沿触发
always @(*)组合逻辑敏感列表自动推导
localparam局部常量localparam DIV = 50000000;
case多分支选择注意default分支
$display仿真打印不可综合,仅仿真用

📊 性能指标

资源估算(FPGA参考)

本设计在典型FPGA上的资源占用估算:LUT约20-120个,FF约30-100个,无BRAM/DSP依赖(特殊模块除外)。时钟频率可达50-100MHz+。Verilator仿真速度约5-10M周期/秒。

← 上一课 📚 目录 下一课 →