
RDMA RC协议数据缓冲模块设计:从需求到验证全解析
在RDMA(远程直接内存访问)RC(可靠连接)协议的硬件实现中,数据缓冲模块是保障传输稳定性的核心枢纽------它既要衔接AXI-Stream接口的高速数据流入,又要配合流量控制模块实现"按需发送",还要解决数据帧的时序错配问题。本文将从工程实践角度,完整拆解一款适配QP状态机、流量控制与PDU解析模块的RDMA RC数据缓冲模块,包含设计思路、核心逻辑、验证方法及集成技巧,代码可直接用于FPGA/IC开发。
一、模块定位:为什么需要专属缓冲模块?
在RDMA RC传输链路中,缓冲模块的核心价值是解决"三大错配"问题,这也是其区别于通用FIFO的关键:
- 时序错配:PDU解析模块输出数据的突发特性,与流量控制模块"Credit耗尽则暂停"的刚性要求存在冲突,缓冲可平滑数据流入流出的节奏;
- 接口错配 :上游PDU解析模块通常输出零散数据,下游传输模块需整帧发送,缓冲模块需保障帧完整性(通过
tlast信号锁存); - 速率错配 :当流量控制模块反馈
send_pause=1(Credit耗尽)时,缓冲模块需暂存新数据,避免数据丢失,恢复后无缝续传。
简单来说,该模块是QP状态机、流量控制、PDU解析三大模块的"数据中转站",其设计质量直接决定RDMA传输的可靠性与吞吐量。
二、核心需求:明确设计边界与指标
结合RDMA RC协议特性及前文实现的QP/flow_ctrl/PDU模块,缓冲模块需满足以下核心需求:
- 接口兼容 :支持AXI-Stream标准接口(
tdata/tvalid/tready/tlast),无缝对接PDU解析模块与传输链路; - 流量联动 :接收flow_ctrl模块的
send_pause信号,暂停/恢复数据发送,无数据丢失; - 背压机制 :缓冲满时输出
backpressure信号,阻止上游PDU模块继续写入,避免数据溢出; - 状态反馈 :输出
buf_full/buf_empty状态,供flow_ctrl模块优化Credit分配策略; - 参数可扩 :数据位宽(
DATA_WIDTH)、缓冲深度(BUF_DEPTH)支持参数配置,适配不同传输速率需求; - 整帧保障 :精准锁存
tlast信号,确保数据帧完整发送,符合RDMA协议"整帧传输"要求。
三、核心设计:从接口到逻辑的完整实现
3.1 模块参数化设计(可扩展性关键)
采用参数化定义核心配置,避免硬编码,适配不同场景需求:
DATA_WIDTH:数据总线位宽,与PDU解析模块保持一致(默认64bit);BUF_DEPTH:缓冲深度(默认16帧,需为2的幂次,简化地址生成);ADDR_WIDTH:地址位宽,由BUF_DEPTH自动计算($clog2(BUF_DEPTH))。
3.2 接口定义(衔接上下游模块)
按"输入-输出"逻辑梳理接口,明确每个信号的作用与对接模块:
| 接口类型 | 信号名 | 位宽 | 作用说明 | 对接模块 |
|---|---|---|---|---|
| 全局信号 | clk | 1bit | 系统时钟(100MHz) | 所有模块 |
| rst_n | 1bit | 异步低复位 | 所有模块 | |
| 流量控制输入 | send_pause | 1bit | 发送暂停信号(1=暂停) | flow_ctrl模块 |
| AXI-Stream接收 | s_axis_tdata | DATA_WIDTH | 上游输入数据(PDU解析后的数据帧) | PDU解析模块 |
| s_axis_tvalid | 1bit | 接收数据有效标志 | PDU解析模块 | |
| s_axis_tlast | 1bit | 接收帧尾标志(标记1帧PDU结束) | PDU解析模块 | |
| s_axis_tready | 1bit | 接收就绪(背压核心,0=拒绝上游数据) | PDU解析模块 | |
| AXI-Stream发送 | m_axis_tdata | DATA_WIDTH | 下游发送数据(至传输链路) | 传输模块 |
| m_axis_tvalid | 1bit | 发送数据有效标志 | 传输模块 | |
| m_axis_tlast | 1bit | 发送帧尾标志 | 传输模块 | |
| m_axis_tready | 1bit | 下游接收就绪 | 传输模块 | |
| 状态输出 | buf_full | 1bit | 缓冲满标志 | flow_ctrl/上层 |
| buf_empty | 1bit | 缓冲空标志 | flow_ctrl/上层 | |
| backpressure | 1bit | 背压信号(=buf_full,简化上游对接) | PDU解析模块 |
3.3 内部核心结构
模块内部由"存储单元+控制逻辑"两部分组成,关键信号包括:
- 存储单元:
buf_mem[BUF_DEPTH-1:0](寄存器数组,实现数据暂存); - 地址计数器:
wr_addr(写地址)、rd_addr(读地址),实现循环读写; - 状态寄存器:
buf_cnt(缓冲数据计数,0~BUF_DEPTH)、tlast_r(帧尾锁存,保障整帧发送)。
3.4 关键逻辑解析(附代码片段)
缓冲模块的核心是"精准控制读写时序,联动外部信号实现可靠传输",以下拆解四大核心逻辑:
1. 写入控制:接收上游数据,锁存帧尾
当上游PDU模块输出有效数据(s_axis_tvalid=1)且缓冲就绪(s_axis_tready=1)时,将数据写入缓冲,并锁存帧尾信号供发送阶段使用:
verilog
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_addr <= {ADDR_WIDTH{1'b0}};
tlast_r <= 1'b0;
end else if (s_axis_tvalid && s_axis_tready) begin
buf_mem[wr_addr] <= s_axis_tdata; // 写入数据
tlast_r <= s_axis_tlast; // 锁存帧尾
// 写地址循环自增
wr_addr <= (wr_addr == BUF_DEPTH - 1) ? 0 : wr_addr + 1;
end
end
2. 读取控制:联动流量控制,暂停/恢复发送
读取逻辑的核心是响应send_pause信号,同时确保帧尾同步输出:
- 当
send_pause=1(Credit耗尽)或缓冲空时,停止发送; - 当
send_pause=0且下游就绪时,从缓冲读取数据并输出:
verilog
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_addr <= 0;
m_axis_tdata <= 0;
m_axis_tvalid <= 0;
m_axis_tlast <= 0;
end else if (!send_pause && !buf_empty) begin
if (m_axis_tready || !m_axis_tvalid) begin
m_axis_tdata <= buf_mem[rd_addr]; // 读取数据
m_axis_tvalid <= 1'b1;
m_axis_tlast <= tlast_r; // 同步输出帧尾
// 读地址循环自增
rd_addr <= (rd_addr == BUF_DEPTH - 1) ? 0 : rd_addr + 1;
tlast_r <= (tlast_r) ? 1'b0 : tlast_r; // 帧尾输出后清零
end
end else begin
m_axis_tvalid <= 1'b0; // 暂停或空缓冲时,停止发送
m_axis_tlast <= 1'b0;
end
end
3. 满空检测:基于计数实现精准状态反馈
通过buf_cnt(缓冲数据个数)判断满空状态,buf_cnt由"读写操作"共同更新:
- 只写不读:
buf_cnt += 1; - 只读不写:
buf_cnt -= 1; - 读写同时:
buf_cnt不变。
verilog
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
buf_cnt <= 0;
end else begin
case ({s_axis_tvalid & s_axis_tready, m_axis_tvalid & m_axis_tready & !send_pause})
2'b01: buf_cnt <= buf_cnt - 1; // 只读
2'b10: buf_cnt <= buf_cnt + 1; // 只写
default: buf_cnt <= buf_cnt; // 无操作或同时读写
endcase
end
end
// 满空状态输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
buf_full <= 0;
buf_empty <= 1;
backpressure <= 0;
end else begin
buf_full <= (buf_cnt == BUF_DEPTH);
buf_empty <= (buf_cnt == 0);
backpressure <= (buf_cnt == BUF_DEPTH); // 背压=缓冲满
end
end
4. 背压控制:避免数据溢出
背压信号 s_axis_tready直接与buf_full联动------缓冲未满则允许上游写入,满则拒绝,从根本上避免数据溢出:
verilog
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
s_axis_tready <= 1'b0;
end else begin
s_axis_tready <= !buf_full; // 缓冲未满则就绪
end
end
四、完整模块代码(可直接复用)
verilog
// RDMA RC 数据缓冲模块(适配flow_ctrl/pdu_parser/QP模块)
// 核心功能:AXI-Stream数据缓存 + 背压输出 + 流量控制暂停联动 + 满/空检测
module rdma_rc_buf #(
parameter DATA_WIDTH = 64, // 数据位宽(与PDU解析模块一致)
parameter BUF_DEPTH = 16, // 缓冲深度(2的幂次)
parameter ADDR_WIDTH = $clog2(BUF_DEPTH) // 地址位宽自动计算
)(
input wire clk, // 100MHz系统时钟
input wire rst_n, // 异步低复位
input wire send_pause, // 发送暂停(来自flow_ctrl)
// AXI-Stream接收接口(上游:PDU解析模块)
input wire [DATA_WIDTH-1:0] s_axis_tdata,
input wire s_axis_tvalid,
input wire s_axis_tlast,
output reg s_axis_tready,
// AXI-Stream发送接口(下游:传输模块)
output reg [DATA_WIDTH-1:0] m_axis_tdata,
output reg m_axis_tvalid,
output reg m_axis_tlast,
input wire m_axis_tready,
// 状态输出
output reg buf_full,
output reg buf_empty,
output reg backpressure
);
// 内部存储与控制信号
reg [DATA_WIDTH-1:0] buf_mem[0:BUF_DEPTH-1]; // 缓冲存储单元
reg [ADDR_WIDTH-1:0] wr_addr; // 写地址计数器
reg [ADDR_WIDTH-1:0] rd_addr; // 读地址计数器
reg [ADDR_WIDTH:0] buf_cnt; // 缓冲数据计数
reg tlast_r; // 帧尾锁存寄存器
// 1. 写入控制逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_addr <= {ADDR_WIDTH{1'b0}};
tlast_r <= 1'b0;
end else if (s_axis_tvalid && s_axis_tready) begin
buf_mem[wr_addr] <= s_axis_tdata;
tlast_r <= s_axis_tlast;
wr_addr <= (wr_addr == BUF_DEPTH - 1) ? {ADDR_WIDTH{1'b0}} : wr_addr + 1'b1;
end
end
// 2. 读取控制逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_addr <= {ADDR_WIDTH{1'b0}};
m_axis_tdata <= {DATA_WIDTH{1'b0}};
m_axis_tvalid <= 1'b0;
m_axis_tlast <= 1'b0;
end else if (!send_pause && !buf_empty) begin
if (m_axis_tready || !m_axis_tvalid) begin
m_axis_tdata <= buf_mem[rd_addr];
m_axis_tvalid <= 1'b1;
m_axis_tlast <= tlast_r;
rd_addr <= (rd_addr == BUF_DEPTH - 1) ? {ADDR_WIDTH{1'b0}} : rd_addr + 1'b1;
tlast_r <= (tlast_r) ? 1'b0 : tlast_r;
end
end else begin
m_axis_tdata <= {DATA_WIDTH{1'b0}};
m_axis_tvalid <= 1'b0;
m_axis_tlast <= 1'b0;
end
end
// 3. 缓冲计数与满空检测
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
buf_cnt <= {ADDR_WIDTH+1{1'b0}};
end else begin
case ({s_axis_tvalid & s_axis_tready, m_axis_tvalid & m_axis_tready & !send_pause})
2'b01: buf_cnt <= buf_cnt - 1'b1;
2'b10: buf_cnt <= buf_cnt + 1'b1;
default: buf_cnt <= buf_cnt;
endcase
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
buf_full <= 1'b0;
buf_empty <= 1'b1;
backpressure <= 1'b0;
end else begin
buf_full <= (buf_cnt == BUF_DEPTH);
buf_empty <= (buf_cnt == {ADDR_WIDTH+1{1'b0}});
backpressure <= (buf_cnt == BUF_DEPTH);
end
end
// 4. 背压控制(接收就绪信号)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
s_axis_tready <= 1'b0;
end else begin
s_axis_tready <= !buf_full;
end
end
endmodule
五、模块验证:确保功能可靠(附测试核心思路)
验证的核心是覆盖"正常场景+边界场景",基于SystemVerilog编写TB,关键测试用例包括:
1. 基础功能验证
- 复位状态:复位后
buf_empty=1、s_axis_tready=1,符合初始状态要求; - 正常读写:写入8帧数据后
buf_cnt=8,读取后buf_cnt=0,数据无错配。
2. 边界场景验证
- 缓冲满背压:写入16帧数据后
buf_full=1、s_axis_tready=0,上游无法继续写入; - 暂停发送:
send_pause=1时m_axis_tvalid=0,暂停释放后数据续传无丢失; - 整帧传输:
tlast信号精准同步,确保1帧数据完整输出。
3. 验证工具与脚本
使用VCS编译,Verdi查看波形,编译脚本参考:
bash
vcs -sverilog tb_rdma_rc_buf.sv rdma_rc_buf.v -debug_access+all -fsdb -P $VERDI_HOME/share/PLI/VCS/LINUX64/novas.tab $VERDI_HOME/share/PLI/VCS/LINUX64/pli.a
六、集成注意事项
- 位宽对齐 :
DATA_WIDTH必须与PDU解析模块、传输模块保持一致(默认64bit); - 时序同步 :所有模块使用同一
clk/rst_n,避免异步交互导致的 metastability; - 参数配置 :根据实际吞吐量需求调整
BUF_DEPTH------高频场景可增大至32/64,资源受限场景可减小至8。
七、总结与扩展
本文设计的RDMA RC缓冲模块,通过"参数化、接口标准化、逻辑精准化"实现了三大核心价值:
- 兼容性:无缝对接QP/flow_ctrl/PDU模块,无需额外适配逻辑;
- 可靠性:背压+满空检测+帧尾锁存,从根本上避免数据丢失与溢出;
- 可扩展性:支持位宽、深度配置,适配不同FPGA/IC芯片资源。
扩展方向:
- 动态深度:通过配置寄存器实现
BUF_DEPTH动态调整; - 多通道扩展:增加通道选择信号,实现多QP共享缓冲;
- 错误检测:新增奇偶校验位,检测数据传输错误。
如果在模块使用或验证中遇到问题,欢迎在评论区交流,也可结合具体场景进一步优化设计细节~