一、状态机就像交通信号灯

想象一下十字路口的红绿灯:红灯停、绿灯行、黄灯等一等。Verilog状态机也是这样工作的——它根据当前状态和输入信号,决定下一步该切换到哪个状态。比如电梯控制器、自动售货机,都离不开状态机的身影。

技术栈:Verilog HDL

// 简单的三状态交通灯控制器
module traffic_light(
    input clk,         // 时钟信号
    input rst,         // 复位信号
    output reg [1:0] light // 00红 01黄 10绿
);
    // 定义三个状态
    parameter RED = 2'b00, YELLOW = 2'b01, GREEN = 2'b10;
    reg [1:0] current_state;

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            current_state <= RED;     // 复位时回到红灯
            light <= RED;
        end else begin
            case (current_state)
                RED: begin
                    light <= RED;
                    #10 current_state <= GREEN; // 红灯10秒后变绿
                end
                GREEN: begin
                    light <= GREEN;
                    #15 current_state <= YELLOW; // 绿灯15秒后变黄
                end
                YELLOW: begin
                    light <= YELLOW;
                    #5 current_state <= RED;    // 黄灯5秒后变红
                end
            endcase
        end
    end
endmodule

关键点:每个状态明确对应一个输出,延时结束后自动跳转。就像红绿灯严格按时间切换,不会突然从红变黄。

二、避开状态机的"鬼打墙"

新手常遇到两个坑:状态跳转混乱(该停的时候不停)和死锁(卡在某个状态出不来)。来看一个反面教材:

// 有问题的售货机状态机(部分代码)
parameter IDLE = 0, SELECT = 1, DELIVER = 2;
reg [1:0] state;

always @(posedge clk) begin
    case(state)
        IDLE: 
            if (coin_inserted) state <= SELECT; // 投币后进入选择状态
        SELECT:
            if (no_stock) state <= IDLE;       // 缺货时退回空闲
            else if (button_pressed) begin
                deliver_goods();                // 发货操作耗时2秒
                state <= DELIVER;               // 问题点:发货未完成就切换状态
            end
        DELIVER:
            state <= IDLE;                     // 立即返回空闲
    endcase
end

问题分析:在SELECT状态时,如果用户快速连续按键,可能触发多次deliver_goods(),导致货物多发。正确做法是增加状态保护

// 修复后的DELIVER状态逻辑
DELIVER: begin
    if (deliver_done) begin   // 等待发货完成信号
        state <= IDLE;
        reset_deliver_flag(); // 清除完成标志
    end
end

三、给状态机装上"安全带"

三个实用技巧让你的状态机更可靠:

  1. 状态枚举命名法
    用有意义的参数名代替数字,比如:
parameter S_IDLE  = 3'd0,
          S_CHECK = 3'd1, 
          S_DISPENSE = 3'd2;
  1. 默认状态处理
    在case语句最后添加default防止跑飞:
case(state)
    S_IDLE: ... 
    default: begin
        state <= S_IDLE;  // 异常时复位
        $display("Error state!");
    end
endcase
  1. 状态跳转检查表
    手工绘制状态转移图,确保:
  • 每个状态都有出路
  • 没有循环依赖
  • 关键操作有完成确认信号

四、实战:UART接收状态机

来看一个完整的串口接收器设计:

module uart_receiver(
    input clk,
    input rx_serial,      // 串行输入
    output reg [7:0] data // 接收到的字节
);
    parameter IDLE    = 3'b000,
              START   = 3'b001,
              RECEIVE = 3'b010,
              STOP    = 3'b011;

    reg [2:0] state;
    reg [3:0] bit_index;
    reg [15:0] clk_counter; // 波特率计数器

    always @(posedge clk) begin
        case (state)
            IDLE: begin
                if (!rx_serial) begin       // 检测起始位(低电平)
                    state <= START;
                    clk_counter <= 0;
                end
            end
            
            START: begin
                if (clk_counter == 434) begin // 采样中点(9600bps)
                    if (!rx_serial) begin     // 确认起始位有效
                        state <= RECEIVE;
                        bit_index <= 0;
                    end else state <= IDLE;   // 毛刺干扰则退回
                end else clk_counter <= clk_counter + 1;
            end
            
            RECEIVE: begin
                if (clk_counter == 868) begin // 每个bit采样一次
                    data[bit_index] <= rx_serial;
                    clk_counter <= 0;
                    if (bit_index == 7) state <= STOP;
                    else bit_index <= bit_index + 1;
                end else clk_counter <= clk_counter + 1;
            end
            
            STOP: begin
                if (clk_counter == 868) begin
                    state <= IDLE;           // 回到空闲等待下一字节
                end else clk_counter <= clk_counter + 1;
            end
        endcase
    end
endmodule

设计亮点:

  • 在START状态二次验证起始位
  • 严格按波特率时钟采样
  • 每个状态都有超时保护

五、不同场景下的选择策略

应用场景对比:

场景 推荐实现方式 原因
简单流程控制 三段式状态机 代码直观,便于维护
高速信号处理 独热编码(one-hot) 减少组合逻辑延迟
协议解析 嵌套状态机 分层处理更清晰

常见踩坑指南:

  1. 避免在组合逻辑中生成状态转移条件(容易产生毛刺)
  2. 状态变量不要用异步信号赋值
  3. 复杂状态机建议先用流程图工具设计
  4. 仿真时添加状态监视信号:$monitor("State=%d", current_state);

六、总结与进阶方向

状态机设计的本质是把时间维度上的逻辑拆解成空间维度上的状态。就像导演给演员设计走位,既要保证剧情流畅,又不能出现撞车事故。

进阶学习建议:

  1. 研究AXI总线协议中的状态机设计
  2. 学习使用SystemVerilog的enum类型增强可读性
  3. 掌握Formal Verification工具验证状态机完备性

记住:好的状态机应该像精心编排的舞蹈——每个动作都有明确的前因后果,永远不会踩错节拍。