xdma axi-stream 回环测试

文章讲述了如何在FPGA中使用VerilogHDL实现XDMA回环功能,包括设置IP、AXI-Stream通信以及在Linux环境下利用Rust进行测试,涉及到PCIe接口、MSIX配置和AXI流控制等技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 rk3588 上测试通过

xdma 回环

建立一个xdma axi stream的ip后,然后生成示例工程,示例工程就能用
fpga:pcie rx – axi-stream master – axi-stream slave – pcie tx
流程:电脑启动读取,然后电脑再在超时时间内写入。或者电脑启动写入,然后电脑再在超时时间内读出。只读取或只写入会报超时,所以需要双线程进行
细节:只有电脑启动读取,pcie tx – axi-stream slave 才会使能 ready 信号,然后 pcie rx – axi-stream master 才能向 pcie tx – axi-stream slave 写数据


pcie xdma 配置

在这里插入图片描述在这里插入图片描述在这里插入图片描述

msix一定要,不然各种超时
在这里插入图片描述
在这里插入图片描述


verilog hdl

work.v:

module work #(
    parameter C_DATA_WIDTH = 64
) (
    // AXI streaming
    output wire [C_DATA_WIDTH-1:0]   s_axis_c2h_tdata_0,  
    output wire                      s_axis_c2h_tlast_0,
    output wire                      s_axis_c2h_tvalid_0,
    input  wire                      s_axis_c2h_tready_0,
    output wire [C_DATA_WIDTH/8-1:0] s_axis_c2h_tkeep_0,
    input  wire [C_DATA_WIDTH-1:0]   m_axis_h2c_tdata_0,
    input  wire                      m_axis_h2c_tlast_0,
    input  wire                      m_axis_h2c_tvalid_0,
    output wire                      m_axis_h2c_tready_0,
    input  wire [C_DATA_WIDTH/8-1:0] m_axis_h2c_tkeep_0,

    // clk、rst
    input  wire sys_rst_n,
    input  wire user_resetn,
    input  wire user_clk,
    input  wire user_lnk_up,

    // led
    output wire [3:0] leds
);

reg [25:0] user_clk_heartbeat;

assign sys_resetn = sys_rst_n;

// led
assign leds[0] = sys_resetn;
assign leds[1] = user_resetn;
assign leds[2] = user_lnk_up;
assign leds[3] = user_clk_heartbeat[25];

// AXI streaming
assign s_axis_c2h_tdata_0  = m_axis_h2c_tdata_0;
assign s_axis_c2h_tlast_0  = 0;
assign s_axis_c2h_tvalid_0 = m_axis_h2c_tvalid_0;
assign s_axis_c2h_tkeep_0  = m_axis_h2c_tkeep_0; // 如果每次都凑够 64bit 就可以直接等于 8'hff
assign m_axis_h2c_tready_0 = s_axis_c2h_tready_0;

// led blink
always @(posedge user_clk) begin
    if(!sys_resetn) begin
        user_clk_heartbeat <= 26'd0;
    end else begin
        user_clk_heartbeat <= user_clk_heartbeat + 1'b1;
    end
end

endmodule

main.v:

module main #(
    parameter PL_LINK_CAP_MAX_LINK_WIDTH = 2, // 1- X1; 2 - X2; 4 - X4; 8 - X8
    parameter C_DATA_WIDTH               = 64
) (
    // pcie
    output [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_txp,
    output [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_txn,
    input  [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_rxp,
    input  [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_rxn,

    // clk、rst
    input  sys_clk_p,
    input  sys_clk_n,
    input  sys_rst_n,

    // led
    output [3:0] leds
);

localparam C_NUM_USR_IRQ = 1;

// pcie clk、rst
wire sys_clk;
wire sys_rst_n_c;

IBUFDS_GTE2 refclk_ibuf (.O(sys_clk), .ODIV2(), .I(sys_clk_p), .CEB(1'b0), .IB(sys_clk_n));
IBUF sys_reset_n_ibuf (.O(sys_rst_n_c), .I(sys_rst_n));

// user
wire user_lnk_up;
wire user_clk;
wire user_resetn;

// irq
reg  [C_NUM_USR_IRQ-1:0] usr_irq_req = 0;

// AXI streaming
wire [C_DATA_WIDTH-1:0]	  m_axis_h2c_tdata_0;
wire 			          m_axis_h2c_tlast_0;
wire 			          m_axis_h2c_tvalid_0;
wire 			          m_axis_h2c_tready_0;
wire [C_DATA_WIDTH/8-1:0] m_axis_h2c_tkeep_0;
wire [C_DATA_WIDTH-1:0]   s_axis_c2h_tdata_0; 
wire                      s_axis_c2h_tlast_0;
wire                      s_axis_c2h_tvalid_0;
wire                      s_axis_c2h_tready_0;
wire [C_DATA_WIDTH/8-1:0] s_axis_c2h_tkeep_0; 

xdma_0 xdma_0_i (
    // clk、rst
    .sys_rst_n(sys_rst_n_c),
    .sys_clk  (sys_clk),

    // pcie
    .pci_exp_txn(pci_exp_txn),
    .pci_exp_txp(pci_exp_txp),
    .pci_exp_rxn(pci_exp_rxn),
    .pci_exp_rxp(pci_exp_rxp),

    // AXI streaming
    .s_axis_c2h_tdata_0 (s_axis_c2h_tdata_0),  
    .s_axis_c2h_tlast_0 (s_axis_c2h_tlast_0),
    .s_axis_c2h_tvalid_0(s_axis_c2h_tvalid_0), 
    .s_axis_c2h_tready_0(s_axis_c2h_tready_0),
    .s_axis_c2h_tkeep_0 (s_axis_c2h_tkeep_0),
    .m_axis_h2c_tdata_0 (m_axis_h2c_tdata_0),
    .m_axis_h2c_tlast_0 (m_axis_h2c_tlast_0),
    .m_axis_h2c_tvalid_0(m_axis_h2c_tvalid_0),
    .m_axis_h2c_tready_0(m_axis_h2c_tready_0),
    .m_axis_h2c_tkeep_0 (m_axis_h2c_tkeep_0),

    .axi_aclk   (user_clk),
    .axi_aresetn(user_resetn),

    // led
    .user_lnk_up(user_lnk_up),

    // irq
    .usr_irq_req(usr_irq_req),
    .usr_irq_ack(),
    .msix_enable(),
);

work #(
    .C_DATA_WIDTH(C_DATA_WIDTH)
) work_i (
    // AXI streaming
    .s_axis_c2h_tdata_0 (s_axis_c2h_tdata_0),  
    .s_axis_c2h_tlast_0 (s_axis_c2h_tlast_0),
    .s_axis_c2h_tvalid_0(s_axis_c2h_tvalid_0), 
    .s_axis_c2h_tready_0(s_axis_c2h_tready_0),
    .s_axis_c2h_tkeep_0 (s_axis_c2h_tkeep_0),
    .m_axis_h2c_tdata_0 (m_axis_h2c_tdata_0),
    .m_axis_h2c_tlast_0 (m_axis_h2c_tlast_0),
    .m_axis_h2c_tvalid_0(m_axis_h2c_tvalid_0),
    .m_axis_h2c_tready_0(m_axis_h2c_tready_0),
    .m_axis_h2c_tkeep_0 (m_axis_h2c_tkeep_0),

    // clk、rst
    .user_clk(user_clk),
    .user_resetn(user_resetn),
    .sys_rst_n(sys_rst_n_c),
    .user_lnk_up(user_lnk_up),

    // led
    .leds(leds)
);

endmodule

constr.xdc:
led 高电平亮。正常情况下,板卡上电待 fpga 程序加载完毕后,第四个灯会一直闪烁,亮灭切换间隔大约为 500ms;第一个灯也会亮,等待约3秒钟就会灭然后等待约 500ms 第一、二、三号灯一起长亮

## configure
set_property CFGBVS VCCO                         [current_design]
set_property CONFIG_VOLTAGE 3.3                  [current_design]
set_property CONFIG_MODE SPIx4                   [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 50      [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4     [current_design]
set_property BITSTREAM.CONFIG.SPI_FALL_EDGE YES  [current_design]
set_property BITSTREAM.GENERAL.COMPRESS TRUE     [current_design]
set_property BITSTREAM.CONFIG.UNUSEDPIN PULLNONE [current_design]

## pcie
# clock
set_property PACKAGE_PIN F6 [get_ports sys_clk_p]

# data
set_property PACKAGE_PIN D9  [get_ports {pci_exp_rxp[0]}]
set_property PACKAGE_PIN B10 [get_ports {pci_exp_rxp[1]}]
set_property PACKAGE_PIN D7  [get_ports {pci_exp_txp[0]}]
set_property PACKAGE_PIN B6  [get_ports {pci_exp_txp[1]}]

# rst
set_property -dict {PACKAGE_PIN N15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

## led
set_property -dict {PACKAGE_PIN V9 IOSTANDARD LVCMOS15} [get_ports {leds[0]}]
set_property -dict {PACKAGE_PIN Y8 IOSTANDARD LVCMOS15} [get_ports {leds[1]}]
set_property -dict {PACKAGE_PIN Y7 IOSTANDARD LVCMOS15} [get_ports {leds[2]}]
set_property -dict {PACKAGE_PIN W7 IOSTANDARD LVCMOS15} [get_ports {leds[3]}]

linux 下 rust 测试 xdma stream 的回环

main.rs:

fn main() {
	use rand::Rng;
	use std::fs::OpenOptions;
	use std::io::{Read, Write};
	
	let (read_tx, read_rx) = mpsc::channel();
	
	thread::spawn(move || {
	    let mut buffer = vec![0u8; 200];
	    let mut file = OpenOptions::new()
	        .read(true)
	        .open("/dev/xdma0_c2h_0")
	        .unwrap();
	    file.read(buffer.as_mut_slice()).unwrap();
	
	    read_tx.send(buffer).unwrap();
	});
	
	thread::spawn(move || {
	    let mut buffer = vec![0u8; 200];
	    for index in 0..(buffer.len() / 2) {
	        let rng: u16 = rand::thread_rng().gen_range(0..=1023);
	        buffer[index * 2] = (rng >> 8) as u8;
	        buffer[index * 2 + 1] = rng as u8;
	    }
	
	    let mut file = OpenOptions::new()
	        .write(true)
	        .open("/dev/xdma0_h2c_0")
	        .unwrap();
	    file.write(buffer.as_slice()).unwrap();
	});
	
	let buffer = read_rx.recv().unwrap();
	let mut data = vec![0u16; 100];
	for index in 0..(buffer.len() / 2) {
	    data[index] = ((buffer[index * 2] as u16) << 8) | (buffer[index * 2 + 1] as u16);
	}
	printfln!("xdma: {}", data);
}

Cargo.toml:

[dependencies]
rand = "*"
<think>好的,我现在要解决的问题是如何将预处理模块的数据通过AXI-Stream接口传输到XDMA。用户之前已经询问了是否需要使用DDR作为缓存,现在他们想了解具体的实现步骤和注意事项。我需要先回顾之前的对话,确保回答的一致性和连贯性。 首先,用户当前的需求是实现FPGA预处理后的数据通过AXI-Stream直接传输到XDMA,而不经过DDR。之前的回答中提到了两种方案,直接流传输和使用DDR缓冲。现在用户关注的是直接流传输的具体实现方法。 接下来,我需要详细解释如何连接预处理模块到XDMAAXI-Stream接口。可能需要涉及到的方面包括:AXI-Stream接口的基本概念、XDMA的配置、数据流的控制机制、时钟域的处理、以及可能的调试方法。 首先,AXI-Stream协议的特点是无地址、持续传输,适用于高速数据流。预处理模块需要生成符合AXI-Stream标准的数据流,包括TREADY、TVALID、TDATA等信号。XDMA的DMA引擎需要能够接收这种流式数据,并通过PCIe传输到主机。 然后,需要确认XDMA IP核的配置是否正确,特别是DMA通道的设置,是否启用了AXI-Stream接口。在Vivado中,可能需要使用AXI Interconnect来连接预处理模块和XDMAAXI-Stream接口,或者直接连接,这取决于XDMA的接口类型。 接下来是数据流的控制。由于AXI-Stream依赖于TREADY/TVALID握手,预处理模块需要能够根据XDMA的接收能力来调整数据发送速率,避免数据溢出或丢失。需要考虑背压机制,当XDMA无法及时接收数据时,预处理模块需要暂停发送。 时钟域的问题也需要考虑。预处理模块可能运行在一个时钟域,而XDMAAXI-Stream接口可能运行在另一个时钟域,特别是PCIe通常使用不同的时钟频率。需要使用异步FIFO或者时钟域转换模块来处理跨时钟域的数据传输,确保数据完整性和时序正确性。 另外,数据包的边界处理也很重要。如果预处理后的数据是分帧或分包的,可能需要添加TLAST信号来标识数据包的结束,这样XDMA或主机端能够正确解析数据。需要确保TLAST信号在适当的时候置位,以区分不同的数据包。 测试和调试部分也是关键。在硬件实现之前,可以通过仿真工具如Vivado Simulator来验证AXI-Stream接口的信号是否符合预期。在板级调试时,使用ILA(Integrated Logic Analyzer)来抓取实时信号,检查数据流的连续性和正确性,以及握手信号是否正常。 可能遇到的问题包括数据不连续、握手信号卡死、时钟域不同步等。需要逐步排查,例如检查TREADY和TVALID的时序是否满足协议要求,是否存在死锁情况,或者时钟域转换是否正确处理了亚稳态问题。 最后,还需要考虑数据对齐和位宽匹配。预处理模块的输出数据位宽需要与XDMAAXI-Stream接口位宽一致,或者通过数据调整逻辑(如位宽转换器)来适配,避免数据截断或拼接错误。 总结来说,实现这一过程需要理解AXI-Stream协议、XDMA的配置、时钟域处理、数据流控制,以及有效的测试方法。需要逐步连接各个模块,确保每个环节都符合协议要求,并进行充分的验证和调试,以确保数据传输的稳定性和正确性。</think>在FPGA设计中,将预处理模块的数据通过 **AXI-Stream接口** 直接传输到 **XDMA**(PCIe DMA引擎)并最终发送到主机GPU,需要以下关键步骤和注意事项: --- ### **1. AXI-Stream接口基本要求** AXI-Stream协议是一种无地址、高吞吐量的流式数据传输协议,核心信号包括: - **TDATA**:数据总线(位宽可配置,如64/128/256位)。 - **TVALID**:发送端数据有效标志。 - **TREADY**:接收端准备好接收数据标志。 - **TLAST**:数据包结束标志(可选)。 - **TKEEP**:字节有效掩码(可选)。 **预处理模块需满足**: - 输出数据需符合AXI-Stream协议时序。 - 根据XDMA的接收能力(TREADY)控制数据发送(TVALID)。 --- ### **2. XDMA配置** 在Vivado中配置XDMA IP核时,需启用AXI-Stream接口: 1. **选择DMA模式**: - 勾选`AXI4-Stream`作为DMA数据接口(而非AXI4-MM)。 2. **设置DMA通道**: - 根据数据流方向选择`H2C`(Host-to-Card)或`C2H`(Card-to-Host)。 - 此处需配置为 **C2H(FPGA到主机)**。 3. **AXI-Stream参数**: - 匹配预处理模块的位宽(如64/128/256位)。 - 设置TDATA位宽和TUSER/TLAST等信号。 --- ### **3. 预处理模块与XDMA的连接** #### **(1) 直接连接(无中间缓存)** - **适用场景**:预处理模块输出速率稳定,且与XDMA接收速率匹配。 - **连接方式**: 将预处理模块的AXI-Stream输出接口直接接入XDMA的C2H通道AXI-Stream输入接口。 ```verilog // 示例:Verilog端口映射 preprocessing_module u_preprocess ( .aclk (axi_clk), // AXI时钟 .aresetn (axi_resetn), // AXI复位(低有效) .m_axis_tdata (xdma_c2h_tdata), // 数据总线 .m_axis_tvalid (xdma_c2h_tvalid), // 数据有效 .m_axis_tready (xdma_c2h_tready), // XDMA接收就绪 .m_axis_tlast (xdma_c2h_tlast) // 数据包结束标志 ); ``` #### **(2) 使用异步FIFO(跨时钟域处理)** - **适用场景**:预处理模块与XDMA接口位于不同时钟域。 - **实现方式**: 插入异步FIFO(如Xilinx的FIFO Generator IP),隔离时钟域并缓冲数据。 ```plaintext 预处理模块(clk1) → 异步FIFO → XDMA(clk2) ``` - **关键参数**: - 设置FIFO深度以容忍时钟频率差异和突发数据。 - 确保写侧(预处理)和读侧(XDMA)的时钟独立。 --- ### **4. 数据流控制与背压机制** - **TREADY/TVALID握手**: XDMA通过拉低`TREADY`通知预处理模块暂停发送数据。 **预处理模块需实现流控逻辑**: - 当`TREADY=0`时,保持`TVALID`和`TDATA`不变,直到`TREADY=1`。 - 避免因背压导致数据丢失或死锁。 #### **示例状态机逻辑**(伪代码): ```verilog always @(posedge axi_clk) begin if (!axi_resetn) begin tvalid <= 0; tdata <= 0; end else begin if (tready && has_data_to_send) begin tvalid <= 1; tdata <= next_data; end else if (!tready) begin tvalid <= tvalid; // 保持当前状态 end else begin tvalid <= 0; end end end ``` --- ### **5. 数据包与帧对齐** - **TLAST信号使用**: 若数据以帧/包为单位传输(如图像帧、网络包),需在每帧末尾拉高`TLAST`。 - XDMA会将`TLAST`信号传递到主机驱动,便于主机解析数据边界。 - **TKEEP信号(可选)**: 用于指示TDATA中哪些字节有效(例如传输非对齐数据时)。 --- ### **6. 时钟与复位** - **时钟一致性**: - XDMAAXI-Stream接口时钟通常由PCIe参考时钟(如125MHz或250MHz)驱动。 - 预处理模块的时钟需与其同步(或通过异步FIFO隔离)。 - **复位同步**: 确保预处理模块和XDMA的复位信号(`aresetn`)同步释放,避免数据错位。 --- ### **7. 主机端配置** - **驱动与DMA缓冲区**: 1. 在主机(PC)端,需编写或使用XDMA驱动程序(如Xilinx提供的内核驱动)。 2. 分配连续的DMA缓冲区,并通过XDMA将数据从FPGA传输到该缓冲区。 3. 使用GPU API(如CUDA)将数据从主机内存拷贝到GPU显存,或启用 **GPUDirect RDMA**(需NVIDIA支持)直接传输到GPU。 --- ### **8. 调试与验证** #### **(1) Vivado仿真** - 使用AXI-Stream VIP(Verification IP)模拟XDMA接收端,验证预处理模块的时序。 - 检查`TREADY/TVALID`握手是否正常,数据是否连续。 #### **(2) ILA抓取信号** -FPGA上插入ILA(Integrated Logic Analyzer),实时捕获AXI-Stream信号: - 确认`TDATA`与预期数据一致。 - 检查`TLAST`是否在帧末尾触发。 - 观察`TREADY`是否因背压被拉低。 #### **(3) 主机端测试** - 使用`dd`命令或自定义应用程序读取XDMA传输的数据,检查完整性和正确性。 --- ### **9. 常见问题与解决方案** | **问题** | **可能原因** | **解决方案** | |-------------------------|---------------------------|---------------------------------------| | 数据丢失 | 预处理模块未响应TREADY背压 | 添加流控逻辑,确保TREADY=0时暂停发送 | | 数据错位 | 时钟域不同步 | 插入异步FIFO或同步电路 | | XDMA无法启动传输 | DMA描述符未正确配置 | 检查主机端驱动是否初始化DMA通道 | | 传输速率低于预期 | PCIe带宽瓶颈 | 优化XDMA传输模式(如使用多队列) | --- ### **10. 示例代码片段** #### **预处理模块AXI-Stream接口实现**(Verilog): ```verilog module preprocessing_module ( input wire aclk, input wire aresetn, output wire [63:0] m_axis_tdata, output wire m_axis_tvalid, input wire m_axis_tready, output wire m_axis_tlast ); reg [63:0] data_buffer; reg tvalid; reg tlast; reg [31:0] counter; // 数据生成逻辑(示例:连续递增数据) always @(posedge aclk) begin if (!aresetn) begin data_buffer <= 0; counter <= 0; tvalid <= 0; tlast <= 0; end else begin if (m_axis_tready || !tvalid) begin data_buffer <= data_buffer + 1; counter <= counter + 1; tvalid <= 1; // 每1024个数据置位TLAST tlast <= (counter == 1023) ? 1 : 0; end end end assign m_axis_tdata = data_buffer; assign m_axis_tvalid = tvalid; assign m_axis_tlast = tlast; endmodule ``` --- ### **总结** 通过AXI-Stream将预处理数据直连XDMA的关键点: 1. **协议匹配**:确保预处理模块的AXI-Stream接口符合时序要求。 2. **流控处理**:正确处理`TREADY`背压,避免数据丢失。 3. **时钟域隔离**:跨时钟域时使用异步FIFO。 4. **主机端配合**:配置DMA缓冲区和驱动,实现高效传输。 此方案适用于低延迟、高吞吐场景,但需严格验证数据完整性和速率匹配性。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值