🤝 第02课:握手协议(2-Phase / 4-Phase)

📚 课程阶段:异步基础(2/5)
🎯 学习目标:深入理解四阶段与两阶段握手协议的原理、实现和性能差异,掌握握手机制的Verilog建模与验证

一、握手协议的本质

握手协议是异步电路的"语言"——它定义了通信双方如何协调数据传输的时序。没有全局时钟告诉你"此刻采样",发送方和接收方必须通过信号交互达成共识:

看似简单,但如何定义"有数据"和"收到了",就产生了不同的握手协议变体。

握手协议设计的三要素

  1. 编码方式:如何用信号电平/边沿表示"有效"?
  2. 归零要求:每次传输后信号是否需要回到初始状态?
  3. 延迟约束:数据通路和控制通路的延迟关系是什么?

二、四阶段握手协议详解

2.1 基本工作原理

四阶段握手(4-Phase Handshake),又称归零握手(Return-to-Zero, RZ),是最经典的异步握手协议。名字来自它需要4个信号跳变完成一次数据传输:

四阶段握手完整时序图: 时间 ─────────────────────────────────────────────────► ┌─── 阶段1 ───┐ ┌─── 阶段3 ───┐ req ┤ └──┤ └───── │ │ _____┘ ┌── 阶段2 ──┐ └── 阶段4 ──┐ ack ┤ └─────────────┘ │ data ====[=== 有效数据 A ===]........[=== 有效数据 B ===] 详细分解: ───────────────────────────────────────────────── 阶段1 (req↑): 发送方将数据放到总线,拉高req → 数据必须先于req到达(delay matching约束) 阶段2 (ack↑): 接收方采样数据,拉高ack → 接收方知道数据已安全存储 阶段3 (req↓): 发送方看到ack↑,撤回数据和req → 这是"归零"步骤,信号回到初始状态 阶段4 (ack↓): 接收方看到req↓,拉低ack → 通信通道回到空闲状态,可以开始下一次传输 ─────────────────────────────────────────────────

2.2 四阶段握手的延迟匹配约束

⚠️ 关键时序约束

在单轨编码的四阶段握手中,数据通路的延迟必须小于请求通路的延迟。否则,接收方看到req↑时数据可能还没稳定:

t_data_path + t_setup ≤ t_req_path

实际设计中通常插入匹配延迟单元(Delay Element)在请求通路上,确保这个不等式成立:

延迟匹配示意: data_in ──[组合逻辑 t_data]──► data_out │ req_in ──[匹配延迟 t_match]──► req_out t_match ≥ t_data + t_setup

2.3 Verilog实现:四阶段握手发送器

// handshake_4phase_sender.v
// 四阶段握手发送端控制器
// 实现完整的4-phase归零握手协议
module handshake_4phase_sender #(
    parameter DATA_WIDTH = 8
)(
    input  wire                  clk,
    input  wire                  rst_n,
    // 本地接口(与上层模块交互)
    input  wire [DATA_WIDTH-1:0] data_in,     // 待发送数据
    input  wire                  send_valid,  // 发送请求(数据有效)
    output wire                  send_ready,  // 发送端可接收新数据
    // 握手接口(与接收端交互)
    output reg  [DATA_WIDTH-1:0] data_o,      // 数据输出
    output reg                   req_o,       // 请求信号
    input  wire                  ack_i        // 应答信号
);

    // 状态编码(one-hot,利于时序)
    localparam S_IDLE = 3'b001;  // 空闲:等待发送请求
    localparam S_REQ  = 3'b010;  // 请求已发:等待应答
    localparam S_DONE = 3'b100;  // 收到应答:撤回请求

    reg [2:0] state, next_state;

    // 状态转移
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= S_IDLE;
        else
            state <= next_state;
    end

    // 下一状态逻辑
    always @(*) begin
        next_state = state;
        case (state)
            S_IDLE: if (send_valid) next_state = S_REQ;
            S_REQ:  if (ack_i)      next_state = S_DONE;
            S_DONE: if (!ack_i)     next_state = S_IDLE;
            default: next_state = S_IDLE;
        endcase
    end

    // 输出逻辑
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_o <= {DATA_WIDTH{1'b0}};
            req_o  <= 1'b0;
        end else begin
            case (state)
                S_IDLE: begin
                    if (send_valid) begin
                        data_o <= data_in;  // 锁存数据
                        req_o  <= 1'b1;     // 阶段1: req↑
                    end
                end
                S_REQ: begin
                    // 等待ack↑
                end
                S_DONE: begin
                    req_o <= 1'b0;  // 阶段3: req↓(归零)
                    if (!ack_i) begin
                        data_o <= {DATA_WIDTH{1'b0}};  // 清除数据
                    end
                end
            endcase
        end
    end

    assign send_ready = (state == S_IDLE);

endmodule

2.4 Verilog实现:四阶段握手接收器

// handshake_4phase_receiver.v
// 四阶段握手接收端控制器
module handshake_4phase_receiver #(
    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_out,    // 接收到的数据
    output reg                   data_valid   // 数据有效脉冲(1周期)
);

    localparam S_IDLE = 2'd0;  // 等待请求
    localparam S_ACK  = 2'd1;  // 已应答,等待请求撤回

    reg [1:0] state;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state     <= S_IDLE;
            ack_o     <= 1'b0;
            data_out  <= {DATA_WIDTH{1'b0}};
            data_valid <= 1'b0;
        end else begin
            data_valid <= 1'b0;  // 默认无效
            case (state)
                S_IDLE: begin
                    if (req_i) begin
                        data_out  <= data_i;   // 采样数据
                        data_valid <= 1'b1;    // 产生有效脉冲
                        ack_o     <= 1'b1;     // 阶段2: ack↑
                        state     <= S_ACK;
                    end
                end
                S_ACK: begin
                    if (!req_i) begin
                        ack_o <= 1'b0;         // 阶段4: ack↓(归零)
                        state <= S_IDLE;
                    end
                end
                default: state <= S_IDLE;
            endcase
        end
    end

endmodule

三、两阶段握手协议详解

3.1 基本工作原理

两阶段握手(2-Phase Handshake),又称非归零握手(Non-Return-to-Zero, NRZ),只检测信号的边沿变化而非电平值:

两阶段握手时序图: 时间 ─────────────────────────────────────────────────► 第1次传输 第2次传输 ┌──阶段1──┐ ┌──阶段1──┐ req ┤ └─────┤ └───── │ │ _____┘ ┌─阶段2─┐ └──阶段2──┐ ack ┤ └─────────────┘ │ data ====[= 数据A =][= 数据B =] 关键区别: - req 和 ack 只需要跳变,不需要回到初始电平 - 奇数次传输: req↑ → ack↑ - 偶数次传输: req↓ → ack↓ - 不需要归零步骤,效率更高 - 但需要边沿检测电路,实现更复杂

3.2 两阶段握手 vs 四阶段握手

特性4-Phase (归零)2-Phase (非归零)
完成一次传输的跳变数4次 (req↑ack↑req↓ack↓)2次 (req边沿ack边沿)
信号归零需要不需要
检测方式电平检测边沿检测
吞吐率较低(4次跳变)较高(2次跳变)
延迟匹配仅req↑需要每次req边沿都需要
实现复杂度简单中等
功耗较高(4次翻转)较低(2次翻转)
典型应用异步FIFO、一般异步通道高性能异步流水线

3.3 Verilog实现:两阶段握手发送器

// handshake_2phase_sender.v
// 两阶段握手发送端控制器
// 检测ack边沿(而非电平)来判断传输完成
module handshake_2phase_sender #(
    parameter DATA_WIDTH = 8
)(
    input  wire                  clk,
    input  wire                  rst_n,
    // 本地接口
    input  wire [DATA_WIDTH-1:0] data_in,
    input  wire                  send_valid,
    output wire                  send_ready,
    // 握手接口
    output reg  [DATA_WIDTH-1:0] data_o,
    output reg                   req_o,
    input  wire                  ack_i
);

    // 边沿检测:检测ack的任何变化
    reg ack_d1, ack_d2;
    wire ack_edge;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            ack_d1 <= 1'b0;
            ack_d2 <= 1'b0;
        end else begin
            ack_d1 <= ack_i;
            ack_d2 <= ack_d1;
        end
    end

    // 检测ack的上升沿或下降沿
    assign ack_edge = (ack_d1 ^ ack_d2);

    // 发送端只需两个状态
    localparam S_IDLE = 1'b0;  // 等待发送请求
    localparam S_WAIT = 1'b1;  // 等待ack边沿

    reg state;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state  <= S_IDLE;
            req_o  <= 1'b0;
            data_o <= {DATA_WIDTH{1'b0}};
        end else begin
            case (state)
                S_IDLE: begin
                    if (send_valid) begin
                        data_o <= data_in;
                        req_o  <= ~req_o;  // 翻转req(产生边沿)
                        state  <= S_WAIT;
                    end
                end
                S_WAIT: begin
                    if (ack_edge) begin    // 检测到ack边沿
                        state <= S_IDLE;   // 传输完成
                    end
                end
            endcase
        end
    end

    assign send_ready = (state == S_IDLE);

endmodule

3.4 Verilog实现:两阶段握手接收器

// handshake_2phase_receiver.v
// 两阶段握手接收端控制器
// 检测req边沿触发数据采样
module handshake_2phase_receiver #(
    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_out,
    output reg                   data_valid
);

    // 边沿检测:检测req的任何变化
    reg req_d1, req_d2;
    wire req_edge;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            req_d1 <= 1'b0;
            req_d2 <= 1'b0;
        end else begin
            req_d1 <= req_i;
            req_d2 <= req_d1;
        end
    end

    assign req_edge = (req_d1 ^ req_d2);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            ack_o      <= 1'b0;
            data_out   <= {DATA_WIDTH{1'b0}};
            data_valid <= 1'b0;
        end else begin
            data_valid <= 1'b0;
            if (req_edge) begin
                data_out   <= data_i;    // 采样数据
                data_valid <= 1'b1;      // 产生有效脉冲
                ack_o      <= ~ack_o;    // 翻转ack(产生边沿)
            end
        end
    end

endmodule

四、延迟匹配单元

在异步电路中,数据通路上的组合逻辑延迟必须被请求通路上的匹配延迟覆盖。Verilog中可以用反相器链实现:

// delay_match_element.v
// 延迟匹配单元
// 在请求通路上插入可控延迟,确保数据先于请求到达
module delay_match_element #(
    parameter STAGES = 4  // 反相器级数,控制延迟量
)(
    input  wire in,
    output wire out
);

    // 生成反相器链
    genvar i;
    wire [STAGES:0] chain;
    assign chain[0] = in;

    generate
        for (i = 0; i < STAGES; i = i + 1) begin : gen_delay
            assign #0.1 chain[i+1] = ~chain[i];
        end
    endgenerate

    // 偶数级:同相;奇数级:反相
    generate
        if (STAGES % 2 == 0) begin : gen_even
            assign out = chain[STAGES];
        end else begin : gen_odd
            assign out = ~chain[STAGES];
        end
    endgenerate

endmodule

五、完整握手通道验证

// tb_handshake_channel.v
// 四阶段与两阶段握手通道对比测试
\`timescale 1ns/1ps

module tb_handshake_channel;

    reg clk, rst_n;

    // ============= 4-phase 测试信号 =============
    reg  [7:0] s4_data_in;
    reg        s4_send_valid;
    wire [7:0] s4_data_o;
    wire       s4_req_o;
    wire       s4_send_ready;
    wire [7:0] r4_data_out;
    wire       r4_data_valid;
    wire       r4_ack_o;

    handshake_4phase_sender #(.DATA_WIDTH(8)) u_s4 (
        .clk(clk), .rst_n(rst_n),
        .data_in(s4_data_in), .send_valid(s4_send_valid),
        .send_ready(s4_send_ready),
        .data_o(s4_data_o), .req_o(s4_req_o), .ack_i(r4_ack_o)
    );

    handshake_4phase_receiver #(.DATA_WIDTH(8)) u_r4 (
        .clk(clk), .rst_n(rst_n),
        .data_i(s4_data_o), .req_i(s4_req_o),
        .ack_o(r4_ack_o),
        .data_out(r4_data_out), .data_valid(r4_data_valid)
    );

    // ============= 2-phase 测试信号 =============
    reg  [7:0] s2_data_in;
    reg        s2_send_valid;
    wire [7:0] s2_data_o;
    wire       s2_req_o;
    wire       s2_send_ready;
    wire [7:0] r2_data_out;
    wire       r2_data_valid;
    wire       r2_ack_o;

    handshake_2phase_sender #(.DATA_WIDTH(8)) u_s2 (
        .clk(clk), .rst_n(rst_n),
        .data_in(s2_data_in), .send_valid(s2_send_valid),
        .send_ready(s2_send_ready),
        .data_o(s2_data_o), .req_o(s2_req_o), .ack_i(r2_ack_o)
    );

    handshake_2phase_receiver #(.DATA_WIDTH(8)) u_r2 (
        .clk(clk), .rst_n(rst_n),
        .data_i(s2_data_o), .req_i(s2_req_o),
        .ack_o(r2_ack_o),
        .data_out(r2_data_out), .data_valid(r2_data_valid)
    );

    // 时钟
    initial clk = 0;
    always #5 clk = ~clk;

    // 测试
    integer sent_4ph, recv_4ph;
    integer sent_2ph, recv_2ph;

    initial begin
        $dumpfile("handshake_channel.vcd");
        $dumpvars(0, tb_handshake_channel);

        rst_n = 0;
        s4_data_in = 0; s4_send_valid = 0;
        s2_data_in = 0; s2_send_valid = 0;
        sent_4ph = 0; recv_4ph = 0;
        sent_2ph = 0; recv_2ph = 0;
        #20 rst_n = 1;
        #10;

        // 连续发送10个数据,对比两种协议的吞吐
        repeat(10) begin
            @(posedge clk);
            // 4-phase 发送
            if (s4_send_ready) begin
                s4_data_in   <= $random;
                s4_send_valid <= 1'b1;
                sent_4ph = sent_4ph + 1;
            end
            // 2-phase 发送
            if (s2_send_ready) begin
                s2_data_in   <= $random;
                s2_send_valid <= 1'b1;
                sent_2ph = sent_2ph + 1;
            end
            @(posedge clk);
            s4_send_valid <= 1'b0;
            s2_send_valid <= 1'b0;
        end

        // 等待所有传输完成
        repeat(100) @(posedge clk);

        $display("4-phase: sent=%0d, received=%0d", sent_4ph, recv_4ph);
        $display("2-phase: sent=%0d, received=%0d", sent_2ph, recv_2ph);
        $display("=== Handshake Test Complete ===");
        $finish;
    end

    // 接收计数
    always @(posedge clk) begin
        if (r4_data_valid) recv_4ph = recv_4ph + 1;
        if (r2_data_valid) recv_2ph = recv_2ph + 1;
    end

endmodule

六、时序分析:握手周期

📊 握手协议的时序模型

四阶段握手周期(一次完整传输):

┌── t1 ──┐┌── t2 ──┐┌── t3 ──┐┌── t4 ──┐ req ┤ └┤ └┤ └┤ └── t1: req↑ → ack↑ (发送到响应) t2: ack↑ → req↓ (响应到归零开始) t3: req↓ → ack↓ (归零传播) t4: ack↓ → 下次req↑ (恢复时间) T_4phase = t1 + t2 + t3 + t4 吞吐率 = 1 / T_4phase

两阶段握手周期:

┌── t1 ──┐┌── t2 ──┐ req ┤ └┤ └── t1: req边沿 → ack边沿 t2: ack边沿 → 下次req边沿 T_2phase = t1 + t2 吞吐率 = 1 / T_2phase ≈ 2 × T_4phase

理论上2-phase的吞吐率约为4-phase的2倍,但实际提升取决于边沿检测的开销。

七、握手机制的扩展:流水线握手

多个握手级联形成异步流水线。每一级在接收数据后立即向下一级发出请求,同时向上一级发出应答:

// async_pipeline_stage.v
// 异步流水线级 — 将4-phase握手转为直通流水线
// 每级包含一个处理单元和一个握手控制器
module async_pipeline_stage #(
    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
);

    reg [DATA_WIDTH-1:0] buffer;    // 中间缓冲
    reg                  full;      // 缓冲满标志

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            buffer <= {DATA_WIDTH{1'b0}};
            data_o <= {DATA_WIDTH{1'b0}};
            req_o  <= 1'b0;
            ack_o  <= 1'b0;
            full   <= 1'b0;
        end else begin
            if (req_i && !full) begin
                // 输入侧:采样数据,应答
                buffer <= data_i;
                ack_o  <= 1'b1;
                full   <= 1'b1;
            end
            if (full && ack_i) begin
                // 输出侧:数据已被下一级接收
                req_o <= 1'b0;
                full  <= 1'b0;
            end
            if (full && !req_o) begin
                // 缓冲满且未发出请求 → 向下级传递
                data_o <= buffer;
                req_o  <= 1'b1;
            end
            if (!req_i && ack_o) begin
                // 归零:输入请求撤回
                ack_o <= 1'b0;
            end
        end
    end

endmodule

八、常见陷阱与调试

⚠️ 握手协议实现中的常见Bug

1. 死锁:双方互相等待

发送方等ack↑才发下一个数据,接收方等req↑才发ack。如果状态机有遗漏的转移,可能永远等待。

调试方法:形式验证检查所有状态是否可达,仿真中设置超时监控。

2. 数据冒险:采样到不稳定数据

req↑时数据还未稳定,或req↓时接收方仍在采样。

调试方法:检查delay matching约束,仿真中监控数据变化和req边沿的时间差。

3. 重复采样:一次数据被接收两次

4-phase中,如果接收方在阶段3(req↓)时又采样了数据。

调试方法:确保data_valid脉冲只在req↑时产生一次。

九、关键概念总结

✅ 本课核心要点

  1. 4-phase握手:4次跳变,归零式,简单可靠,最常用
  2. 2-phase握手:2次跳变,边沿式,效率高但需边沿检测
  3. 延迟匹配:数据通路延迟 ≤ 请求通路延迟 - 建立时间
  4. 流水线握手:多级级联形成异步流水线
  5. 边沿检测:2-phase的关键技术,XOR相邻周期

📝 练习题

1. 分析题:在四阶段握手中,如果发送方在阶段3(req↓)后不等ack↓就立即发新数据,可能发生什么?画出最坏情况的时序图。

2. 设计题:实现一个双向握手通道,两端都可以发起数据传输。需要仲裁机制避免冲突。

3. 计算题:4-phase握手通路中,t1=3ns, t2=2ns, t3=3ns, t4=2ns,计算最大吞吐率。如果改为2-phase(t1=3ns, t2=2ns),吞吐率提升多少?

4. 编程题:为异步流水线级添加背压(back-pressure)逻辑:当下级缓冲满时,当前级应停止向其发送数据,同时向上一级传播压力。

5. 思考题:为什么工业界异步FIFO几乎都使用4-phase而非2-phase?从可靠性、验证难度、时序约束角度分析。

🏆 成就解锁:握手大师

🎯 掌握了4-phase和2-phase两种核心握手协议

📍 里程碑:能够实现和验证异步握手通道

💡 下一步:学习异步状态机设计