🔄 第03课:异步状态机

📚 课程阶段:异步基础(3/5)
🎯 学习目标:理解异步状态机与同步状态机的根本差异,掌握基于握手的状态转移实现,学会用Verilog建模异步FSM并进行正确性验证

一、同步状态机 vs 异步状态机

同步状态机(Synchronous FSM)是数字设计的基石——当前状态和输入通过组合逻辑产生下一状态,在时钟沿统一更新。异步状态机(Asynchronous FSM)没有时钟,状态转移由输入信号的变化直接触发。

维度同步FSM异步FSM
状态更新时机时钟沿输入变化
状态编码任意(binary/one-hot/gray)必须无冒险
组合逻辑要求功能正确即可必须无冒险(hazard-free)
竞争风险时钟消除竞争必须消除本质冒险
速度受时钟周期限制仅受逻辑延迟限制
设计方法成熟(状态图→Verilog)复杂(需考虑冒险/竞争)

二、异步状态机的设计挑战

2.1 冒险(Hazard)

组合逻辑中,当输入变化导致多个路径的延迟不同时,输出可能出现短暂的错误跳变。同步设计中这无关紧要(时钟沿前稳定即可),但异步设计中这会导致错误的状态转移。

静态1冒险示例: A ──┬──[AND]──┐ │ [OR]── Y = AB + AC B ──┤ │ │ │ C ──┴─────────┘ 当 B=1, C=1, A从1→0时: 预期: Y = 1·1 + 0·1 = 1(始终为1) 实际: A变化先到OR门上方路径,后到下方路径 Y可能短暂变0! Y: ────1────0────1──── (glitch!) ↑ 冒险!

消除方法:添加冗余项。Y = AB + AC + BC,其中BC是冗余的一致项(consensus term),确保B=C=1时Y始终为1。

2.2 本质冒险(Essential Hazard)

即使组合逻辑无冒险,状态寄存器的延迟不均也可能导致错误转移。如果某条反馈路径比另一条快,状态机可能"跳过"预期状态。

2.3 状态编码

异步状态机的状态编码必须保证每次状态转移只有一个状态位变化(类似格雷码),否则多位同时变化可能因路径延迟不同到达中间状态。

三、基于握手的异步状态机

在实际工程中,我们不直接设计"纯异步FSM"(风险太高),而是在握手协议框架内实现状态机——用握手信号控制状态转移的时机,用时钟保证状态更新的确定性。这种方式兼顾了异步通信的灵活性和同步设计的可靠性。

3.1 异步FSM架构

基于握手的异步FSM架构: ┌──────────────────────────────────┐ │ Async FSM Module │ │ │ req_i ────────►│ ┌─────────┐ ┌──────────┐ │ │ │握手控制 │──►│ 状态转移 │ │ ack_i ◄────────│ │ (HC) │ │ 逻辑 │ │ │ └─────────┘ └────┬─────┘ │ │ │ │ data_i ───────►│ ┌──────────────────┘ │ │ │ ┌──────────┐ │ data_o ◄────────│ └──│ 状态寄存器│◄──── clk │ │ └──────────┘ │ └──────────────────────────────────┘ 握手控制确保: 1. 只有在req_i有效时才允许状态转移 2. 状态转移完成后发出ack_i 3. 避免输入在状态转移过程中变化

3.2 Verilog实现:异步握手FSM

我们实现一个具有4个状态的异步FSM,用于控制数据流处理:

// async_fsm.v
// 基于握手的异步状态机
// 状态转移由输入握手请求触发,完成后应答
module async_fsm #(
    parameter DATA_WIDTH = 8
)(
    input  wire                  clk,
    input  wire                  rst_n,
    // 异步输入握手
    input  wire [DATA_WIDTH-1:0] data_i,
    input  wire                  req_i,
    output reg                   ack_o,
    // 异步输出握手
    output reg  [DATA_WIDTH-1:0] data_o,
    output reg                   req_o,
    input  wire                  ack_i,
    // 状态输出(调试用)
    output reg  [2:0]            current_state
);

    // 状态定义(格雷码编码,确保每次只变1位)
    localparam S_IDLE    = 3'b000;  // 空闲
    localparam S_RECEIVE = 3'b001;  // 接收数据
    localparam S_PROCESS = 3'b011;  // 处理数据
    localparam S_SEND    = 3'b010;  // 发送结果
    localparam S_WAIT    = 3'b110;  // 等待输出应答

    reg [2:0] next_state;
    reg [DATA_WIDTH-1:0] process_reg;

    // 状态转移逻辑
    always @(*) begin
        next_state = current_state;
        case (current_state)
            S_IDLE:    if (req_i)           next_state = S_RECEIVE;
            S_RECEIVE:                      next_state = S_PROCESS;
            S_PROCESS:                      next_state = S_SEND;
            S_SEND:    if (ack_i)           next_state = S_WAIT;
            S_WAIT:    if (!ack_i && !req_i) next_state = S_IDLE;
            default:                        next_state = S_IDLE;
        endcase
    end

    // 状态更新 + 握手控制
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            current_state <= S_IDLE;
            ack_o         <= 1'b0;
            req_o         <= 1'b0;
            data_o        <= {DATA_WIDTH{1'b0}};
            process_reg   <= {DATA_WIDTH{1'b0}};
        end else begin
            current_state <= next_state;

            case (current_state)
                S_IDLE: begin
                    ack_o <= 1'b0;
                    if (req_i) begin
                        // 采样输入数据
                        process_reg <= data_i;
                        ack_o       <= 1'b1;  // 应答输入
                    end
                end

                S_RECEIVE: begin
                    // 输入应答已发出,等待处理
                end

                S_PROCESS: begin
                    // 数据处理:这里做简单的位反转作为示例
                    process_reg <= {<<{process_reg}};  // 位反转
                    ack_o       <= 1'b0;  // 撤回输入应答
                end

                S_SEND: begin
                    data_o <= process_reg;
                    req_o  <= 1'b1;  // 请求输出
                end

                S_WAIT: begin
                    if (ack_i) begin
                        req_o <= 1'b0;  // 收到输出应答,撤回请求
                    end
                    if (!ack_i && req_o == 1'b0) begin
                        // 归零完成
                    end
                end
            endcase
        end
    end

endmodule

四、异步FSM的形式化验证:SG图

异步FSM的正确性需要通过信号转换图(Signal Transition Graph, STG)进行验证。STG是有向图,节点是信号跳变(如req+、ack-),边表示因果关系:

4-phase握手STG示例: ┌──────────────────────┐ │ │ ▼ │ req+ ──► ack+ ──► req- ──► ack- │ │ └──────────────────────┘ (回到初始状态) 因果关系: req+ → ack+ : 请求导致应答 ack+ → req- : 应答允许撤回请求 req- → ack- : 请求撤回导致应答撤回 ack- → req+ : 应答撤回后可以发新请求 验证规则: 1. 所有信号转换必须可达 2. 不存在死锁(每个转换都有触发条件) 3. 一致性:每个信号的+和-交替出现

五、Petri网建模

异步系统的行为更适合用Petri网描述。Petri网由库所(Place,圆圈)和变迁(Transition,竖线)组成,令牌(Token,黑点)在库所中流动表示系统状态:

4-phase握手的Petri网模型: (p1)───►T1───►(p2)───►T2───►(p3) ● │ │ 令牌 │ │ ▼ ▼ (p4)◄───T4◄───(p5)◄───T3◄───(p6) T1: req↑ (消耗p1的令牌,产生p2的令牌) T2: ack↑ (消耗p2的令牌,产生p3的令牌) T3: req↓ (消耗p3的令牌,产生p6的令牌) T4: ack↓ (消耗p4的令牌,产生p1的令牌) 令牌数量守恒,不存在死锁

六、异步FSM综合示例:异步仲裁器

仲裁器(Arbiter)是异步电路的典型应用——当多个请求同时到达时,决定谁先获得资源。异步仲裁器不需要时钟,可以在请求到达的瞬间开始仲裁:

// async_arbiter.v
// 异步两输入仲裁器
// 使用4-phase握手协议,先到先服务+优先级打破
module async_arbiter (
    input  wire clk,
    input  wire rst_n,
    // 请求通道0
    input  wire req0_i,
    output reg  ack0_o,
    // 请求通道1
    input  wire req1_i,
    output reg  ack1_o,
    // 仲裁结果
    output reg  grant       // 0=通道0获得, 1=通道1获得
);

    // 仲裁器状态
    localparam S_IDLE   = 2'd0;  // 无请求
    localparam S_GRANT0 = 2'd1;  // 授权通道0
    localparam S_GRANT1 = 2'd2;  // 授权通道1

    reg [1:0] state;
    reg       last_grant;  // 上次授权给谁(公平性)

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state      <= S_IDLE;
            ack0_o     <= 1'b0;
            ack1_o     <= 1'b0;
            grant      <= 1'b0;
            last_grant <= 1'b0;
        end else begin
            case (state)
                S_IDLE: begin
                    ack0_o <= 1'b0;
                    ack1_o <= 1'b0;
                    if (req0_i && !req1_i) begin
                        // 只有通道0请求
                        state  <= S_GRANT0;
                        grant  <= 1'b0;
                        ack0_o <= 1'b1;
                    end else if (!req0_i && req1_i) begin
                        // 只有通道1请求
                        state  <= S_GRANT1;
                        grant  <= 1'b1;
                        ack1_o <= 1'b1;
                    end else if (req0_i && req1_i) begin
                        // 同时请求:轮流优先(公平性)
                        if (!last_grant) begin
                            state  <= S_GRANT1;
                            grant  <= 1'b1;
                            ack1_o <= 1'b1;
                            last_grant <= 1'b1;
                        end else begin
                            state  <= S_GRANT0;
                            grant  <= 1'b0;
                            ack0_o <= 1'b1;
                            last_grant <= 1'b0;
                        end
                    end
                end

                S_GRANT0: begin
                    // 通道0已授权,等待其释放
                    if (!req0_i) begin
                        ack0_o <= 1'b0;
                        state  <= S_IDLE;
                    end
                end

                S_GRANT1: begin
                    // 通道1已授权,等待其释放
                    if (!req1_i) begin
                        ack1_o <= 1'b0;
                        state  <= S_IDLE;
                    end
                end

                default: state <= S_IDLE;
            endcase
        end
    end

endmodule

七、亚稳态过滤器(Metastability Filter)

异步仲裁器的核心问题:当两个请求几乎同时到达时,仲裁逻辑可能在亚稳态下做出不一致的判断。工业界使用互斥元素(Mutual Exclusion Element, MUTEX)来解决:

// mutex_element.v
// 互斥元素 — 异步仲裁的亚稳态过滤器
// 确保两个请求不会同时获得授权
// 当两个请求同时到达时,MUTEX进入亚稳态
// 但只有亚稳态解除后才输出授权
module mutex_element (
    input  wire r1,       // 请求1
    input  wire r2,       // 请求2
    output wire g1,       // 授权1
    output wire g2        // 授权2
);

    // 交叉耦合NAND实现互斥
    // 当r1和r2同时为1时,两个NAND输出都可能
    // 暂时处于亚稳态,但最终会稳定到某一侧
    wire n1, n2;

    assign n1 = ~(r1 & n2);  // NAND门1
    assign n2 = ~(r2 & n1);  // NAND门2

    assign g1 = n1;
    assign g2 = n2;

    // 关键性质:
    // g1和g2永远不会同时为1
    // 如果r1先到:n1先变0 → g1=1, g2=0
    // 如果r2先到:n2先变0 → g1=0, g2=1
    // 同时到达:亚稳态最终随机消解

endmodule

八、异步FSM测试

// tb_async_fsm.v
// 异步状态机测试台
\`timescale 1ns/1ps

module tb_async_fsm;

    reg         clk, rst_n;
    reg  [7:0]  data_i;
    reg         req_i;
    wire        ack_o;
    wire [7:0]  data_o;
    wire        req_o;
    wire        ack_i;
    wire [2:0]  state;

    reg         ack_i_reg;

    assign ack_i = ack_i_reg;

    async_fsm #(.DATA_WIDTH(8)) uut (
        .clk(clk), .rst_n(rst_n),
        .data_i(data_i), .req_i(req_i),
        .ack_o(ack_o),
        .data_o(data_o), .req_o(req_o),
        .ack_i(ack_i),
        .current_state(state)
    );

    initial clk = 0;
    always #5 clk = ~clk;

    integer pass_count, fail_count;

    initial begin
        $dumpfile("async_fsm.vcd");
        $dumpvars(0, tb_async_fsm);

        rst_n = 0; req_i = 0; data_i = 0;
        ack_i_reg = 0;
        pass_count = 0; fail_count = 0;
        #20 rst_n = 1;
        #10;

        // 测试1: 发送 0b10110011,期望位反转 0b11001101
        $display("--- Test 1: Bit reverse of 0xB3 ---");
        data_i = 8'hB3;  // 10110011
        @(posedge clk);
        req_i = 1;
        @(posedge clk);
        req_i = 0;

        // 等待输入应答
        wait(ack_o);
        $display("  Input acknowledged, state=%b", state);

        // 等待输出请求
        wait(req_o);
        $display("  Output ready: data_out=0x%02h (expect 0xCD)", data_o);
        if (data_o === 8'hCD)  // 11001101
            pass_count = pass_count + 1;
        else
            fail_count = fail_count + 1;

        // 应答输出
        @(posedge clk);
        ack_i_reg = 1;
        @(posedge clk);
        ack_i_reg = 0;

        // 等待归零完成
        wait(!req_o);
        #50;

        // 测试2: 发送 0x0F
        $display("--- Test 2: Bit reverse of 0x0F ---");
        data_i = 8'h0F;
        @(posedge clk);
        req_i = 1;
        @(posedge clk);
        req_i = 0;

        wait(req_o);
        $display("  Output: data_out=0x%02h (expect 0xF0)", data_o);
        if (data_o === 8'hF0)
            pass_count = pass_count + 1;
        else
            fail_count = fail_count + 1;

        @(posedge clk);
        ack_i_reg = 1;
        @(posedge clk);
        ack_i_reg = 0;
        wait(!req_o);

        // 测试3: 快速连续发送
        $display("--- Test 3: Back-to-back transfers ---");
        repeat(5) begin
            data_i = $random;
            @(posedge clk);
            req_i = 1;
            @(posedge clk);
            req_i = 0;
            wait(req_o);
            @(posedge clk);
            ack_i_reg = 1;
            @(posedge clk);
            ack_i_reg = 0;
            wait(!req_o && !ack_o);
            #10;
        end

        #100;
        $display("=== Results: Pass=%0d Fail=%0d ===", pass_count, fail_count);
        $finish;
    end

endmodule

九、状态编码策略

异步FSM状态编码选择

编码方式优点缺点适用场景
格雷码每次只变1位,无冒险状态数必须是2的幂通用异步FSM
One-hot解码简单,速度快多位同时变化有冒险仅同步FSM
点对点完全定制,最优设计工作量大高性能定制异步
Latch-based面积小,低功耗时序验证困难特殊应用

本课使用格雷码编码:S_IDLE=000, S_RECEIVE=001, S_PROCESS=011, S_SEND=010, S_WAIT=110,确保相邻状态只差1位。

十、关键概念总结

✅ 本课核心要点

  1. 异步FSM没有时钟,状态转移由输入变化触发,需处理冒险和竞争
  2. 冒险消除:添加冗余一致项,格雷码状态编码
  3. 基于握手的FSM:用握手信号控制状态转移时机,兼顾安全性和实用性
  4. 仲裁器是异步FSM的典型应用,需解决亚稳态问题
  5. MUTEX元素:交叉耦合NAND,保证互斥授权
  6. Petri网/STG是异步系统行为建模和验证的形式化工具

📝 练习题

1. 分析题:为什么异步FSM必须使用格雷码编码?如果两个状态位同时变化,画出最坏情况的时序图说明可能到达的中间状态。

2. 设计题:将async_fsm扩展为3输入仲裁器,添加第3个请求通道。确保公平性(每个通道的等待时间大致相同)。

3. 验证题:为async_fsm的STG添加形式化属性:确保ack_o永远不会在没有req_i的情况下产生,且req_o永远不会在没有完成输入握手的情况下产生。

4. 编程题:实现一个异步FSM,功能类似于同步Moore机——输出仅取决于当前状态,不取决于输入。用4-phase握手控制状态转移。

5. 思考题:MUTEX元素的亚稳态消解时间理论上没有上界。在实际芯片中,什么机制保证了它最终会稳定?这个时间上限如何影响仲裁器的性能?

🏆 成就解锁:状态编织者

🎯 掌握了异步状态机的设计与验证

📍 里程碑:能设计无冒险的异步FSM并用形式化工具验证

💡 下一步:学习Muller C门——异步电路的基本构建块