在FPGA与主机通信的各种方案中,PCIE凭借其高带宽、低延迟的特性成为首选。但传统轮询模式就像让CPU不断打电话问"数据好了没",效率低下且占用大量CPU资源。这次我们基于Xilinx Kintex UltraScale系列FPGA(具体型号xcku060-ffva1156-2-i),采用XDMA中断模式构建了一套完整的PCIE3.0通信系统。
核心架构包含三个关键部分:
特别提示:中断模式相比轮询可降低85%以上的CPU占用率,实测在持续传输场景下CPU负载从90%+降至15%左右
在Vivado 2019.1中配置XDMA IP时,这些参数需要特别注意:
tcl复制set_property -dict [list \
CONFIG.pl_link_cap_max_link_width {X8} \
CONFIG.pl_link_cap_max_link_speed {8.0_GT/s} \
CONFIG.axi_data_width {256_bit} \
CONFIG.axisten_freq {250} \
CONFIG.pf0_device_id {7038} \
CONFIG.en_axi_slave_if {true} \
CONFIG.enable_jtag_dbg {false} \
] [get_ips xdma_0]
关键配置解析:
自定义中断模块xdma_inter.v的核心逻辑:
verilog复制module xdma_inter (
input wire clk,
input wire rst_n,
input wire [3:0] user_irq_req, // 用户逻辑中断请求
output wire irq_to_xdma, // 给XDMA的中断信号
// AXI-Lite寄存器接口
input wire [31:0] axi_awaddr,
input wire axi_awvalid,
// ...其他AXI信号省略
);
// 中断状态寄存器组
reg [31:0] irq_status;
reg [31:0] irq_enable;
reg [31:0] irq_clear;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
irq_status <= 32'b0;
end else begin
// 状态寄存器更新逻辑
irq_status <= (irq_status | user_irq_req) & ~irq_clear;
end
end
// 中断信号生成
assign irq_to_xdma = |(irq_status & irq_enable);
endmodule
这段代码实现了:
Linux驱动中中断处理的核心逻辑:
c复制static irqreturn_t xdma_irq_handler(int irq, void *dev_id)
{
struct xdma_dev *dev = dev_id;
u32 status;
// 读取中断状态寄存器
status = ioread32(dev->reg_base + IRQ_STATUS_REG);
if (!status)
return IRQ_NONE; // 不是我们的中断
// 将待处理中断加入工作队列
spin_lock(&dev->irq_lock);
dev->pending_irqs |= status;
spin_unlock(&dev->irq_lock);
schedule_work(&dev->irq_work);
// 清除中断(写1清零)
iowrite32(status, dev->reg_base + IRQ_CLEAR_REG);
// 内存屏障确保操作顺序
mmiowb();
return IRQ_HANDLED;
}
注意事项:
上位机通过XDMA API进行数据传输测速:
cpp复制void SpeedTestThread::run()
{
QElapsedTimer timer;
const int test_size = 1024 * 1024 * 256; // 256MB测试数据
QVector<quint8> buffer(4096, 0xAA);
// 打开设备
int fd = open("/dev/xdma0", O_RDWR);
// 写测试
timer.start();
for (int i = 0; i < test_size; i += buffer.size()) {
write(fd, buffer.data(), buffer.size());
}
qint64 write_time = timer.elapsed();
// 读测试(类似代码省略)
// 计算带宽
double write_speed = (double)test_size / (1024*1024) / (write_time / 1000.0);
emit updateSpeed(write_speed, read_speed);
}
当传输小数据包时,频繁中断会导致性能下降。我们可以在FPGA侧实现中断合并:
verilog复制// 在中断控制器中添加合并逻辑
reg [15:0] irq_counter;
always @(posedge clk) begin
if (data_valid && !fifo_empty) begin
irq_counter <= irq_counter + 1;
if (irq_counter >= THRESHOLD) begin
trigger_irq <= 1'b1;
irq_counter <= 0;
end
end
end
使用多描述符环形缓冲区提升吞吐量:
c复制struct dma_descriptor {
u64 src_addr;
u64 dst_addr;
u32 length;
u32 control;
};
#define NUM_DESC 256
struct dma_descriptor desc_ring[NUM_DESC] __aligned(64);
// 初始化描述符环
void init_descriptors(void)
{
for (int i = 0; i < NUM_DESC; i++) {
desc_ring[i].control = DESC_CTRL_IRQ | DESC_CTRL_EOP;
if (i == NUM_DESC - 1)
desc_ring[i].control |= DESC_CTRL_CYCLE;
}
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 驱动加载失败 | PCIe设备未识别 | 检查FPGA是否正确配置PCIE IP核 |
| 传输速度不达标 | AXI时钟未约束 | 在Vivado中添加适当的时钟约束 |
| 偶发数据错误 | DDR时序不满足 | 使用MIG IP核重新校准DDR |
在Vivado中设置SignalTap触发条件示例:
tcl复制create_debug_core u_ila_0 ila
set_property C_DATA_DEPTH 8192 [get_debug_cores u_ila_0]
set_property C_TRIGIN_EN false [get_debug_cores u_ila_0]
# 监控关键信号
set_property port_width 1 [get_debug_ports u_ila_0/clk]
set_property port_width 4 [get_debug_ports u_ila_0/probe0]
set_property PROBE0 {user_irq_req[3:0]} [get_debug_cores u_ila_0]
在不同传输模式下的性能对比:
| 测试场景 | 轮询模式 | 中断模式 | 提升幅度 |
|---|---|---|---|
| 小包传输(4KB) | 120MB/s | 850MB/s | 608% |
| 大块传输(256MB) | 2.1GB/s | 3.4GB/s | 62% |
| CPU占用率 | 92% | 15% | 降低83% |
实测发现,当中断频率超过5kHz时,建议采用以下优化措施: