通过构建基于 AXI Stream 协议的树形数据流切换架构(包含 N 选 1 仲裁器和 1 分 N 分发器),利用多级流水线寄存器打拍技术,将原本集中式的大扇出(Fanout)信号分解为多层级的小扇出网络,从而显著降低布线拥塞并提升 FPGA 最高工作频率。
核心原理与优势分析
树形结构通过将大规模的多路选择或分发任务分解为多个小规模的子任务(如 2 选 1 或 N 选 1),并在每一级逻辑之间插入寄存器(Pipeline Registers),实现了物理层面的信号隔离与时序优化。
| 优化维度 | 传统扁平化设计痛点 | 树形结构 + 寄存器打拍方案优势 |
|---|---|---|
| 扇出 (Fanout) | 单个控制信号需驱动大量负载,导致布线延迟大,成为时序瓶颈 。 | 将大扇出拆解为多级小扇出,每级仅驱动少量下级模块,大幅降低单节点负载 。 |
| 工作频率 | 长组合逻辑路径和高压摆率限制导致 F_{max} 难以提升。 | 寄存器打拍切断了长组合逻辑链,将关键路径限制在局部小树节点内,显著提升 F_{max} 。 |
| 地址/通道管理 | 业务模块需自行维护复杂的地址映射和路由逻辑。 | 切换模块自动根据控制位(CTL)重写通道 ID,业务模块无需感知路由细节 。 |
| 扩展性 | 增加输入/输出端口需重新设计整个多路选择器。 | 支持参数化递归实例化,可灵活构建任意规模(N路)的树形网络 。 |
关键技术实现:N 选 1 仲裁器 (Tree Arbiter)
该模块采用递归生成(Generate-Recursion)方式构建。当输入端口数大于基数(如 N=2)时,先并行实例化多个底层仲裁器,再将它们的输出作为输入传递给上一级仲裁器,形成树状拓扑。关键步骤包含通道 ID 的自动注入与流水线寄存。
systemverilog
// 树形 N 选 1 AXI Stream 仲裁器
// 功能:将 NUM_IN 路输入汇聚为 1 路输出,通过树形结构降低扇出
module tree_packet_arb_n_to_1 # (
parameter DAT_BYTS = 8,
parameter DAT_BITS = DAT_BYTS*8,
parameter CTL_BITS = 8,
parameter NUM_IN = 8, // 总输入路数
parameter LOG2_NUM_IN = NUM_IN == 1 ? 1 : $clog2(NUM_IN),
parameter OVR_WRT_BIT = CTL_BITS - LOG2_NUM_IN, // 控制位中用于覆盖通道 ID 的起始位
parameter N = 2, // 树的分支因子 (如 2 叉树)
parameter MAX = NUM_IN // 最大路数常量
) (
input i_clk, i_rst,
if_axi_stream.sink i_n_axi [NUM_IN-1:0], // 多路输入接口
if_axi_stream.source o_axi // 单路输出接口
);
localparam MOD_BITS = $clog2(DAT_BYTS);
localparam NUM_IN_GRP = (NUM_IN+N-1)/N; // 计算本级需要的子模块数量
generate
genvar g, h;
// 定义内部互联接口
if_axi_stream #(.DAT_BYTS(DAT_BYTS), .DAT_BITS(DAT_BITS), .CTL_BITS(CTL_BITS)) o_axi_int [NUM_IN_GRP-1:0] (i_clk);
for (g = 0; g < NUM_IN_GRP; g++) begin: GEN_TREE_LEVEL
// 计算当前组实际有效的输入数量 localparam NUM_IN_INT = (g+1)*N <= NUM_IN ? N : (NUM_IN % N);
if_axi_stream #(.DAT_BYTS(DAT_BYTS), .DAT_BITS(DAT_BITS), .CTL_BITS(CTL_BITS)) i_axi_int [NUM_IN_INT-1:0] (i_clk);
// 1. 输入连接与通道 ID 注入
for (h = 0; h < NUM_IN_INT; h++) begin
always_comb begin // 复制数据流信号
i_axi_int[h].copy_if_comb(i_n_axi[g*N + h].dat, i_n_axi[g*N + h].val, i_n_axi[g*N + h].sop,
i_n_axi[g*N + h].eop, i_n_axi[g*N + h].err, i_n_axi[g*N + h].mod,
i_n_axi[g*N + h].ctl);
// 反压传递
i_n_axi[g*N + h].rdy = i_axi_int[h].rdy;
// 关键优化:在入口处自动重写控制位,标记数据来源通道 ID
if (MAX == NUM_IN)
i_axi_int[h].ctl[OVR_WRT_BIT +: LOG2_NUM_IN] = g*N + h;
end
end
// 2. 实例化底层仲裁器 (带流水线寄存器 PIPELINE=1)
packet_arb_bp # (
.DAT_BYTS ( DAT_BYTS ),
.DAT_BITS ( DAT_BITS ),
.CTL_BITS ( CTL_BITS ),
.NUM_IN ( NUM_IN_INT ),
.OVR_WRT_BIT ( OVR_WRT_BIT ),
.PIPELINE ( 1 ), // 启用寄存器打拍,切断组合逻辑,提高频率
.PRIORITY_IN ( 0 ),
.OVERRIDE_CTL ( 0 )
)
packet_arb_bp_inst (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_axi ( i_axi_int ),
.o_axi ( o_axi_int[g] )
);
end
// 3. 递归构建上层树或输出最终结果
if (NUM_IN_GRP > 1) begin
// 若子模块输出多于 1 路,递归调用自身构建上一级树
tree_packet_arb_n_to_1 # (
.DAT_BYTS ( DAT_BYTS ),
.DAT_BITS ( DAT_BITS ),
.CTL_BITS ( CTL_BITS ),
.NUM_IN ( NUM_IN_GRP ),
.OVR_WRT_BIT ( OVR_WRT_BIT ),
.N ( N ),
.MAX ( MAX )
)
tree_upper_level (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_n_axi ( o_axi_int ),
.o_axi ( o_axi )
);
end else begin
// 若仅剩一路,直接连通
always_comb begin
o_axi.copy_if_comb(o_axi_int[0].dat, o_axi_int[0].val, o_axi_int[0].sop, o_axi_int[0].eop,
o_axi_int[0].err, o_axi_int[0].mod, o_axi_int[0].ctl);
o_axi_int[0].rdy = o_axi.rdy;
end
end
endgenerate
endmodule
关键技术实现:1 分 N 分发器 (Tree Distributor)
分发器同样采用树形结构,依据数据包控制位(CTL)中的通道 ID 将数据路由至指定输出端口。通过在树的中继节点插入流水线寄存器(pipeline_bp_if),确保高速时钟下信号能稳定传输至叶子节点。
systemverilog
// 树形 1 分 N AXI Stream 分发器
// 功能:将 1 路输入根据 CTL 位分发至 NUM_OUT 路输出,利用树形结构降低扇出
module tree_packet_arb_1_to_n # (
parameter DAT_BYTS = 8,
parameter DAT_BITS = DAT_BYTS*8,
parameter CTL_BITS = 8,
parameter NUM_OUT = 8,
parameter LOG2_NUM_OUT = NUM_OUT == 1 ? 1 : $clog2(NUM_OUT),
parameter OVR_WRT_BIT = CTL_BITS - LOG2_NUM_OUT,
parameter N = 2, // 树分支因子
parameter MAX = NUM_OUT
) (
input i_clk, i_rst,
if_axi_stream.sink i_axi, // 单路输入
if_axi_stream.source o_n_axi[NUM_OUT-1:0] // 多路输出
);
localparam LOG2_MAX = MAX == 1 ? 1 : $clog2(MAX);
localparam NUM_OUT_GRP = (NUM_OUT+N-1)/N;
generate
genvar g, h;
// 情况 A: 输出数量较少,直接进行逻辑判断分发
if (NUM_OUT_GRP == 1) begin: FIRST_STAGE_GEN
logic [NUM_OUT-1:0] rdy_i;
always_comb i_axi.rdy = |rdy_i; // 只要有一个输出准备好,输入即可发送
for (h = 0; h < NUM_OUT; h++) begin: FINAL_GEN
logic in_range_i;
// 根据 CTL 位计算目标通道索引
always_comb rdy_i[h] = o_n_axi[h].rdy && in_range_i;
always_comb in_range_i = i_axi.ctl[OVR_WRT_BIT +: LOG2_MAX] / (MAX/NUM_OUT) == h;
always_comb begin
// 仅当匹配当前通道时拉高 valid 信号
o_n_axi[h].copy_if_comb(i_axi.dat, i_axi.val && in_range_i, i_axi.sop, i_axi.eop,
i_axi.err, i_axi.mod, i_axi.ctl);
end
end
end else begin
// 情况 B: 输出数量多,构建多级树形结构
if_axi_stream #(.DAT_BYTS(DAT_BYTS), .DAT_BITS(DAT_BITS), .CTL_BITS(CTL_BITS)) i_pipe [NUM_OUT_GRP-1:0] (i_clk);
// 1. 递归调用自身,先将数据分发到各个子树组
tree_packet_arb_1_to_n # (
.DAT_BYTS ( DAT_BYTS ),
.DAT_BITS ( DAT_BITS ),
.CTL_BITS ( CTL_BITS ),
.NUM_OUT ( NUM_OUT_GRP ),
.OVR_WRT_BIT ( OVR_WRT_BIT ),
.N ( N ),
.MAX ( MAX )
)
tree_upper_dist (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_axi ( i_axi ),
.o_n_axi ( i_pipe )
);
// 2. 对每个子树组的输入进行流水线打拍,并分发到具体端口
for (g = 0; g < NUM_OUT_GRP; g++) begin: GEN_TREE_LEVEL
localparam NUM_OUT_INT = N;
if_axi_stream #(.DAT_BYTS(DAT_BYTS), .DAT_BITS(DAT_BITS), .CTL_BITS(CTL_BITS)) o_pipe (i_clk);
logic [NUM_OUT_INT-1:0] rdy_o;
always_comb o_pipe.rdy = |rdy_o;
// 关键优化:插入流水线级,降低长路径延迟,提高频率
pipeline_bp_if #(
.DAT_BITS ( DAT_BITS ),
.CTL_BITS ( CTL_BITS ),
.NUM_STAGES ( 1 ) // 插入1 级寄存器
)
pipeline_stage (
.i_rst ( i_rst ),
.i_clk ( i_clk ),
.i_if ( i_pipe[g] ),
.o_if ( o_pipe )
);
for (h = 0; h < NUM_OUT_INT; h++) begin: FINAL_LEAF_GEN
logic in_range_o;
// 计算叶子节点的目标范围
always_comb in_range_o = o_pipe.ctl[OVR_WRT_BIT +: LOG2_MAX] / (MAX/NUM_OUT) == g*N + h;
always_comb begin
rdy_o[h] = o_n_axi[g*N + h].rdy && in_range_o;
o_n_axi[g*N + h].copy_if_comb(o_pipe.dat, o_pipe.val && in_range_o, o_pipe.sop,
o_pipe.eop, o_pipe.err, o_pipe.mod, o_pipe.ctl);
end
end
end
endendgenerate
endmodule
实施要点总结
- 寄存器打拍(Pipeline Register) :在
packet_arb_bp和pipeline_bp_if模块中显式启用流水线模式(PIPELINE=1或NUM_STAGES=1)。这是降低 Fanout 和提升频率的核心,它将原本跨越整个多路选择器的组合逻辑路径切割为多个短路径 。 - 自动通道管理 :利用
CTL总线的高位动态写入通道 ID(i_axi_int[h].ctl[...] = g*N + h),使得下游模块无需硬编码地址逻辑,只需解析数据包自带的控制字即可识别来源或去向,简化了顶层逻辑 。 - 参数化递归 :通过
NUM_IN_GRP和递归实例化,代码可自动适应任意规模的端口数,无需手动修改层级结构,保证了设计的通用性和可维护性 。