你会不会好奇:DMA 是怎么在 2022 年的高端科技市场上逆势击败 AI 的?靠稳定?靠高门槛?靠多功能?
不,统统都不是。
靠的是 AI 的领头人被抓了。上央视新闻的那种。
现实有时候就是这么朴实无华且枯燥。
AI 领头人被捕,树倒猢狲散,底下代理跑的跑、抓的抓。那又和 DMA 有什么关系?就算 DMA 赢了,和 FPGA 工程师又有什么关系?
关系很简单。
还记得上面说的,FPGA 工程师第一次被 DMA 拿来当挡箭牌,是因为某款 75T 开发板即将上市吗?
那时候市面上全是一千多一块的 35T。不到四位数的 75T 一旦上市,可想而知会直接引爆市场。那些还在观望、预算不足、期盼更好产品的玩家,总算等到了入手时机。
而这个国内第一块 75T 品牌的主理人之一,恰好是某款 AI 龙头的代理。实际上,早期大多数 DMA 卖家都卖过 AI,或者说,那时候大伙更相信 AI。这个词太唬人了,不是吗?尤其是伴随着那年 ChatGPT 的震撼问世。
不过直到现在,“AI 科技”到底是不是真的 AI 产品,仍然是个谜。至少我是这么认为的:现在所谓的 AI 科技,和真正 AI应用之间的血缘关系,远没有和图像识别来得近。
AI 领头人都被拿下了,底下的小弟还卖什么命?于是纷纷转投另一座山头谋财。也正是在这种背景下,那块被寄予厚望的板子上市在即,却面临一场殃及池鱼的危机。
当时大家并没有像“菜就多练”时期那样害怕,打心底里认为 DMA 真的是擦边球,不违法,是正儿八经的设备。只是害怕被有售卖 AI 黑历史的代理波及、误伤罢了。
为了给产品打造一个人畜无害的形象,大家开始集思广益。也就是在那时,我提出了这个概念:
既然 DMA 是开发板,开发板属于 FPGA,那就当 FPGA 开发板卖——本来也确实是 FPGA 开发板。
至于固件,就说自己是 FPGA 工程师,给开发板开发配套固件——绝不是用来规避检测什么的。
那时候,并没有太多人在意这个称呼。不管是同行还是玩家,只觉得新瓶装旧酒,挺唬人。直到风雨欲来,人心惶惶的时候,才重新想起这层为 DMA 量身打造的避难所,妙手偶得的身份标签。
我一直对此做法很不齿。虽然首先提出这个概念并加以宣传的人是我,但除了第一世之外,我倒是从没再打过 FPGA 相关的标签。
原因有两个。
一个是 DMA 确确实实改变了我的命。它让我看到过去无法想象的世界,也让我明白,掩耳盗铃没有意义。你出不出事,取决于你的衣食父母满不满意——指客户;也取决于天老爷想不想管你——懂的都懂。
另一个是,我自认很擅长营销。总是比别人更容易把东西卖出去,所以我不屑于使用曾经用过的套路。
QQ、VX、DC、TG;团购、拼团、线下安装、海外大牛、作者一手、直播写固、GitHub 开源;邀新积分换固件、推荐购买返现金、捆绑赠送次等固件、固件池、激活固;绑定 DNA 就是单人固、远程采集、闲鱼矩阵、淘宝排名、硬件品牌化、固件个人 IP 化、群友演员化、作者神化……等等等等。那些在当时从未有过的打法,如今你可能司空见惯了。
开头时我说,时至今日我依然没用过 DMA,这话是真的。我从没有在游戏里开过,并不是我为了标榜自己道德有多高尚,而是我不喜欢玩游戏。但这么些年我经常会在扪心自问,我真的没用过 DMA 吗?
别担心,我不是精神分裂了。我确确实实没在游戏里用过 DMA,但我的人生真真切切地靠 DMA改变了。别人用 DMA 改游戏,我靠 DMA 改了自己那条本来一眼望到头的路。说好听点叫逆天改命,说难听点,也就是人生里开了一次。
我感谢 DMA。
如果不是 DMA,我可能只是一个在北上广深漂泊的牛马,直到生命燃尽的耗材;一个终其一生都不知道莱佛士顶层下的南山区是什么样的光景,不知道阿勒泰的秋季真的像置身于另一个世界,不知道东京霓虹灯下有着怎样的放纵时节,也不知道纽约的夏天其实并没有想象中那么热烈的普通人。
这话矫情吗?真他妈矫情。但是真的,发自内心的。
DMA 没有赐予我财富自由,但赠与了我走向财富自由路上的第一桶金——最重要的第一桶金(仍在实现财富自由的路上)。
我时常会想,绝大多数国人,绝大多数普通人,并不比那些生来上位的人差多少。王侯将相宁有种乎,早就刻进了每一个国人的血脉里。只是大多数人差的不是能力,是那一次能坐上牌桌的机会。
只要作弊不被发现就不算作弊。我一直是这么认为的。
所以从小时候玩 4399 开始,我就不排斥这些。如果人生是一场游戏,那绝大多数普通人只能自嘲一声 NPC。你可能会预判我,说你是不是要宣泄些仇富言论,生得好就算人生作弊?当然不是,生得好说明人家抽卡运气好,现在二游那么多,抽卡这种事,愿赌服输。
人生是可以作弊的,打个最最常见的比方。如果建模好,愿意放下尊严去换取利益,对于老老实实、勤勤恳恳的人来说,是不是算作弊?当然这是令人不齿的。从小受到的义务教育告诉我们要懂得礼义廉耻,之乎者也的道理连幼儿园的小孩子都知道。
但制定这些规矩,教授这些道理的人,自己做得到吗?我不知道,我只知道三胖让光之国子民遵守的规则,自己却不必遵守。
圈里摸爬滚打这么些年,说一套做一套的人十有八九,包括我自己。有时候我会想,那些标榜道德高尚、游戏里从不作弊的人,是真的不想,还是不敢承担后果?那些游戏里追求公平的人,现实里如果有一条不公平的捷径给他走,他会大义凛然地拒绝吗?
反正我不会。如果有人说我就是拒绝,我也不认为真有那么高尚的人。在游戏里追求绝对的公平,却接受不公平的现实。
我并不想在这里强行升华,也不想卖弄什么情绪。说这些只是因为现在我很无聊。有点像陈奕迅《陀飞轮》里唱的那样:得到了一些,又失去了一些;拥有了很多,却也不知道还该追什么。
所以,我选择做一些能感动自己的事情。
感谢 DMA。感谢过去支持过我的家人。也由衷希望 DMA 能够复苏过来重活第二世。
这话会不会太像直播间主播感谢家人了?
“我 Chovy,感谢你给我感谢好了啊!那些被你圈了米的家人,是那么容易释怀的吗?”
我得找补一句:我没圈米过,也没欺骗过。营业的时候,卖的都是货真价实,服务也算恰到好处。
斟酌后删减了许多不能说的,其实还有很多很多话想说。但这篇 DMA 固件制作教程的废话,到这里也该结束了。
接下来的内容,是真正的干货。认真看。如果有些步骤不懂存在疑问,我建议你去问 GPT-5.5 或 Opus 4.8,而不是满世界找人类问。人类是利益交换、自私自利的生物,AI 只需要你开个会员,很便宜。 推荐你配合另外两篇一起看,一篇讲面对 VT-d我们并非无计可施,一篇教授如何根据PCie设备全自动生成固件,尽可能让DMA成为全流程免费项目(软件纯黑产自己想办法)。
AI不推荐用中转站。毕竟中转站这东西,某种程度上和过去的固件市场异曲同工。能理解吗?
教程以大众化的Windows系统做示范。Linux可能有用,但这里的主要流程不需要 lspci、setpci、sysfs 或 Linux BAR 转储。教程旨在用于受控实验室环境、硬件学习、互操作性测试和安全研究发布。 禁止用于绕过访问控制、反作弊系统或受保护环境。 前言随笔小作文,10000%虚构世界观由Ai生成!全文内容与教程目的无任何关联。
· 生成影子配置空间 COE 数据
· 生成一个 WriteMask COE
· 正确设置 DSN
· 避免编辑错误的源文件
· 使用 Vivado 生成
· 刷入bit测试
· 与 设备管理器、Arbor、TeleScan PE 以及 cold boot 进行验证
· 在bit失败救砖
1.3 什么算成功
我直接告诉你bit的分级。
| 水平 | 结果 | 证明了什么 |
|---|---|---|
| 1 | 设备出现在设备管理器 | 仅限基本列举。这不是完全的成功。 |
| 2 | 硬件ID、类别、子系统、BAR看起来正确 | PCIe 的身份和资源布局接近。 |
| 3 | Arbor/TeleScan 回读与供体计划一致 | 配置空间和能力结构是合理的。 |
| 4 | 命令寄存器和配置写入行为正常 | WriteMask 和 shadow 写入路径正在工作。 |
| 5 | BAR 读取完整并返回计划数据 | BAR 响应路径至少在测试过的偏移量上有效。 |
| 6 | 驱动程序未绑定代码 10/12/31 | BAR 的行为、中断、PM 和配置写入对于该驱动程序的启动路径来说已经足够好了。 |
| 7 | 驱动程序在正常工作负载下运行正常 | 基本的驱动绑定是不够的;驱动必须能 |
如果你只改了VID/DID,你只不过自欺欺人。
如果设备管理器不能稳定驱动,那并不算成功。
如果你没有进行回读检查,你就没有验证任何内容。
如果你没有冷启动, 不要说bit是稳定运行的。
1.4 软件
只说Windows 国内没什么人用Linux吧:
· 设备管理器
· Arbor
· TeleScan PE
· HxD
· PowerShell
· Python 在 Windows
· Vivado 图形用户界面
· Vivado Tcl Shell
· CH347 工具
· OpenOCD
· Vivado Hardware Manager
1.5 过去那些错误的教程汇总
把这张表格读两遍,甚至毒教程的危害。
| 来自垃圾教程的悟道 | 正确答案 |
|---|---|
| 使用 RTL8153 USB 网卡作为供体。 | RTL8153 是 USB,而不是直接的 PCIe 端点。使用真正的 PCIe 网卡,例如 RTL8111、RTL8168、RTL8125、Intel 82574 或 Intel I210。 |
| 只记录VID/DID。 | 不够。你还需要修订、类别、子系统、BAR 布局、功能、MSI/MSI-X、DSN、驱动程序版本和资源。 |
| "rw[203] 和影子 rw[206] 在 pcileechpciecfg*a7.sv。" | 不正确。他 在src/pcileech*fifo.sv。 |
| "rw[21] 是主中止启用。" | 不正确。rw[21] 是命令寄存器自动设置。rw[20] 是状态寄存器自动清除。 |
| "pcie7x0coretop.v 是一个普通的源文件。" | 通常是错误的。 是在 .gen/… 下由 Vivado 生成的 IP 输出文件,并且可以被覆盖。 |
| Zero4K 模拟任何 BAR。 | 不。Zero4K 是 4KB。更大的 BAR 需要更多逻辑或更小的设备。 |
| 所有开发板烧录器一样。 | 不。Squirrel、CaptainDMA、Enigma、ZDMA、AC701以及其他板块可能使用不同的工具。 |
| 刷好bit后只需软重启。 | 不。关机,如果需要,断开电源,等待,然后开机,再进行验证。 |
1.6 继续
只有在以下情况下你才准备好继续:
· Windows所需软件安装齐全(这个不用教吧?)
· 一个想做成bit的PCIe设备,而不是一个 USB 设备
· 你知道你的DMA芯片型号
· 你明白VID/DID-only的工作不是真正的bit定制
2. 硬件
2.1 选择 DMA 板
| 板卡系列 | 常见 FPGA | 常用上游文件夹 | 常见刷入路径 |
|---|---|---|---|
| Squirrel | XC7A35T FGG484 | PCIeSquirrel/ | OpenOCD / Squirrel 更新端口 |
| LeetDMA 35T | XC7A35T FGG484 | PCIeSquirrel/ | Squirrel兼容或供应商工具 |
| CaptainDMA M2 35T x1 | XC7A35T CSG325 | CaptainDMA/35t325*x1/ | CH347 / M2 闪存模块 |
| CaptainDMA M2 35T x4 | XC7A35T CSG325 | CaptainDMA/35t325*x4/ | CH347 / M2 闪存模块 |
| CaptainDMA 35T FGG484 | XC7A35T FGG484 | CaptainDMA/35t484*x1/ | 特定供应商的 |
| CaptainDMA 75T | XC7A75T FGG484 | CaptainDMA/75t484*x1/ | CH347 / 供应商工具 |
| CaptainDMA 100T | XC7A100T FGG484 | CaptainDMA/100t484-1/ | CH347 / 供应商工具 |
| Enigma X1 | XC7A75T FGG484 | EnigmaX1/ | Vivado Hardware Manager 或供应商流程 |
| ZDMA | XC7A100T FGG484 | ZDMA/ | Vivado/JTAG/vendor 流 |
| AC701 | Kintex-7 开发板 | ac701_ft601/ | Vivado Hardware Manager |
错误的板子= 错误的部件、错误的引脚、错误的时钟、错误的刷入、损坏的 bit。
2.2 选择设备
适合小白练手的设备:
· Realtek PCIe 网卡:RTL8111, RTL8168, RTL8125
· Intel PCIe 网卡:82574, I210
· 简单 PCIe 串行卡
· 如果你接受额外的复杂性,简单的 PCIe USB 控制器
不适合小白的设备:
· RTL8153 USB 网卡 - 不是 PCIe
· GPU - 巨大的 BAR、功率管理、bit、驱动程序握手
· Wi-Fi/Bluetooth - NVM,校准,bit加载
· NVMe/RAID/HBA - 队列,门铃,MSI-X,有状态控制器逻辑
· 如果你只打算使用Zero4K省事,任何带有巨型BAR的设备
2.3 必要
这都没有你做个什么bit:
· DMA FPGA 板
· PCIe 设备
· Windows 主机
· Windows 辅机
· 连接线
· 烧录器
· 救砖bit
3. 采集设备
3.1 采集的含义
采集不是为了VID/DID。
如果只改这个,那你的bit就是垃圾中垃圾。
· 厂商 ID
· 设备 ID
· 修订 ID
· 类代码
· 子系统厂商 ID
· 子系统ID
· BAR0-BAR5 类型和大小
· 32位与64位 BAR 信息
· 可预取标志
· 功能指针
· 标准功能链
· 扩展功能链
· MSI 或 MSI-X 详情
· DSN 的存在及其值(如果存在)
· Windows 驱动版本
· 设备管理器 资源
· 设备管理器 events/status
采集质量
| 水平 | 名称 | 你所拥有的 | 判定 |
|---|---|---|---|
| 0 | 仅 VID/DID | VENxxxx&DEVxxxx,别无其他 | 不可接受。这不是设备采集。 |
| 1 | 设备管理器 基础 | 硬件ID、资源、驱动程序选项卡、事件选项卡 | 最低身份证据,仍然很弱。 |
| 2 | 解码配置 | Arbor 或 TeleScan PE header/BAR/capability 解码 | 可用于小白身份和 BAR 设置。 |
| 3 | 完整 Windows 解码 | BAR0-BAR5,标准功能,扩展功能,MSI/MSI-X,DSN,驱动程序版本 | 真实 Windows 设备采集。 |
| 4 | 可重新检查的原始数据样式 | 工具导出,.tlscan,保存的配置 bytes/table,或结构化 offset/DWORD 文件 | 强大。你可以重新生成 COE 并审核偏移。 |
| 5 | 设备-bit设备差异已准备好 | 设备和bit设备记录可以逐字段比较 |
质量评级:
· 0级不可接受。
· 1级只是一个起点。
· 2级可以让你开始行动。
· 3级是最低要求。
· 4级是COE生成不再是猜测的阶段。
· 5级是你实际上可以证明你的bit与你的计划一致的阶段。
3.3 先创建设备文件夹
在处理Vivado之前创建此文件夹:
C:/dma/donor//
device-manager-hardware-ids.png
device-manager-resources.png
device-manager-events.png
device-manager-driver-version.txt
arbor-export.txt
arbor-config-space.png
telescan-save-file.tlscan
config-offsets.csv
config-dwords.csv
bar-layout.png
capabilities.png
extended-capabilities.png
msi-msix.png
donor_specs.md
3.4 设备管理器采集
在 Windows 上安装设备,并让普通驱动程序加载。
打开:
设备管理器 -> View -> Devices by connection
找到设备设备,然后采集:
硬件 ID**
Right click device -> 属性 -> Details -> Hardware Ids
普通输出示例:
PCI\VEN10EC&DEV8125&SUBSYS012310EC&REV05
PCI\VEN10EC&DEV8125&SUBSYS_012310EC
PCI\VEN10EC&DEV8125&CC_020000
PCI\VEN10EC&DEV8125&CC_0200
要提取的内容:
Vendor ID: 10EC
Device ID: 8125
Subsystem Vendor ID: 10EC
Subsystem ID: 0123
Revision ID: 05
Class Code: 020000
错误输出:
PCI\VEN10EC&DEV8125
这不完整。你仍然需要修订、类别、子系统、BAR、功能和驱动程序数据。
资源
属性 -> Resources
记录:
· 内存范围条目
· 如果存在 I/O 范围条目
· 中断 求
· 冲突设备列表
使用此信息进行 BAR 分配和资源冲突检查。
驱动程序
属性 -> Driver
保存:
Provider:
Date:
Version:
Digital Signer:
驱动版本很重要。不同的驱动版本可以读取不同的BAR偏移量或需要不同的功能。
事件
属性 -> Events
保存install/start消息。稍后,如果你的bit设备出现代码10或代码31,这可以为你提供对比资料。
3.5 Arbor 采集
以管理员身份运行Arbor。
Right click Arbor.exe -> Run as administrator
步骤:
- 打开本地系统
- 扫描或重新扫描PCIe设备
- 通过插槽、名称或VID:DID找到设备
- 打开PCI配置解码
- 采集头字段
- 采集BAR布局
- 采集标准功能
- 采集扩展功能
- 采集MSI/MSI-X
-
如果支持,导出文本
记录这些字段:
| 字段 | 示例 | 用途 |
|---|---|---|
| 厂商 ID | 10EC | PCIe IP 标识 |
| 设备 ID | 8125 | PCIe IP 标识 |
| 修订 ID | 05 | PCIe IP 标识 |
| 类代码 | 020000 | PCIe IP 标识 |
| 子系统厂商 ID | 10EC | PCIe IP 或影子 |
| 子系统ID | 0123 | PCIe IP 或影子 |
| 功能指针 | 0x40 | 能力链 |
| BAR0 type/size | 内存,32 位,16KB | Vivado BAR 配置 |
| MSI/MSI-X | MSI 存在,MSI-X 不存在 | 写掩码和 BAR 规划 |
| DSN | 存在或不存在 | cfg_dsn 和影子数据 |
正常 Arbor 结果:
Header decodes cleanly.
BARs have sane power-of-two sizes.
Capabilities chain walks forward and terminates.
Extended capabilities are readable if present.
错误 Arbor 结果:
Vendor ID: FFFF
Device ID: FFFF
BAR size unknown
Capability chain broken
Extended config unreadable
在不确定设备采集是错误还是工具受限之前, 勿继续。
3.6 TeleScan PE 采集
TeleScan 可以保存可重新打开的采集文件。
步骤:
-
以管理员身份运行 TeleScan PE -
扫描本地 PCIe 设备 -
选择供体 -
保存 .tlscan 文件 -
截取 BAR 布局截图 -
截取功能截图 -
截取扩展功能截图 -
截取 MSI/MSI-X 截图
不要仅依赖设备名称的一个截图。 保存实际的采集文件。
Capture limitation: BAR layout captured, but BAR contents not captured.
Capture limitation: MSI-X table offset known, but BAR table readback not tested yet.
```
如果你只有截图,不要写“完整设备采集”。 准确说明你拥有什么。
- Windows capture level:
- Raw-like export available:
- Capture limitations:
- 4KB config available:
Identity
- Vendor ID:
- Device ID:
- Revision ID:
- Class Code:
- Subsystem Vendor ID:
- Subsystem ID:
BAR Layout
| BAR | Enabled | Type | Width | Prefetchable | Size | Windows resource range |
|-----|---------|------|-------|--------------|------|------------------------|
| BAR0 | | | | | | |
| BAR1 | | | | | | |
| BAR2 | | | | | | |
| BAR3 | | | | | | |
| BAR4 | | | | | | |
| BAR5 | | | | | | |
Capabilities
| Offset | ID | Name | Next | Important fields |
|--------|----|------|------|------------------|
Extended Capabilities
| Offset | ID | Name | Version | Next | Important fields |
|--------|----|------|---------|------|------------------|
Interrupts
- INTx:
- MSI-X:
- MSI:
- MSI-X Table BIR:
- MSI-X Table Offset:
- MSI-X PBA BIR:
- MSI-X PBA Offset:
DSN
- Present:
- Raw bytes:
- Lower DWORD:
- Upper DWORD:
- 64-bit cfg_dsn value:
Target Readback Comparison
| Field | Donor | Target | Category | Notes |
|---|---|---|---|---|
| VID/DID | stable/runtime/intentional/bug | |||
| Revision/Class | stable/runtime/intentional/bug | |||
| Subsystem IDs | stable/runtime/intentional/bug | |||
| BAR layout | stable/runtime/intentional/bug | |||
| Capability chain | stable/runtime/intentional/bug | |||
| Extended capability chain | stable/runtime/intentional/bug | |||
| MSI/MSI-X | stable/runtime/intentional/bug | |||
| Command/Status | stable/runtime/intentional/bug |
Difference Categories
- Stable fields that should match:
- Runtime fields that may change:
- Intentional differences:
- Bugs:
Notes
- Anything weird:
- Anything not captured:
- Anything intentionally different:
3.11 设备bit设备差异
最终bit设备必须与设备源进行比较。“看起来很接近”不行。
| 差异区域 | 设备源 | bit设备源 |
|---|---|---|
| 设备名称和硬件ID | 设备管理器 设备截图 | 设备管理器 bit设备截图 |
| Resources/BAR 范围 | 设备管理器 资源 | 设备管理器 资源 |
| 配置头 | Arbor/TeleScan 设备 | Arbor/TeleScan bit设备 |
| 关键偏移 | donor_specs.md / CSV | Arbor/TeleScan bit设备回读 |
| BAR 布局 | Arbor/TeleScan 设备 | Arbor/TeleScan bit设备 |
| 能力链 | Arbor/TeleScan 设备 | Arbor/TeleScan bit设备 |
| 扩展功能 | Arbor/TeleScan 设备 | Arbor/TeleScan bit设备 |
| MSI/MSI-X | Arbor/TeleScan 设备 | Arbor/TeleScan bit设备 |
| Command/Status 行为 | 受控配置写入测试 | 受控配置写入测试 |
分类每个差异:
| 类别 | 含义 | 示例 |
|---|---|---|
| 稳定字段 | 应匹配设备计划 | VID/DID、类代码、子系统 ID |
| 运行时字段 | 预期在运行时更改 | 命令,状态,BAR 分配地址,MSI 启用 |
| 有意的差异 | 你选择了 并记录下来了 | 链路宽度不同于供体硬件 |
| 缺陷 | 应该匹配或正确行为但未实现 | 能力指针损坏,BAR 大小错误,VID/DID 过时 |
如果你无法分别差异在哪,不要忽视。标记为未知然后去问AI。
3.12 采集检查
· donor_specs.md已填写
· 截图已保存
· 采集质量等级已知晓
· 采集限制已写下
· 驱动版本已保存
· BAR 布局已知
· 能力链已知
· MSI/MSI-X 已知
· DSN 状态已知
· COE 生成存在类似原始的导出或手动偏移表
· 已准备好设备-bit设备差异字段
· 你可以解释每个 BAR 应该是什么
如果你只有 VENxxxx&DEVxxxx,那你之前在干嘛?
4. 环境设置
4.1 Windows 版本推荐
推荐:
· Windows 10 22H2
· Windows 11 23H2 或更新版本
避免:
· 大幅修改的“精简”Windows 镜像
· 驱动安装损坏的系统
· OneDrive 同步下的路径
错误:
Python was not found
在继续之前修正 PATH。
4.5 Git 安装
验证:
git --version
正常:
git version 2.x.x.windows.x
4.6 路径规则
使用仅包含 ASCII 的短路径。
示例:
C:/dma/
C:/dma/pcileech-fpga/
C:/dma/work/squirrel-rtl8125/
C:/dma/donor/rtl8125/
C:/dma/builds/
错误:
C:/Projects/DMA Firmware New Final Final/
C:/Users/Your Name/Desktop/My DMA Firmware Test/
C:/Users/Name/OneDrive/Desktop/dma/
避免:
· 空格
· 中文路径
· 非 ASCII 字符
· 云同步文件夹
· 长路径
· 受保护的文件夹
Vivado 路径错误是在小白群体经常发生。
4.7 管理员权限和驱动程序
以管理员身份运行:
· Arbor
· TeleScan PE
· CH347
· OpenOCD
· Vivado Hardware Manager
常见 Windows 驱动问题:
| 症状 | 可能原因 |
|---|---|
| CH347 未检测到 | 驱动缺失或驱动错误 |
| FTDI/OpenOCD 无法打开设备 | WinUSB/FTDI 驱动不匹配 |
| Vivado Hardware Manager 没有发现bit设备 | 缺少电缆驱动,板子未通电,JTAG 端口错误 |
| Arbor 无法读取配置 | 非管理员,工具限制,设备未激活 |
| TeleScan 显示过期设备 | 需要重新扫描,设备已禁用,驱动失败 |
4.8 环境检查
当以下条件满足时,你已准备好:
· Vivado 启动
· 已安装 7 系列支持
· Python 正常工作
· Git 正常工作
· VS Code 可以打开仓库
· Arbor 或 TeleScan 以管理员身份运行
· HxD 已安装
· 在刷bit之前,你的烧录器可以检测到板子正常刷bit
| 你想修改的内容 | 修改位置 |
|---|---|
| 供应商 ID / 设备 ID | Vivado PCIe IP GUI / pcie7x0.xci |
| 修订 ID | Vivado PCIe IP GUI / pcie7x0.xci |
| 类代码 | Vivado PCIe IP GUI / pcie7x0.xci |
| 子系统供应商 / 子系统 ID | Vivado PCIe IP GUI 如果暴露,否则影子配置计划 |
| DSN 信号 | src/pcileechpciecfga7.sv, rw[127:64] / cfgdsn |
| 命令自动设置 | src/pcileechpciecfg*a7.sv, rw[21] |
| 状态自动清除 | src/pcileechpciecfg*a7.sv, rw[20] |
| 影子读取 zero/data 开关 | src/pcileech*fifo.sv, rw[203] |
| 影子配置写入启用 | src/pcileech*fifo.sv, rw[206] |
| 影子配置字节 | ip/pcileech*cfgspace.coe |
| 写掩码字节 | ip/pcileechcfgspacewritemask.coe |
| Zero4K BAR 字节 | ip/pcileechbarzero4k.coe |
| BAR 路由 / BAR 响应器 | src/pcileechtlps128bar*controller.sv 或板级等效 |
| 生成 PCIe 核心包装器 | .gen/sources1/ip/…/pcie7x0core_top.v 如果绝对必要 |
5. 项目初始化
5.1 克隆上游
使用 PowerShell:
mkdir C:\dma
cd C:\dma
git clone https://github.com/ufrisk/pcileech-fpga.git
cd C:\dma\pcileech-fpga
git rev-parse HEAD | Out-File C:\dma\upstream-commit.txt
你去狼头仓库手动下载也可以
5.2 复制正确的文件夹
Squirrel 示例:
Copy-Item C:\dma\pcileech-fpga\PCIeSquirrel C:\dma\work\squirrel-rtl8125 -Recurse
cd C:\dma\work\squirrel-rtl8125
使用你实际的文件夹。
5.3 文件修改映射
正确修改位置,避免毒教程误导。
| 你想修改的内容 | 修改位置 |
|---|---|
| 供应商 ID / 设备 ID | Vivado PCIe IP GUI / pcie7x0.xci |
| 修订 ID | Vivado PCIe IP GUI / pcie7x0.xci |
| 类代码 | Vivado PCIe IP GUI / pcie7x0.xci |
| 子系统供应商 / 子系统 ID | Vivado PCIe IP GUI 如果暴露,否则影子配置计划 |
| DSN 信号 | src/pcileechpciecfga7.sv, rw[127:64] / cfgdsn |
| 命令自动设置 | src/pcileechpciecfg*a7.sv, rw[21] |
| 状态自动清除 | src/pcileechpciecfg*a7.sv, rw[20] |
| 影子读取 zero/data 开关 | src/pcileech*fifo.sv, rw[203] |
| 影子配置写入启用 | src/pcileech*fifo.sv, rw[206] |
| 写掩码字节 | ip/pcileechcfgspacewritemask.coe |
| Zero4K BAR 字节 | ip/pcileechbarzero4k.coe |
| BAR 路由 / BAR 响应器 | src/pcileechtlps128bar*controller.sv 或板级等效 |
| 生成 PCIe 核心包装器 | .gen/sources1/ip/…/pcie7x0core_top.v 如果绝对必要 |
5.4 生成 Vivado 项目 - GUI 路径
- 从开始菜单打开 Vivado
- 打开 Tcl 控制台
- 切换到你复制的板卡文件夹:
cd C:/dma/work/squirrel-rtl8125
source vivadogenerateproject.tcl -notrace
正常:
Project opens.
Sources panel is populated.
PCIe IP appears.
No fatal Tcl errors.
错误:
ERROR: file not found
ERROR: part not found
ERROR: IP failed to generate
修复:
· 错误的文件夹
· 缺少 Vivado 设备支持
· 路径错误
· 错误的 Vivado 版本
5.5 生成原厂bit
自定义之前:
source vivado_build.tcl -notrace
正常:
Synthesis completed
Implementation completed
bitstream generation completed
Timing met
错误:
Synthesis failed
Implementation failed
Timing failed
Critical warning about wrong part or constraints
5.6 原厂bit检查
仅当满足以下条件,才算完成:
· 项目已生成
· 原厂bit已 生成
· 时序报告已保存
· 原厂bit已烧录
· bit设备 Windows 检测到原装设备
· PCILeech 可以正常工作
· 冷启动仍然检测到原装设备
这一步最好做,有救砖bit可以跳过。
6. PCIe 核心配置
在 Vivado 中:
6.1 在 Vivado GUI 中打开 PCIe IP
Sources -> find pcie7x0.xci -> double click -> Re-customize IP
如果找不到 PCIe IP,可能是生成的项目或文件夹错误。
6.2 更改身份字段
使用设备数据:
| 字段 | 来源 | 示例 |
|---|---|---|
| 厂商 ID | 硬件 ID / Arbor | 10EC |
| 设备 ID | 硬件 ID / Arbor | 8125 |
| 修订 ID | 硬件 ID / Arbor | 05 |
| 类代码 | 硬件 ID / Arbor | 020000 |
| 子系统厂商 ID | 硬件 ID / Arbor | 10EC |
| 子系统ID | 硬件 ID / Arbor | 0123 |
常见类代码:
| 类 | 含义 |
|---|---|
| 020000 | 以太网控制器 |
| 0C0330 | USB xHCI 控制器 |
| 010601 | SATA AHCI 控制器 |
| 040300 | 高清音频控制器 |
仅仅更改 VID/DID是小白最容易当成bit制作的误区。
6.3 配置 BAR
在 PCIe IP 的 BAR 选项卡中,匹配原设备:
· 已启用或已禁用
· 内存或 I/O
· 32 位或 64 位
· 可预取或不可预取
· 大小
例子:
BAR0: enabled, memory, 32-bit, non-prefetchable, 16KB
BAR1: disabled
BAR2: disabled
BAR3: disabled
BAR4: disabled
BAR5: disabled
不要将 Windows 运行时内存范围复制到bit中。Windows 在启动时分配该地址。
6.4 64 位 BAR 规则
如果 BAR0 是64位:
BAR0 = low 32 bits
BAR1 = high 32 bits
BAR1 不可用。
如果 BAR2 是64位:
BAR2 = low 32 bits
BAR3 = high 32 bits
IP upgrade required
Part not found
Output product generation failed
在 IP 实际重新生成之前不要继续。
6.6 生成文件警告
一些毒教程会教你改:
pcie7x0coretop.v
该文件通常生成在:
可能会被重新生成的输出产品覆盖。
如果必须编辑生成的参数,例如扩展功能指针:
· 记下准确的文件路径
· 首先复制原始文件
· 进行最小化编辑
· 立即重新 生成
· 在bit生成之前确认编辑已保存
· 不要信任来自另一个 Vivado 版本的行号
PS:.gen下的文件不是稳定的手写源代码。Vivado 可以替换。
每次重新生成 IP 输出产品时:
[ ] Check whether generated wrapper edits still exist
[ ] Check whether capability pointer edits still exist
[ ] Check whether BAR/IP parameters still match the donor plan
[ ] Check whether COE files are still attached to the correct BRAM/ROM IP
[ ] Save a git diff or backup patch
不要相信毒教程中说的“编辑第450行”或者哪一行。那个行号属于别人的 Vivado 版本、项目路径和生成的输出。
重新生成后的正常情况:
Your intended generated-file edit still exists.
IP output products are current.
Git diff shows only expected changes.
重新生成后的错误情况:
Your edit disappeared.
Generated file path changed.
Vivado upgraded IP unexpectedly.
COE reverted to old file.
如果你看到错误情况, 在 生成前停止并修复。一个被重新生成的文件悄悄撤销你的修改,是新手容易浪费好几天的白干的原因之一。
6.7 PCIe 核心检查
在以下情况下继续:
· IP GUI 有正确的 ID
· BAR 与设备类型和尺寸匹配
· 64位 BAR 配对正确
· 输出产品已重新生成
· bit设备 Windows 后续读取显示预期标识
7. 影子空间
7.1 影子配置空间的作用
影子配置空间允许 FPGA 从 BRAM 数据中回答选定的 PCIe 配置读取。
可用于:
· 如果子系统区域未由 IP 处理
· 能力结构
· 扩展能力
· 类似供体的配置字节
· DSN 能力字节
不要盲目使用 来覆盖:
· BAR 运行时地址
· BAR 大小行为
· 命令寄存器
· 状态寄存器
· 字段已被 PCIe IP 拥有
PS:底层原理 - 为什么本地 Xilinx 配置空间可被检测Xilinx 7 系列 PCIe IP 可以正常枚举,但其默认配置行为在可写位、status/RW1C 行为、功能布局、BAR 大小响应和枚举副作用与设备不匹配时,仍然看起来像一个通用的 FPGA 端点。Windows 和 PCIe 工具不仅读取 VID/DID; 会遍历功能链,写入控制位,清除状态位,调整 BAR 大小,并期望整个配置空间的内容保持内部一致。
7.2 重要文件
| 文件 | 用途 |
|---|---|
| src/pcileech*fifo.sv | rw[203] 影子读取行为和 rw[206] 影子写入使能 |
| src/pcileechtlps128cfgspace*shadow.sv | 配置 read/write 影子逻辑 |
| ip/pcileech*cfgspace.coe | 影子配置空间数据 |
| ip/pcileechcfgspacewritemask.coe | 写掩码数据 |
| brampciecfgspace | 为配置数据生成 BRAM |
| drompciecfgspace*writemask | 为写掩码生成 ROM |
7.3 启用影子读取数据
打开:
src/pcileech_fifo.sv
查找:
rw[202] <= 1'b1; // CFGTLP PROCESSING ENABLE
rw[203] <= 1'b1; // CFGTLP ZERO DATA
rw[204] <= 1'b1; // CFGTLP FILTER TLP FROM USER
rw[205] <= 1'b1; // PCIE BAR PIO ON-BOARD PROCESSING ENABLE
rw[206] <= 1'b0; // CFGTLP PCIE WRITE ENABLE
对于影子回读:
rw[203] <= 1'b0; // return BRAM data instead of zero data
如果接管点后的配置读取为全零, 先检查这个。
7.4 COE 工作流程概览
你的配置 COE 可以来自:
· Arbor 导出的文本
· TeleScan exported/saved 数据
· 手动记录的设备字段
· HxD 编辑的 binary/hex 数据
· 一个 生成1024个DWORD的 Python 脚本
手动编 CSV 是最差的。这对于小白测试可以接受,但不要假装是专业采集。
优先顺序:
- 可重新打开的 TeleScan 采集加导出的配置数据。
-
保留偏移的 Arbor 导出。 -
在 HxD 中验证的 Binary/hex 导出。 -
从工具数据生成的结构化 offset/DWORD CSV。 -
从截图手动输入的 CSV。
不要盲目附加十六进制行。
正确规则:
config byte offset -> exact COE DWORD index
DWORD index = offset / 4
如果一个偏移量发生变化,整个配置映像就会无效。
你的 COE 过程必须捕捉到:
· 错误的深度
· 错误的字节序
· 缺失的 0x34 功能指针
· 当设备具有扩展功能时,缺少 0x100 扩展功能头
· 标准功能循环
· 扩展功能循环
· 复制的 Windows 运行时 BAR 地址
运行时 BAR 地址在静态配置数据中是无效的。如果 设备管理器 表示 BAR0 在 D0000000-D0003FFF 被分配,那是该次启动的操作系统分配,而不是你应该盲目粘贴到配置空间 COE 的值。
offset,dword
0x000,812510EC
0x004,00100006
0x008,02000005
0x02C,012310EC
0x034,00000040
0x040,48010001
0x100,00030001
然后创建:
C:/dma/tools/makecfgspacecoe.py
python
import csv
import sys
if len(sys.argv) != 3:
print("Usage: python makecfgspacecoe.py configdwords.csv pcileechcfgspace.coe")
sys.exit(1)
csv_path = sys.argv[1]
out_path = sys.argv[2]
dwords = ["00000000"] * 1024
with open(csv_path, newline="") as f:
reader = csv.DictReader(f)
for row in reader:
offset = int(row["offset"], 16)
value = row["dword"].strip().replace("0x", "").replace("0X", "").upper()
if offset % 4 != 0:
raise SystemExit(f"Offset not DWORD aligned: 0x{offset:03X}")
if len(value) != 8:
raise SystemExit(f"DWORD must be 8 hex digits at 0x{offset:03X}: {value}")
index = offset // 4
if index < 0 or index >= 1024:
raise SystemExit(f"Offset out of 4KB config space: 0x{offset:03X}")
dwords[index] = value
checks = {
0x000: "VID/DID",
0x008: "Class/Revision",
0x02C: "Subsystem IDs",
0x034: "Capability Pointer",
0x100: "Extended Capability Header",
}
print("Validation preview:")
for off, name in checks.items():
print(f"0x{off:03X} {name}: {dwords[off // 4]}")
with open(out_path, "w", newline="\n") as f:
f.write("memoryinitializationradix=16;\n")
f.write("memoryinitializationvector=\n")
for i, value in enumerate(dwords):
f.write(value)
f.write(";\n" if i == len(dwords) - 1 else ",\n")
在 PowerShell 中运行:
python C:\dma\tools\makecfgspacecoe.py C:\dma\donor\rtl8125\configdwords.csv C:\dma\donor\rtl8125\pcileechcfgspace.coe
正常输出:
0x000 VID/DID: 812510EC
0x008 Class/Revision: 02000005
0x02C Subsystem IDs: 012310EC
0x034 Capability Pointer: 00000040
0x100 Extended Capability Header: 00030001
错误输出:
0x000 VID/DID: 00000000
0x008 Class/Revision: 00000000
这意味着你的 CSV 不完整或偏移错误。
seen = set()
while cap_ptr:
if cap_ptr in seen:
raise SystemExit(f"Standard capability loop at 0x{cap_ptr:02X}")
if capptr < 0x40 or capptr > 0xFC:
raise SystemExit(f"Suspicious standard capability pointer: 0x{cap_ptr:02X}")
seen.add(cap_ptr)
capid = byteat(cap_ptr)
nxt = byteat(capptr + 1)
print(f"CAP 0x{capptr:02X}: id=0x{capid:02X} next=0x{nxt:02X}")
cap_ptr = nxt
ext_ptr = 0x100
seen = set()
while extptr and extptr < 0x1000:
hdr = u32(ext_ptr)
if hdr == 0:
break
if ext_ptr in seen:
raise SystemExit(f"Extended capability loop at 0x{ext_ptr:03X}")
seen.add(ext_ptr)
ext_id = hdr & 0xFFFF
nxt = (hdr >> 20) & 0xFFF
print(f"EXT 0x{extptr:03X}: id=0x{extid:04X} next=0x{nxt:03X}")
if nxt and (nxt < 0x100 or nxt >= 0x1000 or nxt % 4):
raise SystemExit(f"Bad extended capability next pointer: 0x{nxt:03X}")
ext_ptr = nxt
for off in range(0x10, 0x28, 4):
val = u32(off)
if val & 0xFFFF0000 and not (val & 0x1):
print(f"WARNING: BAR-looking nonzero value at 0x{off:02X}: {dword(off)}")
print(" Make sure this is not a copied Windows runtime BAR address.")
print("COE sanity check completed.")
运行:
python C:\dma\tools\checkcfgspacecoe.py C:\dma\donor\rtl8125\pcileech_cfgspace.coe
正常:
Key offsets print expected values.
Capability chain prints cleanly.
Extended capability chain prints cleanly or stops cleanly.
COE sanity check completed.
错误:
Bad COE depth
Standard capability loop
Bad extended capability next pointer
0x000 VID/DID: 00000000
WARNING: BAR-looking nonzero value
在 Vivado 之前修复 COE。一个损坏的 COE 不会因为你生成了bit就变好。
7.8 HxD 方法
当你有二进制或十六进制转储时,HxD 也很好用。
流程:
-
在 HxD 中打开转储 -
转到偏移 0x00 -
确认 VID/DID 字节 -
确认 revision/class 字节 -
转到偏移 0x08 -
转到偏移 0x2C -
确认子系统字节 -
转到偏移 0x34 -
确认功能指针 -
转到偏移 0x100 -
如果存在,确认扩展功能头
如果HxD偏移量与你的记录不匹配,则你的capture/export不可信。
7.9 在Vivado中替换COE
替换:
ip/pcileech_cfgspace.coe
然后在Vivado中:
-
使用COE找到BRAM IP -
如有需要,重置输出产品 -
生成输出产品 -
确认没有COE文件错误 -
重建
正常:
BRAM output products regenerated successfully.
错误:
cannot open COE file
invalid radix
invalid memoryinitializationvector
修复COE格式。
7.10 影子配置 检查
仅当满足以下条件时,影子配置才完成:
· pcileech_cfgspace.coe由偏移量生成
· 关键偏移 0x00、0x08、0x2C、0x34、0x100 已检查
· 如果使用扩展配置,COE 深度已验证为 1024 个 DWORD
· 字节序已验证
· 标准功能链没有循环
· 扩展功能链没有循环
· 运行时 BAR 地址不会盲目复制
· rw[203] 在 pcileech_fifo.sv 中设置正确
· BRAM 输出产品已重新生成
· Arbor/TeleScan 中的bit设备回读显示预期字节
没有回读 = 没有成功
8. 寄存保护
8.1 写掩码的原因
实际 PCIe 配置空间既不是“全只读”,也不是“全可写”
示例:
· VID/DID 不应可写
· 类代码不应可写
· 命令寄存器必须有可写位
· 状态寄存器有 RW1C 位
· PMCSR 有可写的电源状态位
· MSI address/data 由 Windows 写入
· MSI-X enable/function 掩码由 Windows 写入
· PCIe 设备控制有可写位
如果你的 WriteMask 错误,Windows 可能会枚举设备,但驱动程序安装失败。
| 水平 | 名称 | 含义 | 判定 |
|---|---|---|---|
| 0 | 全部只读 | 不能写入任何内容 | 对真实 Windows 行为不正确。只适合用于验证静态回读。 |
| 1 | 小白测试掩码 | 少数明显字段可写 | 仅链接测试。不要称其为设备正确。 |
| 2 | 类型 0 头掩码 | Command/Status 和基本头行为处理更好 | 适合枚举测试。但对许多驱动仍然不够。 |
| 3 | 支持能力感知的掩码 | PM/MSI/MSI-X/PCIe/AER 在设备能力偏移处处理 | 严重的配置空间行为。 |
| 4 | 驱动绑定掩码 | 掩码行为经过 Windows 驱动写入和bit设备回读优化 | 对顽固驱动必需。 |
警告:
· 命令并非简单的“所有 16 位可写”。
· 状态不是正常可写内存;许多位是 RW1C。
· 功能偏移在不同的设备之间不是固定的。
· MSI、MSI-X、PMCSR、PCIe 设备控制、链路控制和 AER 必须在设备的实际偏移处处理。
8.3 启用影子配置写入
打开:
src/pcileech_fifo.sv
设置:
rw[206] <= 1'b1; // CFGTLP PCIE WRITE ENABLE
再次说明:这是 pcileechfifo.sv,不是 pcileechpciecfga7.sv。
8.4 写入掩码 COE 生成器
小白起点。不是 PCIe 规格模型。
仅第1级。 证明路径,而非设备的正确性。
创建:
C:/dma/tools/makewritemaskcoe.py
python
import sys
if len(sys.argv) != 2:
print("Usage: python makewritemaskcoe.py pcileechcfgspacewritemask.coe")
sys.exit(1)
out_path = sys.argv[1]
mask = ["00000000"] * 1024
Type 0 header basics.
mask[0x004 // 4] = "0000FFFF" # Command/Status beginner mask. Refine per donor.
BARs: prefer PCIe IP ownership. If shadow owns BARs, do not blindly enable all.
Leave 0x10-0x24 read-only here unless you know your shadow path owns sizing.
Interrupt Line at 0x3C low byte can be writable on many devices.
mask[0x03C // 4] = "000000FF"
Example capability areas. Refine after parsing donor capabilities.
PMCSR usually sits inside PM capability, not fixed for every donor.
MSI/MSI-X fields are capability-offset dependent.
with open(out_path, "w", newline="\n") as f:
f.write("memoryinitializationradix=16;\n")
f.write("memoryinitializationvector=\n")
for i, value in enumerate(mask):
f.write(value)
f.write(";\n" if i == len(mask) - 1 else ",\n")
print("Generated:", out_path)
print("Check 0x000:", mask[0x000 // 4], "(VID/DID should be read-only)")
print("Check 0x004:", mask[0x004 // 4], "(Command/Status beginner writable bits)")
print("Check 0x008:", mask[0x008 // 4], "(Class/Revision should be read-only)")
运行:
python C:\dma\tools\makewritemaskcoe.py C:\dma\donor\rtl8125\pcileechcfgspacewritemask.coe
8.5 更好的写入掩码必须处理的内容
简单生成器只是一个起点。
一个高级的 WriteMask 必须考虑:
| 区域 | 行为 |
|---|---|
| 0x00 VID/DID | 只读 |
| 0x04 Command/Status | 命令可写位,状态 RW1C |
| 0x08 Class/Revision | 只读 |
| 0x10-0x24 BARs | 通常让 PCIe IP 处理大小 |
| 0x2C 子系统 ID | 通常只读 |
| PM 功能 | PMCSR 可写位 |
| MSI-X 功能 | MSI-X enable/function 掩码可写 |
| PCIe 功能 | 设备控制和链路控制可写位 |
| AER | 错误状态 RW1C, mask/control 可写 |
不要假装简单掩码是完美的。测试。
8.6 能力偏移量不是固定的
不要写假设以下情况的掩码:
PM is always at 0x40
MSI is always at 0x50
PCIe capability is always at 0x60
MSI-X is always at 0x70
该布局可能与某个教程匹配,但在你的设备上失败。
使用设备的能力链:
0x34 -> first capability -> next -> next -> 0x00
然后在实际偏移处应用掩码。这就是复制指南与理解设备之间的区别。
8.7 替换 WriteMask COE
替换:
ip/pcileechcfgspacewritemask.coe
然后在 Vivado 中重新生成 ROM/IP。
正常:
drompciecfgspace_writemask output products generated.
错误:
memory vector width mismatch
COE parse error
file not found
测试:
| 测试 | 预期 |
|---|---|
| 尝试写入 VID/DID | 值必须保持不变 |
| 切换允许的命令位 | 允许的位应该改变 |
| 写入不支持的命令位 | 不支持的位不应保持 |
| 清除状态 RW1C 位 | 状态行为应合理 |
| 如果暴露,启用 MSI/MSI-X | 控制位应反映 Windows 配置 |
坏结果:
VID/DID changes after write
Command register never changes
MSI Enable cannot be set
Status bits never clear
Device disappears after config write
那些是 WriteMask/shadow-write 的问题,而不是“只是再次更改 VID/DID”的问题。
8.9 掩码 检查
当满足以下条件时调用 WriteMask 完成:
· rw[206] 在 pcileech_fifo.sv 中已启用
· WriteMask COE 存在
· ROM 输出产品已重新生成
· VID/DID 保持只读状态
· 可写命令位可以更改
· Status/RW1C 行为合理
· 如果公开,PMCSR/MSI/MSI-X/PCIe 控制字段在实际设备偏移处处理
· 驱动程序相关的功能写入按计划工作
· 经过受控写入后,Windows bit设备保持稳定
9. BAR 实现
9.1 BAR 不仅仅是 Vivado 中的大小
Vivado BAR 配置告诉 Windows 设备 请求了哪些资源。
BAR 的实现告诉 Windows 当软件读取或写入这些资源时会发生什么。
那些是不同的事情。
如果 Windows 可以分配 BAR 但你的 FPGA 不返回任何内容,驱动程序仍然可能失败。
9.2 BAR 类型
每个 BAR 的记录:
· enabled/disabled
· 内存或 I/O
· 32 位或 64 位
· 可预取
· 大小
· Windows 资源范围
· MSI-X table/PBA 引用(如果有)
不要将 I/O BAR 行为实现为内存 BAR 行为。
| 水平 | 名称 | 含义 |
|---|---|---|
| 0 | 无 | BAR 存在,但自定义响应没有意义 |
| 1 | Zero/static 4KB | 简单的 Zero4K 风格 BRAM |
| 2 | 类似静态设备的表格 | 返回重要偏移量的 captured/planned 值 |
| 3 | 有状态模型 | 处理写入、状态位、复位位、中断、队列 |
9.4 BAR 回读工具
使用:
· 如果可用,Arbor BAR/resource 读取视图
· 如果可用,TeleScan PE BAR/memory 读取视图
· 如果能够读取 MMIO,使用厂商诊断工具
· 自己的 Windows 测试 driver/tool 用于实验室回读
BAR 验证有不同等级:
| 水平 | 你已经证明的 | 你没有证明的 |
|---|---|---|
| 0 | 设备管理器 显示资源 | 仅分配 BAR。不是 BAR 响应。 |
| 1 | 第一个 DWORD 可以读取 | 一条读取路径可用。不是整个 BAR。 |
| 2 | 前 4KB 可以读取 | 静态 4KB 响应可用。不是更大的 BAR。 |
| 3 | 不支持的偏移是安全的 | Decode/default 路径更安全。不是有状态行为。 |
| 4 | 写入和回读有效 | 一些寄存器存储行为可用。不是完整设备行为。 |
| 5 | 有状态寄存器行为 | 已为测试路径建模 Reset/status/interrupt-like 行为。 |
资源选项卡中有 BAR 并不是 BAR 模拟。 只是意味着 Windows 分配了地址范围。
最小 BAR 测试:
| 测试 | 预期 |
|---|---|
| 读取 BAR 首个 DWORD | 完成并保持稳定值 |
| 读取前4KB | 无超时,无崩溃 |
| 读取不支持的偏移 | 安全默认完成 |
| 读取较大BAR的高偏移 | 如果BAR的大小超过4KB,必须能工作 |
| 写入预期的控制寄存器 | 预期状态变化或安全处理 |
错误的BAR输出:
read timeout
all 00000000 when not expected
all FFFFFFFF
random changing values
target freezes
driver fails immediately after BAR read
这意味着BAR的响应路径可能有问题。
9.5 动态BAR响应
静态case表对于第一次读回测试是可以的。但不是高级的BAR仿真。
一个真正的Windows驱动程序不仅仅从BAR空间读取固定的ID。 会写入控制位,按门铃,轮询状态,发布描述符,屏蔽中断,重置引擎,并期望状态向前推进。如果你的BAR模块始终返回相同的硬编码DWORD,充其量也只是2级。
9.5.1 不要混淆BAR邮箱和PCIe VDM
很重要:
| 术语 | 真正的含义 |
|---|---|
| PCIe VDM | 厂商定义的消息TLP。这是一种PCIe消息事务类型,而不是普通的BAR MMIO寄存器写入。 |
| BAR邮箱 | 供应商定义的 command/status 协议,通过 MMIO 寄存器在 BAR 内实现。这是大多数 Windows 驱动程序实际上使用的方式。 |
| 门铃 | 一个 BAR 写操作,用于告诉设备使用描述符、处理命令或更新状态。 |
| 完成 | PCIe 对读取请求的响应。即使寄存器不受支持,BAR 读取也必须完成。 |
如果你需要实际的 PCIe VDM TLP 支持,你必须在 TLP RX 路径中解码消息 TLP。不要假装 BAR 写操作就是 PCIe VDM。在本指南中,实际bit设备是 BAR 邮箱,因为这是大多数设备驱动首先访问的。
保持各层分离:
Config space:
src/pcileechtlps128cfgspace_shadow.sv
- CfgRd0 / CfgWr0
- identity, capabilities, WriteMask
BAR space:
src/pcileechtlps128bar_controller.sv
- MemRd / MemWr
- mailbox, doorbell, status, stream counters
Interrupt/control context:
src/pcileechpciecfg_a7.sv
- MSI/MSI-X enable state
- cfg_interrupt path
影子配置空间不应成为你的 BAR 状态机。保持 独立。配置证明你是谁。BAR 行为证明你做什么。
9.5.2 动态 BAR 邮箱的寄存器映射
此示例模拟了一个小型供应商定义的 BAR 协议。 将寄存器含义替换为你的设备的实际 BAR 转储和驱动跟踪。
| 偏移量 | 名称 | 访问 | 行为 |
|---|---|---|---|
| 0x000 | DEVICE*SIGNATURE | RO | 稳定的类似设备的特征 |
| 0x004 | VERSION*CAPS | RO | 版本和功能位 |
| 0x008 | STATUS | RO | 就绪、忙碌、IRQ 待处理、流模式、错误 |
| 0x00C | CONTROL | RW | 启用、软件复位、IRQ 确认、环回、流模式 |
| 0x010 | DOORBELL | WO | 开始命令处理 |
| 0x014 | HOSTMSGLO | RW | 主机命令有效载荷低 DWORD |
| 0x018 | HOSTMSGHI | RW | 主机命令有效载荷高 DWORD |
| 0x01C | HOSTMSGLEN | RW | 有效载荷长度或描述符计数 |
| 0x020 | RESP*LO | RO | 设备响应低DWORD |
| 0x024 | RESP*HI | RO | 设备响应高DWORD |
| 0x028 | STREAM*COUNTER | RO | 启用时前进 |
| 0x02C | PACKET*COUNTER | RO | 处理完类似网络门铃后递增 |
| 0x030 | AUDIOFRAMECOUNTER | RO | 流模式下递增 |
| 0x034 | IRQ*STATUS | RW1C 风格 | 待处理事件位 |
| 0x038 | IRQ*MASK | RW | 中断掩码 |
| 0x03C | ERROR_STATUS | RW1C 风格 | 粘性错误位 |
基本设备行为:写入改变状态,读取反映该状态,状态移动,不支持的偏移安全完成。
9.5.3 即插即用SystemVerilog BAR 状态机
与示例 BAR 实现中的端口形状相同 src/pcileechtlps128bar_controller.sv。
将其用作选定 BAR 实现的替代方案:
pcileechbarimpldynamicmailbox i_bar0(
.rst ( rst ),
.clk ( clk ),
.wraddr ( wraddr ),
.wrbe ( wrbe ),
.wrdata ( wrdata ),
.wrvalid ( wrvalid && wr_bar[0] ),
.rdreqctx ( rdreqctx ),
.rdreqaddr ( rdreqaddr ),
.rdreqvalid ( rdreqvalid && rdreqbar[0] ),
.rdrspctx ( barrspctx[0] ),
.rdrspdata ( barrspdata[0] ),
.rdrspvalid ( barrspvalid[0] )
);
module pcileechbarimpldynamicmailbox(
input rst,
input clk,
input [31:0] wr_addr,
input [3:0] wr_be,
input [31:0] wr_data,
input wr_valid,
input [87:0] rdreqctx,
input [31:0] rdreqaddr,
input rdreqvalid,
output bit [87:0] rdrspctx,
output bit [31:0] rdrspdata,
output bit rdrspvalid
);
localparam [31:0] DEVICESIGNATURERESET = 32'h50414D44; // bytes 44 4D 41 50 = "DMAP"
localparam [31:0] VERSIONCAPSRESET = 32'h0001_0007;
localparam [11:0] REGDEVICESIGNATURE = 12'h000;
localparam [11:0] REGVERSIONCAPS = 12'h004;
localparam [11:0] REG_STATUS = 12'h008;
localparam [11:0] REG_CONTROL = 12'h00C;
localparam [11:0] REG_DOORBELL = 12'h010;
localparam [11:0] REGHOSTMSG_LO = 12'h014;
localparam [11:0] REGHOSTMSG_HI = 12'h018;
localparam [11:0] REGHOSTMSG_LEN = 12'h01C;
localparam [11:0] REGRESPLO = 12'h020;
localparam [11:0] REGRESPHI = 12'h024;
localparam [11:0] REGSTREAMCOUNTER = 12'h028;
localparam [11:0] REGAUDIOFRAME_COUNTER= 12'h030;
localparam [11:0] REGPACKETCOUNTER = 12'h02C;
localparam [11:0] REGIRQSTATUS = 12'h034;
localparam [11:0] REGIRQMASK = 12'h038;
localparam [11:0] REGERRORSTATUS = 12'h03C;
localparam [7:0] PROC_IDLE = 8'd0;
localparam [7:0] PROC_SHORT = 8'd8;
localparam [7:0] PROC_MEDIUM = 8'd32;
localparam [7:0] PROC_LONG = 8'd96;
bit [31:0] control_reg;
bit [31:0] hostmsglo;
bit [31:0] hostmsghi;
bit [31:0] hostmsglen;
bit [31:0] response_lo;
bit [31:0] response_hi;
bit [31:0] stream_counter;
bit [31:0] packet_counter;
bit [31:0] audioframecounter;
bit [31:0] irq_status;
bit [31:0] irq_mask;
bit [31:0] error_status;
bit [31:0] last_doorbell;
bit [7:0] process_timer;
bit [7:0] reset_timer;
bit [87:0] rdreqctx_d;
bit [31:0] rdreqaddr_d;
bit rdreqvalid_d;
wire controlenable = controlreg[0];
wire controlloopback = controlreg[3];
wire controlstreammode = control_reg[4];
wire enginebusy = (processtimer != PROC_IDLE);
wire resetbusy = (resettimer != 8'd0);
wire irqpending = ((irqstatus & irq_mask) != 32'd0);
makestatus[5] = resetbusy; // reset in progress
makestatus[6] = (errorstatus != 0); // sticky error present
makestatus[15:8] = processtimer;
makestatus[31:16] = packetcounter[15:0];
end
endfunction
function automatic [31:0] read_register;
input [11:0] offset;
begin
case (offset)
REGDEVICESIGNATURE: readregister = DEVICESIGNATURE_RESET;
REGVERSIONCAPS: readregister = VERSIONCAPS_RESET;
REGSTATUS: readregister = make_status();
REGCONTROL: readregister = control_reg;
REGDOORBELL: readregister = last_doorbell;
REGHOSTMSGLO: readregister = hostmsglo;
REGHOSTMSGHI: readregister = hostmsghi;
REGHOSTMSGLEN: readregister = hostmsglen;
REGRESPLO: readregister = responselo;
REGRESPHI: readregister = responsehi;
REGSTREAMCOUNTER: readregister = streamcounter;
REGPACKETCOUNTER: readregister = packetcounter;
REGAUDIOFRAMECOUNTER: readregister = audioframecounter;
REGIRQSTATUS: readregister = irqstatus;
REGIRQMASK: readregister = irqmask;
REGERRORSTATUS: readregister = errorstatus;
default: read_register = 32'h00000000;
endcase
end
endfunction
task automatic start_processing;
input [31:0] doorbell_value;
begin
lastdoorbell <= doorbellvalue;
if (!control_enable) begin
errorstatus <= errorstatus | 32'h00000001;
irqstatus <= irqstatus | 32'h00000004;
processtimer <= PROCIDLE;
end else if (engine_busy) begin
errorstatus <= errorstatus | 32'h00000002;
irqstatus <= irqstatus | 32'h00000004;
end else begin
if (hostmsglen[15:0] <= 16'd64) begin
processtimer <= PROCSHORT;
end else if (hostmsglen[15:0] <= 16'd1500) begin
processtimer <= PROCMEDIUM;
end else begin
processtimer <= PROCLONG;
end
responselo <= hostmsglo ^ doorbellvalue ^ 32'hA5A5_5A5A;
responsehi <= hostmsghi + hostmsglen + 32'h00001001;
end
end
endtask
packetcounter <= packetcounter + 1'b1;
if (controlstreammode) begin
audioframecounter <= audioframecounter + hostmsglen[15:0];
responselo <= responselo + 32'h0000_0040;
responsehi <= responsehi ^ 32'h55AA_00FF;
end else begin
responselo <= responselo + 32'h0000_0001;
responsehi <= responsehi + packetcounter + 32'h00000001;
end
if (control_loopback) begin
responselo <= hostmsg_lo;
responsehi <= hostmsg_hi;
end
irqstatus <= irqstatus | 32'h00000001;
end
endtask
always @ (posedge clk) begin
if (rst) begin
control_reg <= 32'd0;
hostmsglo <= 32'd0;
hostmsghi <= 32'd0;
hostmsglen <= 32'd0;
response_lo <= 32'd0;
response_hi <= 32'd0;
stream_counter <= 32'd0;
packet_counter <= 32'd0;
audioframecounter <= 32'd0;
irq_status <= 32'd0;
irq_mask <= 32'd0;
error_status <= 32'd0;
last_doorbell <= 32'd0;
processtimer <= PROCIDLE;
reset_timer <= 8'd0;
rdreqctx_d <= 88'd0;
rdreqaddr_d <= 32'd0;
rdreqvalid_d <= 1'b0;
rdrspctx <= 88'd0;
rdrspdata <= 32'd0;
rdrspvalid <= 1'b0;
end else begin
rdreqctxd <= rdreq_ctx;
rdreqaddrd <= rdreq_addr;
rdreqvalidd <= rdreq_valid;
rdrspctx <= rdreqctx_d;
rdrspdata <= readregister(rdreqaddrd[11:0]);
rdrspvalid <= rdreqvalid_d;
if (controlenable && !resetbusy) begin
streamcounter <= streamcounter + 1'b1;
end
if (reset_timer != 8'd0) begin
resettimer <= resettimer - 1'b1;
if (reset_timer == 8'd1) begin
control_reg[1] <= 1'b0;
processtimer <= PROCIDLE;
response_lo <= 32'd0;
response_hi <= 32'd0;
irqstatus <= irqstatus | 32'h00000002;
error_status <= 32'd0;
end
end
if (processtimer != PROCIDLE) begin
processtimer <= processtimer - 1'b1;
if (process_timer == 8'd1) begin
complete_processing();
end
end
if (wr_valid) begin
case (wr_addr[11:0])
REG_CONTROL: begin
controlreg <= applywstrb(controlreg, wrdata, wr_be);
if (wrbe[0] && wrdata[1]) begin
reset_timer <= 8'd32;
control_reg[1] <= 1'b1;
end
if (wrbe[0] && wrdata[2]) begin
irq_status <= 32'd0;
end
end
REG_DOORBELL: begin
startprocessing(wrdata);
end
REGHOSTMSG_LO: begin
hostmsglo <= applywstrb(hostmsglo, wrdata, wr_be);
end
REGHOSTMSG_HI: begin
hostmsghi <= applywstrb(hostmsghi, wrdata, wr_be);
end
REGHOSTMSG_LEN: begin
hostmsglen <= applywstrb(hostmsglen, wrdata, wr_be);
end
REGIRQSTATUS: begin
irqstatus <= irqstatus & ~applywstrb(32'd0, wrdata, wr_be);
end
REGIRQMASK: begin
irqmask <= applywstrb(irqmask, wrdata, wr_be);
end
REGERRORSTATUS: begin
errorstatus <= errorstatus & ~applywstrb(32'd0, wrdata, wr_be);
end
default: begin
errorstatus <= errorstatus | 32'h80000000;
end
endcase
end
end
end
endmodule
仍然使用 case,但仅作为地址解码器。返回的数据不是静态的。 取决于主机写入、定时器、控制位、复位状态、中断状态、流模式以及之前的门铃。
9.5.4 端到端 MRd 到 CplD 闭合
实际驱动程序的握手走这条路径。如果你不能走通,你还没有调试 BAR 行为。
Windows kernel driver
|
| Memory Read TLP (MRd)
| - requester ID
| - tag
| - byte enables
| - BAR hit address
v
Motherboard Root Complex
|
v
Xilinx 7-Series PCIe Hard IP
|
| RX AXI-Stream from PCIe core
| src/pcileechpciea7.sv
|
| .maxisrxtdata -> tlprx.data
| .maxisrxtkeep -> tlprx.keep
| .maxisrxtlast -> tlprx.last
| .maxisrxtvalid -> tlprx.valid
v
PCILeech TLP router
|
| src/pcileechpcietlp_a7.sv
| pcileechtlps128barcontroller ipcileechtlps128bar_controller
v
BAR read engine
|
| src/pcileechtlps128bar_controller.sv
| pcileechtlps128bar_rdengine
|
| rdreqvalid = 1
| rdreqbar[0] = 1
| rdreqaddr = decoded BAR offset
| rdreqctx carries requester/tag/length/low address metadata
v
Dynamic BAR model
|
| pcileechbarimpldynamicmailbox
| rdreqaddr[11:0] == REGDEVICESIGNATURE
| readregister(REGDEVICE_SIGNATURE) = 32'h50414D44
| bytes on the bus = 44 4D 41 50 = "DMAP"
v
Latency shaping
|
| pcileechbarlatencypipe #(.LATENCYCLKS(16))
| makes the completion timing less toy-like and easier to tune
v
BAR read completion engine
|
| src/pcileechtlps128bar_controller.sv
| rdrspctx / rdrspdata / rdrspvalid
| pcileechtlps128bar_rdengine preserves original requester tag
v
PCILeech TX mux
|
| src/pcileechpcietlp_a7.sv
| tlpsbarrsp -> pcileechtlps128sinkmux1 -> tlpstx
v
Xilinx PCIe TX path
|
| Completion with Data (CplD TLP)
| original tag returned
| payload DWORD returned
v
Windows kernel driver receives the MMIO read result
关键是标签闭合。驱动程序可以发出多个未完成的读取。完成必须将正确的标签和 求者上下文返回。这就是 rdreqctx 存在的原因。不要在 BAR 响应者中丢弃。
对于REGDEVICESIGNATURE,状态机触发是稳定且精确的:
pcileechbarlatency_pipe #(
.LATENCY_CLKS(16)
) ibar0latency (
.rst ( rst ),
.clk ( clk ),
.rdreqctx ( bar0rawrsp_ctx ),
.rdreqdata ( bar0rawrsp_data ),
.rdreqvalid ( bar0rawrsp_valid ),
.rdrspctx ( barrspctx[0] ),
.rdrspdata ( barrspdata[0] ),
.rdrspvalid ( barrspvalid[0] )
);
然后pcileechtlps128bar_rdengine执行数据包关闭工作:
rdrspctx -> requester ID, tag, byte count, lower address
rdrspdata -> byte-swapped into CplD payload ordering
rdrspvalid -> emits the completion beat
tlpsout -> forwarded as tlpsbar_rsp
在当前上行风格的代码中,BAR 响应路径像这样为 TX 多路复用器提供数据:
// File: src/pcileechpcietlp_a7.sv
pcileechtlps128barcontroller ipcileechtlps128bar_controller(
.rst ( rst ),
.clk ( clk ),
.baren ( dshadow2fifo.baren ),
.tlpsin ( tlpsrx ),
.tlpsout ( tlpsbar_rsp.source )
);
pcileechtlps128sinkmux1 ipcileechtlps128sink_mux1(
.rst ( rst ),
.clk ( clk ),
.tlpsout ( tlpstx ),
.tlpsin1 ( tlpscfg_rsp.sink ),
.tlpsin2 ( tlpsbar_rsp.sink ),
.tlpsin3 ( tlpsrx_fifo.sink ),
.tlpsin4 ( tlpsstatic )
);
这是闭环:
MRd tag enters on RX.
rdreqctx preserves the tag.
BAR model chooses the payload.
latency pipe shapes the response timing.
rdengine emits CplD with the original tag.
pcileechpcietlp_a7.sv muxes it back to the PCIe TX path.
driver receives the exact response for its original outstanding read.
如果标签错误,驱动程序可能会收到 认为未完成的读取操作的完成信号。如果rdrspvalid从不触发,根复合体将等待直到超时。如果在驱动程序期望状态变化时有效负载是静态的,握手将在软件层失败。
5. Write 0x00C CONTROL bit0 = 1.
6. Write 0x010 DOORBELL.
7. Poll 0x008 STATUS until busy clears.
8. Read RESPLO / RESPHI.
9. Read PACKET_COUNTER.
10. Write 0x034 IRQ_STATUS with the pending bits to clear them.
良好表现:
STATUS busy bit toggles.
RESPLO/RESPHI change after doorbell.
PACKET_COUNTER increments.
IRQ_STATUS sets after command completion.
RW1C-style clear works for IRQSTATUS and ERRORSTATUS.
Unsupported writes do not crash the target.
不良表现:
STATUS never changes.
DOORBELL write has no effect.
Read completion times out.
RESP values are the same before and after command processing.
IRQ_STATUS cannot clear.
BAR read freezes the machine.
如果你的驱动程序发送了一个 BAR 握手并返回了一个固定的静态表,则驱动程序失败是铁定的。 生成状态机或记录缺失的行为。
9.6 BAR 检查
在以下情况下调用 BAR 工作完成:
· Windows 资源显示预期的 BAR 范围
· Arbor/TeleScan 可以读取测试过的 BAR 偏移
· 第一个 DWORD 读取稳定
· 使用 Zero4K 时第一个 4KB 读取稳定
· 不支持的偏移不会导致机器崩溃
· 对需要的寄存器测试 write/readback 行为
· 高偏移量已针对大于4KB的BAR进行测试
· 如果暴露MSI-X,则处理MSI-X table/PBA区域
· BAR验证级别已记录
| ID | 名称 |
|---|---|
| 0x01 | 电源管理 |
| 0x05 | MSI |
| 0x10 | PCI Express |
| 0x11 | MSI-X |
| 0x09 | 厂商特定 |
错误链:
0x40 -> 0x60 -> 0x40
那会形成循环。Windows 上跑不通。
10.2 扩展能力链
扩展能力起始于:
0x100
通用ID:
| ID | 名称 |
|---|---|
| 0x0001 | 高级错误报告 |
| 0x0003 | 设备序列号 |
| 0x0018 | 延迟容忍报告 |
| 0x001E | L1 电源管理子状态 |
如果你的影子 COE 仅覆盖 256 字节,则缺少扩展功能。这不是完整的设备克隆。
10.3 DSN
在 PCILeech 7 系列风格项目中,DSN 通常在此处驱动:
src/pcileechpciecfg_a7.sv
寻找:
rw[127:64] <= 64'h0000000101000A35; // cfg_dsn
仅当设备具有 DSN 并且你已验证字节顺序时,将其设置为设备 DSN。
还要确保影子扩展能力字节一致。如果 cfg_dsn显示一个值,而配置空间 DSN 显示另一个值,则你创建了一个矛盾。
10.4 MSI 和 MSI-X
MSI:
· 配置空间字段由 Windows 写入
· WriteMask 必须允许正确的字段
· 如果 MSI 启用无法保持,驱动程序可能会失败
MSI-X:
· 功能存在于配置空间中
· MSI-X 表存在于 BAR 内存中
· PBA 存在于 BAR 内存中
· Windows 写入表项
如果你暴露 MSI-X 但没有实现 table/PBA BAR 区域, 预期驱动程序会失败。
10.5 PMCSR
电源管理不是装饰。
PMCSR 可以影响:
· D0/D3 状态
· PME 启用
· PME 状态
· 驱动程序 resume/start 行为
静态配置字节没有可写的 PMCSR 行为,也足以枚举,但会导致驱动程序启动失败。
10.6 能力检查
当能力准备就绪时:
· 标准链在 Arbor/TeleScan 中干净地遍历
· 如果存在,扩展链也干净地遍历
· DSN 持续地为 present/absent
· 如果暴露,MSI-X table/PBA BAR 区域被处理
· MSI/MSI-X 字段与供体计划匹配
· 考虑 PMCSR 可写行为
· Windows bit设备回读与计划匹配
11. TLP 响应实现
11.1 问题
配置空间让你被枚举。
BAR/TLP 行为让你通过真实软件。
驱动程序读取 BAR 寄存器以判断设备是否真实。如果你的 BAR 逻辑返回无效数据、零值、全F或没有完成,你将看到代码10、驱动程序启动失败或崩溃。
11.2 BAR 路由所在位置
常见的上游样式位置:
src/pcileechtlps128bar_controller.sv
这将 BAR reads/writes 路由到响应逻辑。
不要粘贴随机代码片段,除非与当前模块端口和信号匹配。
11.3 伪代码陷阱
许多网上现存的代码片段使用假的或旧的信号名称:
baseaddressregister
drdreqaddr
如果这些信号在你的文件中不存在,代码片段就不能直接编译。
优秀的教程代码必须包括:
· 完整的模块上下文
· 正确的端口
· 正确的 BAR 索引
· 正确的偏移计算
· 正确的延迟
· 正确的完成路径
· 成功的 生成证明
11.4 有状态行为示例
真实驱动程序可能期望:
· 复位位自动清除
· 复位后状态位变化
· 中断屏蔽寄存器存储写入值
· EEPROM/NVM 窗口返回特定值
· MAC 地址出现在特定设备位置
· MSI-X 表写入保持有效
· queue/doorbell 寄存器行为安全
如果你的供体驱动程序读取这些并得到无意义值,Code 10 是正常的。
11.5 驱动绑定调试
如果驱动绑定失败
检查:
· BAR 回读
· MSI/MSI-X
· PMCSR
· EEPROM/NVM 窗口
· 重置位
· 状态位
· 中断掩码
· 设备管理器 事件
· Windows 如有需要,事件查看器
静态 BAR 数据只是工作的一半。时序也很重要。
11.6 完成延迟调节
真正的设备不会永远用同样看起来像玩具的模式来响应每一个 MMIO 读取请求。有些读取请求返回很快,有些取决于内部状态,有些寄存器在复位后才稳定。如果你的响应器总是返回同样的 DWORD 而没有可信的延迟,你不是在模拟设备;你只是证明完成路径没有死掉。
使用真正的上游风格文件:
| 文件 | 需要接触的内容 |
|---|---|
| src/pcileechpciea7.sv | Xilinx 7 系列 PCIe 包装器。核心发出的 RX 通常作为 maxisrxtdata 接入 tlprx.data。 |
| src/pcileechpcietlp*a7.sv | TLP 管道处理 RX/TX。 |
| src/pcileechtlps128bar*controller.sv | BAR 路由和 BAR 响应器选择。这通常是进行 BAR 行为工作的地方。 |
| src/pcileechpciecfg_a7.sv | Config/control 上下文和 PCIe 中断接口信号。 |
来自 pcileechtlps128bar_controller.sv`的关键规则:用户 BAR 响应器核心必须保持已知的读取延迟。除非你理解完成多路复用和时序,否则不要让 BAR0 延迟两个时钟周期,BAR1 随机,BAR2 即时。
添加受控延迟的一种干净方法是在你的 BAR 逻辑产生 DWORD 后,将响应 context/data 流水线处理:
// Drop this helper near the BAR implementation area in:
// src/pcileechtlps128bar_controller.sv
//
// Keep LATENCY_CLKS consistent with the rest of your BAR responders.
// 16 clocks at 62.5 MHz is about 256 ns. Microsecond-level behavior
// needs tens or hundreds of clocks depending on your PCIe user clock.
module pcileechbarlatency_pipe #(
parameter integer LATENCY_CLKS = 16
)(
input rst,
input clk,
input [87:0] rdreqctx,
input [31:0] rdreqdata,
input rdreqvalid,
output bit [87:0] rdrspctx,
output bit [31:0] rdrspdata,
output bit rdrspvalid
);
bit [87:0] ctxpipe [0:LATENCYCLKS-1];
bit [31:0] datapipe [0:LATENCYCLKS-1];
bit validpipe [0:LATENCYCLKS-1];
integer i;
always @ (posedge clk) begin
if (rst) begin
for (i = 0; i < LATENCY_CLKS; i = i + 1) begin
ctx_pipe[i] <= 88'd0;
data_pipe[i] <= 32'd0;
valid_pipe[i] <= 1'b0;
end
end else begin
validpipe[0] <= rdreq_valid;
ctxpipe[0] <= rdreq_ctx;
datapipe[0] <= rdreq_data;
for (i = 1; i < LATENCY_CLKS; i = i + 1) begin
ctxpipe[i] <= ctxpipe[i-1];
datapipe[i] <= datapipe[i-1];
validpipe[i] <= validpipe[i-1];
end
end
end
always @ (*) begin
rdrspctx = ctxpipe[LATENCYCLKS-1];
rdrspdata = datapipe[LATENCYCLKS-1];
rdrspvalid = validpipe[LATENCYCLKS-1];
end
endmodule
在真实的 BAR 实现内部使用,而不是作为随机的顶层补丁:
wire [87:0] rawrspctx;
wire [31:0] rawrspdata;
wire rawrspvalid;
// Your donor-specific register decode produces rawrsp* first.
// Then the latency pipe shapes the external completion timing.
pcileechbarlatency_pipe #(
.LATENCY_CLKS(16)
) ibar0latency (
.rst ( rst ),
.clk ( clk ),
.rdreqctx ( rawrspctx ),
.rdreqdata ( rawrspdata ),
.rdreqvalid ( rawrspvalid ),
.rdrspctx ( rdrspctx ),
.rdrspdata ( rdrspdata ),
.rdrspvalid ( rdrspvalid )
);
如果你将其添加到一个暴露的 BAR, 规范其他暴露的响应器,或者证明 从不参与同一路径。现有示例的核心通常是双时钟响应器;混合具有不同未知延迟的活动响应器是一个调试陷阱。
首先使用普通的延迟类别:
| 寄存器类型 | 实用实验室延迟模型 |
|---|---|
| 简单的 status/capability 读取 | 固定短延迟 |
| reset/status 转换 | 延迟直到你的复位状态机完成 |
| EEPROM/NVM-like 窗口 | 更长的固定延迟或分阶段准备位 |
| queue/doorbell 寄存器 | 写入存储状态,回读遵循源行为 |
不要盲目地向配置读取添加随机抖动。如果主机在早期枚举期间调整 BAR 大小、检查功能或清除状态位,不一致的时序可能会将普通错误变成 POST 挂起。
11.7 中断活动心跳
中断不是装饰。如果设备驱动程序期望 MSI/MSI-X 活动,而你的bit从未驱动中断路径,则驱动绑定在正常工作负载下仍可能失败或看起来无响应。
不能替代真实设备逻辑。将其用作 PCIe 中断路径、Windows 驱动事件路径和 config/MSI 接线的实验室心跳。
不要混淆两个 rw[206] 位置src/pcileech_fifo.sv使用 rw[206] 来进行配置影子写使能。src/pcileech_pcie_cfg_a7.sv也有一个本地 rw[206],但在那里 驱动 ctx.cfg_interrupt。相同的索引,不同的模块上下文。编辑错误的那个就是旧教程制作假修复的方法。
将此补丁合并到 src/pcileechpciecfg_a7.sv的现有更新路径中。不要创建多个总是驱动相同 rw[]位的 always 块。
// Lab interrupt heartbeat for MSI path validation.
// File: src/pcileechpciecfg_a7.sv
//
// Integrate into the existing rw[] update path. Do not double-drive
// rw[205], rw[206], rw[207], rw[199:192], or rw[204:200].
localparam integer IRQHEARTBEATCLKS = 32'd6250000; // ~100 ms at 62.5 MHz
bit [31:0] irqheartbeatctr;
bit [3:0] irqpulsectr;
always @ (posedge clk) begin
if (rst) begin
irqheartbeatctr <= 32'd0;
irqpulsectr <= 4'd0;
rw[199:192] <= 8'h00; // cfginterruptdi
rw[204:200] <= 5'd0; // cfgpciecapinterrupt_msgnum
rw[205] <= 1'b0; // cfginterruptassert
rw[206] <= 1'b0; // cfg_interrupt
rw[207] <= 1'b0; // cfginterruptstat
end else begin
rw[205] <= 1'b0;
rw[206] <= 1'b0;
rw[207] <= 1'b0;
if (ctx.cfginterruptmsienable && ctx.cfginterruptrdy) begin
if (irqheartbeatctr == IRQHEARTBEATCLKS) begin
irqheartbeatctr <= 32'd0;
irqpulsectr <= 4'd4;
end else begin
irqheartbeatctr <= irqheartbeatctr + 1'b1;
end
if (irqpulsectr != 0) begin
irqpulsectr <= irqpulsectr - 1'b1;
rw[199:192] <= 8'h00; // MSI data/vector input
rw[204:200] <= 5'd0; // interrupt message number
rw[206] <= 1'b1; // one-cycle cfg_interrupt pulse
end
end else begin
irqheartbeatctr <= 32'd0;
irqpulsectr <= 4'd0;
end
end
end
重点:
· 这验证了中断路径; 并不会使你的 BAR 模型正确。
· MSI-X 并不因为存在中断脉冲就“完成”。
· 如果 MSI-X 被暴露,包含 MSI-X 表和 PBA 的 BAR 必须正确工作。
· 如果设备仅在队列设置后产生中断,则盲目的心跳可能看起来不正确。如果你的设备行为需要如此, 在真正的BAR写入后进行门控。
· 测试后保存Windows事件查看器、驱动程序日志和Arbor/TeleScan回读。没有回读就没有证据。
11.8 TLP 生命周期和PMCSR状态对齐
静态ID是简单的部分。难点在于当主机发送真实PCIe事务时,使端点行为保持一致。
11.8.1 TLP生命周期
Type 0 PCIe 端点在早期启动期间会看到三种实际的事务类型:
Host Root Complex
|
| 1. CfgRd0 / CfgWr0
v
Xilinx 7 Series PCIe IP
|
| maxisrxtdata / tlprx.data
v
PCILeech TLP pipeline
|
+--> Config TLP path
| src/pcileechtlps128cfgspace_shadow.sv
| - CfgRd0 returns shadow config data
| - CfgWr0 updates shadow data through WriteMask
|
+--> BAR path
| src/pcileechtlps128bar_controller.sv
| - MemRd routes to selected BAR model
| - MemWr updates BAR model state
|
+--> Interrupt/control context
src/pcileechpciecfg_a7.sv
- MSI/MSI-X enable state
- PMCSR state from core context
- function-level reset and link state signals
重要的解码已经存在于当前上游风格的 PCILeech 文件中:
// File: src/pcileechtlps128cfgspace_shadow.sv
wire pcierxrden = tlps_in.tvalid
&& tlps_in.tuser[0]
&& (tlps_in.tdata[31:25] == 7'b0000010); // CfgRd0/CfgRd1
wire pcierxwren = tlps_in.tvalid
&& tlps_in.tuser[0]
&& (tlps_in.tdata[31:25] == 7'b0100010); // CfgWr0/CfgWr1
wire [9:0] pcierxaddr = tlps_in.tdata[75:66];
wire [31:0] pcierxdata = tlps_in.tdata[127:96];
wire [3:0] pcierxbe = {tlpsin.tdata[32], tlpsin.tdata[33], tlpsin.tdata[34], tlpsin.tdata[35]};
BAR reads/writes 然后经过:
src/pcileechtlps128bar_controller.sv
MemRd TLP -> read engine -> rdreqvalid / rdreqbar / rdreqaddr
selected BAR model -> rdrspctx / rdrspdata / rdrspvalid
completion engine -> Completion with Data
MemWr TLP -> write engine -> wrvalid / wrbar / wraddr / wrbe / wr_data
selected BAR model -> state update
bit必须保持这些响应的一致性:
| 主机操作 | bit行为必须保持一致 |
|---|---|
| CfgRd0 读取 PM 能力 | PM 能力在公告的偏移处存在,并且链接清晰。 |
| CfgWr0 写入 PMCSR D3hot | PMCSR 回读发生变化,BAR 模型进入低功耗安全状态。 |
| CfgWr0 写入命令内存空间启用 | BAR 的读取只有在内存空间启用后才有意义。 |
| CfgWr0 启用 MSI/MSI-X | 中断行为必须与公开的功能及 BAR table/PBA 计划相匹配。 |
| MemRd 读取 reset/status 寄存器 | 状态机必须返回一个稳定的类似设备的过渡,而不是随机的零。 |
| MemWr 写入复位位 | 如果设备清除了复位位,复位位也应自清。 |
牢记 - 动态行为胜过静态克隆如果事务故事自相矛盾,原始配置克隆仍可能失败。如果 PMCSR 表示 D3hot,但 BAR 寄存器仍表现如 D0,或 MSI-X 已启用,但 table/PBA BAR 无响应,bit就不是设备式的。 仅仅是一个带位流的配置空间截图。
11.8.2 PMCSR 重要位
电源管理功能 ID 是 0x01。PMCSR 通常位于:
PMCSR byte offset = PM capability base + 0x04
PMCSR size = 16 bits
核心位:
| PMCSR 位 | 名称 | 实际行为 |
|---|---|---|
| 1:0 | 电源状态 | 00b D0,11b D3hot 是你最常看到的状态。 |
| 8 | PME 启用 | 主机可以在低功耗转换之前设置此项。 |
| 15 | PME 状态 | 在实际硬件中为 RW1C。单纯的 WriteMask 不足以完美模拟 RW1C。 |
除非你的源 PM 功能实际从 0x40 开始,否则不要将 PMCSR 硬编码为 0x44。解析功能链。
11.8.3 PMCSR 状态镜像
该模块镜像 PMCSR 从配置写入 TLP 的状态。 不会替换影子 BRAM 路径。 会向 BAR 模型提供电源状态信号,以便 D0/D3 的行为保持与 PMCSR 一致。
从 src/pcileechtlps128cfgspace_shadow.sv 的配置影子解码或等效的抽取解码路径为其供能。
// File: src/pcileechtlps128cfgspace_shadow.sv or a small included helper
//
// Purpose:
// - Track PMCSR PowerState and PME Enable from CfgWr0 traffic.
// - Provide deviceind3hot to BAR responder logic.
// - Keep PME Status separate because plain WriteMask cannot model RW1C correctly.
//
// cfgwraddr is a DWORD address, matching pcierxaddr in the shadow module.
// PMCAPBASE is a byte offset from the donor capability chain.
module pcileechpmcsrstate_model #(
parameter integer PMCAPBASE = 8'h40
)(
input rst,
input clk,
input cfgwrvalid,
input [9:0] cfgwraddr,
input [3:0] cfgwrbe,
input [31:0] cfgwrdata,
input functionlevelreset,
input functionlevelreset,
output reg [1:0] pmpowerstate,
output reg pmpmeenable,
output reg pmpmestatus,
output wire deviceind0,
output wire deviceind3hot
);
localparam integer PMCSRBYTEOFFSET = PMCAPBASE + 4;
localparam [9:0] PMCSRDWADDR = ((PMCAPBASE + 4) >> 2);
localparam integer PMCSRBYTELANE = ((PMCAPBASE + 4) & 3);
wire cfgwrhitspmcsr = cfgwrvalid && (cfgwraddr == PMCSRDW_ADDR);
wire [15:0] pmcsrwrvalue =
(PMCSRBYTELANE == 0) ? cfgwrdata[15:0] :
(PMCSRBYTELANE == 1) ? cfgwrdata[23:8] :
(PMCSRBYTELANE == 2) ? cfgwrdata[31:16] :
16'h0000;
wire pmcsrlowbyte_written =
(PMCSRBYTELANE == 0) ? cfgwrbe[0] :
(PMCSRBYTELANE == 1) ? cfgwrbe[1] :
(PMCSRBYTELANE == 2) ? cfgwrbe[2] :
cfgwrbe[3];
wire pmcsrhighbyte_written =
(PMCSRBYTELANE == 0) ? cfgwrbe[1] :
(PMCSRBYTELANE == 1) ? cfgwrbe[2] :
(PMCSRBYTELANE == 2) ? cfgwrbe[3] :
1'b0;
assign deviceind0 = (pmpowerstate == 2'b00);
assign deviceind3hot = (pmpowerstate == 2'b11);
always @ (posedge clk) begin
if (rst || functionlevelreset) begin
pmpowerstate <= 2'b00;
pmpmeenable <= 1'b0;
pmpmestatus <= 1'b0;
end else begin
if (cfgwrhitspmcsr && pmcsrlowbytewritten) begin
pmpowerstate <= pmcsrwrvalue[1:0];
pmpmeenable <= pmcsrwrvalue[8];
end
if (cfgwrhitspmcsr && pmcsrhighbytewritten && pmcsrwrvalue[15]) begin
pmpmestatus <= 1'b0;
end
end
end
endmodule
在你的 BAR 响应器中使用该状态。在 D3hot 中,不要继续假装设备处于完全激活状态:
// Example BAR read behavior gated by PMCSR state.
// Drop inside a donor-specific BAR implementation in:
// src/pcileechtlps128bar_controller.sv
always @ (posedge clk) begin
if (rst) begin
rdrspctx <= 88'd0;
rdrspdata <= 32'd0;
rdrspvalid <= 1'b0;
end else begin
rdrspctx <= rdreqctx;
rdrspvalid <= rdreqvalid;
if (deviceind3hot) begin
case (rdreqaddr[11:0])
12'h000: rdrspdata <= 32'h00000000;
12'h004: rdrspdata <= 32'h00000000;
12'h008: rdrspdata <= 32'h00000000;
default: rdrspdata <= 32'h00000000;
endcase
end else begin
case (rdreqaddr[11:0])
12'h000: rdrspdata <= 32'h81680001;
12'h004: rdrspdata <= 32'h00000001;
12'h008: rdrspdata <= 32'h00000000;
12'h00C: rdrspdata <= 32'h00000000;
default: rdrspdata <= 32'h00000000;
endcase
end
end
end
这个例子故意很无聊。无聊是好的。首先使状态转换确定性。然后用你的 BAR 转储和驱动程序跟踪中的特定设备寄存器行为替换 DWORD。
11.8.4 PMCSR 验证标准
PMCSR 的行为直到你能够显示以下内容才算完整:
· PM 功能基数是从提供者那里解析的,而不是猜测的;
· PMCSR 偏移有文档记录;
· PMCSR WriteMask 允许在正确的偏移处设置电源状态和 PME 使能;
· PME 状态 RW1C 要么有模型,要么明确标记为未实现;
· BAR 在 PMCSR 进入 D3hot 时行为会改变或安全空闲;
· 功能级复位将模型恢复到 D0;
· Windows 读回确认 PMCSR 转换;
· 冷启动仍然可以正常枚举。
如果bit完全忽略 PMCSR, 记录下来。
11.9 TLP 响应检查
在以下情况下继续:
· BAR 控制器可以干净 生成
· 时序仍然通过
· BAR 读回在 Windows 上有效
· 所需的偏移量返回计划值
· 所需的写入已处理
· 驱动行为有所改善或故障原因已知
12. Zero4K
12.1 Zero4K 的适用情况
当满足以下情况时,Zero4K 很有用:
· 你需要一个简单的首次 BAR 响应
· 设备使用一个小的 BAR 区域
· 测试的偏移量在 4KB 范围内
· 驱动程序主要读取静态寄存器
Zero4K不是完整的 BAR 仿真。
Zero4K 只证明这一点:
The first 4KB of one static BAR response can be read.
解释- Zero4K 的实际操作Zero4K 是一个静态的 4KB MMIO 响应器:主机软件写入选定的 BAR 偏移量,然后回读来,并看到的是稳定的数据,而不是超时、全零噪声或全 FF。这可以满足基本的 BAR write/readback 探测,但 不能证明复位逻辑、MSI-X table/PBA 行为、EEPROM/NVM 窗口或真实驱动状态机。
Zero4K不能证明:
· 更大的 BAR 覆盖范围
· MSI-X 表行为
· MSI-X PBA 行为
· 复位位行为
· 状态位行为
· EEPROM/NVM 窗口行为
· write/readback 行为
· 驱动绑定
如果设备 BAR 大于 4KB, 记录:
BAR size:
Covered by Zero4K:
Not covered:
Offsets tested:
Offsets not tested:
MSI-X table/PBA inside covered range:
Driver-critical offsets inside covered range:
如果你不能填写,那就说明你不明白 Zero4K 是什么。
12.2 上生成pcileechbarzero4k.coe
创建一个 CSV:
offset,dword
0x000,12345678
0x004,00000000
0x008,ABCDEF01
创建:
C:/dma/tools/makezero4kcoe.py
python
import csv
import sys
if len(sys.argv) != 3:
print("Usage: python makezero4kcoe.py bardwords.csv pcileechbar_zero4k.coe")
sys.exit(1)
csv_path = sys.argv[1]
out_path = sys.argv[2]
dwords = ["00000000"] * 1024
with open(csv_path, newline="") as f:
reader = csv.DictReader(f)
for row in reader:
offset = int(row["offset"], 16)
value = row["dword"].strip().replace("0x", "").replace("0X", "").upper()
if offset % 4 != 0:
raise SystemExit(f"Offset not aligned: 0x{offset:03X}")
if offset >= 4096:
raise SystemExit(f"Zero4K only supports first 4KB: 0x{offset:03X}")
if len(value) != 8:
raise SystemExit(f"DWORD must be 8 hex digits: {value}")
dwords[offset // 4] = value
with open(out_path, "w", newline="\n") as f:
f.write("memoryinitializationradix=16;\n")
f.write("memoryinitializationvector=\n")
for i, value in enumerate(dwords):
f.write(value)
f.write(";\n" if i == len(dwords) - 1 else ",\n")
print("Generated Zero4K COE:", out_path)
print("0x000:", dwords[0])
print("0x004:", dwords[1])
print("0xFFC:", dwords[1023])
运行:
python C:\dma\tools\makezero4kcoe.py C:\dma\donor\rtl8125\bar0dwords.csv C:\dma\donor\rtl8125\pcileechbar_zero4k.coe
12.3 在 Vivado 安装 Zero4K COE
替换:
ip/pcileechbarzero4k.coe
然后:
- 找到 brambarzero4k
- 如有需要,重置输出
- 生成输出
- 重建
- 在 Windows 上测试 BAR 的回读
12.4 Zero4K 故障迹象
不良迹象:
BAR is 16KB but only first 4KB works
MSI-X table offset is outside 4KB
driver writes register but readback never changes
BAR read returns all zero
BAR read returns all F
BAR read times out
MSI-X table is outside 4KB
driver expects EEPROM/NVM window
reset/status register needs stateful behavior
如果你的 BAR 型号不完整,别怪 Vivado。
12.5 Zero4K检查
当满足以下条件时,Zero4K 是可接受的:
· 使用 1024 个 DWORDs 生成 COE
· 检查字节顺序
· 重新生成 brambarzero4k
· 第一个 DWORD 正确读取
· 前 4KB 无超时读取
· 已记录覆盖和未覆盖的 BAR 范围
· 如果存在 MSI-X,则记录 MSI-X table/PBA 覆盖情况
13. 生成烧录
13.1 生成前检查表
生成前:
[ ] Stock firmware already works
[ ] Donor capture complete
[ ] Correct board folder selected
[ ] Correct FPGA part and package confirmed
[ ] Vivado version recorded
[ ] PCIe IP IDs configured
[ ] BAR type/size configured
[ ] cfg_dsn set if needed
[ ] rw[203] set in pcileech_fifo.sv
[ ] rw[206] set in pcileech_fifo.sv if using WriteMask
[ ] pcileech_cfgspace.coe generated and checked
[ ] pcileechcfgspacewritemask.coe generated and checked
[ ] pcileechbarzero4k.coe generated if using Zero4K
[ ] BAR responder plan documented
[ ] recovery method confirmed
13.2 Vivado GUI生成路径
在 Vivado 中:
- 为已更改的 IP 生成输出
- 运行综合
- 仅在需要时打开综合设计
- 运行实现
-
生成bit -
打开时序汇总 -
打开利用率报告 -
保存日志和报告
正常:
Synthesis completed successfully
Implementation completed successfully
write_bitstream completed successfully
Timing constraints are met
错误:
Synthesis failed
Implementation failed
Timing failed
Critical warning about clock/pin/part
IP output products out of date
如果实现失败或时序明显错误, 不要烧录。
13.3 Vivado Tcl 快捷路径
打开:
Start Menu -> Vivado Tcl Shell
然后:
cd C:/dma/work/squirrel-rtl8125
source vivadogenerateproject.tcl -notrace
source vivado_build.tcl -notrace
reporttimingsummary -file timing_summary.txt
report_utilization -file utilization.txt
PowerShell 日志采集示例:
mkdir C:\dma\builds\logs
& "C:\Xilinx\Vivado\2023.2\bin\vivado.bat" -mode batch -source C:\dma\work\squirrel-rtl8125\vivadobuild.tcl -notrace 2>&1 | Tee-Object C:\dma\builds\logs\vivadobuild.log
输出路径会有所不同,但常见为:
13.4 保存历史
不要制作一次性bit。保存历史。
当你希望别人或未来的自己能够复现时。
最低历史
C:/dma/builds/2026-xx-xx-board-donor-v1/
windows-version.txt
vivado-version.txt
driver-version.txt
board-folder.txt
fpga-part-package.txt
donor_specs.md
vivado_build.log
timing_summary.txt
utilization.txt
firmware.bit
firmware.bin
sha256.txt
flash-tool-screenshot.png
device-manager-hardware-ids.png
device-manager-resources.png
arbor-or-telescan-target-readback.txt
cold-boot-result.txt
完整历史
C:/dma/builds/2026-xx-xx-board-donor-v1/
windows-version.txt
vivado-version.txt
driver-version.txt
board-folder.txt
fpga-part-package.txt
donor_specs.md
git-diff.patch
vivado_build.log
timing_summary.txt
utilization.txt
firmware.bit
firmware.bin
sha256.txt
flash-tool-screenshot.png
device-manager-hardware-ids.png
device-manager-resources.png
arbor-target-readback.txt
telescan-target.tlscan
cold-boot-result.txt
device-manager-events.png
device-manager-driver-status.png
arbor-donor-export.txt
telescan-donor.tlscan
config-dwords.csv
generated-coe-check-output.txt
writemask-check-notes.txt
bar-readback-notes.txt
flash-tool-log.txt
photos/
PowerShell 哈希值:
Get-FileHash .\firmware.bit -Algorithm SHA256
Get-FileHash .\firmware.bin -Algorithm SHA256
刷入bit
这个不用教,如果你bit都不会刷也别学着做bit了!
13.6 SRAM 加载
容易自欺的地方
| 操作 | 会发生什么 |
|---|---|
| SRAM / 易失性加载 | FPGA 运行新的映像直到断电 |
| 闪存编程 | 映像在断电后仍然存在 |
如果热重启显示新bit但冷启动返回旧bit,你可能只加载了 SRAM。
如何判断你只加载了 SRAM:
new firmware works immediately after programming
Windows restart still may show it
full power-off makes old firmware return
flash tool log does not show flash erase/program/verify
command name says load, configure, or pld load instead of program flash
如何判断bit烧录好了:
tool erased flash
tool programmed flash
tool verified flash
device still shows new firmware after full power removal
13.7 冷启动验证
刷好后:
-
关闭 Windows -
如果需要,关闭电源供应或拔掉电源 -
等待 10-30 秒 -
开机 -
启动 Windows -
检查 设备管理器 -
检查 Arbor/TeleScan 读回
13.8 Build/Flash检查
当以下条件满足时 Build/flash 完成:
· 生成 bit/bin
· 审核时间
· 已执行冷启动
· Windows 在冷启动后看到预期设备
Device status: This device is working properly.
Hardware IDs show expected VEN/DEV/SUBSYS/REV.
Resources show expected memory ranges.
No conflict listed.
错误:
Unknown device
Code 10
Code 12
Code 31
No Resources tab
BAR resource missing
Device failed to start
14.2 Arbor/TeleScan bit设备回读
刷好后,使用 Arbor 或 TeleScan PE 在bit设备上
将bit设备与设备记录进行比较:
| 区域 | 必须检查 |
|---|---|
| 0x00 | VID/DID |
| 0x08 | Revision/Class |
| 0x10-0x24 | BARs |
| 0x2C | 子系统 ID |
| 0x34 | 功能指针 |
| 0x40+ | 标准功能 |
| 0x100+ | 扩展功能 |
然后分类差异:
| 差异类型 | 示例 | 应做的事情 |
|---|---|---|
| 稳定字段 | VID/DID、Revision/Class、子系统ID、能力ID | 应该与受赠计划匹配。如有错误, 修正。 |
| 运行时字段 | 命令、状态、BAR 分配地址、MSI 启用、PM 状态 | 记录下来。如果变化,不要惊慌。 |
| 有意的差异 | FPGA 链路宽度不同、MSI-X 为测试故意禁用 | 记录原因。 |
| 缺陷 | 损坏的能力指针、错误的 BAR 大小、缺失扩展能力、陈旧的 VID/DID | 在宣告成功前修复。 |
你的差异表不应该写“差不多”。应该写稳定、运行时、故意或错误。
错误的回读:
0x40 and later all zero
capability chain broken
extended capabilities missing
BAR values look like donor runtime addresses
VID/DID still default PCILeech
修复相关的bit步骤。不要随意更改更多ID。
14.3 写掩码和指令寄存器测试
如果支持受控写入, 使用 Arbor/TeleScan 或其他可靠的 PCI 配置编辑器。
测试:
-
读取指令寄存器 -
切换允许的命令位 -
读回 -
尝试写入 VID/DID -
确认 VID/DID 未更改 -
清除合理的 Status/RW1C 位 -
确认设备仍然正常运行
正常:
Command writable bits stick.
VID/DID remain unchanged.
Status behavior is reasonable.
Device remains present.
错误:
Command never changes
VID/DID become corrupted
device disappears after config write
MSI enable cannot stick
这就是 WriteMask/shadow 写入行为。
14.4 BAR 读回测试
使用 Arbor、TeleScan、供应商诊断工具或你自己的实验室 BAR 读工具。
最小值:
· BAR 首个DWORD
· BAR 前4KB
· 不支持的偏移量
· 如果 BAR 大于 4KB,则偏移量过高
· 如果存在 MSI-X,则 MSI-X table/PBA 偏移量
正常:
BAR read completes.
Values are stable.
Unsupported offsets return safe values.
Target does not freeze.
错误:
read timeout
all zero unexpectedly
all FFFFFFFF
random values
target freezes
driver fails after BAR access
修复 BAR 响应路径。
14.5 驱动绑定失败
驱动绑定调试顺序:
- Confirm stock firmware still works.
- Confirm PCIe identity: VID/DID/Revision/Class/Subsys.
- Confirm BAR type, size, 64-bit pairing, and Resources tab.
- Confirm config-space readback in Arbor/TeleScan.
- Confirm WriteMask and Command register behavior.
- Confirm BAR readback.
- Confirm MSI/MSI-X capability and table/PBA behavior.
- Confirm PMCSR behavior.
- Confirm reset/status registers in BAR space.
- Then read Driver tab and Events.
如果你从代码 10 直接跳回更改 VID/DID,那只是猜测。
代码 10 - 设备无法启动
真正原因:
· 缺少 BAR 寄存器行为
· MSI/MSI-X 损坏
· PMCSR 损坏
· 缺少 EEPROM/NVM 窗口
· 重置位从未清除
· 状态位从未改变
· 中断屏蔽行为错误
· 驱动期望真实硬件状态
不要通过再次更改VID/DID来修复代码10。
代码12 - 资源不足
可能原因:
· BAR 大小过大
· BAR 配对无效
· 64位 BAR 配置错误
· 资源冲突
· bit设备 BIOS/slot 问题
代码31 - 驱动程序加载失败
可能原因:
· class/subsystem 不正确
· 驱动程序不匹配
· 缺少功能行为
· Windows 驱动程序包问题
未知设备
可能原因:
· VID/DID/class 不匹配
· 驱动程序未安装
· 配置空间不完整
· 子系统不匹配
14.6 常见失败案例
| 症状 | 可能原因 | 首先要检查的事项 |
|---|---|---|
| 设备管理器 完全未显示设备 | 烧录失败,位流错误,PCIe 链接问题,板子未供电 | 恢复出厂bit和 PCIe 链接 |
| 未知设备 | identity/class/subsystem 不匹配 | 硬件 ID 和类别代码 |
| 代码 10 | BAR/MSI/PMCSR/state 行为缺失 | BAR 回读和事件标签 |
| 代码 12 | BAR 资源冲突或大小不正确 | 资源选项卡 |
| 代码 31 | 驱动程序包或匹配问题 | 驱动程序标签和事件 |
| 资源标签为空 | BAR 被禁用,枚举不完整,driver/device 过早失败 | Vivado BAR 设置和配置回读 |
| BAR 大小错误 | Vivado IP BAR size/type 错误或影子覆盖了 BAR 寄存器 | PCIe IP BAR 标签和bit设备 BAR 尺寸 |
| 0x40 配置全部为零 | rw[203] 仍然是零数据或 COE 未加载 | pcileech_fifo.sv 和 BRAM 重新生成 |
| 扩展功能缺失 | COE 仅覆盖256B,扩展链中断,错误接管 | COE 深度和 0x100 回读 |
| VID/DID 未更改 | IP 未重新生成或闪存已过时 | PCIe IP,bit时间戳 |
| VID/DID 已损坏 | WriteMask 允许在 0x00 写入 | 写掩码在 0x00 |
| 写命令无法保持 | rw[206] 已禁用或掩码错误 | 写掩码 |
| MSI 启用无法保持 | MSI 功能掩码错误或 MSI 偏移假设不正确 | 设备功能偏移和写掩码 |
| BAR 读取超时 | 无完成路径 | BAR 控制器 |
| BAR 读取全为零 | Zero/static 响应器使用不当,COE 未加载,BAR 索引错误 | BAR COE 和 BAR 路由 |
| BAR 读取全为 F | 无设备响应,不支持的路径,完成失败 | BAR 控制器和 PCIe 完成路径 |
| 冷启动返回旧bit | 仅加载 SRAM | 持久闪存过程 |
| Vivado 手动擦除编辑 | 输出产品已重新生成 | 生成的文件差异 |
| COE 未被 BRAM 使用 | COE 路径错误,BRAM 未重新生成,IP 输出过时 | Vivado IP 定制和生成的输出产品 |
| 粘贴后 生成失败 | 粘贴的伪代码 | 模块 ports/signals |
| 使用了错误的板卡文件夹 | pins/part/project 不正确 | 上游板矩阵和 FPGA 包 |
| FPGA part/package 错误 | 约束和bit错误 | Vivado 部件设置和板子标记 |
| CH347/OpenOCD/Vivado 硬件管理器看不到板子 | 编程器驱动,错误的电缆,板子模式,电源 | Windows 设备管理器 编程器入口 |
| 刷写后板子死亡 | 错误的板子文件夹,错误的 FPGA 包,错误镜像 | 恢复 image/tool |
14.7 商业bit设备诊断矩阵
不要凭感觉调试硬件故障。
使用矩阵。将症状放在一行,收集证据,并攻击第一个失败的层。PCIe 的错误只有在跳过层时才显得神秘。
五级验证堆栈:
| 水平 | 层 | 必须证明的内容 | 主要证据 |
|---|---|---|---|
| 0 | 电源 / 时钟 / 复位 | 主板供电,FPGA 配置,PCIe refclk/reset 合理 | 编程器检测,闪存日志,ILA reset/link 探针 |
| 1 | 链路训练 | 在操作系统驱动工作之前端点达到稳定链路 | BIOS 可见性,plltssmstate,plphylnk*up,冷启动 |
| 2 | 枚举 / 配置 | 类型 0 配置 reads/writes 行为 | 设备管理器,Arbor/TeleScan,CfgRd0/CfgWr0 ILA |
| 3 | BAR / TLP | MMIO reads/writes 完成及状态变化 | BAR 读回,ILA rd*rsp_valid,动态 BAR 状态 |
| 4 | 驱动工作负载 | 驱动启动并在正常操作中存活 | 事件标签,驱动程序日志,工作负载测试,冷启动重复 |
14.7.1 POST 挂起 / BIOS冻结 / 无法启动
| 症状 | 深层根本原因 | 需要收集的硬性证据 | 针对性修复 | 不要这样做 |
|---|---|---|---|---|
| 主板在操作系统启动前冻结 | BAR 求对于bit MMIO 分配窗口过大 | BIOS POST 代码,资源在原版bit和自定义bit之间不同,BAR 大小在 PCIe IP中 | 将 BAR 大小缩小到符合设备实际值;禁用未使用的BAR;确认64位配对 | 不要增加 BAR 大小因为“更大看起来真实” |
| 仅在自定义bit下 POST 挂起 | PCIe 链路训练超时或 LTSSM 转换不稳定 | ILA plltssmstate、plphylnkup、plinitiallinkwidth,冷启动采集 | 修复 PCIe IP speed/lane 设置;先测试 Gen1;验证 slot/adaptor 信号完整性 | 链路训练失败时不要编辑 VID/DID |
| 电路板在一个插槽工作但在另一个插槽不工作 | MMIO 孔径、ASPM、拆分或通道布线差异 | 两个插槽使用相同的bit,BIOS 资源映射,链路 width/speed 回读显 | 测试简单 x1 Gen1 配置文件;更换插槽;在实验室测试中禁用 BIOS 中的激进 ASPM | 不要因为一个插槽可用就假设 FPGA 镜像已验证 |
| 冷启动后设备消失,但热重启可用 | 仅 SRAM 加载,闪存不持久,或者 PCIe 在断电后训练表现不佳 | 闪存工具日志,冷启动结果,bit/bin 哈希值,编程器验证输出 | 编程持久闪存;验证 erase/program/verify;断电重启并重新读取配置 | 不要将热重启称为闪存验证 |
| 更改板卡文件夹后 POST 失败 | 错误的 FPGA part/package 或错误的 .xdc 引脚 | Vivado 零件名称、封装、板卡标记、.xdc 差异、时序关键警告 | 使用正确的板卡文件夹;验证 FPGA 封装;恢复已知良好的约束条件 | 不要强制使用错误的约束生成bit |
| 时序失败后板卡无法枚举 | PCIe 用户时钟 / 复位交叉因实现失败而中断 | timing_summary.rpt,最差负时序,非约束时钟 | 在刷写前修复时序;减少逻辑;为 BAR 状态机建立流水线 | 不要刷写明显有时序错误的bit |
| 刷写成功但卡片无法使用 | 镜像系列错误、刷写偏移错误,或供应商工具写入了不兼容的镜像 | IDCODE、闪存日志、板子丝印、恢复原厂测试 | 恢复原厂bit;确认确切的 FPGA 和闪存型号;从正确的项目重新 生成 | 不要尝试随机的板子镜像 |
POST 级规则:
No stable PCIe link = do not debug WriteMask.
No stable flash persistence = do not debug Windows driver binding.
No correct FPGA package = do not debug BAR behavior.
14.7.2 设备管理器 黄色三角:代码 10 / 代码 43
代码10和代码43不能通过更改ID来解决。 表示驱动程序或Windows设备堆栈触发了与设备契约不匹配的行为。
| 症状 | 深层根本原因 | 需要收集的证据 | 定向测试 | 针对性修复 |
|---|---|---|---|---|
| 驱动程序启动后立即出现代码10 | BAR读取返回timeout/all-zero/all-FF | Arbor/TeleScanBAR读取,事件选项卡,ILABAR rdreqvalid 对比 rdrspvalid | 读取第一个DWORD,第一个4KB,不支持的偏移 | 为驱动程序触及的寄存器路径实现BAR响应器 |
| 配置写入后出现代码10 | WriteMask阻止了必需的可写位 | Arbor/TeleScanbefore/after CfgWr0,ILA pcierxwren,bit设备配置差异 | 切换命令,MSI 启用,PMCSR 供体偏移的电源状态 | 重新生成支持能力的写掩码 |
| 重置寄存器写入后的代码10 | BAR 重置位从不自动清除 | BAR 跟踪:写入重置位,重复状态读取 | 写入重置位,轮询状态 | 在BAR模型中添加重置timer/state转换 |
| 代码43 / 设备报告问题 | 驱动程序看到不可能的status/capability组合 | 设备事件,配置回读,BAR状态值 | 驱动程序启动后比较供体和bit设备 | 修复矛盾:MSI-X已暴露但表失效,PMCSR D3hot但BAR活跃,AER损坏 |
| MSI 启用失败,无法保持 | 能力偏移量或 WriteMask 位错误 | 实际 MSI 能力基址配置差异 | 写入 MSI 消息控制,然后回读 | 解析设备能力链;屏蔽实际偏移 |
| MSI-X 已启用,然后驱动失败 | MSI-X table/PBA BAR 区域缺失 | MSI-X 能力表 offset/BIR,在 table/PBA 读取 BAR | Read/write MSI-X 表项和 PBA 区域 | 实现 table/PBA 行为,否则不公开 MSI-X |
| PMCSR 写入导致驱动启动失败 | 电源状态位不可写或无 D0/D3 行为 | PM 功能基础,PMCSR 读回,BAR D3hot 后行为 | 写入 D3hot 然后 D0,读取 PMCSR 和 BAR 状态 | 添加 PMCSR 写掩码和 PM 感知 BAR 状态 |
| VID/DID 在测试写入后损坏 | 写掩码允许恒等写入 | 在 0x00 上进行 Arbor/TeleScan 写入测试 | 尝试写入 0x00.L,然后读回 | 将恒等区域设置为只读;恢复设备影子 COE |
写掩码bit设备流程:
- Capture donor capability offsets.
- Capture target config before driver bind.
- Start driver.
- Capture target config after failure.
- Diff runtime fields only.
- For each failed field:
- Is the bit supposed to be writable?
- Is it RW1C?
- Is the offset donor-correct?
- Did the write hit
pcileech_tlps128_cfgspace_shadow.sv? - Did
rw[206]insrc/pcileech_fifo.svallow the write path?
- Regenerate WriteMask.
- Re-test only that field.
对代码 10/43 的修复通常比 设备管理器 所说的深一层。
14.7.3 驱动加载导致 BSOD
驱动加载期间的 BSOD 是严重故障。除非证明否则,将其视为协议崩溃。
| 崩溃时机 | 深层根本原因 | 需要收集的证据 | ILA / 软件探测 | 针对性修复 |
|---|---|---|---|---|
| 驱动程序启动后立即 BSOD | 驱动程序 MMIO 读取未完成或响应非法 | Windows 崩溃时间, BAR 读回, ILA rdreqvalid 无 rdrspvalid | 驱动绑定后首次 MemRd 触发 BAR | 对每个已解码和不支持的偏移返回安全完成 |
| BSOD 在 BAR 写握手后 | FPGA 忽略 doorbell/mailbox 写入 | ILA wrvalid, wraddr, wr_data,后续状态轮询 | 写入 doorbell/status/control 偏移时触发 | 添加动态 BAR 状态机;更新 busy/status/response |
| BSOD 在 MSI/MSI-X 启用后 | 中断能力显示为是,行为显示为否 | MSI/MSI-X 配置差异,IRQ 状态,table/PBA BAR 访问 | 监控 MSI 启用写入和表写入 | 在设备计划中实现 MSI-X table/PBA 或禁用 MSI-X |
| BSOD 在重置路径中 | 重置位从不清除或清除太 fast/late | BAR 重置写入,重复状态读取,驱动超时 | 在重置寄存器写入时触发 | 添加来自设备追踪的重置计时器和自清行为 |
| BSOD 在工作负载下 | Queue/descriptor/stream 寄存器为静态 | BAR write/read 序列,packet/audio 计数器,驱动日志 | 采集门铃写入和状态轮询循环 | 添加队列深度、计数器、描述符或流状态建模 |
| 仅在冷启动后 BSOD | 闪存持久性或链路训练不稳定 | 冷启动采集、链路状态、闪存验证 | 比较温启动与冷启动的 ILA 跟踪 | 修复闪存编程或 PCIe PHY/link 设置 |
BSOD 分类顺序:
- Restore stock firmware and prove target machine is stable.
- Boot custom firmware without binding the donor driver if possible.
- Read config space with Arbor/TeleScan.
- Read BAR first DWORD and unsupported offset.
- Enable ILA trigger on BAR MemRd/MemWr.
- Bind driver and capture the last successful TLP.
- If the last TLP is MemRd with no completion, fix completion path.
- If the last TLP is MemWr doorbell followed by polling, fix BAR state machine.
- If the last TLP is MSI-X table write, fix table/PBA behavior.
- Re-test from cold boot.
不要不断重启进入 BSOD 并改变随机 ID。采集失败的事务。
14.7.4 证据图
| 证据 | 证明 | 不证明 |
|---|---|---|
| 设备管理器 可见 | 基本枚举 | 驱动程序正确性 |
| 资源选项卡 | BAR 分配 | BAR 响应 |
| Arbor/TeleScan 配置差异 | 配置标识和写入行为 | BAR 状态机 |
| BAR 回读 | 完成路径和基本 MMIO | 驱动程序工作负载正确性 |
| ILA CfgRd/CfgWr 触发器 | 枚举事务路径 | BAR 模型行为 |
| ILA MemRd/MemWr 触发器 | 驱动程序寄存器流量 | 高级设备语义正确性 |
| 时序报告 | 生成时序健康状况 | 功能正确性 |
| 冷启动 | 闪存持久性和链路训练 | 完整驱动程序行为 |
事情很简单:每一个严重的故障都对应一个层次、一个工件和一个初步修复。如果你只写着“黄色三角”,那你还没有开始调试。
14.8 正常与错误输出示例
Vivado 正常
Synthesis completed successfully.
Implementation completed successfully.
write_bitstream completed successfully.
Timing constraints are met.
Vivado 错误
CRITICAL WARNING: timing constraints not met
ERROR: part not found
ERROR: module not found
ERROR: failed to generate output products
PCIe IP 再生正常
Generate Output Products completed.
IP status is current.
Expected .xci changes remain.
Expected generated-file edits still exist if you made any.
PCIe IP 再生错误
IP output products failed.
Vivado upgraded the IP unexpectedly.
Generated wrapper edit disappeared.
COE path reverted.
COE 解析正常
memoryinitializationradix=16 accepted
memoryinitializationvector accepted
BRAM/ROM output products generated
sanity-check script shows expected 0x00/0x08/0x2C/0x34/0x100
COE 解析错误
invalid radix
invalid memory initialization vector
wrong number of DWORDs
0x000 is 00000000
capability chain loop detected
runtime BAR-looking value warning
设备管理器 正常
This device is working properly.
Hardware IDs contain expected VEN/DEV/SUBSYS/REV.
Resources show memory ranges.
设备管理器 错误
Code 10
Code 12
Code 31
Unknown device
No resources
Arbor/TeleScan 正常
Header decodes correctly.
Capability chain is valid.
BAR sizes match donor plan.
Extended capabilities are present if expected.
Arbor/TeleScan 错误
FFFF:FFFF
capability pointer invalid
0x40+ all zero
BAR size wrong
extended config missing
BAR 读取正常
first DWORD returns expected value
4KB read completes
unsupported offset returns safe default
BAR 读取错误
timeout
all zero
all F
random changing values
system freeze
闪存正常
programmer detected
target FPGA ID matches expected family
programming completes without verify error
tool log saved
闪存错误
programmer not found
unexpected IDCODE
verify failed
tool only performed SRAM load when you expected flash programming
冷启动正常
power removed
system powered back on
device enumerates with new firmware
Arbor/TeleScan readback still matches target plan
冷启动错误
device returns to old firmware
device disappears
target hangs at POST
BAR/resources differ from warm reboot
14.9 最终验证检查
[ ] Donor capture folder complete
[ ] Windows driver version saved
[ ] Stock firmware baseline works
[ ] Correct board folder used
[ ] Correct FPGA part/package used
[ ] PCIe IP IDs correct
[ ] BAR type/size correct
[ ] Shadow config COE generated by offset
[ ] 0x00/0x08/0x2C/0x34/0x100 validated
[ ] rw[203] edited in pcileech_fifo.sv
[ ] WriteMask COE generated
[ ] rw[206] edited in pcileech_fifo.sv
[ ] cfg_dsn matches DSN plan
[ ] MSI/MSI-X plan documented
[ ] Zero4K limitation documented
[ ] Build log saved
[ ] Timing report saved
[ ] Utilization report saved
[ ] bit/bin hashes saved
[ ] Flash tool screenshot saved
[ ] Cold boot completed
[ ] 设备管理器 screenshots saved
[ ] Arbor/TeleScan target readback saved
[ ] BAR readback tested
[ ] Driver status recorded
如果你无法提供,说明你没有完成。
14.10 Windows 验证 检查
Windows 验证只有在以下情况完成时才算完成:
· 设备管理器 状态已保存
· 硬件ID已保存
· 资源已保存
· 事件已保存
· Arbor 或 TeleScan bit设备回读已保存
· 设备与bit设备的差异已分类
· WriteMask 行为已测试或明确标记为未测试
· BAR 验证级别已记录
· 驱动绑定结果已记录
· 冷启动结果已记录
高级:使用 Vivado ILA 进行硬件调试
当一块板子枚举一次后随机死机,仅截图是不够的。使用 ILA 并查看总线。
对于棘手的情况 使用 ILA:
· bit设备在 POST 期间挂起
· 设备只有在热重启后才出现
· 偏移 0x40 之后配置空间读取为零
· BAR 尺寸设置一次后就失败
· Windows 显示代码 10,但设备身份看起来正确
· 一次生成的 IP 更改悄悄地改变了 TLP 路径
ILA.1 探测正确的 RX 路径
在当前上游风格的 PCILeech 7 系列项目中,PCIe 端点接收接口通常已连接:
src/pcileechpciea7.sv
寻找:
.maxisrxtdata ( tlprx.data ),
.maxisrxtkeep ( tlprx.keep ),
.maxisrxtlast ( tlprx.last ),
.maxisrxtvalid ( tlprx.valid )
一些生成的 Xilinx 包装器视图将端点 RX 端标记为 saxisrx_* 或通过不同层次结构暴露。不要与名称斗争。真正的规则很简单:
Probe the RX stream coming out of the PCIe core and entering the PCILeech TLP pipeline.
有用的探针:
| 探针 | 为什么 |
|---|---|
| tlp*rx.data[63:0] | 来自 PCIe 核心的 64 位 RX 路径中的原始 TLP header/data |
| tlp*rx.keep[7:0] | 哪些字节是有效的 |
| tlp*rx.last | 数据包结束 |
| tlp*rx.valid | 数据包节拍有效 |
| tlp*rx.user[21:0] | 如果在你的 生成中暴露,则为边带标志 |
| tlps*in.tdata[127:0] | 由 shadow/BAR 逻辑使用的重新打包 128 位 TLP 流 |
| tlps*in.tuser[0] | IfAXIS128 中的第一个 DWORD 标记 |
| pcierxrden | 在 pcileechtlps128cfgspace*shadow.sv 中检测到配置读取 |
| pcierxwren | 在 pcileechtlps128cfgspace*shadow.sv 中检测到配置写入 |
| bramrdvalid | 生成了影子配置读取响应 |
| rd*rsp_valid | 生成了 BAR 读取完成 |
如果你只探针 VID/DID,你并没有在调试。 探针数据包路径。
ILA.2 对配置读取和写入触发
config-shadow 模块已经显示了有用的解码:
src/pcileechtlps128cfgspace_shadow.sv
当前上游风格解码:
wire pcierxrden = tlps_in.tvalid
&& tlps_in.tuser[0]
&& (tlps_in.tdata[31:25] == 7'b0000010); // CfgRd0/CfgRd1
wire pcierxwren = tlps_in.tvalid
&& tlps_in.tuser[0]
&& (tlps_in.tdata[31:25] == 7'b0100010); // CfgWr0/CfgWr1
ILA 触发计划:
Trigger 1: tlps_in.tvalid == 1
Trigger 2: tlps_in.tuser[0] == 1
Trigger 3: tlps_in.tdata[31:25] == 7'b0000010 or 7'b0100010
Capture: tlpsin.tdata, tlpsin.tkeepdw, tlpsin.tuser, pcierxrden, pcierxwren, bramrd_valid
Depth: 4096 samples minimum if your FPGA has enough BRAM
Window: pre-trigger 25%, post-trigger 75%
这告诉你:
| ILA 结果 | 含义 |
|---|---|
| 没有配置 TLP 到达 | 链路训练、复位、时钟或 PCIe 核心问题。停止编辑 COE。 |
| CfgRd0 到达但没有响应路径触发 | Shadow 配置解码、rw[203]、过滤器或 BRAM 路径错误。 |
| CfgWr0 在挂起之前到达 | 主机正在更改 Command/Status/BAR/PM 状态;检查 WriteMask 和可写行为。 |
| 配置路径工作正常,BAR 读取从不完成 | BAR 控制器或选定的 BAR 实现错误。 |
| 完成存在,但数据已偏移 | Endian/order/offset 错误。检查 DWORD 索引。 |
ILA.3 POST-失败工作流程
按顺序进行此操作。不要一次更改五个项目。
- 使用 ILA 生成已知良好的标准或基线bit。
- 保存基线采集以进行正常枚举。
- 使用相同的 ILA 探针 生成修改后的bit。
- 冷启动bit设备,而不仅仅是 Windows 重启。
- 采集第一个 CfgRd0 和第一个 CfgWr0。
- 与基线比较:
· 第一配置读取偏移
· 第一配置写入偏移
· 命令寄存器写入
· BAR 尺寸序列
· 能力指针遍历
· 首次 BAR MMIO 读取
- 仅修复第一个出错分支。
- 重建并重复。
正常的早期枚举应该看起来很无聊:
CfgRd0 arrives
shadow/config response returns
CfgWr0 updates expected writable bits
BAR sizing reads/writes complete
capability chain reads continue
BAR MMIO reads complete if the driver starts
错误的早期枚举看起来像这样:
CfgRd0 arrives once, then nothing
CfgWr0 to Command register happens, then device disappears
BAR sizing request receives nonsense
capability pointer jumps into zero space
BAR read request appears but rdrspvalid never fires
如果 ILA 表示主机从未发送数据包,那么错误不在你的 BAR 响应器。如果 ILA 表示数据包已到达而你从未回应,那么错误在你。
ILA.4 ILA 检查
在以下情况下继续:
· ILA 探针已连接到实际的 RX/TLP 路径
· 触发器采集 CfgRd0 和 CfgWr0
· 基线采集已保存
· 失败的bit采集已保存
· 首次偏离基线已识别
· 修复仅应用于第一个出错分支
· 新的冷启动采集确认了修复
自动化工具与脚本
自动化仅在保留偏移量时有用。一个移动了单个 DWORD 的脚本会生成看起来专业但损坏的 bit。
AUTO.1 将原始配置十六进制转换为 SystemVerilog 数组
接受常见的 lspci -xxxx/TeleScan 风格的十六进制行。纯 lspci -vvv对于功能文本有用,但通常缺少此解析器所需的原始字节图像。如果采集没有带偏移前缀的十六进制行, 使用附录 A 中的 lspci -xxxxxxx或从 TeleScan 导出原始配置字节。
00: ec 10 25 81 07 04 10 00 05 00 00 02 10 00 00 00
10: 04 00 00 f0 00 00 00 00 04 00 00 f2 00 00 00 00
输出适用于 SystemVerilog 测试逻辑或手动比较的小端 DWORD 数组。
创建:
C:/dma/tools/confighexto_sv.py
python
import re
import sys
if len(sys.argv) != 3:
print("Usage: python confighextosv.py confighex.txt cfg_space.sv")
sys.exit(1)
src_path = sys.argv[1]
out_path = sys.argv[2]
cfg = [0x00] * 4096
line_re = re.compile(r"^\s*([0-9a-fA-F]{2,3}):\s*((?:[0-9a-fA-F]{2}\s*)+)")
with open(src_path, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
m = line_re.match(line)
if not m:
continue
offset = int(m.group(1), 16)
values = [int(x, 16) for x in re.findall(r"[0-9a-fA-F]{2}", m.group(2))]
for i, value in enumerate(values):
pos = offset + i
if pos >= len(cfg):
raise SystemExit(f"Offset outside 4KB config space: 0x{pos:03X}")
cfg[pos] = value
def dword_at(offset):
PCIe config bytes are byte-addressed. SystemVerilog DWORD value here
is little-endian: byte 0 becomes bits [7:0], byte 3 becomes bits [31:24].
return (
cfg[offset]
| (cfg[offset + 1] << 8)
| (cfg[offset + 2] << 16)
| (cfg[offset + 3] << 24)
)
with open(out_path, "w", encoding="utf-8", newline="\n") as f:
f.write("// Generated from raw config hex. Verify key offsets before use.\n")
f.write("logic [31:0] cfg_space [0:1023] = '{\n")
for idx in range(1024):
offset = idx * 4
comma = "," if idx != 1023 else ""
f.write(f" 10'd{idx}: 32'h{dword_at(offset):08X}{comma} // 0x{offset:03X}\n")
f.write("};\n")
for offset in (0x000, 0x008, 0x02C, 0x034, 0x100):
print(f"0x{offset:03X}: 32'h{dword_at(offset):08X}")
print("Wrote:", out_path)
运行:
python C:\dma\tools\confighextosv.py C:\dma\donor\rtl8125\confighex.txt C:\dma\donor\rtl8125\cfg_space.sv
错误输出:
0x000: 32'hFFFFFFFF
0x008: 32'h00000000
0x034: points outside captured range
正确输出:
0x000: vendor/device DWORD matches donor
0x008: revision/class DWORD matches donor
0x02C: subsystem DWORD matches donor
0x034: capability pointer matches donor plan
0x100: extended capability header matches donor plan if present
不要盲目地将此数组粘贴到 COE 文件中。COE 生成有其自身的格式和偏移规则。除非你有意将一个 SystemVerilog 数组连接到你的设计中,否则将其用作 conversion/checking 工具。
AUTO.2 最小Vivado批处理 生成脚本
对于重复 生成,保留一个小的 Tcl 包装器并保存日志。具体项目脚本因板卡文件夹而异,但流程应该很乏味:
创建:
C:/dma/tools/buildpcileechbatch.tcl
tcl
if { $argc < 2 } {
puts "Usage: vivado -mode batch -source buildpcileechbatch.tcl -tclargs "
exit 1
}
set board_dir [lindex $argv 0]
set jobs [lindex $argv 1]
cd $board_dir
if {[file exists vivadogenerateproject.tcl]} {
source vivadogenerateproject.tcl
}
if {[file exists vivado_build.tcl]} {
source vivado_build.tcl
} else {
resetrun synth1
launchruns synth1 -jobs $jobs
waitonrun synth_1
launchruns impl1 -tostep writebitstream -jobs $jobs
waitonrun impl_1
}
openrun impl1
reporttimingsummary -file buildtimingsummary.rpt
reportutilization -file buildutilization.rpt
puts "Build finished."
puts "Timing report: [file normalize buildtimingsummary.rpt]"
puts "Utilization report: [file normalize build_utilization.rpt]"
从Vivado Tcl Shell 或能看到 vivado.bat 的 PowerShell 运行:
vivado.bat -mode batch -source C:\dma\tools\buildpcileechbatch.tcl -tclargs C:\dma\pcileech-fpga\PCIeSquirrel 8
每次 生成后归档:
Vivado version
Windows version
board folder
FPGA part/package
build log
buildtimingsummary.rpt
build_utilization.rpt
bit/bin file
SHA256 hash
flash tool screenshot
cold boot result
附录 A:高级Linux原始采集
不能替代 Windows 工作流,小白在第一次 Windows 生成时不需要。当可以干净采集时,比截图更好。
在以下情况下使用此附录:
· 你想要完整的4KB配置空间字节采集;
· 你想要 BAR 二进制转储;
· 你想要 Linux 端 setpci 写入测试;
· 你想要比单靠GUI截图更强大的设备-bit设备差异。
不要使用此附录来假装 Linux 是强制的。 不是。Windows-优先仍然是主要路线。
A.1 确定设备 BDF
在 Linux 设备采集机上:
lspci -nn
lspci -Dnn
例子:
0000:03:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8125 2.5GbE Controller [10ec:8125] (rev 05)
设置:
BDF=0000:03:00.0
mkdir -p donor-capture
A.2 采集可读 PCIe 数据
lspci -s "$BDF" -nn > donor-capture/lspci-nn.txt
lspci -s "$BDF" -vvv > donor-capture/lspci-vvv.txt
lspci -s "$BDF" -xxxx > donor-capture/config-256.txt
lspci -s "$BDF" -xxxxxxx > donor-capture/config-4096.txt
这会给你带来:
| 文件 | 用途 |
|---|---|
| lspci-nn.txt | 身份和类别摘要 |
| lspci-vvv.txt | BAR,功能,链路信息,MSI/MSI-X |
| config-256.txt | 标准 256 字节配置视图 |
| config-4096.txt | 可访问时的扩展 4KB 配置视图 |
如果lspci -xxxxxxx 没有显示完整的 4KB 视图, 记录该限制。不要静默地称其为完整的原始采集。
A.3 采集原始 4KB 配置字节
cp "/sys/bus/pci/devices/$BDF/config" donor-capture/config-4096.bin
hexdump -C donor-capture/config-4096.bin | head -40
预期:
00000000 ec 10 25 81 …
该示例将对应于:
Vendor ID: 10ec
Device ID: 8125
如果文件仅为 256 字节或无法读取, 记录下来:
Capture limitation: Linux sysfs config file did not provide full 4KB.
A.4 采集 BAR 资源布局
cat "/sys/bus/pci/devices/$BDF/resource" > donor-capture/resource.txt
ls -l "/sys/bus/pci/devices/$BDF/resource"* > donor-capture/resource-files.txt
使用 来映射:
· BAR 索引;
· 分配的运行时地址;
· 资源标志;
· 哪个 resourceN 文件映射到哪个 BAR。
再次提醒:运行时 BAR 地址不是bit常量。不要将其粘贴到配置空间 COE。
A.5 BAR 二进制转储
转储 BAR0 的前 4KB:
sudo dd if="/sys/bus/pci/devices/$BDF/resource0" of=donor-capture/bar0-4k.bin bs=4096 count=1 iflag=direct
hexdump -C donor-capture/bar0-4k.bin | head -40
如果 BAR0 更大且需要更多:
sudo dd if="/sys/bus/pci/devices/$BDF/resource0" of=donor-capture/bar0-64k.bin bs=4096 count=16 iflag=direct
记录转储时的时间:
BAR dump state:
- before driver bind:
- after driver bind:
- after device reset:
- after MSI/MSI-X enabled:
- after normal traffic:
BAR 读取可能会对某些设备产生副作用。在实验室设备上采集,而不是在生产系统上。
A.6 VFIO/IOMMU 注意
一些采集工作流程将设备绑定到 vfio-pci,以防止正常驱动程序访问。
这通常要求在设备采集机器上启用 IOMMU。
与 DMA bit设备测试(可能禁用 IOMMU)不冲突。不同的机器,不同的用途。
A.7 离线 4096 字节物理配置转储
Windows-first 仍然是主要方法。但是当 Windows HVCI/VBS、内核隔离或驱动策略阻止 Arbor/TeleScan 读取超过 0x100 时,停止猜测。将设备机移动到 Linux 实验箱,并获取原始字节。
输出不美观。一个 4096 字节的物理配置空间工件,可以在审查、哈希和脚本转换中幸存。
提示 - 安全启动和内核锁定当启用安全启动并锁定内核时,一些 Linux 发行版会限制 PCI 的原始配置或 MMIO 访问。如果 /sys/bus/pci/devices/$BDF/config 拒绝完整读取, 在采集机器上禁用安全启动,重启,然后重试。记录状态。不要将失败的 4KB 采集悄悄降级为截图。
硬工作流程:
!/usr/bin/env bash
set -euo pipefail
BDF="${1:-0000:01:00.0}"
OUT="${2:-donor-offline-capture}"
mkdir -p "$OUT"
echo "[*] Capture target: $BDF"
echo "[*] Output folder: $OUT"
if command -v mokutil >/dev/null 2>&1; then
mokutil --sb-state | tee "$OUT/secure-boot-state.txt"
else
echo "mokutil not installed; Secure Boot state not recorded" | tee "$OUT/secure-boot-state.txt"
fi
test -e "/sys/bus/pci/devices/$BDF/config"
test -r "/sys/bus/pci/devices/$BDF/config"
lspci -s "$BDF" -nn | tee "$OUT/lspci-nn.txt"
lspci -s "$BDF" -vvv | tee "$OUT/lspci-vvv.txt"
lspci -s "$BDF" -xxxx > "$OUT/config-256.txt"
lspci -s "$BDF" -xxxxxxx > "$OUT/config-4096.txt" || true
cat "/sys/bus/pci/devices/$BDF/resource" > "$OUT/resource.txt"
ls -l "/sys/bus/pci/devices/$BDF"/resource* > "$OUT/resource-files.txt"
sudo dd \
if="/sys/bus/pci/devices/$BDF/config" \
of="$OUT/donorextendedcfg.bin" \
bs=1 \
count=4096 \
status=progress
ACTUALSIZE="$(stat -c '%s' "$OUT/donorextended_cfg.bin")"
echo "$ACTUALSIZE" > "$OUT/donorextended_cfg.size"
if [ "$ACTUAL_SIZE" -ne 4096 ]; then
echo "[!] Bad capture size: expected 4096 bytes, got $ACTUAL_SIZE"
exit 1
fi
hexdump -C "$OUT/donorextendedcfg.bin" | tee "$OUT/donorextendedcfg.hexdump.txt" >/dev/null
sha256sum "$OUT"/* | tee "$OUT/SHA256SUMS"
echo "[+] Raw 4096-byte config capture complete:"
echo " $OUT/donorextendedcfg.bin"
运行:
chmod +x capture-donor-raw.sh
sudo ./capture-donor-raw.sh 0000:01:00.0 donor-rtl8125-raw
工作流程背后的直接指令是:
sudo dd if=/sys/bus/pci/devices/0000:01:00.0/config of=donorextendedcfg.bin bs=1 count=4096 status=progress
正常输出:
4096 bytes copied
donorextendedcfg.bin size = 4096
SHA256SUMS generated
0x000 bytes match donor VID/DID
0x100 contains extended capability header or documented zero
错误输出:
Permission denied
Operation not permitted
Only 256 bytes copied
0x100-0xFFF all zero when donor should expose extended capabilities
修复采集机器。不要对有问题的供体转储进行临时补丁处理。
A.8 将原始二进制转储转换为 PCILeech COE 和 WriteMask
脚本读取 donorextendedcfg.bin,对不应盲目克隆的字段进行清理,生成 pcileechcfgspace.coe,生成 pcileechcfgspacewritemask.coe,生成 vivadopcicoreconfig.tcl,并写入一个小的 Markdown 报告。
COE 和 WriteMask 的生成逻辑保持不变。升级部分是 Vivado Tcl 输出:脚本从原始配置字节和 BAR0 size/type 中提取供体身份字段 Linux resource.txt,然后生成一个 Tcl 文件,使 Xilinx PCIe 硬 IP 对齐,无需 GUI 手动输入。
是为适应本指南现有的文件映射而 生成的:
| 输出 | 安装位置 |
|---|---|
| pcileech*cfgspace.coe | ip/pcileech*cfgspace.coe |
| pcileechcfgspacewritemask.coe | ip/pcileechcfgspacewritemask.coe |
| vivadopcicore*config.tcl | 在重新生成 PCIe IP 输出产品之前,在 Vivado Tcl 控制台中引用源 |
| donor*raw_report.md | 保存在设备证据文件夹中 |
创建:
C:/dma/tools/linuxrawcfgtopcileech_coe.py
python
import argparse
import hashlib
from pathlib import Path
CONFIG_SIZE = 4096
DWORDCOUNT = CONFIGSIZE // 4
CAPIDPM = 0x01
CAPIDMSI = 0x05
CAPIDPCIE = 0x10
CAPIDMSIX = 0x11
EXTIDAER = 0x0001
EXTIDDSN = 0x0003
def u16le(buf, off):
return buf[off] | (buf[off + 1] << 8)
def u32le(buf, off):
return (
buf[off]
| (buf[off + 1] << 8)
| (buf[off + 2] << 16)
| (buf[off + 3] << 24)
)
def put_u16le(buf, off, value):
buf[off] = value & 0xFF
buf[off + 1] = (value >> 8) & 0xFF
def put_u32le(buf, off, value):
buf[off] = value & 0xFF
buf[off + 1] = (value >> 8) & 0xFF
buf[off + 2] = (value >> 16) & 0xFF
buf[off + 3] = (value >> 24) & 0xFF
def coedwordsfrom_bytes(buf):
return [u32le(buf, i * 4) for i in range(DWORD_COUNT)]
def write_coe(path, dwords):
with open(path, "w", encoding="ascii", newline="\n") as f:
f.write("memoryinitializationradix=16;\n")
f.write("memoryinitializationvector=\n")
for index, value in enumerate(dwords):
f.write(f"{value:08X}")
f.write(";\n" if index == len(dwords) - 1 else ",\n")
def sha256_file(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for block in iter(lambda: f.read(1024 * 1024), b""):
h.update(block)
return h.hexdigest()
def walkstandardcaps(buf):
caps = []
ptr = buf[0x34]
seen = set()
while ptr:
if ptr in seen:
raise SystemExit(f"Standard capability loop at 0x{ptr:02X}")
if ptr < 0x40 or ptr > 0xFC:
raise SystemExit(f"Bad standard capability pointer 0x{ptr:02X}")
if ptr & 0x03:
raise SystemExit(f"Unaligned standard capability pointer 0x{ptr:02X}")
seen.add(ptr)
cap_id = buf[ptr]
next_ptr = buf[ptr + 1]
caps.append((ptr, capid, nextptr))
ptr = next_ptr
return caps
def walkextendedcaps(buf):
caps = []
ptr = 0x100
seen = set()
while ptr:
if ptr in seen:
raise SystemExit(f"Extended capability loop at 0x{ptr:03X}")
if ptr < 0x100 or ptr >= CONFIG_SIZE:
raise SystemExit(f"Bad extended capability pointer 0x{ptr:03X}")
if ptr & 0x03:
raise SystemExit(f"Unaligned extended capability pointer 0x{ptr:03X}")
hdr = u32le(buf, ptr)
if hdr == 0:
break
ext_id = hdr & 0xFFFF
version = (hdr >> 16) & 0xF
next_ptr = (hdr >> 20) & 0xFFF
caps.append((ptr, extid, version, nextptr))
seen.add(ptr)
ptr = next_ptr
return caps
def maskbits(maskdwords, byteoffset, bitcount, bit_mask):
for bit in range(bit_count):
if bit_mask & (1 << bit):
absolutebit = byteoffset * 8 + bit
dwordindex = absolutebit // 32
bitindex = absolutebit % 32
if dwordindex >= len(maskdwords):
raise SystemExit(f"WriteMask bit outside config space: byte 0x{byte_offset:03X}")
maskdwords[dwordindex] |= (1 << bit_index)
def sanitizecfg(buf, keepcommand, keep_bars):
out = bytearray(buf)
if not keep_command:
status = u16le(out, 0x06)
put_u16le(out, 0x04, 0x0000)
put_u16le(out, 0x06, status)
if not keep_bars:
for off in range(0x10, 0x28, 4):
put_u32le(out, off, 0x00000000)
return out
def parselinuxresource_file(path):
entries = []
with open(path, "r", encoding="ascii", errors="strict") as f:
for index, line in enumerate(f):
parts = line.strip().split()
if len(parts) < 3:
continue
start = int(parts[0], 16)
end = int(parts[1], 16)
flags = int(parts[2], 16)
if start == 0 and end == 0:
size = 0
elif end >= start:
size = end - start + 1
else:
size = 0
entries.append({
"index": index,
"start": start,
"end": end,
"flags": flags,
"size": size,
})
return entries
def vivadobarsizename(sizebytes):
size_map = {
128: "128_Bytes",
256: "256_Bytes",
512: "512_Bytes",
1024: "1_KB",
2048: "2_KB",
4096: "4_KB",
8192: "8_KB",
16384: "16_KB",
32768: "32_KB",
65536: "64_KB",
131072: "128_KB",
262144: "256_KB",
524288: "512_KB",
1048576: "1_MB",
2097152: "2_MB",
4194304: "4_MB",
8388608: "8_MB",
16777216: "16_MB",
33554432: "32_MB",
67108864: "64_MB",
134217728: "128_MB",
268435456: "256_MB",
}
if sizebytes not in sizemap:
raise SystemExit(f"Unsupported BAR size for Vivado Tcl mapping: {size_bytes} bytes")
return sizemap[sizebytes]
def buildbar0vivadoconfig(buf, resourceentries, overridebar0size):
bar0_raw = u32le(buf, 0x10)
bar0isio = bool(bar0_raw & 0x1)
bar0type = "IO" if bar0is_io else "Memory"
if overridebar0size is not None:
bar0sizebytes = overridebar0size
elif resourceentries and len(resourceentries) >= 1:
bar0sizebytes = resource_entries[0]["size"]
else:
raise SystemExit(
"BAR0 size cannot be derived from config bytes alone. "
"Place Linux resource.txt next to donorextendedcfg.bin or pass --bar0-size-bytes."
)
bar0enabled = bar0size_bytes > 0
bar0size = vivadobarsizename(bar0sizebytes) if bar0enabled else "4KB"
return {
"enabled": bar0_enabled,
"sizebytes": bar0size_bytes,
"size": bar0_size,
"type": bar0_type,
"raw": bar0_raw,
}
def writevivadopcicoretcl(path, vendorid, deviceid, revisionid, classcode, subsystemvendor, subsystemid, bar0_config):
bar0enabledtext = "true" if bar0_config["enabled"] else "false"
lines = [
"# Auto-generated from donor physical PCIe config capture.",
"# Do not hand-type these values in Vivado GUI.",
"# Let code align code.",
"",
"set pcieip [getips pcie7x0]",
"if { [llength $pcie_ip] != 1 } {",
" error \"Expected exactly one PCIe IP named pcie7x0\"",
"}",
"",
"# Auto-generated PCIe Core alignment from donor physical config capture.",
"# Do not align IDs and BAR sizes by hand in Vivado GUI.",
"set_property -dict [list \",
f" CONFIG.vendorid {{{vendorid:04X}}} \",
f" CONFIG.deviceid {{{deviceid:04X}}} \",
f" CONFIG.revisionid {{{revisionid:02X}}} \",
f" CONFIG.classcode {{{classcode:06X}}} \",
f" CONFIG.subsystemvendorid {{{subsystem_vendor:04X}}} \",
f" CONFIG.subsystemid {{{subsystemid:04X}}} \",
f" CONFIG.bar0enabled {{{bar0enabled_text}}} \",
f" CONFIG.bar0size {{{bar0config['size']}}} \",
f" CONFIG.bar0type {{{bar0config['type']}}} \",
"] [getips pcie7x_0]",
"",
"reportproperty [getips pcie7x0]",
"",
]
with open(path, "w", encoding="utf-8", newline="\n") as f:
f.write("\n".join(lines))
def buildwritemask(buf, standardcaps, extended_caps):
mask = [0] * DWORD_COUNT
Type 0 header basics.
Command Register bits: I/O Space, Memory Space, Bus Master, Interrupt Disable.
Status RW1C is not modeled by a plain mask; leave it read-only here.
mask_bits(mask, 0x04, 16, 0x0407)
Interrupt Line is commonly writable. Interrupt Pin remains read-only.
mask_bits(mask, 0x3C, 8, 0xFF)
for ptr, cap_id, _nextptr in standardcaps:
if capid == CAPID_PM:
PMCSR at PM capability + 0x04:
bits 1:0 PowerState, bit 8 PME Enable.
bit 15 PME Status is RW1C; do not claim plain WriteMask models it.
mask_bits(mask, ptr + 0x04, 16, 0x0103)
elif capid == CAPID_MSI:
msg_ctl = u16le(buf, ptr + 0x02)
is64bit = bool(msgctl & (1 << 7))
pervectormasking = bool(msg_ctl & (1 << 8))
Message Control: MSI Enable and Multiple Message Enable.
mask_bits(mask, ptr + 0x02, 16, 0x0071)
Message Address.
mask_bits(mask, ptr + 0x04, 32, 0xFFFFFFFF)
if is_64bit:
mask_bits(mask, ptr + 0x08, 32, 0xFFFFFFFF)
data_off = ptr + 0x0C
mask_off = ptr + 0x10
else:
data_off = ptr + 0x08
mask_off = ptr + 0x0C
Message Data.
maskbits(mask, dataoff, 16, 0xFFFF)
Per-vector Mask bits are writable; Pending bits are not.
if pervectormasking:
maskbits(mask, maskoff, 32, 0xFFFFFFFF)
elif capid == CAPID_MSIX:
MSI-X Message Control: Function Mask and MSI-X Enable.
Table/PBA behavior lives in BAR memory, not in this config mask.
mask_bits(mask, ptr + 0x02, 16, 0xC000)
elif capid == CAPID_PCIE:
Device Control and Link Control are writable in real devices.
Refine per donor after readback.
mask_bits(mask, ptr + 0x08, 16, 0xFFFF)
mask_bits(mask, ptr + 0x10, 16, 0xFFFF)
for ptr, ext_id, _version, nextptr in extended_caps:
if extid == EXTID_AER:
AER status registers are RW1C and need real logic for perfect behavior.
Masks/control registers are writable.
mask_bits(mask, ptr + 0x08, 32, 0xFFFFFFFF) # Uncorrectable Error Mask
mask_bits(mask, ptr + 0x0C, 32, 0xFFFFFFFF) # Uncorrectable Error Severity
mask_bits(mask, ptr + 0x14, 32, 0xFFFFFFFF) # Correctable Error Mask
mask_bits(mask, ptr + 0x18, 32, 0xFFFFFFFF) # Advanced Error Cap/Control
elif extid == EXTID_DSN:
DSN is identity data. Keep it read-only.
pass
return mask
def formatcapsreport(standardcaps, extendedcaps):
lines = []
lines.append("## Standard Capabilities")
if standard_caps:
for ptr, capid, nextptr in standard_caps:
lines.append(f"- 0x{ptr:02X}: id=0x{capid:02X}, next=0x{nextptr:02X}")
else:
lines.append("- none")
lines.append("")
lines.append("## Extended Capabilities")
if extended_caps:
for ptr, extid, version, nextptr in extended_caps:
lines.append(f"- 0x{ptr:03X}: id=0x{extid:04X}, version={version}, next=0x{nextptr:03X}")
else:
lines.append("- none")
return "\n".join(lines)
raw = inputpath.readbytes()
if len(raw) != CONFIG_SIZE:
raise SystemExit(f"Expected 4096 bytes, got {len(raw)} bytes: {input_path}")
vendor_id = u16le(raw, 0x00)
device_id = u16le(raw, 0x02)
revision_id = raw[0x08]
class_code = (raw[0x0B] << 16) | (raw[0x0A] << 8) | raw[0x09]
subsystem_vendor = u16le(raw, 0x2C)
subsystem_id = u16le(raw, 0x2E)
standardcaps = walkstandard_caps(raw) if (u16le(raw, 0x06) & (1 << 4)) else []
extendedcaps = walkextended_caps(raw)
resourcepath = Path(args.resourcefile) if args.resourcefile else (inputpath.parent / "resource.txt")
resourceentries = parselinuxresourcefile(resourcepath) if resourcepath.exists() else []
bar0config = buildbar0vivadoconfig(raw, resourceentries, args.bar0size_bytes)
sanitized = sanitizecfg(raw, keepcommand=args.keepcommand, keepbars=args.keep_bars)
cfgdwords = coedwordsfrombytes(sanitized)
maskdwords = buildwritemask(raw, standardcaps, extendedcaps)
cfgpath = outputdir / "pcileech_cfgspace.coe"
maskpath = outputdir / "pcileechcfgspacewritemask.coe"
tclpath = outputdir / "vivadopcicore_config.tcl"
reportpath = outputdir / "donorrawreport.md"
writecoe(cfgpath, cfg_dwords)
writecoe(maskpath, mask_dwords)
writevivadopcicoretcl(
tcl_path,
vendor_id,
device_id,
revision_id,
class_code,
subsystem_vendor,
subsystem_id,
bar0_config,
)
report = []
report.append("# Donor Raw Config Conversion Report")
report.append("")
report.append(f"- Input: {input_path}")
report.append(f"- Input SHA256: {sha256_file(input_path)}")
report.append(f"- Linux resource file: {resource_path}")
report.append(f"- Vendor ID: 0x{vendor_id:04X}")
report.append(f"- Device ID: 0x{device_id:04X}")
report.append(f"- Revision ID: 0x{revision_id:02X}")
report.append(f"- Class Code: 0x{class_code:06X}")
report.append(f"- Subsystem Vendor ID: 0x{subsystem_vendor:04X}")
report.append(f"- Subsystem ID: 0x{subsystem_id:04X}")
report.append(f"- BAR0 enabled: {bar0_config['enabled']}")
report.append(f"- BAR0 raw config DWORD: 0x{bar0_config['raw']:08X}")
report.append(f"- BAR0 size bytes: {bar0_config['size_bytes']}")
report.append(f"- BAR0 Vivado size: {bar0_config['size']}")
report.append(f"- BAR0 Vivado type: {bar0_config['type']}")
report.append(f"- Command register cleared in COE: {not args.keep_command}")
report.append(f"- Runtime BAR values cleared in COE: {not args.keep_bars}")
report.append("")
report.append(formatcapsreport(standardcaps, extendedcaps))
report.append("")
report.append("## Generated Files")
report.append(f"- {cfg_path.name}")
report.append(f"- {mask_path.name}")
report.append(f"- {tcl_path.name}")
report.append("")
report.append("## Hard Warnings")
report.append("- Plain WriteMask does not perfectly model RW1C status behavior.")
report.append("- MSI-X table and PBA behavior must be implemented in BAR memory.")
report.append("- Runtime BAR addresses from Linux/Windows are cleared by default.")
report.append("- BAR size for Vivado Tcl comes from Linux resource.txt or --bar0-size-bytes, not from the runtime BAR DWORD alone.")
report.append("- Review every writable bit against donor readback before calling it final.")
with open(report_path, "w", encoding="utf-8", newline="\n") as f:
f.write("\n".join(report) + "\n")
print("Generated:", cfg_path)
print("Generated:", mask_path)
print("Generated:", tcl_path)
print("Generated:", report_path)
print(f"VID/DID: {vendorid:04X}:{deviceid:04X}")
print(f"Class/Revision: {classcode:06X} rev {revisionid:02X}")
print(f"Subsystem: {subsystemvendor:04X}:{subsystemid:04X}")
print(f"BAR0: enabled={bar0config['enabled']} size={bar0config['size']} type={bar0_config['type']}")
print("Standard capabilities:", len(standard_caps))
print("Extended capabilities:", len(extended_caps))
if name == "main":
main()
运行:
python3 linuxrawcfgtopcileechcoe.py donor-rtl8125-raw/donorextended_cfg.bin donor-rtl8125-raw/generated
脚本会自动查找:
donor-rtl8125-raw/resource.txt
该文件是自动提取 BAR0 大小所必需的。仅 PCI 配置字节显示运行时 BAR flags/address 位; 不能证明孔径大小。Linux resource.txt 文件提供分配的资源窗口大小。
Windows PowerShell 也可用:
python C:\dma\tools\linuxrawcfgtopcileechcoe.py C:\dma\donor\rtl8125\donorextended_cfg.bin C:\dma\donor\rtl8125\generated
安装生成的文件:
generated/pcileechcfgspace.coe -> ip/pcileechcfgspace.coe
generated/pcileechcfgspacewritemask.coe -> ip/pcileechcfgspacewritemask.coe
generated/vivadopcicore_config.tcl -> source in Vivado Tcl Console
generated/donorrawreport.md -> donor evidence folder
如果存在此 Tcl,不要用眼睛和手在 Vivado GUI 中输入 ID 和 BAR 大小。在 Vivado Tcl 控制台中引用生成的文件:
source C:/dma/donor/rtl8125/generated/vivadopcicore_config.tcl
让代码对齐代码。
生成的 Tcl 示例:
Auto-generated from donor physical PCIe config capture.
Do not hand-type these values in Vivado GUI.
Let code align code.
set pcieip [getips pcie7x0]
if { [llength $pcie_ip] != 1 } {
error "Expected exactly one PCIe IP named pcie7x0"
}
Auto-generated PCIe Core alignment from donor physical config capture.
Do not align IDs and BAR sizes by hand in Vivado GUI.
set_property -dict [list \
CONFIG.vendor_id {10EC} \
CONFIG.device_id {8125} \
CONFIG.revision_id {05} \
CONFIG.class_code {020000} \
CONFIG.subsystemvendorid {10EC} \
CONFIG.subsystem_id {0123} \
CONFIG.bar0_enabled {true} \
CONFIG.bar0size {16KB} \
CONFIG.bar0_type {Memory} \
] [getips pcie7x_0]
reportproperty [getips pcie7x0]
在执行 source 后:
Reset output products for the PCIe IP.
Regenerate output products.
Rebuild.
Then verify target readback.
正常脚本输出:
Generated: donor-rtl8125-raw/generated/pcileech_cfgspace.coe
Generated: donor-rtl8125-raw/generated/pcileechcfgspacewritemask.coe
Generated: donor-rtl8125-raw/generated/vivadopcicore_config.tcl
Generated: donor-rtl8125-raw/generated/donorrawreport.md
VID/DID: 10EC:8125
Class/Revision: 020000 rev 05
Subsystem: 10EC:0123
BAR0: enabled=True size=16_KB type=Memory
Standard capabilities: 5
Extended capabilities: 3
错误脚本输出:
Expected 4096 bytes, got 256 bytes
BAR0 size cannot be derived from config bytes alone
Standard capability loop at 0x40
Bad extended capability pointer 0xFFC
如果脚本失败,说明设备的采集数据有问题,或者设备的布局需要你手动检查。不要强行让脚本从损坏的证据中生成 COE。
A.9 原始转储转换审核
在 Vivado 之前:
[ ] donorextendedcfg.bin is exactly 4096 bytes
[ ] SHA256SUMS saved
[ ] 0x00 VID/DID matches donor
[ ] 0x08 class/revision matches donor
[ ] 0x2C subsystem IDs match donor
[ ] 0x34 capability pointer walks cleanly
[ ] 0x100 extended capability chain walks cleanly or is documented absent
[ ] runtime BAR values were cleared unless you intentionally own that path
[ ] Command register initial state was reviewed
[ ] PMCSR offset found from capability chain
[ ] MSI/MSI-X writable fields found from capability chain
[ ] RW1C limitations documented
[ ] generated COE files installed into the correct Vivado IP
[ ] vivadopcicore_config.tcl generated
[ ] Vivado PCIe IP sourced from Tcl instead of hand-entered GUI values
[ ] PCIe IP output products regenerated after Tcl source
比截图更好。盲目转换的原始字节仍然是一种快速生成损坏的 bit的方法。
A.10 Linux 原始采集 检查
当满足以下条件时,Linux 原始采集已完成:
· lspci -nn 已保存;
· lspci -vvv 已保存;
· 如果不完整,则尝试并记录限制;
· 如果可访问,则复制 /sys/bus/pci/devices/$BDF/config;
· donorextendedcfg.bin 在声称完全采集时正好为 4096 字节;
· pcileech_cfgspace.coe 由原始字节生成或限制已记录;
· pcileechcfgspacewritemask.coe 已生成或 WriteMask 来源已记录;
· 资源布局已保存;
· 必需的 BAR 二进制转储已保存;
· SHA256 哈希已保存;
· 采集期间的设备状态已记录。
附录 B:高级验证命令
这些命令是高级验证工具。补充 Windows 回读;并不替代 Windows 首选路径。
B.1 Linux 配置回读
在刷新bit设备bit并启动 Linux 测试bit设备后:
lspci -nn
lspci -s "$BDF" -vvv
lspci -s "$BDF" -xxxx
lspci -s "$BDF" -xxxxxxx
与设备比较:
0x00 VID/DID
0x08 Revision/Class
0x10 BAR0
0x2C Subsystem IDs
0x34 Capability Pointer
0x100 Extended Capability Header
将差异分类为:
· 稳定字段不匹配;
· 运行时字段差异;
· 故意差异;
· 错误。
B.2 setpci 写入测试
仅在实验室机器上使用。
读取命令:
sudo setpci -s "$BDF" COMMAND
保存原始值:
ORIG=$(sudo setpci -s "$BDF" COMMAND)
测试可写命令位:
sudo setpci -s "$BDF" COMMAND=0006
sudo setpci -s "$BDF" COMMAND
sudo setpci -s "$BDF" COMMAND="$ORIG"
测试只读身份行为:
sudo setpci -s "$BDF" 00.L=ffffffff
sudo setpci -s "$BDF" 00.L
预期:
· VID/DID 不应变为 ffff:ffff;
· 允许的命令位应改变;
· 不支持的位不应保留;
· 设备应保持存在。
如果命令未改变, 检查 rw[206] 在 src/pcileech_fifo.sv 中以及 WriteMask COE。
如果 VID/DID 发生改变,你的 WriteMask 是危险的。
B.3 BAR 在 Linux 上的回读
BAR0的前4KB:
sudo dd if="/sys/bus/pci/devices/$BDF/resource0" of=target-bar0-4k.bin bs=4096 count=1 iflag=direct
hexdump -C target-bar0-4k.bin | head -40
仅在预计内容是静态时比较哈希:
sha256sum donor-bar0-4k.bin target-bar0-4k.bin
不要期望状态寄存器、计数器、中断表或状态字段的哈希值匹配。
B.4 PCILeech 探测和基准测试
从PCILeech控制机器:
pcileech probe
pcileech benchmark
记录:
pcileech version:
connection type:
board:
target:
probe output:
benchmark output:
stability notes:
PCILeech基准测试证明DMA通信路径可用。这并不证明设备BAR的行为、WriteMask的正确性或驱动程序的兼容性。
B.5 生成归档和哈希
Linux:
sha256sum *.bit *.bin > SHA256SUMS.txt
Windows PowerShell:
Get-FileHash .\firmware.bit -Algorithm SHA256
Get-FileHash .\firmware.bin -Algorithm SHA256
推荐归档:
builds/2026-xx-xx-board-donor-v1/
notes.md
git-diff.patch
vivado-version.txt
windows-version.txt
upstream-commit.txt
board-folder.txt
fpga-part-package.txt
build.log
timing-summary.rpt
utilization.rpt
firmware.bit
firmware.bin
SHA256SUMS.txt
donor-config-4096.bin
target-config-4096.bin
donor-bar0-4k.bin
target-bar0-4k.bin
pcileech-probe.txt
pcileech-benchmark.txt
screenshots/
不要分发或归档没有来源的名为final.bin的文件。
B.6 有用的Vivado Tcl命令
source vivadogenerateproject.tcl -notrace
source vivado_build.tcl -notrace
reporttimingsummary -file timing_summary.txt
report_utilization -file utilization.txt
可选的IP管理检查:
reportipstatus
如果你手动编辑了生成的文件, 在每次重新生成 IP 后重新检查。
附录 C:术语表
| 术语 | 定义 |
|---|---|
| AER | 高级错误报告,一种 PCIe 扩展的错误报告和控制功能。 |
| BAR | 基址寄存器;声明 PCIe 设备所求的内存或 I/O 资源。 |
| BDF | PCI/PCIe 功能的总线:设备。功能地址。 |
| BRAM | FPGA 内的块 RAM。用于静态配置空间或 BAR 响应数据。 |
| CFGTLP | PCIe 配置事务层数据包。 |
| COE | Vivado 内存初始化文件,用于初始化 BRAM/ROM IP。 |
| 命令寄存器 | PCI 配置寄存器,控制 I/O 空间、内存空间、总线主控以及其他位。 |
| DSN | 设备序列号,一个 PCIe 扩展功能,包含 64 位序列值。 |
| DWORD | 32 位值。 |
| 端点 | PCIe 根复合体下的设备;设备和 DMA 板卡表现为端点。 |
| 扩展功能 | PCIe 功能结构,起始于配置空间偏移 0x100。 |
| IOMMU / VT-d / AMD-Vi | DMA remapping/protection 功能。在某些设备采集工作流中有用;根据bit设备设置可能会阻止 DMA 测试。 |
| MSI | 消息信号中断。中断数据通过 PCI 配置空间字段进行配置。 |
| MSI-X | 扩展 MSI。使用配置空间功能加上 table/PBA 结构体,位于 BAR 内。 |
| PMCSR | PM 功能中的 Control/Status 电源管理寄存器。 |
| PIO | 编程 I/O,通常用于简单的 BAR 访问处理。 |
| RW1C | Read/Write-1-to-Clear。写入 1 清除状态位。 |
| 影子配置空间 | BRAM 支持的配置空间响应路径,用于返回类似设备的配置字节。 |
| TLP | 事务层数据包,PCIe 事务使用的数据包格式。 |
| 写掩码 | 按位掩码控制主机写入可以更新的配置空间位。 |
| XCI | Xilinx 核心实例文件,包含 Vivado IP 配置。 |
| Zero4K | 简单的 4KB 静态 BAR 响应器。有用,但不是完整的 BAR 仿真。 |
附录 D:文件参考
| 文件 | 用途 |
|---|---|
| src/pcileechpciecfg*a7.sv | PCIe 配置管理,cfg*dsn,命令寄存器自动设置 rw[21],状态自动清除 rw[20]。 |
| src/pcileech*fifo.sv | CFGTLP 影子开关。影子 rw[203] 和影子写使能 rw[206] 在此。 |
| src/pcileechtlps128cfgspace*shadow.sv | 影子配置空间 read/write 逻辑和 WriteMask 应用。 |
| src/pcileechtlps128bar*controller.sv | BAR 路由和 BAR 响应器逻辑。 |
| ip/pcileech*cfgspace.coe | 影子配置空间 BRAM 初始化。 |
| ip/pcileechcfgspacewritemask.coe | 配置空间写掩码 ROM 初始化。 |
| ip/pcileechbarzero4k.coe | Zero4K BAR BRAM 初始化。 |
| pcie7x0.xci | Vivado PCIe IP 配置,包括 ID 和 BAR 设置(在暴露时)。 |
| .gen/sources1/ip/…/pcie7x0core*top.v | Vivado 生成的 PCIe 包装器。IP 重新生成时可能会被覆盖。 |
| vivadogenerateproject.tcl | 板级项目生成脚本。 使用正确板子文件夹中的脚本。 |
| vivado*build.tcl | 板级 生成脚本。 使用正确板子文件夹中的脚本。 |
| *.bit | Vivado bit文件。 |
| *.bin | Raw/programming 项目流程生成的镜像。 |
最后提醒:
· rw[203] 和影子rw[206] 位于 src/pcileech_fifo.sv。
· rw[21] 在 src/pcileechpciecfg_a7.sv 中是命令寄存器自动设置,而非主控终止使能。
· 生成的 .gen文件不是稳定的手写源代码。
· COE 文件必须附加到正确生成的 BRAM/ROM IP 并重新生成。
评论区