:从零搭一个 XDMA 直通工程——逐模块讲清数据流与控制流》)
PCIe AXI-Stream 架构一从零搭一个 XDMA 直通工程——逐模块讲清数据流与控制流系列《PCIe 四架构实测》第 1 篇 · 工程搭建上一篇《PCIe 四架构实测零为什么要做这个实验——硬件平台、数据帧格式与测量口径》平台XC7K325TFFG900-2I · Vivado 2023.1 · XDMA · PCIe Gen2 ×8 · Windows 上位机Python ctypes读法这一篇的目标是把每个模块、每条连线都讲一遍不留白。看到表格别跳过——XDMA 的配置项、我们自己的控制信号答案全在表里。文章目录PCIe AXI-Stream 架构一从零搭一个 XDMA 直通工程——逐模块讲清数据流与控制流[toc]0 这一篇要交给你什么1 架构二的思路什么叫直通1.1 一句话定义1.2 直通意味着什么1.3 整体平台长什么样2 建 Vivado 工程六步到能综合3 XDMA IP 配置逐项说清为什么这么设4 九个 BD 组件一个都不留白4.1 顺便把两个模块内部状态机也讲了5 数据流向三条通路5.1 a控制流——唯一的一条5.2 bC2H 数据流FPGA → 主机5.3 cGEN_ECHO 一问一答回路测延迟用的6 控制流向谁接谁、谁不接谁7 寄存器映射表权威版7.1 顺带说清每个指标怎么测的8 上板 bring-up 六步每步给期望现象9 我在这四件事上各卡了一天10 复现清单照抄即可11 小结 下一篇附本篇用到的图与源文件0 这一篇要交给你什么读完你应当能做到对应小节独立建一个 Vivado 工程把 XDMA 配成AXI-Stream 直通模式§2、§3说清 BD 里9 个组件各自是什么、为什么需要它§4画出三条数据通路并说清每一拍发生了什么§5知道自己写的控制信号接到了谁的哪根线以及哪两根故意不接§6看懂寄存器表知道哪个字段谁能写、谁在读§7复现上板 bring-up 六步并知道每步的期望现象§8避开我踩过的4 个坑§9一句话概括这一篇“从空工程到能跑通、并且我知道为什么能跑通。”1 架构二的思路什么叫直通1.1 一句话定义直通Streaming / Bypass 数据不在 FPGA 里停下来。数据从 A 点进来直接走到 B 点出去中间没有任何一块存储器。对照另外三种架构架构数据路径代价① DDR 大缓存ADC → 写入 DDR3 → 再读出 → PCIe多了 DDR 读写两趟但能吸收突发、能重传② Streaming 直通本篇数据 → 一拍一个 128 bit 拍 → PCIe省了存储也省掉了吸收突发的能力③ BRAM 轻缓存数据 → 写 BRAM256 KB→ 再读出缓存小但延迟比 DDR 低④ 多通道直通 多条 C2H 通道并行逻辑面积换带宽1.2 直通意味着什么省掉存储器带来三个直接后果后面三篇的数字全都从这里长出来FPGA 侧延迟极低——收完最后一拍下一拍就能发我们实测是2 拍 8 ns篇 3 详解没有弹性缓冲——上游来多快下游就得多快一旦下游PCIe慢一拍数据只能靠 AXI-Stream 的tready反压住上游不能重传——丢了就是丢了。所以必须靠帧头和校验字来判断有没有丢而不是靠缓存里还有备份。第 3 点决定了本项目的帧格式必须是可自校验的见篇 0 §6。第 2 点决定了 LOOP 回路上必须插一个 FIFO§4 第 9 个组件。1.3 整体平台长什么样几个必须记住的基线数字项值说明链路PCIe Gen2 ×85.0 GT/s × 8 lane板卡线路上限理论带宽5.0 GT/s × 8 × 8/10 ÷ 8 4 000 MB/s8b/10b 编码 ⇒ 乘 0.8再 ÷8 换成字节用户时钟user_clk 250 MHz250 MHz × 128 bit 4 GB/s恰好等于线速时钟不成为瓶颈AXI 数据位宽128 bit 16 B / 拍全设计统一一拍 16 字节器件xc7k325tffg900-2-2 速度等级板卡实物长这样——左侧那排金手指旁边就印着PCIe 2.0 X8白纸黑字不是我可以随便写的为什么要盯住 4000 MB/s因为后面所有百分比都以它为分母。98.6% 线速就是这个数除出来的。这张算式是全系列唯一的标尺。2 建 Vivado 工程六步到能综合新建工程Vivado 2023.1 → RTL Project → 器件选xc7k325tffg900-2注意是ffg900不是ffg676。加引脚约束pin.xdc。本设计顶层端口只有两个pcie_rst_n复位按键和lnk_up_led链路建立指示灯。其余引脚PCIe 差分对、100 MHz 参考时钟由 XDMA IP 自己拉出来在 XDC 里按板卡管脚位置绑定。建 Block Design不要用纯 RTL 顶层。理由XDMA 是 IP它的接口AXI-Lite 主口、两条 AXI-Stream、时钟复位都要在 BD 里连手写 RTL 例化 IP 反而更容易连错。BD 还有一个好处——连线可视化出问题一眼能看出来。加 Xilinx IPxdma_0、util_ds_buf、axi_smcAXI SmartConnect、axis_data_fifo_0。加自写模块data_gen、data_verify、stream_mux、ts_counter用“Add Module to Block Design”加进来。加进来之后它们在 BD 里叫data_gen_0之类的实例名类型是module_ref模块引用——这个类型很关键§9 的坑 #4 就栽在它身上。寄存器块S_AXI_LITE_0。这个是自己写的 HDL再打包成 IPCreate and Package IP——因为它需要跟 AXI-Lite 总线握手用打包 IP 更省事。Generate Output Products、Validate Design、Create HDL Wrapper、Generate Bitstream。一个容易被忽略的细节BD 里module_ref和打包 IP的刷新机制不一样module_ref改了 HDL右键 →Refresh Module Reference 重跑综合即可打包 IP改了 HDL还要再Generate Output Products。另外改了端口名虽然不用重新 package但必须在全工程做一次令牌替换并刷新否则 BD 里还挂着旧名字。3 XDMA IP 配置逐项说清为什么这么设这是本篇最该抄的一段。下表每一项都对应实际.xci里的值。配置页项值为什么BasicModeAXI-Stream架构二的核心数据不走地址映射直接流。改成 AXI-MM 就是架构一/三BasicDevice ID / Vendor ID7028/10EE主机按这个认设备上位机里写死DEV_7028BasicLink Speed / Width5.0 GT/s / ×8板卡线路只有 Gen2 ×8BasicReference Clock100 MHz板卡上的差分晶振BasicAXI Data Width128 bit250 MHz × 128 bit 4 GB/s 线速BasicC2H / H2C 通道数1 / 1架构四就是把这个改成 2—— 本器件 XDMA IP 的通道数上限就是 27 系列 Gen2 IP 最多 2 条 C2H / 2 条 H2C要 4 条得换 UltraScale 器件BasicRIDsC2H32/ H2C16读请求可以同时挂起多少条直接影响读吞吐PCIeAXI-Lite Master开启窗口 1 MB主机能读写用户寄存器——控制流的唯一入口PCIeBARBAR0 AXI-Lite寄存器窗口MSI-X中断开启1 vector主机靠它知道一帧到了篇 3 会对比阻塞等 / 忙轮询缓存HAS_CACHE0AXI-Stream 模式不涉及三处最该记住的Mode AXI-Stream这是架构二这个名号的来源。同一块板、同一套用户逻辑只把 Mode 换成 AXI-MM就是架构一/三。这是四架构能横向对比的前提。AXI Data Width 128 bit250 MHz × 128 bit 4 GB/s。位宽再大没用线速卡在那再小就成了瓶颈。C2H / H2C 各 1 条通道这就是后面吞吐只有 133 MB/s的第一现场——不是 bug是配置决定的。架构四会把它改成2 条通道——注意这是本器件的硬上限不是我们不想开到 4。4 九个 BD 组件一个都不留白上面是我重画的清版。Vivado 里实际的 BD 界面长这样同一张图方便对照#组件类型它是什么 / 为什么需要1xdma_0Xilinx IPPCIe 硬核 DMA 引擎。对外是 PCIe 引脚对内给出一条 H2C 流主机→FPGA、一条 C2H 流FPGA→主机、一个 AXI-Lite 主口读写用户寄存器、一个时钟axi_aclkuser_clk250 MHz2util_ds_bufXilinx IP差分时钟输入缓冲。板卡的 100 MHz 是差分信号转成 XDMA 能吃的单端3axi_smcXilinx IPAXI-Lite 互联SmartConnect把 XDMA 的主口接到寄存器块的从口。它只走控制不走数据——这一点新手常混4S_AXI_LITE_0打包 IP寄存器文件。把主机写来的 32 bit 拆成start / transfer_mode / echo_en / clr_stat / frame_len / rate_div / data_mode等信号再把各模块的统计量拼回 0x10~0x3C 供主机读5data_gen_0module_ref假数据帧生成器。每拍产 1 个 128 bit按帧格式拼帧头、填计数器载荷、算校验字rate_div控制降速GEN_ECHO 模式下收到请求帧就回一帧6data_verify_0module_ref帧解析器 校验器 打戳器。接在 H2C 流上逐 lane 校验载荷、查 MAGIC/长度/校验和、查 SEQ 连续性、给到达帧打时间戳并把请求帧信息锁存给data_gen7stream_mux_0module_ref二选一开关。transfer_mode 0选 LOOP 回路、 1选data_gen。未选中的那一路把tready拉 0让它以为下游没收数据原地保持不丢8ts_counter_0module_ref全局秒表。48 bit 自由跑计数器 250 MHz回绕周期 13 天data_gen和data_verify各自在需要的那一拍直接采当前值9axis_data_fifo_0Xilinx IP弹性缓冲。LOOP 回路上必须有它——H2C 和 C2H 是两条独立节奏的流GEN 通路不需要data_gen自己就是源想发就发4.1 顺便把两个模块内部状态机也讲了data_gen_04 态状态干什么什么时候走S_IDLE等startstart 1→S_PREPS_PREP锁本帧参数长度/SEQ/SEED/FLAGS采ts_now写进帧头TS_TX 把校验和算好echo_en ? echo_req : 1成立 →S_SENDstart 0→S_IDLES_SEND每拍发 1 个 128 bit 拍rate_div在这层插入间隔尾拍握手 →S_ENDS_END一帧结束start仍为 1 →S_PREP编下一帧否则 →S_IDLEdata_verify_03 态P_H0第 1 拍 帧头前半取 MAGIC/SEQ/TS_TX/FLAGS这一拍采ts_now作为 TS_RX→P_H1第 2 拍 帧头后半取 LENGTH/SEED备好载荷预算→P_BODY第 3 拍起 载荷 校验字收到tlast即帧结束。⚠️data_verify的这套状态机只有在transfer_mode 1时才走。transfer_mode 0LOOP时它是纯组合旁通不解析、不动统计——所以 LOOP 模式下调那些统计寄存器会一直是 0这不是坏了。5 数据流向三条通路一个 BD 里其实只有三条通路。搞清这三条整个工程就没有黑盒了。5.1 a控制流——唯一的一条主机Python ctypes └─ DeviceIoControl / ReadFile → BAR0 └─ xdma_0 / M_AXI_LITE └─ axi_smcAXI-Lite 互联 └─ S_AXI_LITE_0寄存器文件 ├─ start → data_gen_0, data_verify_0 ├─ transfer_mode → stream_mux_0, data_verify_0 ★ 不接 data_gen ├─ echo_en → data_gen_0, data_verify_0 ├─ clr_stat → data_gen_0, data_verify_0 ★ 不接 ts_counter_0 ├─ frame_len → data_gen_0 ├─ rate_div → data_gen_0 └─ data_mode → data_gen_05.2 bC2H 数据流FPGA → 主机[GEN 模式] data_gen_0/m_axis ──▶ stream_mux_0/gen_axis ──┐ ├─▶ stream_mux_0/m_axis ──▶ xdma_0/S_AXIS_C2H_0 ──▶ 主机 [LOOP 模式] │ xdma_0/M_AXIS_H2C_0 ──▶ data_verify_0/s_axis │ └─▶ data_verify_0/m_axis ──▶ axis_data_fifo_0 ──▶ stream_mux_0/loop_axis ──┘逐拍说明这就是当小学生讲的部分一拍 128 bit 16 字节250 MHz 下每秒 2.5 亿拍 ⇒ 4 GB/s。握手tvalid我有数据和tready我能收同时为高的那一拍才算数据真的传过去了。只有tvalid高、tready低 下游在反压数据必须原地不动地hold住。帧尾tlast 1的那一拍是本帧最后一拍。不足 16 字节怎么办用tkeep标出这一拍里哪几个字节是有效的16 bit一位对应一个字节。这一小结藏了第 2 篇的伏笔如果帧长让最后一拍只剩下 4 个有效字节tkeep就变成0x000F——听上去无害实测却能让吞吐掉 20%。篇 2 会讲透。5.3 cGEN_ECHO 一问一答回路测延迟用的主机写请求帧 ──▶ xdma_0/M_AXIS_H2C_0 ──▶ data_verify_0/s_axis │ ├─ 收到帧头那一拍采 ts_now → echo_ts_rx t1 │ 并从帧头取出 seq / len / seed 锁存 └─ 帧末拍tlast 握手发 echo_req1 拍脉冲 │ ▼ data_gen_0看到 echo_req ⇒ 锁参数、采 ts_now → 帧头 TS_TX t2⇒ 组帧 │ ▼ stream_mux_0/gen_axis ──▶ xdma_0/S_AXIS_C2H_0 ──▶ 主机读到响应帧 │ 主机用响应帧里的两个时间戳相减TS_TX − RSV[47:0] ⇒ 不用 ILA 也能读出 FPGA 内部耗时 这两个时间戳的作用在篇 3TS_TX − RSV[47:0]得到的不是回环延迟而是k Δk 请求帧的拍数Δ H2C 路的拍间空隙。真正的回环延迟只有2 拍 8 ns得用 ILA 逐拍量出来。同一个机制两种读法差 7500 倍——这就是篇 3 的钩子。6 控制流向谁接谁、谁不接谁这张图里最值钱的不是打勾的格子是打叉的格子★ ①transfer_mode不接data_gen_0。data_gen不需要知道现在是什么模式——它只看echo_req有请求就回一帧没有就自由跑。模式由stream_mux_0决定把哪一路送出去和data_verify_0决定解不解析共同决定。如果你下意识把transfer_mode也接到data_gen那你的状态机会分裂成两套逻辑早晚不一致。★ ②clr_stat不接ts_counter_0。ts_counter是墙上的钟不是统计量。被清零会让清零前后的两个时间戳不可比相减会得到一个天文数字。而且延迟是同域相减48 bit 天然处理回绕——这个秒表永远不需要清零。③ 所有统计量都是从模块流向寄存器文件只读。这就是为什么读寄存器要先读错误类0x10~0x1C再看别的出现异常时CRC_ERR/SEQ_ERR会告诉你帧有问题而不是让你去猜。7 寄存器映射表权威版主机侧读写的全部就是下面这张表。偏移名称位域 / 含义0x00ID/VER架构二固定0xA5C302000x04CTRLbit0start/ bit1transfer_mode1 GEN0 LOOP/ bit2echo_en/ bit4clr_stat电平位0x08MODE_CFGframe_len[15:0]/rate_div[23:16]/data_mode[31:28]0x10FRAMES_OK正确帧计数0x14CRC_ERR数据完整性错误校验和 / 载荷 /tkeep非法0x18SEQ_ERRSEQ 不连续次数0x1CFIRST_BAD_SEQ第一个坏帧的 SEQ初值0xFFFFFFFF 没错过0x20LAT_MIN最小延迟单位4 ns0x24/0x28LAT_ACC_LO/HI延迟累加器64 bit0x2CLAT_CNT延迟统计帧数0x30LAT_LAST最近一帧的延迟0x34/0x38WM_HIGH/OVERFLOW架构三BRAM用本期恒 00x3CTX_COUNT已发帧计数常用公式平均延迟(拍) ((LAT_ACC_HI 32) | LAT_ACC_LO) / LAT_CNT 丢帧率 1 − (FRAMES_OK CRC_ERR SEQ_ERR) / TX_COUNT 带宽利用率 吞吐 / 4000 MB/s⚠️ 丢帧率公式只在transfer_mode 1且主机确实往 H2C 写数据时才有效。LOOP 模式下没人往 H2C 写TX_COUNT在涨、分子恒 0算出来是-100%纯属误用。起手式按顺序写不要合并成一次写① 写 0x08 MODE_CFG 帧长 / 分频 / 数据模式 ② 回读 0x08 确认 ③ 写 0x04 0x12 bit4 clr_stat bit1 transfer_mode清零 选路 ④ 写 0x04 0x02 撤掉 clr_stat ⑤ 写 0x04 0x03 start transfer_mode起跑 ⑥ 读 0x3C 看帧数在涨 ⑦ 写 0x04 0x00 停两个手一抖就废的写法写0x11clr start 同置clr_stat是电平位会把data_gen钉死在S_IDLE板子看起来彻底不动写0x1只有 start漏了transfer_mode位走的是 LOOP 旁通C2H 读回来 0 字节。7.1 顺带说清每个指标怎么测的指标怎么测的关键注意点板上产数速率理论 4000 / rate_divMB/s实测用TX_COUNT(0x3C) 增量 × 帧长 ÷ 时间窗只算有效载荷不含 36 字节帧开销主机实收吞吐主机侧收到的有效载荷字节数 ÷ 耗时QueryPerformanceCounter高精度计时组帧与拷贝必须移出计时窗否则测的是 Python 不是 PCIeFPGA 内部延迟① 响应帧内两个时间戳相减② ILA 逐拍数两种口径结论差 7500 倍必须分清篇 3错误率 / 丢帧率读FRAMES_OK/CRC_ERR/SEQ_ERR/TX_COUNT仅transfer_mode 1有意义8 上板 bring-up 六步每步给期望现象步动作期望现象不通过怎么办1Generate Bitstream → 下载Vivado 提示 programming successfullnk_up_led点亮灯不亮 链路没训练上先查参考时钟、复位、pcie_rst_n时序2装 XDMA 驱动设备管理器出现 4 个节点xdma0_h2c_0/xdma0_c2h_0/xdma0_user/xdma0_control只出现部分 INF 里的设备 ID 没对上10EE:70283认设备pcie_bench list打印出DEV_7028IsPresent True出现DEV_7018是残留的旧设备记录不是你的板子4冒烟读 ID读0x00得到0xA5C30200读不到 AXI-Lite 通路或 BAR0 没通§6 的控制链5冒烟读统计读0x10/0x14/0x18是 00x1C是0xFFFFFFFF全 0 且0x1C也不是全 F 寄存器块没被复位6起跑§7 起手式0x3C持续增长0x14/0x18保持 0数字不涨 检查transfer_mode有没有真的写进去9 我在这四件事上各卡了一天这一段是别人抄不走的部分——四个坑都有源码 / 日志佐证。#坑现象真因解法1stream_mux端口名与极性双错按文档写transfer_mode 1结果上板卡死端口叫s0/s1毫无语义注释还和 BD 实际接线恰好相反加上选择极性翻转 ⇒ 选到 LOOP 路并把data_gen的tready拉 0 ⇒data_gen永远出不了S_SEND端口改名为gen_axis/loop_axis端口名即语义极性改为transfer_mode ? gen : loopBD 侧 Refresh Module Reference2clr_stat是电平位不是脉冲写0x11clr start 同置后板子彻底不动clr_stat 1把data_gen钉死在S_IDLE分两步写0x12clr 选路→0x02撤 clr→0x03起跑3漏写transfer_mode位C2H 读出 0 字节只写了0x1starttransfer_mode还是 0 ⇒ 走 LOOP 旁通用 §7 的起手式别自己拼4module_ref改完不刷新改了 HDL上板行为一点没变BD 里的module_ref缓存了旧接口Reset 对应的 OOC 综合 run Refresh Module Reference Validate Design10 复现清单照抄即可# 环境 Vivado 2023.1 器件 xc7k325tffg900-2 XDMA: Mode AXI-Stream, Gen2 x8, AXI Data Width 128 bit, C2H/H2C 1/1 AXI-Lite Master 开1 MB, BAR0 AXI-Lite, MSI-X 开 上位机: Windows Python ctypes无需 C 驱动节点 xdma0_h2c_0 / xdma0_c2h_0 / xdma0_user / xdma0_control # 起手式写寄存器按顺序 0x08 MODE_CFG → 回读确认 0x04 0x12 → 0x04 0x02 → 0x04 0x03 读 0x3C 看帧数 → 0x04 0x00停 # 验收架构二 ① ΔCRC_ERR ΔSEQ_ERR 0 ② 末段吞吐 ≥ 最快段 × 98% ③ 全程无读超时 ④ FPGA 的 FRAMES_OK 增量 主机实际发帧数11 小结 下一篇本篇把工程从空目录搭到了能跑通且完全可解释9 个组件各有明确职责没有一个是多余的三条通路控制 / C2H / GEN_ECHO覆盖了全部数据与控制行为两条故意不接的控制线transfer_mode不接data_gen、clr_stat不接ts_counter是设计上的取舍不是遗漏四个坑都是接口语义问题不是时序或逻辑问题——所以它们很难靠仿真发现必须上板。下一篇篇 2工程搭好了跑起来才发现——FPGA 内部计数3942.7 MB/s98.6% 线速主机read只有约 133 MB/s差 30 倍。是链路的锅吗附本篇用到的图与源文件文件内容fig/csdn_p0_system.png平台基线主机软件栈 · PCIe 链路 · FPGA 用户逻辑fig/csdn_p1_bd_block.pngBD 框图9 个组件、三条互连fig/bd.png/fig/system1.pdfVivado 实际 BD 界面截图 / 矢量版fig/csdn_p1_ctrl_matrix.png控制信号分发矩阵fig/csdn_p1_data_paths.png三条数据通路控制 / C2H / GEN_ECHOfig/csdn_p1_fsm.pngdata_gen4 态 data_verify3 态状态机fig/csdn_p1_frame_format.png假数据帧格式字节偏移 拍内字段序src/data_gen.v/data_verify.v/stream_mux.v/ts_counter.v四个自写 RTL 模块system.bd/system_xdma_0_0.xciBD 与 XDMA IP 配置pcie_bench.py上位机Python ctypes