2. Verilog状态机编码风格:一段式、两段式、三段式状态机的区别与选择

状态机这东西,说白了就是数字逻辑里的“大脑”。它决定你的电路在什么时候该干什么事。我刚入行那会儿,写状态机全凭感觉,代码写得跟流水账似的。后来被老工程师骂过几次,才老老实实把编码风格给捋顺了。

今天咱们就聊聊状态机的三种主流写法:一段式、两段式、三段式。它们各有各的脾气,选对了事半功倍,选错了……嗯,调试的时候有你受的。

2.1 一段式状态机:简单粗暴,但别滥用

一段式,就是把状态跳转和输出逻辑全塞进一个always块里。代码看起来最紧凑,新手最爱用。

// 一段式状态机示例
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        out <= 0;
    end else begin
        case (state)
            IDLE: begin
                if (start) begin
                    state <= WORK;
                    out <= 1;
                end else begin
                    state <= IDLE;
                    out <= 0;
                end
            end
            WORK: begin
                if (done) begin
                    state <= IDLE;
                    out <= 0;
                end else begin
                    state <= WORK;
                    out <= 1;
                end
            end
            default: begin
                state <= IDLE;
                out <= 0;
            end
        endcase
    end
end

你看,一个always块搞定所有。但这里有个坑——输出是寄存器输出,比组合逻辑输出晚一个时钟周期。我在项目中遇到过这种情况:明明状态已经跳过去了,输出却还赖着不走,导致下游逻辑误判。

⚠ 注意:一段式状态机的输出是时序输出,会延迟一拍。如果你的设计对时序要求严格,比如需要组合逻辑输出立即响应状态变化,那一段式就不太合适。

我个人习惯是:只有非常简单的状态机(比如3-4个状态,输出逻辑简单)才用一段式。复杂一点?别偷懒,换两段式或三段式。

2.2 两段式状态机:经典实用,我推荐新手入门

两段式把状态跳转和输出逻辑分开。第一个always块负责状态跳转(时序逻辑),第二个always块负责输出逻辑(组合逻辑)。

// 两段式状态机示例
// 第一段:状态跳转
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= IDLE;
    else
        state <= next_state;
end

// 第二段:组合逻辑输出
always @(*) begin
    next_state = state;
    out = 0;
    case (state)
        IDLE: begin
            if (start) begin
                next_state = WORK;
                out = 1;
            end
        end
        WORK: begin
            if (done) begin
                next_state = IDLE;
                out = 0;
            end else begin
                out = 1;
            end
        end
        default: begin
            next_state = IDLE;
            out = 0;
        end
    endcase
end

两段式的优势很明显:代码结构清晰,状态跳转和输出逻辑分离。调试的时候,你一眼就能看出状态是怎么变的,输出是怎么来的。

我曾经在一个通信协议解析模块里用过两段式。那个状态机有十几个状态,输出信号也复杂。如果用一段式,代码会乱成一锅粥。两段式让我能快速定位问题——状态跳转不对?查第一段。输出不对?查第二段。分工明确,省心不少。

💡 小技巧:两段式的输出是组合逻辑,没有延迟。但要注意组合逻辑容易产生毛刺。如果输出要驱动对毛刺敏感的电路(比如时钟使能),建议在输出端加一级寄存器打一拍。

2.3 三段式状态机:工程首选,大型项目必备

三段式把状态机拆成三个部分:状态跳转(时序)、次态逻辑(组合)、输出逻辑(时序)。说白了,就是两段式的基础上,把输出也做成寄存器输出。

// 三段式状态机示例
// 第一段:状态跳转
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= IDLE;
    else
        state <= next_state;
end

// 第二段:次态逻辑
always @(*) begin
    next_state = state;
    case (state)
        IDLE: begin
            if (start) next_state = WORK;
        end
        WORK: begin
            if (done) next_state = IDLE;
        end
        default: next_state = IDLE;
    endcase
end

// 第三段:输出逻辑(时序)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        out <= 0;
    else begin
        case (next_state)
            IDLE: out <= 0;
            WORK: out <= 1;
            default: out <= 0;
        endcase
    end
end

你想想看,三段式的好处是什么?输出是寄存器输出,没有毛刺,时序干净。而且状态跳转和输出逻辑完全解耦,想改输出?只动第三段就行,不影响状态机核心逻辑。

我记得有一次做Lattice的FPGA项目,芯片资源比较紧张。我用三段式状态机,综合工具能自动优化寄存器的位置,时序收敛得特别快。换成两段式,组合逻辑输出路径太长,setup time老是过不了。从那以后,但凡状态机超过5个状态,我无脑选三段式

🔑 核心区别总结:
  • 一段式:状态+输出全在一个always块,输出是时序的,延迟一拍。适合极简场景。
  • 两段式:状态跳转时序,输出组合。结构清晰,但输出有毛刺风险。适合中等复杂度。
  • 三段式:状态跳转时序,次态组合,输出时序。无毛刺,时序好,可维护性强。工程首选。

2.4 怎么选?我的实战建议

别纠结,直接看场景:

场景 推荐风格 理由
状态数 <= 4,输出简单 一段式 代码少,够用就行
状态数 5-10,输出组合逻辑 两段式 结构清晰,调试方便
状态数 > 10,或输出对时序敏感 三段式 时序好,可维护性强
Lattice/低端FPGA 三段式 资源利用率高,时序易收敛
团队协作项目 三段式 代码规范,别人容易看懂

我个人习惯是:新项目一律三段式起步。为什么?因为后期需求变更太常见了。你今天觉得状态机简单,明天产品经理就给你加两个状态、三个输出。一段式改起来?改到想骂人。三段式?加个case分支就行,稳得很。

⚠ 避坑指南:我曾经在一个项目里,用两段式状态机驱动一个外部芯片的时序。组合逻辑输出直接连到芯片的使能引脚,结果因为毛刺导致芯片频繁误触发。后来改成三段式,输出加了一级寄存器,问题立刻解决。所以,只要输出要驱动外部器件,或者对时序有要求,别犹豫,上三段式

2.5 知识体系一览

下面这张图,把三种风格的核心逻辑和适用场景串起来了。你保存下来,写代码前瞄一眼,心里就有数了。

Verilog状态机编码风格对比 一段式 1个always块 状态+输出全时序 输出延迟一拍 适用:极简状态机 两段式 2个always块 状态跳转时序 输出组合逻辑 适用:中等复杂度 三段式 3个always块 状态跳转时序 输出时序(无毛刺) 适用:大型/时序敏感 选择建议 简单 → 一段式 | 中等 → 两段式 | 复杂/时序敏感 → 三段式 工程实战:无脑三段式起步,后期改需求不慌

好了,三种风格的区别和选择就聊到这儿。说白了,没有绝对的好坏,只有合不合适。你写代码的时候多想想:这个状态机以后会不会改?输出有没有毛刺风险?团队其他人能不能看懂?想清楚了,选哪种都行。


专注资料整理