握手协议是异步电路的"语言"——它定义了通信双方如何协调数据传输的时序。没有全局时钟告诉你"此刻采样",发送方和接收方必须通过信号交互达成共识:
看似简单,但如何定义"有数据"和"收到了",就产生了不同的握手协议变体。
四阶段握手(4-Phase Handshake),又称归零握手(Return-to-Zero, RZ),是最经典的异步握手协议。名字来自它需要4个信号跳变完成一次数据传输:
在单轨编码的四阶段握手中,数据通路的延迟必须小于请求通路的延迟。否则,接收方看到req↑时数据可能还没稳定:
t_data_path + t_setup ≤ t_req_path
实际设计中通常插入匹配延迟单元(Delay Element)在请求通路上,确保这个不等式成立:
// 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
// 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
两阶段握手(2-Phase Handshake),又称非归零握手(Non-Return-to-Zero, NRZ),只检测信号的边沿变化而非电平值:
| 特性 | 4-Phase (归零) | 2-Phase (非归零) |
|---|---|---|
| 完成一次传输的跳变数 | 4次 (req↑ack↑req↓ack↓) | 2次 (req边沿ack边沿) |
| 信号归零 | 需要 | 不需要 |
| 检测方式 | 电平检测 | 边沿检测 |
| 吞吐率 | 较低(4次跳变) | 较高(2次跳变) |
| 延迟匹配 | 仅req↑需要 | 每次req边沿都需要 |
| 实现复杂度 | 简单 | 中等 |
| 功耗 | 较高(4次翻转) | 较低(2次翻转) |
| 典型应用 | 异步FIFO、一般异步通道 | 高性能异步流水线 |
// 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
// 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
四阶段握手周期(一次完整传输):
两阶段握手周期:
理论上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
1. 死锁:双方互相等待
发送方等ack↑才发下一个数据,接收方等req↑才发ack。如果状态机有遗漏的转移,可能永远等待。
调试方法:形式验证检查所有状态是否可达,仿真中设置超时监控。
2. 数据冒险:采样到不稳定数据
req↑时数据还未稳定,或req↓时接收方仍在采样。
调试方法:检查delay matching约束,仿真中监控数据变化和req边沿的时间差。
3. 重复采样:一次数据被接收两次
4-phase中,如果接收方在阶段3(req↓)时又采样了数据。
调试方法:确保data_valid脉冲只在req↑时产生一次。
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两种核心握手协议
📍 里程碑:能够实现和验证异步握手通道
💡 下一步:学习异步状态机设计