一、两相握手协议:从四相到两相的进化

各位同学,今天我们来聊聊两相握手协议。说实话,我刚接触这个协议时,第一反应是——这不就是把四相协议的四个边沿砍掉两个吗?后来在实际项目中踩了坑,才发现事情没那么简单。

两相握手协议,英文叫 Two-Phase Handshake,也有人叫它边沿触发握手。它和四相协议最大的区别,说白了就是:四相协议看电平,两相协议看边沿。

1.1 两相 vs 四相:核心差异

我习惯用一个比喻来理解:四相协议就像两个人轮流举手示意,每次举手都要放下来再举;两相协议则是每次举手只改变状态,不用刻意放下来。

来看个具体对比:

对比项 四相协议 两相协议
触发方式 电平触发 边沿触发
状态数 4个状态 2个状态
握手周期 4个边沿 2个边沿
信号变化 每次都要归零 只翻转,不归零
功耗 较高(翻转次数多) 较低(翻转次数少)
适用场景 同步设计、低速接口 异步设计、低功耗场景

你想想看,四相协议每次传输都要经历:请求拉高 → 应答拉高 → 请求拉低 → 应答拉低。这四个步骤里,有一半的时间花在"归零"上。两相协议直接砍掉了归零动作,效率自然就上来了。

1.2 边沿触发机制

两相协议的核心,就是边沿触发。什么意思呢?

在四相协议里,发送方把 req 拉高,接收方看到高电平就知道有请求。但在两相协议里,接收方看的是 req 有没有发生变化——不管是上升沿还是下降沿,只要变了,就说明有新请求。

我记得有一次做低功耗芯片设计,项目要求接口功耗必须控制在 1mW 以内。四相协议怎么优化都差一点,后来换成两相协议,功耗直接降了 40%。原因很简单:信号翻转次数少了一半。

关键点:两相协议中,每个信号只表示"状态发生了变化",而不表示"状态是高还是低"。所以接收方必须用边沿检测电路来捕捉变化。

1.3 状态机设计

两相协议的状态机,比四相简单得多。我一般用两个状态来描述:

  • IDLE(空闲态):等待请求边沿
  • BUSY(忙碌态):处理数据,等待应答边沿

来看一个典型的发送端状态机:

// 两相握手发送端状态机
typedef enum {
    IDLE,
    WAIT_ACK
} tx_state_t;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        req   <= 0;
    end else begin
        case (state)
            IDLE: begin
                if (data_valid) begin
                    req <= ~req;      // 翻转请求信号
                    state <= WAIT_ACK;
                end
            end
            
            WAIT_ACK: begin
                if (ack != req) begin  // 检测到应答边沿
                    state <= IDLE;
                end
            end
        endcase
    end
end

嗯,这里要注意:判断应答是否到达,不是看 ack 是高还是低,而是看 ack 和 req 是否相等。如果 ack == req,说明接收方还没响应;如果 ack != req,说明接收方已经翻转了 ack,响应到达了。

接收端的状态机也类似:

// 两相握手接收端状态机
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        ack <= 0;
        data_out <= 0;
    end else begin
        if (req != ack) begin  // 检测到请求边沿
            data_out <= data_in;
            ack <= ~ack;       // 翻转应答信号
        end
    end
end

个人经验:我曾经在一个项目中,接收端直接用组合逻辑判断 req != ack,结果出现了毛刺。后来加了两级同步器,问题就解决了。跨时钟域处理,两相协议同样不能省。

1.4 优缺点分析

两相协议不是万能的,它有明显的优势和劣势。

优点:

  • 效率高:每个握手周期只需要两次信号翻转,比四相快一倍
  • 功耗低:信号翻转次数少,动态功耗自然低
  • 状态机简单:两个状态就能搞定,代码量少
  • 适合异步设计:不需要全局时钟同步,天然支持异步接口

缺点:

  • 边沿检测复杂:需要额外的边沿检测电路,增加面积
  • 调试困难:信号一直在翻转,波形看起来眼花缭乱
  • 复位状态敏感:上电后 req 和 ack 的初始值必须一致,否则会误触发
  • 不支持多数据打包:每次握手只能传一笔数据,不像四相协议可以流水线

避坑指南:我曾经在一个项目中,复位后 req 初始化为 0,ack 初始化为 1,结果一上电就触发了握手。后来我养成了习惯:所有两相协议的 req 和 ack 必须同步复位到相同值。

1.5 知识体系图

下面这张图,是我自己总结的两相握手协议知识体系,你可以把它当作学习路线图:

两相握手协议知识体系 两相握手协议 工作原理 工程实现 边沿触发机制 与四相协议对比 状态机设计 优缺点分析 适用场景:低功耗设计、异步接口、高速传输 两相协议的核心:用边沿代替电平,用翻转代替归零 状态机只有两个状态:IDLE 和 WAIT_ACK 判断条件:req != ack 表示握手进行中,req == ack 表示空闲

1.6 什么时候选两相?

我个人总结了一个选择原则:

  • 功耗敏感项目(比如 IoT、可穿戴设备)→ 优先考虑两相
  • 高速接口(比如 DDR、SerDes)→ 两相协议效率更高
  • 异步跨时钟域 → 两相协议天然支持,不需要额外同步
  • 调试友好型项目 → 还是用四相吧,波形看起来舒服多了

好了,两相握手协议的核心内容就这些。记住一句话:两相协议用边沿说话,四相协议用电平说话。理解了这个本质,剩下的都是细节。

课后思考:如果让你设计一个两相协议的接收端,如何保证在高速时钟下不会漏掉请求边沿?提示:想想边沿检测电路的建立时间约束。

可可精品资料库