什么是 Active Device(“主动设备”)?

“Active Device(主动设备)”这一说法,其实是开发者中广泛使用但并不严谨的术语。
与其说设备是“主动的”,不如说:

设备会频繁向 Root Complex 发送中断事件,而这些中断在很多高性能设备中,往往与 DMA(Direct Memory Access,直接内存访问) 数据传输同时出现。

当一次 DMA 传输完成后,设备通常会通过 中断(Interrupt) 的方式通知 CPU 数据已经准备就绪。

这一机制常被形象地称为 Doorbell(门铃)机制 ——
就像快递员按门铃通知你包裹已送达一样,设备通过中断“敲门”提醒 CPU。


DMA 与中断之间的关系

一个典型流程如下:

  1. 设备向系统内存发起 DMA 读 / 写
  2. DMA 传输完成
  3. 设备向 CPU 发送中断
  4. CPU 响应中断并处理数据

如果一个设备持续地产生 DMA 并伴随中断通知,在驱动语境中就会被称为“Active Device”。


PCIe 中断的三种实现方式

在 PCIe(Peripheral Component Interconnect Express)体系中,中断主要有三种方式:

  • Legacy Interrupt
    传统 PCI 中断方式,依赖物理中断线,效率低、共享严重,已逐步淘汰。
  • MSI(Message Signaled Interrupt)
    通过内存写事务触发中断,更高效,是现代 PCIe 设备的主流方式。
  • MSI-X(Message Signaled Interrupt Extended)
    MSI 的增强版本,支持更多中断向量和独立配置,高性能设备(网卡、NVMe、GPU)普遍采用。

当设备在 PCIe 配置空间(CFG Space)中启用了 MSI 或 MSI-X 后,设备将使用相应机制向 Root Complex(RC)发送中断。


如何让你的设备“表现为 Active”

使用 Xilinx PCIe IP 的 CFG Interrupt 接口(Legacy / MSI)

在 Xilinx PCIe IP Core 中,可以通过 cfg interrupt 接口 向 RC 发送中断信号。
以下为一个完整、连续的示例代码,用于周期性地产生中断:

assign ctx.cfg_interrupt_di             = cfg_int_di;
assign ctx.cfg_pciecap_interrupt_msgnum = cfg_msg_num;
assign ctx.cfg_interrupt_assert         = cfg_int_assert;
assign ctx.cfg_interrupt                = cfg_int_valid;
assign ctx.cfg_interrupt_stat           = cfg_int_stat;

always @ ( posedge clk_pcie ) begin
   if ( rst ) begin
       cfg_int_valid <= 1'b0;
       cfg_msg_num <= 5'b0;
       cfg_int_assert <= 1'b0;
       cfg_int_di <= 8'b0;
       cfg_int_stat <= 1'b0;
   end else if (cfg_int_ready && cfg_int_valid) begin
       cfg_int_valid <= 1'b0;
   end else if (o_int) begin
       cfg_int_valid <= 1'b0;
   end
end

time int_cnt = 0;
always @ ( posedge clk_pcie ) begin
   if (rst) begin
       int_cnt <= 0;
   end else if (int_cnt == 32'd100000) begin
       int_cnt <= 0;
   end else if (int_enable) begin
       int_cnt <= int_cnt + 1;
   end
end

assign o_int = (int_cnt == 32'd100000);

该方式在 Legacy Interrupt 或 MSI 模式下完全有效,也是许多示例工程和仿真设备常用的做法。


⚠️ 重要限制:CFG Interrupt 不支持 MSI-X

当你的模拟设备包含 MSI-X Capability(MSI-X CAP)时,上述方法将无法发送中断

原因在于:

Xilinx PCIe IP Core 提供的 cfg interrupt 接口本身并不支持 MSI-X 中断类型。

一旦 RC 为该设备启用了 MSI-X:

  • cfg interrupt 接口将被忽略
  • 必须使用 MSI-X 规定的方式发送中断

MSI-X 的正确做法:手动构造并发送 TLP

从 PCIe 协议层面来看:

MSI / MSI-X 中断,本质上就是一次特定格式的 Memory Write TLP。

因此,在 MSI-X 模式下,必须由用户逻辑:

  • 手动构造 MEMWR64 TLP
  • 将中断数据写入 MSI-X Table 中指定的地址
  • 通过 TX 通道发送给 Root Complex

下面是一个完整、未拆分的 MSI-X 中断发送示例

bit msix_valid;
bit msix_has_data;
bit[127:0] msix_tlp;

assign tlps_static.tdata[127:0] = msix_tlp; 
assign tlps_static.tkeepdw      = 4'hf;
assign tlps_static.tlast        = 1'b1;
assign tlps_static.tuser[0]     = 1'b1;
assign tlps_static.tvalid       = msix_valid;
assign tlps_static.has_data    = msix_has_data;

wire [31:0] HDR_MEMWR64 = 32'b01000000_00000000_00000000_00000001;
wire [31:0] MWR64_DW2   = {`_bs16(pcie_id), 8'b00000000, 8'b00001111};
wire [31:0] MWR64_DW3   = {i_addr[31:2], 2'b0};
wire [31:0] MWR64_DW4   = {i_data};

always @ ( posedge clk_pcie ) begin
    if ( rst ) begin
        msix_valid <= 1'b0;
        msix_has_data <= 1'b0;
        msix_tlp <= 127'b0;
    end else if (msix_valid) begin
        msix_valid <= 1'b0;
    end else if (msix_has_data && tlps_static.tready) begin
        msix_valid <= 1'b1;
        msix_has_data <= 1'b0;
    end else if (o_int) begin
        msix_has_data <= 1'b1;
        msix_tlp <= {MWR64_DW4, MWR64_DW3, MWR64_DW2, HDR_MEMWR64};
    end
end

time int_cnt = 0;
always @ ( posedge clk_pcie ) begin
    if (rst) begin
        int_cnt <= 0;
    end else if (int_cnt == 32'd100000) begin
        int_cnt <= 0;
    end else if (int_enable) begin
        int_cnt <= int_cnt + 1;
    end
end

assign o_int = (int_cnt == 32'd100000);

总结

  • “Active Device”并非严格术语,本质是 DMA + 中断行为
  • Xilinx CFG Interrupt:
    • ✔ 支持 Legacy / MSI
    • ❌ 不支持 MSI-X
  • 启用 MSI-X 的设备:
    • 必须手动构造并发送 MEMWR TLP
  • 从协议角度看:
    • 中断 ≈ 一次特殊的 PCIe Memory Write

📌 完整实现代码将发布在:

最后更新于 2026-01-14