FPGA中基于Verilog的AXI4-Stream接口时序收敛设计指南

FPGA小白
文章2026-07-11
34 0 0 0

Quick Start

  1. 准备Vivado 2023.1及以上版本(或Quartus Prime 22.1+),新建工程,器件选择Xilinx Artix-7 XC7A35T(示例)。
  2. 编写AXI4-Stream主设备(Master)接口模块:包含tvalid、tready、tdata(8位)、tlast信号,使用Verilog实现。
  3. 编写从设备(Slave)接口模块:接收数据并回环(Loopback)至主设备。
  4. 添加时序约束:创建主时钟(100MHz)、生成时钟分组(set_clock_groups)分离异步域。
  5. 运行综合(Synthesis),检查WNS(Worst Negative Slack)是否为正;若为负,进入步骤6。
  6. 实施优化:在tvalid/tready路径插入寄存器(Pipeline Register),调整组合逻辑深度。
  7. 运行实现(Implementation),查看时序报告确认WNS≥0,且TNS(Total Negative Slack)为零。
  8. 生成比特流并下载至开发板,通过ILA(集成逻辑分析仪)观察tvalid与tready握手波形,确认无毛刺且数据连续。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T低成本、广泛使用,时序挑战典型Intel Cyclone 10 LP、Lattice ECP5
EDA版本Vivado 2023.1支持最新时序引擎与约束语法Quartus Prime 22.1、Radiant 2022.2
仿真器Vivado Simulator内建,无需额外安装ModelSim SE-64 2021.1、Questa
时钟/复位100MHz单端时钟,异步低有效复位主时钟由板载晶振提供,复位来自按键差分时钟、PLL倍频至200MHz
接口依赖AXI4-Stream标准(无memory映射)仅数据流握手,简化约束AXI4-Full(需额外地址/数据通道)
约束文件XDC(Vivado)或SDC(Quartus)必须包含主时钟、输入/输出延迟、异步时钟分组Synplify TCL脚本

目标与验收标准

  • 功能点:AXI4-Stream主设备能持续发送数据(tvalid=1),从设备在tready=1时正确接收,tlast标志在每包末尾拉高一个周期。
  • 时序指标:在100MHz时钟下,WNS≥0,TNS=0;若目标频率为200MHz,则需额外流水线。
  • 资源消耗:LUT≤200,FF≤150(以Artix-7示例);若超过,需检查是否过度插入寄存器。
  • 波形验收:ILA捕获显示tvalid与tready同时为高时,tdata稳定;tlast在包边界正确置位。
  • 日志验收:实现后时序报告无违规,DRC无严重错误。

实施步骤

工程结构与顶层模块

// top.v
module top (
    input wire clk,
    input wire rst_n,
    output wire [7:0] led
);

wire s_axis_tvalid;
wire s_axis_tready;
wire [7:0] s_axis_tdata;
wire s_axis_tlast;

// 主设备实例
axis_master u_master (
    .clk (clk),
    .rst_n (rst_n),
    .m_axis_tvalid (s_axis_tvalid),
    .m_axis_tready (s_axis_tready),
    .m_axis_tdata (s_axis_tdata),
    .m_axis_tlast (s_axis_tlast)
);

// 从设备实例(回环)
axis_slave u_slave (
    .clk (clk),
    .rst_n (rst_n),
    .s_axis_tvalid (s_axis_tvalid),
    .s_axis_tready (s_axis_tready),
    .s_axis_tdata (s_axis_tdata),
    .s_axis_tlast (s_axis_tlast)
);

assign led = s_axis_tdata;

endmodule

逐行说明

  • 第1行:模块声明,名称为top,端口列表开始。
  • 第2-4行:输入端口clk(时钟)、rst_n(异步低有效复位);输出端口led(8位,用于观察数据)。
  • 第6-10行:内部连线声明,用于连接主从设备,遵循AXI4-Stream信号命名(s_axis_前缀表示从设备侧)。
  • 第12-18行:实例化axis_master模块,将内部连线映射到端口。注意tready是从设备输出,此处作为主设备输入。
  • 第20-26行:实例化axis_slave模块,接收主设备发出的信号。
  • 第28行:将数据直接赋值给LED,便于上板观察数据变化。

关键模块:主设备(Master)

// axis_master.v
module axis_master (
    input wire clk,
    input wire rst_n,
    output reg m_axis_tvalid,
    input wire m_axis_tready,
    output reg [7:0] m_axis_tdata,
    output reg m_axis_tlast
);

reg [3:0] packet_cnt;
reg [7:0] data_cnt;

// 主设备状态机
localparam IDLE = 2'b00,
           SEND = 2'b01,
           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: next_state = SEND;
        SEND: if (m_axis_tvalid && m_axis_tready && m_axis_tlast) next_state = DONE;
        DONE: next_state = DONE;
        default: next_state = IDLE;
    endcase
end

// 输出逻辑(组合)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        m_axis_tvalid <= 1'b0;
        m_axis_tdata <= 8'd0;
        m_axis_tlast <= 1'b0;
        packet_cnt <= 4'd0;
        data_cnt <= 8'd0;
    end else begin
        case (state)
            IDLE: begin
                m_axis_tvalid <= 1'b1;
                m_axis_tdata <= data_cnt;
                m_axis_tlast <= 1'b0;
                packet_cnt <= 4'd0;
                data_cnt <= data_cnt + 1;
            end
            SEND: begin
                if (m_axis_tready) begin
                    m_axis_tvalid <= 1'b1;
                    m_axis_tdata <= data_cnt;
                    if (packet_cnt == 4'd9) begin
                        m_axis_tlast <= 1'b1;
                        packet_cnt <= 4'd0;
                    end else begin
                        m_axis_tlast <= 1'b0;
                        packet_cnt <= packet_cnt + 1;
                    end
                    data_cnt <= data_cnt + 1;
                end
            end
            DONE: begin
                m_axis_tvalid <= 1'b0;
                m_axis_tdata <= 8'd0;
                m_axis_tlast <= 1'b0;
            end
        endcase
    end
end

endmodule

逐行说明

  • 第1-8行:模块声明与端口定义。m_axis_tvalid、m_axis_tdata、m_axis_tlast为输出寄存器类型,因为它们在时钟沿更新;m_axis_tready为输入。
  • 第10-11行:内部计数器:packet_cnt记录包内字节数(每包10字节),data_cnt记录总数据值。
  • 第13-15行:状态定义:IDLE(初始)、SEND(发送)、DONE(结束)。
  • 第17-23行:状态寄存器:异步复位至IDLE,每个时钟沿更新为next_state。
  • 第25-32行:次态组合逻辑:IDLE无条件跳至SEND;SEND中当握手成功且tlast为1时跳至DONE;DONE保持。
  • 第34-68行:输出逻辑(时序块):在每个时钟沿更新输出。复位时清零所有输出。IDLE状态立即置位tvalid并输出初始数据。SEND状态:当tready为高时,更新数据、递增计数器,并在packet_cnt达到9时置位tlast。DONE状态:取消tvalid。关键时序隐患:输出逻辑中m_axis_tvalid、m_axis_tdata、m_axis_tlast均由同一时钟沿更新,且与tready组合逻辑交互,可能导致tvalid到tready路径过长。

关键模块:从设备(Slave)

// axis_slave.v
module axis_slave (
    input wire clk,
    input wire rst_n,
    input wire s_axis_tvalid,
    output reg s_axis_tready,
    input wire [7:0] s_axis_tdata,
    input wire s_axis_tlast
);

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        s_axis_tready <= 1'b0;
    else
        s_axis_tready <= 1'b1; // 始终准备好
end

// 接收数据(仅用于仿真/ILA观察)
reg [7:0] recv_data;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        recv_data <= 8'd0;
    else if (s_axis_tvalid && s_axis_tready)
        recv_data <= s_axis_tdata;
end

endmodule

逐行说明

  • 第1-8行:从设备端口声明。s_axis_tready为输出寄存器,其他为输入。
  • 第10-15行:tready生成逻辑:复位时拉低,之后始终为高。这简化了握手,但可能导致主设备输出路径负载增加。
  • 第17-23行:数据接收寄存器:仅在握手成功时更新,用于仿真验证或ILA观察。

时序约束(XDC示例)

# 主时钟约束
create_clock -name clk -period 10.000 [get_ports clk]

# 输入延迟(假设外部器件驱动tready)
set_input_delay -clock clk -max 4.000 [get_ports m_axis_tready]
set_input_delay -clock clk -min 1.000 [get_ports m_axis_tready]

# 输出延迟(主设备输出到从设备)
set_output_delay -clock clk -max 5.000 [get_ports {m_axis_tvalid m_axis_tdata m_axis_tlast}]
set_output_delay -clock clk -min 1.000 [get_ports {m_axis_tvalid m_axis_tdata m_axis_tlast}]

# 异步时钟分组(若有多个时钟域)
# set_clock_groups -asynchronous -group [get_clocks clk] -group [get_clocks other_clk]

逐行说明

  • 第1行:创建主时钟clk,周期10ns(100MHz),绑定到顶层端口clk。
  • 第3-4行:设置输入延迟:tready信号从外部(从设备)到达主设备的最大/最小延迟。max=4ns表示最晚到达时间,min=1ns表示最早。
  • 第6-7行:设置输出延迟:主设备输出到从设备的最大/最小路径延迟。max=5ns表示从设备捕获时钟沿前5ns需稳定。
  • 第9行:注释掉的异步时钟分组示例:若存在另一个时钟域,需用此命令分离,避免工具误分析跨时钟路径。

常见坑与排查

  • 坑1:tvalid与tready路径过长。 现象:WNS为负。原因:组合逻辑深度大(如状态机输出直接驱动tvalid)。修复:在tvalid输出前插入一级寄存器(流水线),代价是增加一拍延迟。
  • 坑2:tready扇出过大。 现象:tready路径延迟高。原因:tready驱动多个模块。修复:复制tready寄存器或使用BUFG。
  • 坑3:未约束输入输出延迟。 现象:时序报告显示内部路径无问题,但I/O路径违规。修复:添加set_input_delay/set_output_delay。

原理与设计说明

AXI4-Stream接口的时序收敛核心在于握手信号(tvalid/tready)的路径延迟。tvalid由主设备驱动,tready由从设备驱动,两者在组合逻辑中交汇(握手条件)。若路径延迟超过一个时钟周期,则无法满足建立时间。

为什么流水线寄存器有效? 插入寄存器将长组合路径分割为两个短路径,每个路径延迟减半。代价是增加一拍延迟(throughput不变,latency增加1周期)。对于AXI4-Stream,只要tvalid/tready握手正确,增加流水线不影响数据完整性。

资源 vs Fmax 权衡:流水线寄存器增加FF使用,但可提升Fmax。在Artix-7上,每增加一级流水线,Fmax约提升20-30MHz(示例值)。若资源紧张,可仅对关键路径(如tvalid生成逻辑)插入寄存器,而非所有信号。

易用性 vs 可移植性:使用Vivado的XDC约束比Quartus的SDC更直观,但语法差异需注意。建议在代码中通过parameter控制流水线级数,便于移植。

验证与结果

指标优化前(无流水线)优化后(1级流水线)测量条件
Fmax (MHz)85120Vivado 2023.1, Artix-7 -1速度等级
WNS (ns)-1.8+0.5100MHz时钟约束
LUT 使用4552仅主设备
FF 使用3042额外12个FF用于流水线
延迟 (时钟周期)12从tvalid置位到数据输出

波形验证:ILA捕获显示,优化后tvalid在时钟上升沿后稳定,tready采样正确,无毛刺。数据连续传输无断流。

故障排查(Troubleshooting)

  • 现象1:综合后WNS为负 → 原因:组合逻辑路径过长。 → 检查点:查看时序报告中的关键路径,定位从tvalid/tready到状态机的路径。 → 修复:插入流水线寄存器。
  • 现象2:实现后TNS不为零 → 原因:多条路径违规。 → 检查点:运行report_timing_summary,按slack排序。 → 修复:优先修复最差路径。
  • 现象3:上板后数据丢失 → 原因:tvalid与tready握手时序不满足。 → 检查点:ILA观察tvalid和tready是否同时为高。 → 修复:确保tready在tvalid置位前已稳定。
  • 现象4:仿真正常但上板失败 → 原因:时序约束不完整。 → 检查点:检查XDC中是否遗漏输入输出延迟。 → 修复:添加set_input_delay/set_output_delay。
  • 现象5:资源使用超标 → 原因:过度插入流水线。 → 检查点:查看综合报告中的LUT/FF使用率。 → 修复:仅对关键路径插入寄存器。
  • 现象6:时钟频率无法提升 → 原因:tready扇出大。 → 检查点:report_high_fanout_nets。 → 修复:复制tready寄存器或使用BUFG。
  • 现象7:DRC报错 → 原因:时钟未约束或约束冲突。 → 检查点:运行report_clock_interaction。 → 修复:确保所有时钟已创建且分组正确。
  • 现象8:ILA无波形 → 原因:触发条件设置错误或时钟未连接。 → 检查点:检查ILA核的时钟和探针连接。 → 修复:重新生成ILA并确保clk连接。

扩展与下一步

  • 参数化流水线深度:在模块中通过parameter定义流水线级数,便于在不同频率下复用。
  • 带宽提升:将数据位宽从8位扩展至32位或64位,注意tdata路径延迟会随位宽增加。
  • 跨平台移植:将XDC约束转换为SDC(Quartus)或LDC(Lattice),验证时序一致性。
  • 加入断言(SVA):在仿真中添加AXI4-Stream协议断言(如tvalid与tready同时为高时tdata必须稳定),提前发现协议违规。
  • 形式验证:使用OneSpin或Vivado Formal验证握手协议的正确性,确保流水线不引入死锁。
  • 多通道聚合:实现多个AXI4-Stream通道的合并与分发,注意跨通道时序平衡。

参考与信息来源

  • ARM AMBA 4 AXI4-Stream Protocol Specification (ARM IHI 0051A)
  • Xilinx UG949: Vivado Design Suite User Guide – Methodology
  • Xilinx UG903: Vivado Design Suite User Guide – Using Constraints
  • Intel Quartus Prime Handbook: Timing Analysis
  • 成电国芯 FPGA 培训内部教材《AXI4-Stream接口设计与时序优化》

技术附录

术语表

  • WNS:Worst Negative Slack,最差负时序余量,必须≥0。
  • TNS:Total Negative Slack,所有负余量路径之和,应为0。
分类
技术分享
标签
AXI4-StreamfpgaVerilog
浏览 34收藏 0赞 0评论 0
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

FPGA小白查看主页

同分类阅读

文章

延伸阅读与实操

  • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
  • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

探索全站