4. 单路握手状态机设计:从发送端到接收端的可靠传输
Valid-ready握手协议,说白了就是数字世界里最朴素的“你准备好了吗?我准备好了!”的对话机制。我刚开始接触这个协议时,觉得它简单得有点无聊——不就是两个信号来回拉高拉低嘛。但后来在项目中吃过亏才明白,越是简单的东西,越容易在细节上翻车。
今天我们就来设计一个单路valid-ready握手状态机。别小看这个“单路”,它是所有复杂总线协议(比如AXI、Avalon)的基础模块。你把这个吃透了,后面学什么协议都轻松。
4.1 握手协议的基本规则
Valid-ready握手的核心规则其实就三条:
- 发送端:数据准备好后,拉高valid信号
- 接收端:准备好接收时,拉高ready信号
- 传输发生:valid和ready同时为高,数据被成功接收
嗯,这里要注意一个关键点——valid信号一旦拉高,必须保持到握手成功为止。不能中途撤回去。我在项目中见过有人把valid当成脉冲信号用,结果接收端只采到半个数据,整个系统乱套了。
4.2 状态机设计思路
单路握手状态机,我习惯把它分成三个状态:
| 状态 | 含义 | valid输出 | ready输出 |
|---|---|---|---|
| IDLE | 空闲,等待发送 | 0 | 1(如果接收端空闲) |
| SEND | 数据已发出,等待握手 | 1 | 0 |
| DONE | 握手成功,传输完成 | 0 | 1 |
你想想看,为什么需要三个状态?两个状态不行吗?
我曾经也这么想过,后来发现两个状态会出问题——如果发送端和接收端同时准备好,数据瞬间就传过去了,但状态机还没来得及记录这次传输。三个状态的好处是:每个传输周期都有明确的开始、等待和结束,不会丢数据。
4.3 状态转移图
下面我用SVG画了一张状态转移图,把整个流程可视化出来。你看一眼就明白了。
这张图其实已经把整个逻辑说清楚了。IDLE状态下,一旦有数据要发送,就跳转到SEND。在SEND状态,如果接收端ready为低,就原地等待;如果ready为高,握手成功,进入DONE。DONE状态表示这次传输结束,可以回到IDLE准备下一笔。
4.4 Verilog代码实现
光说不练假把式。下面是我写的一个单路握手状态机代码,你直接拿去用都没问题。
module handshake_fsm (
input wire clk,
input wire rst_n,
input wire data_valid, // 发送端数据有效
input wire ready_in, // 接收端准备好
output reg valid_out, // 发送端valid
output reg ready_out, // 发送端ready(接收端视角)
output reg handshake_done // 握手成功标志
);
// 状态编码
localparam IDLE = 2'b00;
localparam SEND = 2'b01;
localparam DONE = 2'b10;
reg [1:0] state, next_state;
// 状态寄存器
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 (data_valid)
next_state = SEND;
end
SEND: begin
if (ready_in)
next_state = DONE;
// 否则保持SEND
end
DONE: begin
next_state = IDLE; // 自动回到空闲
end
default: next_state = IDLE;
endcase
end
// 输出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
valid_out <= 1'b0;
ready_out <= 1'b1;
handshake_done <= 1'b0;
end else begin
case (next_state)
IDLE: begin
valid_out <= 1'b0;
ready_out <= 1'b1;
handshake_done <= 1'b0;
end
SEND: begin
valid_out <= 1'b1;
ready_out <= 1'b0;
handshake_done <= 1'b0;
end
DONE: begin
valid_out <= 1'b0;
ready_out <= 1'b1;
handshake_done <= 1'b1;
end
default: begin
valid_out <= 1'b0;
ready_out <= 1'b1;
handshake_done <= 1'b0;
end
endcase
end
end
endmodule
4.5 仿真验证要点
写完代码怎么验证?我一般会关注这几个场景:
- 正常握手:valid和ready同时为高,数据一拍内传完
- 接收端等待:ready一直为低,valid保持,直到ready拉高
- 发送端等待:valid为低,ready为高,状态机不动作
- 背靠背传输:上一笔刚传完,下一笔立即开始
- 复位行为:复位后状态机回到IDLE,所有输出清零
你想想看,如果接收端在握手成功的那一拍同时拉低ready,会发生什么?嗯,这个问题我当年面试时被问过。答案是:握手已经发生了,ready的变化不影响当前传输。所以代码里用next_state来判断输出,而不是用state,就是为了避免这种边界情况。
4.6 实际项目中的扩展
单路握手状态机虽然简单,但它是很多复杂模块的基石。我在做DMA控制器时,就用这个状态机作为每个通道的传输控制核心。只需要加一个数据计数器,就能实现指定长度的突发传输。
另外,如果你需要处理多个数据源,可以把valid和ready信号扩展成总线形式。每个数据源有自己的valid,接收端用仲裁逻辑决定先处理谁。但底层每个通道的握手逻辑,还是我们今天讲的这个三状态机。
好了,这一章的内容就到这里。代码你可以直接拿去仿真跑一跑,看看波形是不是符合预期。有什么问题,欢迎随时交流。
可可精品资料库