全设备仿真的定制固件开发指南


全设备仿真的定制固件开发指南

全设备仿真的定制固件开发指南


特别鸣谢慷慨捐助的传奇人物,我将很快与您联系。如果您愿意,请DM我,我会在本文中添加鸣谢并提供更多信息!

正在将此指南整理到维基中。欢迎提供帮助!


作者留言及指南状态:

我正在透明地分享这一切,因为最近的日子异常艰难。除了因欺诈性退款造成的巨大经济损失外,我还面临多重生活和健康困境,严重影响了我上网和投入项目的时间。坦率地说,在这些个人困难中,继续创作像本指南这样全面的资源已成为一项深刻的挣扎。

这预计是主指南的最后一次重大迭代。对于已经熟悉基本硬件概念(例如,FTDI芯片的功能)的更有经验的用户,我们将提供一个简洁的精简版。

如果您觉得这项工作有价值并能够提供帮助,任何形式的支持都将不胜感激。您的慷慨使我尽管面临持续的挑战,仍能继续为这个社区做出贡献。我真诚地希望本指南已经并将继续成为一份宝贵的资源。


纪念与献词

Ross

本指南谨献给
Ross Freeman (1947–1989) 的记忆

作为一位富有远见的工程师、杰出的密歇根人,以及Xilinx的联合创始人,Ross Freeman被广泛认为是现场可编程门阵列(FPGA)技术之父,该技术彻底改变了计算领域。

在1984年,半导体行业主要专注于固定功能芯片之时,Freeman敢于想象一种不同的范式:制造后可以重新编程的硬件。他的革命性专利(#4,870,302)和对可重构计算的不懈倡导,开启了一个四十年后仍在改变我们世界的科技范式。

他的开创性创新使得在无需承担传统ASIC开发高昂成本的情况下,快速原型化和部署定制芯片解决方案成为可能,从而使硬件设计民主化,并加速了无数领域的技术进步。

如今,Freeman的愿景驱动着人工智能、高性能计算、电信、汽车系统、航空航天应用以及他在世时仅是梦想的许多其他领域的尖端发展。

他于2009年被追授进入国家发明家名人堂,其遗产不仅体现在硅片中,更体现在挑战我们所有人质疑既定限制并想象新可能性的技术勇气精神中。

“FPGA的最终目标是制造可编程逻辑器件,以取代标准数字芯片。” — Ross Freeman


目录

第一部分:基础概念

  1. 引言
  2. 关键定义
  3. 设备兼容性
  4. 要求
  5. 收集捐赠设备信息
  6. 初始固件定制
  7. Vivado项目设置与定制

第二部分:中级概念与实现

  1. 高级固件定制
  2. 仿真设备特定功能
  3. 事务层数据包(TLP)仿真

第三部分:高级技术与优化

  1. 构建、烧录与测试
  2. 高级调试技术
  3. 故障排除
  4. 仿真精度与优化
  5. 固件开发最佳实践
  6. 其他资源
  7. 联系方式
  8. 支持与贡献

第一部分:基础概念


1. 引言

1.1 本指南的目的

本指南的总体目标是让您掌握开发基于现场可编程门阵列(FPGA)设备的定制直接内存访问(DMA)固件的知识和实践技能。这种专用固件允许您的FPGA精确地仿真其他PCIe(Peripheral Component Interconnect Express)硬件设备的身份和行为。这种仿真是一种强大的技术,在多个高级领域具有深远意义:

硬件安全研究

  • 漏洞发现:通过仿真设备,您可以创建一个受控环境,向主机驱动程序发送格式错误或意外数据,系统性地进行模糊测试,以发现可能通过硬件外设利用的漏洞(例如,缓冲区溢出、竞态条件)。
  • 驱动程序分析:观察操作系统和特定驱动程序如何与硬件交互。您可以仿真具有非标准配置或未文档化功能的设备,以了解驱动程序行为、识别安全假设或逆向工程专有协议。
  • 侧信道分析:虽然更复杂,但仿真设备可以通过精确控制外设操作,潜在地协助进行与通过时序或功耗分析进行信息泄漏相关的实验。

红队演练与渗透测试

  • 绕过安全措施:仿真一个看似良性或白名单的硬件设备(例如,一个常见的网卡或存储控制器),以获取DMA权限。一旦实现,这允许直接与系统内存交互,可能绕过在更高软件层运行的端点检测和响应(EDR)系统或反恶意软件解决方案。
  • 隐蔽持久性:仿真恶意设备可以提供一种隐蔽的方式来维护对受损系统的访问,因为它可能比基于软件的植入物更难检测。
  • 利用信任关系:系统通常对连接的硬件有隐式信任。定制固件可以通过模仿被授予特定权限或访问的设备来利用这一点。

系统调试与诊断

  • 可复现的测试平台:创建高度特定的硬件场景,以可靠地复现可能仅在特定设备状态或数据模式下发生的难以捉摸的错误。
  • 故障注入:故意仿真有缺陷的设备行为(例如,错误的TLP形成、延迟响应),以测试主机系统及其驱动程序的健壮性和错误处理能力。

硬件测试与验证

  • 驱动程序开发:在物理原型可用之前,或为了模拟比物理可访问的更广泛的硬件变体,针对仿真硬件配置文件测试新的或修改的驱动程序。
  • 合规性测试:虽然不能替代官方合规性测试,但仿真设备可以帮助预验证PCIe协议遵守的某些方面。

传统系统支持与互操作性

  • 仿真老旧、停产或难以采购的PCIe设备,以保持传统系统运行或弥合不同硬件代之间的兼容性差距。

通过学习本指南,您将熟练掌握:

  • 精细地从物理“捐赠”PCIe设备中提取识别属性和配置细节。
  • 修改和扩展现有开源FPGA固件框架(主要关注广泛使用的PCILeech-FPGA项目),以采用捐赠设备的身份。
  • 配置和利用以Xilinx Vivado为核心的专业FPGA开发工具链,以及Visual Studio Code等基本代码编辑工具。
  • 对PCIe架构的分层模型、DMA数据传输机制以及低级别复制硬件行为的固件开发细微之处,形成扎实的理解。

1.2 目标读者

本指南专为已具备计算机系统、硬件原理和软件开发基础到中级知识的个人量身定制。内容技术性强,并假定读者具备进行详细、低级别工作的能力。具体来说,它面向以下人群:

  • 固件开发人员:旨在为FPGA设计或改编固件的工程师,特别是涉及高速数据传输(DMA)和通过PCIe直接硬件接口操作的应用。强烈建议具备Verilog/VHDL背景和FPGA开发工具经验。
  • 硬件工程师:参与PCIe硬件设计、测试或验证的专业人员。本指南可以帮助创建复杂的测试线束或在更大的系统设计中仿真组件。预计熟悉PCIe协议和数字设计。
  • 网络安全专业人员与研究人员
    • 漏洞研究员与漏洞利用开发人员:希望探索硬件级攻击面或开发利用DMA的概念验证漏洞。操作系统内部、内存管理和驱动程序架构的理解至关重要。
    • 红队成员:寻求通过直接硬件操作来获取系统访问、持久性和数据窃取的先进技术操作员。
    • 数字取证与事件响应人员:虽然本指南侧重于攻击,但理解这些技术有助于识别和分析复杂的基于硬件的攻击。
  • FPGA爱好者与高级业余爱好者:有FPGA项目经验,渴望应对PCIe通信和硬件仿真等复杂挑战的个人。愿意深入研究数据手册和技术规范是关键。

学习曲线可能很陡峭,特别是如果PCIe或高级FPGA概念是新知识。然而,本指南旨在将复杂主题分解为可管理的步骤。

1.3 如何使用本指南

本指南分为三个逻辑递进的部分,旨在逐步构建您的知识:

  • 第一部分:基础概念:这第一部分至关重要。它介绍了核心术语、PCIe和DMA的基本原理、必要的硬件和软件堆栈(包括Xilinx Vivado和PCILeech-FPGA框架等工具的设置说明),以及从目标“捐赠”设备获取重要信息和进行基本固件修改的初始程序。强烈建议按顺序彻底学习本部分。
  • 第二部分:中级概念与实现:(后续章节)在基础知识之上,本部分将引导您进行更高级的固件定制。主题将包括微调PCIe操作参数、仿真设备特定寄存器和功能(如电源管理状态和消息信号中断 - MSI/MSI-X),以及初步理解事务层数据包(TLP)的构建和解释。
  • 第三部分:高级技术与优化:(后续章节)最后一部分将探讨复杂的调试方法(包括使用集成逻辑分析仪 - ILA和外部PCIe协议分析仪)、优化固件性能和仿真精度的技术、常见和复杂问题的全面故障排除,以及关于最佳实践的关键讨论,特别关注开发和部署仿真PCIe设备的安全影响。

学习本指南的步骤

  • 顺序学习:特别是对于第一部分和第二部分,请按顺序学习各节,因为后面的概念建立在前面的基础之上。
  • 动手实践:这是一份实践指南。请在您自己的硬件上积极执行设置步骤、代码修改和实验。
  • 适应您的环境:文件路径、特定设备ID和软件版本可能有所不同。理解指令背后的概念,以使其适应您的特定设置。
  • 查阅外部资源:PCIe规范和FPGA文档是您的最终参考。本指南进行简化和引导,但深入研究通常需要查阅原始资料。
  • 迭代开发:固件开发很少是线性的。预期会进行迭代、调试和改进您的设计。广泛使用故障排除部分和调试技术。

您将使用HDL(PCILeech-FPGA中的SystemVerilog)、FPGA综合和实现工具(Vivado),并可能使用主机端编程工具和PCIe分析实用程序。


2. 关键定义

牢固掌握以下术语对于理解PCIe设备仿真和定制固件开发的复杂性至关重要。这些术语将在整个指南中广泛使用。

  • DMA (Direct Memory Access) (直接内存访问):

    • 定义:现代计算机体系结构的一项基本功能,允许硬件外设(如网卡、GPU或您的基于FPGA的仿真设备)直接读取和写入主系统内存(RAM),而无需CPU参与每个字节的传输。
    • 重要性:DMA对于高性能I/O操作至关重要。通过将数据传输任务从CPU卸载,它使CPU能够执行其他计算,显著提高整体系统吞吐量和效率。在本指南中,您的FPGA将利用DMA与主机系统的内存进行交互,这是一种在安全研究和红队演练中经常被利用的强大功能。
  • PCIe (Peripheral Component Interconnect Express) (外围组件互连高速):

    • 定义:一种高速串行计算机扩展总线标准,旨在取代旧的总线标准,如PCI、PCI-X和AGP。它采用点对点拓扑结构,每个设备通过独立的串行链路连接到根联合体(通常是芯片组或CPU的一部分)。通信通过数据包进行。
    • 重要性:PCIe是连接高性能外设到主板的主导标准。理解其协议、分层架构(物理层、数据链路层、事务层)和配置机制对于仿真任何现代硬件设备至关重要。
  • TLP (Transaction Layer Packet) (事务层数据包):

    • 定义:PCIe协议事务层的数据交换基本单位。TLP负责在PCIe设备之间传输请求(例如,内存读/写、I/O读/写、配置读/写)和完成(对请求的响应)。每个TLP由一个报头、一个可选的数据有效载荷和一个可选的端到端CRC(ECRC)组成。
    • 重要性:为了精确仿真设备,您的FPGA固件必须能够正确地形成、传输、接收和解释与捐赠设备行为匹配的TLP。理解TLP类型、格式和流控制对于高级仿真至关重要。
  • BAR (Base Address Register) (基地址寄存器):

    • 定义:位于PCIe设备的配置空间内,BAR是特殊的寄存器,设备通过它们向主机系统请求地址空间资源。一个设备最多可以有六个32位BAR(或更少,或成对的32位BAR可以形成64位BAR)。这些寄存器定义了设备用于向主机CPU公开其寄存器和内部内存的内存映射I/O(MMIO)区域或I/O端口区域的起始地址和大小。
    • 重要性:当主机系统枚举PCIe设备时,它会读取BAR以确定设备的内存和I/O要求,然后分配并用系统中物理地址图中的实际基地址来编程这些BAR。您的仿真设备必须精确定义其BAR以匹配捐赠设备,以便主机操作系统和驱动程序能够正确地与其交互。
  • FPGA (Field-Programmable Gate Array) (现场可编程门阵列):

    • 定义:一种集成电路(IC),可以在制造后由设计者或客户进行配置——因此称为“现场可编程”。FPGA包含一个可编程逻辑块阵列和可重构互连的层次结构,允许这些块“连接”起来以实现定制数字逻辑电路。
    • 重要性:FPGA是本指南中使用的核心硬件。其可重构特性使其成为仿真其他硬件设备的理想选择,因为您可以定义精确的逻辑和接口来模仿捐赠设备的PCIe存在和行为。
  • MSI/MSI-X (Message Signaled Interrupts / Message Signaled Interrupts Extended) (消息信号中断 / 扩展消息信号中断):

    • 定义:允许PCIe设备通过向系统定义的内存地址写入特殊消息(TLP,特别是内存写入TLP)来向CPU传递中断的机制,而不是使用专用的物理中断线(如传统PCI)。MSI-X是MSI的增强版,提供更多的中断向量和更大的灵活性。
    • 重要性:大多数现代PCIe设备使用MSI或MSI-X以实现更高效、更灵活的中断处理。精确仿真通常需要实现捐赠设备选择的中断机制,包括配置MSI/MSI-X能力结构并正确生成中断消息。
  • DSN (Device Serial Number) (设备序列号):

    • 定义:一个64位全局唯一标识符,可由PCIe设备可选实现。如果存在,它通常位于设备配置空间内的扩展能力结构中。
    • 重要性:虽然并非所有设备都具有DSN,但某些驱动程序或管理软件可能会使用它进行唯一标识、许可或跟踪。正确仿真它对于完全透明和避免检测到仿真设备可能很重要。
  • PCIe Configuration Space (PCIe配置空间):

    • 定义:与每个PCIe功能(一个设备可以有多个功能)关联的标准化256字节(对于Type 0、端点设备)或4KB地址区域。此空间包含有关设备的重要信息,包括其厂商ID、设备ID、类别代码、修订ID、BAR、能力指针以及各种状态和控制寄存器。主机系统使用特殊的配置读和配置写TLP访问此空间。
    • 重要性:配置空间是PCIe设备的“身份证”。设备仿真的第一步就是将捐赠设备配置空间的相关部分精确复制到您的FPGA固件中。主机系统使用此信息来识别、配置和分配资源给设备。
  • Donor Device (捐赠设备):

    • 定义:您旨在在FPGA上仿真其身份和行为的物理PCIe硬件设备。该设备作为提取配置细节(厂商ID、设备ID、BAR设置、能力等)和行为模式的来源。
    • 重要性:您的仿真 fidelity 直接取决于您能够多么精确和完整地收集并复制捐赠设备的特性。
  • Root Complex (RC) (根联合体):

    • 定义:PCIe层级结构中将CPU和内存子系统连接到PCIe结构的实体。它代表CPU生成PCIe事务,并处理下游PCIe设备发起的事务。它还执行初始的总线枚举和配置。
    • 重要性:您的仿真设备在与主机系统通信时,将主要与根联合体(或与其连接的交换机)交互。
  • Endpoint (EP) (端点):

    • 定义:位于PCIe结构外围,消费或生产数据的一种PCIe设备。示例包括网卡、显卡、存储控制器以及您将要编程的FPGA设备。端点请求资源并向根联合体发起事务。
    • 重要性:在本指南中,您的FPGA将被编程为充当一个端点设备,仿真一个特定的捐赠端点。
  • HDL (Hardware Description Language) (硬件描述语言):

    • 定义:一种专用计算机语言,用于描述电子电路的结构、设计和操作,特别是数字逻辑电路。常见的HDL包括Verilog和VHDL。
    • 重要性:您将在PCILeech-FPGA项目中使用Verilog(特别是SystemVerilog,Verilog的扩展)来定义仿真设备的定制逻辑。
  • Bitstream (比特流):

    • 定义:加载到FPGA上的最终配置文件,用于编程其逻辑块和互连,从而实现您的定制硬件设计。它是FPGA开发工具(如Xilinx Vivado)的编译输出。
    • 重要性:生成和烧录正确的比特流是将定制固件部署到FPGA的最终步骤。

3. 设备兼容性

成功且精确的PCIe设备仿真取决于确保您选择的基于FPGA的硬件和主机系统配置完全兼容。本节详细介绍了支持的FPGA平台、关键的PCIe硬件注意事项以及设置开发环境所需的系统要求。

3.1 支持的基于FPGA的硬件

虽然本指南提供了一种可适用于各种基于FPGA的DMA硬件的通用方法,但我们的主要示例和具体说明将侧重于 Xilinx 7系列FPGA,由于其性能和可访问性的平衡,它们在开源DMA板中很常见。Squirrel DMA (35T) 卡因其受欢迎程度以及与PCILeech-FPGA框架的良好兼容性而受到强调。

定制PCIe IP核和开发硬件描述语言(HDL)逻辑的核心原则和技术广泛适用于以下FPGA系列和特定板卡:

  • Squirrel (Artix-7 35T)
    • 描述:一种广泛可用且经济高效的基于FPGA的DMA设备,采用Xilinx Artix-7 35T FPGA。它为标准内存采集任务以及各种基本到中级设备仿真项目提供了足够的逻辑资源和内存。它是初次接触基于FPGA的DMA的优秀起点。
    • 主要特点:Artix-7提供了良好的性能价格比,适用于教育和研究目的。
  • Enigma-X1 (Artix-7 75T)
    • 描述:与35T相比,提供增强的逻辑和内存资源的中级FPGA,通常基于Xilinx Artix-7 75T FPGA。这为更复杂的仿真场景、更大的内存映射区域或需要额外FPGA逻辑的更复杂的DMA操作提供了更大的灵活性。
    • 主要特点:增加的逻辑单元和块RAM(BRAM)支持更复杂的设计。
  • ZDMA (Artix-7 100T)
    • 描述:基于更高性能的Artix-7 100T FPGA,针对要求更高的内存交互和大量的读/写操作进行了优化。此板卡适用于大规模DMA解决方案、高吞吐量仿真或需要大量片上内存的项目。
    • 主要特点:100T变体在资源方面提供了显著升级,是突破仿真界限的理想选择。
  • Kintex-7 (K325T, K410T等)
    • 描述:代表高级别,Kintex-7 FPGA(例如K325T、K410T)为高度复杂的项目、大规模DMA解决方案以及需要更高PCIe通道数或速度(例如,Gen3 x8/x16)的应用提供了强大的功能。虽然价格更昂贵,但它们提供了更多的逻辑、DSP切片和内存,从而能够仿真高度复杂和苛刻的捐赠设备。
    • 主要特点:用于更快PCIe世代的高性能收发器,丰富的逻辑和内存资源,适用于复杂设计。

关于FPGA系列的重要说明:尽管原理相似,但不同的Xilinx 7系列FPGA(Artix-7、Kintex-7、Zynq-7000 PS/PL)之间,特定的IP核配置和时钟结构可能略有不同。请始终参考特定板卡文档和您所选FPGA系列的Xilinx PCIe IP核用户指南。PCILeech-FPGA项目通常提供板卡特定的Tcl脚本和源文件以简化此过程。

3.2 PCIe硬件注意事项

为了确保基于FPGA的DMA设备在仿真中平稳无限制地运行,需要仔细考虑一些PCIe特定和主机系统功能,并在某些情况下进行修改。

  • IOMMU / VT-d / AMD-Vi 设置
    • 建议:对于初始设置和测试,强烈建议在系统的BIOS/UEFI设置中禁用IOMMU(Intel的定向I/O虚拟化技术 - VT-d)或AMD的等效技术(AMD-Vi)
    • 理由:IOMMU是为DMA功能设备提供内存管理单元的硬件组件。它们执行地址转换,类似于CPU的MMU,并且可以强制执行内存访问权限。虽然它们对于安全和虚拟化(防止恶意设备访问未经授权的内存区域)至关重要,但它们限制DMA设备对系统内存的访问,可能干扰内存采集和设备仿真。禁用IOMMU允许DMA设备不受限制地访问内存,这对于高级仿真和安全研究目的通常是必要的。
    • 位置:通常在BIOS/UEFI中的“CPU Configuration”、“Virtualization”、“Advanced Settings”或“I/O Virtualization”下找到。
  • 内核DMA保护(Windows)/ Thunderbolt安全级别(Linux)
    • 建议(Windows) :在现代Windows系统中禁用内核DMA保护功能。这包括基于虚拟化的安全性(VBS)内存完整性(HVCI)等设置。这些功能利用IOMMU来防止通过Thunderbolt或PCIe连接的外部外设进行未经授权的DMA攻击。
    • 步骤(Windows)
      • 访问Windows安全设置:开始 > 设置 > 隐私和安全性 > Windows 安全中心 > 设备安全性
      • 在“核心隔离”下,点击“核心隔离详细信息”。
      • 关闭“内存完整性”。
      • 您可能还需要在BIOS/UEFI中禁用安全启动,因为VBS通常依赖于它。
      • 注意:禁用这些功能会显著降低您系统的安全态势,使其容易受到包括涉及恶意DMA设备的各种攻击。这应该只在专用测试系统上进行,而不是在您的主机器上,并且在您了解风险的安全、隔离环境中进行。
    • 建议(Linux/Thunderbolt) :如果使用带有Thunderbolt端口的系统,请了解并可能调整BIOS/UEFI中的Thunderbolt安全级别。较低的安全级别(例如,“无安全”、“用户授权”)通常是任意Thunderbolt/PCIe设备在未经明确主机批准的情况下执行DMA所必需的。
  • PCIe插槽要求
    • 建议:使用与FPGA设备要求物理匹配的兼容PCIe插槽。大多数基于Artix-7的DMA卡在PCIe Gen2 x1或x4下运行。
    • 理由
      • 物理匹配:x1卡可以插入x1、x4、x8或x16插槽,但x4卡至少需要x4插槽。
      • 性能:虽然x4卡可能在x1插槽中工作(如果物理连接是开放式或已修改的),但它将以x1速度运行,严重限制数据传输速率。为了获得最佳性能和精确仿真捐赠设备的功能,请确保FPGA板卡安装在提供至少仿真链路宽度和速度的插槽中(例如,如果您要仿真Gen2 x4设备,请在主机上使用Gen2 x4插槽)。
    • 主板BIOS设置:一些主板允许配置PCIe插槽速度(例如,强制Gen1或Gen2)。确保这些设置不与您期望的仿真速度冲突。

3.3 系统要求

建立一个健壮的开发环境对于高效的固件开发、综合和调试至关重要。

  • 主机系统
    • 处理器:现代多核CPU对于运行Vivado等FPGA开发工具至关重要,这些工具在综合和实现过程中计算密集。(例如,Intel Core i5/i7/i9 或 AMD Ryzen 5/7/9 等效处理器,建议8代或更新)。
    • 内存(RAM) :强烈建议最低16 GB RAM;对于复杂FPGA设计,32 GB 或更高是理想选择,因为Vivado会消耗大量内存,尤其是在实现阶段。
    • 存储:一个固态硬盘(SSD)并至少有 200 GB 的可用空间 至关重要。FPGA工具安装(仅Vivado就可能超过50 GB)、项目文件以及综合/实现输出会迅速占用磁盘空间。SSD的速度能显著缩短构建时间。
    • 操作系统
      • Windows 10/11 (64位 专业版或企业版) :Xilinx Vivado 和许多硬件调试工具广泛支持。请记住内核DMA保护的注意事项。
      • 兼容的Linux发行版 (64位) :Ubuntu LTS(长期支持)版本(例如 20.04、22.04)是Vivado常用且支持良好的系统。Linux通常为脚本编写和低级PCIe交互工具提供更灵活的环境。
  • 外围设备
    • JTAG编程器:将编译后的比特流烧录到基于FPGA的DMA卡上绝对必需。示例包括Xilinx Platform Cable USB II、Digilent JTAG-HS3 或某些开发板上集成的JTAG编程器。确保它与您的FPGA板卡和Vivado兼容。
    • PCIe插槽:如第3.2节所述,确保您的主机系统有可用的兼容PCIe插槽用于DMA卡。
    • USB端口:用于连接JTAG编程器,并可能用于连接FPGA板卡的UART/串行控制台以进行调试输出。

4. 要求

本节概述了进行PCIe设备仿真定制固件开发所必需的基本硬件和软件组件,以及推荐的环境设置。在开始之前,具备这些先决条件将简化您的开发过程。

4.1 硬件

  • 捐赠PCIe设备
    • 目的:这是您打算在FPGA上仿真其配置和行为的物理硬件设备。它作为关键识别细节、寄存器值和操作特性的权威来源。
    • 示例:常见示例包括标准网卡(NIC)、SATA或NVMe存储控制器、USB控制器,或任何其他您可以安全地从系统中移除进行分析的通用PCIe扩展卡。强烈建议使用对系统操作非必需的设备,因为您将检查其低级配置。
  • DMA FPGA卡
    • 描述:一种基于FPGA的开发板,专门设计或改编用于通过PCIe接口执行直接内存访问(DMA)操作。这是您的定制固件将加载到的平台。
    • 示例:如第3.1节所述,兼容卡包括 Squirrel (Artix-7 35T)Enigma-X1 (Artix-7 75T)ZDMA (Artix-7 100T) 或各种基于 Kintex-7 的解决方案。确保您选择的卡具有PCIe金手指连接器。
  • JTAG编程器
    • 目的:这个关键工具促进了您的开发PC与DMA卡上FPGA之间的通信。它用于将编译后的比特流编程(烧录)到FPGA上,更重要的是,用于使用Vivado的硬件管理器和集成逻辑分析仪(ILA)等工具进行交互式调试。
    • 示例
      • Xilinx Platform Cable USB II:Xilinx FPGA传统且广泛兼容的编程器。确保您已安装必要的驱动程序。
      • Digilent JTAG-HS3 / JTAG-HS2:流行且可靠的编程器,以良好的Vivado集成和支持而闻名。HS3提供更快的编程速度。
      • 集成JTAG:某些FPGA板可能具有板载USB转JTAG桥(例如FTDI芯片),这消除了对独立编程器的需求。请查阅您的板卡文档。

4.2 软件

  • Xilinx Vivado Design Suite
    • 描述:Xilinx(现为AMD)官方的、全面的FPGA开发环境。Vivado对于综合您的HDL代码、将设计实现到目标FPGA上、生成最终比特流以及执行硬件调试至关重要。它包括必要的IP核、编译器和实用程序。
    • 下载:访问Xilinx(AMD)官方下载页面:https://www.xilinx.com/support/download.html
    • 版本说明:虽然一些旧指南可能引用Vivado 2020.1等旧版本,但强烈建议下载与您的目标FPGA系列(Artix-7、Kintex-7)兼容的最新稳定版本(例如Vivado 2023.x或更高版本)。PCILeech-FPGA项目通常支持较新的Vivado版本。
  • Visual Studio Code
    • 描述:Microsoft出品的高度可定制且功能丰富的代码编辑器。它是编写和编辑Verilog/SystemVerilog HDL代码的绝佳选择,因为它拥有广泛的扩展生态系统,提供语法高亮、代码检查、自动补全和版本控制集成等功能。
    • 下载https://code.visualstudio.com/
  • PCILeech-FPGA
    • 描述:一个用于基于FPGA的DMA开发的开源框架和基础代码库。它提供了即插即用的PCIe IP核实例化和一个结构良好的项目,是定制固件的绝佳起点。本指南将大量利用其架构。
    • 仓库https://github.com/ufrisk/pcileech-fpga
  • Arbor (MindShare)
    • 描述:一款强大且用户友好的软件工具,专门设计用于深入扫描和分析PCIe设备。它提供了对连接PCIe硬件的配置空间、功能和寄存器的详细洞察,对于收集捐赠设备信息来说非常有价值。
    • 下载:可从MindShare网站获取:https://www.mindshare.com/(您可能需要导航到他们的软件部分)。
    • 注意:通常需要创建账户,并且可能提供限时试用。
  • 替代PCIe设备分析工具
    • Telescan PE (Teledyne LeCroy)
    • OS原生工具(用于基本检查)
      • Windows设备管理器:在设备属性的“详细信息”选项卡下提供基本的厂商ID、设备ID、子系统ID和类别代码信息。
      • Linux lspci​ ** 工具**:一个强大的命令行工具,用于检查PCIe设备。使用lspci -nn查看厂商/设备ID,lspci -vvv查看包括BAR和功能在内的详细信息,lspci -s <BUS:DEV.FUN> -xxxx用于原始配置空间转储。

4.3 环境设置

一个干净且正确配置的开发环境对于避免常见陷阱并确保流畅的工作流程至关重要。

4.3.1 安装Xilinx Vivado设计套件

步骤

  1. 访问Xilinx (AMD) Vivado下载页面https://www.xilinx.com/support/download.html
  2. 下载适当版本:选择与您的操作系统兼容的最新稳定版Vivado,更重要的是,它必须与您的特定FPGA设备(例如Artix-7、Kintex-7)兼容。查阅Vivado发行说明以了解设备支持情况。
  3. 运行安装程序:执行下载的安装程序并仔细遵循屏幕上的说明。
  4. 选择必要组件:在安装过程中,系统会提示您选择要安装的设备家族。至关重要的是,选择与您的FPGA板卡对应的设备家族(例如,Artix-7/Kintex-7的“7 Series”) 。这与安装所有家族相比,能节省大量磁盘空间。确保您选择“设计工具”(综合、实现)和“编程与调试”组件。
  5. 启动Vivado:安装完成后,启动Vivado以确认它能无错误打开,并且许可证(如果适用)已正确配置。

4.3.2 安装Visual Studio Code

步骤

  1. 访问Visual Studio Code下载页面https://code.visualstudio.com/
  2. 下载并安装:下载适用于您操作系统的安装程序,并遵循标准安装提示。
  3. 安装HDL支持扩展:安装VS Code后,打开它并导航到扩展视图(Ctrl+Shift+X或Cmd+Shift+X)。搜索并安装适用于Verilog/SystemVerilog的相关扩展,例如:
    • Verilog-HDL/SystemVerilog (由mshr-h提供)
    • VHDL (如果您也使用VHDL)
      这些扩展提供了语法高亮、代码检查和其他有用的功能。

4.3.3 克隆PCILeech-FPGA仓库

此仓库包含您将要修改的基础固件结构和脚本。

步骤

  1. 打开终端或命令提示符:(例如,Windows上的Git Bash,Linux上的Terminal)。
  2. 导航到您想要的目录:选择一个您想存储项目的位置。
    1
    2
    cd ~/Projects/ # 在Linux/macOS上
    cd C:\Users\YourUsername\Documents\Projects\ # 在Windows上
  3. 克隆仓库
    1
    git clone https://github.com/ufrisk/pcileech-fpga.git
  4. 导航到克隆的目录
    1
    cd pcileech-fpga
    这将是您的主项目目录。PCILeech-FPGA项目通常包含不同板卡变体的子目录(例如pcileech-artix-7-50tpcileech-squirrel-35t)。您将根据您的特定硬件导航到相关的板卡特定目录。

4.3.4 设置一个干净的开发环境

建议:始终在隔离或专用的环境中工作,尤其是在处理低级硬件和潜在的安全隐患时。

步骤

  1. 使用专用开发机或虚拟机
    • 物理机:如果可能,使用一台单独的物理计算机进行FPGA开发和测试。这可以防止在您的主机器上发生意外的系统不稳定或安全风险。
    • 虚拟机(VM) :虚拟机可以是隔离开发环境的好选择。然而,通常需要向虚拟机进行直接PCIe直通(PCIe热插拔或VT-d直通),FPGA卡才能被正确检测和操作,这可能配置复杂,并且如果操作不当,仍然可能暴露主机。对于初始工具安装和代码编辑,虚拟机完全没问题。
  2. 最小化后台应用程序:确保没有其他资源密集型应用程序正在运行,这些应用程序可能会干扰Vivado在综合和实现过程中的性能。
  3. 禁用冲突软件:在开发和测试期间,暂时禁用任何可能干扰低级硬件访问或JTAG通信的防病毒、防火墙或安全软件。完成工作后请记得重新启用它们。

5. 收集捐赠设备信息

精确的设备仿真取决于精细地提取和复制捐赠设备的关键信息。这种全面的数据收集使您的FPGA能够忠实地模仿目标硬件的PCIe配置和行为,确保与主机系统接口时的兼容性和功能性。

5.1 使用Arbor进行PCIe设备扫描

Arbor 是一款强大且用户友好的工具,专为深入扫描PCIe设备而设计。它提供了对连接硬件配置空间的详细洞察,使其成为提取设备仿真所需信息的宝贵资源。

5.1.1 安装Arbor

要开始使用Arbor进行设备扫描,您必须首先在系统上安装该软件。

步骤:

  1. 访问Arbor下载页面:
    • 使用您偏好的网页浏览器导航到MindShare官方网站(https://www.mindshare.com/)。您需要找到他们的“Software”或“Downloads”部分来定位Arbor。
    • 确保您直接访问该网站,以避免任何恶意重定向。
  2. 创建账户(如果需要):
    • Arbor可能要求您创建用户账户才能访问下载链接。
    • 提供必要的信息,例如您的姓名、电子邮件地址和组织。
    • 如果出现提示,请验证您的电子邮件以激活您的账户。
  3. 下载Arbor:
    • 登录后,找到Arbor的下载部分。
    • 选择与您的操作系统兼容的版本(例如,Windows 10/11 64位)。
    • 点击 Download 按钮并将安装程序保存到计算机上已知的位置。
  4. 安装Arbor:
    • 找到下载的安装程序文件(例如,ArborSetup.exe)。
    • 右键单击安装程序并选择 以管理员身份运行 以确保它具有必要的权限。
    • 按照屏幕上的说明完成安装过程。
      • 接受许可协议。
      • 选择安装目录。
      • 如果需要,选择创建桌面快捷方式。
  5. 验证安装:
    • 安装完成后,确保Arbor列在您的“开始”菜单或桌面上。
    • 启动Arbor以确认它能无错误打开。

5.1.2 扫描PCIe设备

安装Arbor后,您可以继续扫描系统中的PCIe设备。

步骤:

  1. 启动Arbor:
    • 双击桌面上的Arbor图标或通过“开始”菜单找到它。
    • 如果用户账户控制(UAC)提示,允许应用程序对设备进行更改。
  2. 导航到本地系统选项卡:
    • 在Arbor界面中,找到导航窗格或选项卡。
    • 单击 Local System 以访问扫描本地机器的工具。
  3. 扫描PCIe设备:
    • 查找 ScanRescan 按钮,通常位于界面的顶部或底部。
    • 点击 Scan/Rescan 以启动检测过程。
    • 等待扫描过程完成;这可能需要几分钟,具体取决于连接的设备数量。
  4. 审查检测到的设备:
    • 扫描完成后,Arbor将显示所有检测到的PCIe设备的列表。
    • 设备通常会列出其名称、设备ID和其他识别信息。

5.1.3 识别捐赠设备

识别正确的捐赠设备对于精确仿真至关重要。

步骤:

  1. 在列表中找到您的捐赠设备:
    • 滚动浏览Arbor检测到的设备列表。
    • 查找与您的捐赠硬件的品牌和型号匹配的设备。
    • 设备可能按其厂商名称、设备类型或功能列出。
  2. 验证设备详细信息:
    • 单击设备以选中它。
    • 确认 Device IDVendor ID 与您的捐赠设备匹配。
      • 提示: 这些ID通常可以在设备文档或制造商网站上找到。对于常见设备,快速在网上搜索“[设备名称] Vendor ID Device ID”通常能得到结果。
  3. 查看详细配置:
    • 选中设备后,找到并单击类似 View DetailsProperties 的选项。
    • 这将打开一个详细视图,显示设备的配置空间和功能。
  4. 与物理硬件交叉引用:
    • 如果列出了多个类似设备,请将 Slot NumberBus Address 与安装捐赠设备的物理插槽交叉引用。这有助于确认您正在分析正确的硬件。

5.1.4 捕获设备数据

从捐赠设备中提取详细信息对于精确仿真至关重要。

要提取的信息:

  • 设备ID (0xXXXX): 唯一标识设备型号的16位标识符。
  • 厂商ID (0xYYYY): 分配给制造商的16位标识符。
  • 子系统ID (0xZZZZ): 标识特定子系统或变体(例如,产品线中的特定型号)。
  • 子系统厂商ID (0xWWWW): 标识子系统的厂商(通常与主厂商ID相同,但对于OEM版本可能会有所不同)。
  • 修订ID (0xRR): 指示设备的硬件修订级别。
  • 类别代码 (0xCCCCCC): 一个24位代码,定义设备的主要功能/类型(例如,0x020000用于以太网控制器,0x010802用于NVMe控制器)。这有助于操作系统加载通用驱动程序。
  • 基地址寄存器 (BARs):
    • 定义设备使用的内存或I/O地址区域的寄存器。
    • 包括BAR0到BAR5,每个都可能是32位或64位。对于每个BAR,请记录其 类型(内存或I/O)位宽(32位或64位)大小(例如,256 MB,4KB)可预取状态(是/否) 。这对于内存映射至关重要。
  • 功能: 列出支持的功能及其配置,通常在配置空间中的链表结构中找到。示例包括:
    • PCIe功能结构:PCIe链路速度(例如,Gen2,Gen3),链路宽度(例如,x1,x4),最大载荷大小,最大读取请求大小。
    • MSI/MSI-X功能结构:消息信号中断信息,包括支持的向量数量。
    • 电源管理功能结构:支持的电源状态(D0,D1,D2,D3hot,D3cold)。
  • 设备序列号 (DSN): 一个64位唯一标识符,如果设备支持(在“设备序列号”扩展功能中找到)。并非所有设备都实现了此功能。

步骤:

  1. 导航到PCI配置选项卡:
    • 在设备详细视图中,找到并选择 PCI ConfigConfiguration Space 选项卡。这通常会以解码视图显示原始配置空间寄存器。
  2. 记录相关详细信息:
    • 仔细记录上面列出的每个所需字段。
    • 使用截图或将值复制到文本文件、专用电子表格或结构化文档格式中以确保准确性。
    • 确保十六进制值正确记录,包括是否使用0x前缀。
  3. 展开功能列表:
    • 查找标记为 CapabilitiesAdvanced Features 的部分。这些通常是可点击或可展开以显示子部分的。
    • 记录存在的每个功能及其相关参数(例如,MSI消息控制,电源状态标志,当前/最大PCIe链路设置)。
  4. 详细检查BAR:
    • 在配置空间中,找到BAR0到BAR5的条目。
    • 对于每个活动的BAR,记录其分配的大小、是内存映射还是I/O、其位宽(32位或64位)以及是否可预取。这些信息通常在Arbor的GUI中清晰显示。
  5. 保存数据以备参考:
    • 将所有提取的信息编译成一个组织良好的文档(例如,Markdown文件、.txt文件或Excel电子表格)。
    • 为每个部分清晰标记,以便在固件定制期间轻松参考。

5.2 提取和记录设备属性

捕获数据后,理解每个属性的重要性并确保其准确记录对于成功仿真至关重要。

确保您已准确记录以下内容:

  1. 设备ID:
    • 目的: 唯一标识PCIe设备的特定型号。
    • 仿真用法: 对于主机操作系统(OS)正确识别仿真设备至关重要,更重要的是,它能尝试加载适当的设备驱动程序。
  2. 厂商ID:
    • 目的: 标识PCIe设备的制造商。
    • 仿真用法: 与设备ID结合使用,形成主机操作系统用于将设备与相应驱动程序匹配的唯一标识符(VendorID:DeviceID)。
  3. 子系统ID和子系统厂商ID:
    • 目的: 这些可选ID允许区分同一厂商设备的变体,或区分主厂商/设备ID可能为通用的OEM特定版本。
    • 仿真用法: 对于仿真具有多种配置的设备或OEM提供的设备很重要,因为驱动程序可能会专门查找这些值。
  4. 修订ID:
    • 目的: 指示设备的硬件修订级别。
    • 仿真用法: 有助于识别可能需要不同驱动程序、固件或具有细微行为差异的特定硬件版本。
  5. 类别代码:
    • 目的: 一个24位代码,用于对设备的通用功能进行分类(例如,0x020000用于以太网控制器,0x010802用于NVMe控制器,0x0C0300用于USB主机控制器)。它由基本类别、子类别和编程接口组成。
    • 仿真用法: 允许操作系统理解设备的通用功能,并在找不到特定厂商驱动程序时加载通用类别驱动程序。这对于初始设备识别至关重要。
  6. 基地址寄存器(BARs):
    • 目的: 定义设备用于寄存器、内部缓冲区或配置空间扩展的内存映射或I/O端口地址区域。主机操作系统在枚举期间将物理地址分配给这些BAR。
    • 仿真用法: 对于将仿真设备的内部内存和寄存器映射到主机系统的地址空间至关重要。每个BAR的大小、类型(内存/I/O,32/64位)和可预取状态必须与捐赠设备精确匹配。
  7. 功能:
    • 目的: 列出设备支持的高级功能,如高级错误报告、电源管理、MSI/MSI-X、PCIe高级功能(如AER、VC/PF)等。每个功能由一个具有其自身寄存器的结构定义。
    • 仿真用法: 对于准确复制捐赠设备如何宣传其功能以及主机系统如何与这些功能交互(例如,中断传递机制、电源状态转换、错误报告)至关重要。
  8. 设备序列号(DSN):
    • 目的: 设备的唯一64位标识符,通常是可选的扩展功能。
    • 仿真用法: 虽然可选,但某些驱动程序或管理应用程序可能会专门查询并依赖DSN进行识别、许可或安全检查。准确仿真此功能可以防止您的设备被检测为通用或修改的外设。

数据收集的最佳实践:

  • 组织数据: 创建一个结构化的文档或电子表格。为每个属性使用清晰的标题和子标题。模板会很有益。
  • 包含单位和格式: 始终注明大小的单位(例如,MB、KB),并为十六进制值使用一致的格式(例如,0x123416'h1234)。
  • 与规范交叉引用(如果可能): 如果可用,查阅捐赠设备的数据手册或公开可用的规范以验证值。这有助于识别原始扫描中不明显或不寻常的配置。
  • 保护数据: 安全存储收集到的信息。请注意,这些数据可能包含专有或敏感信息。
  • 理解“缺少什么”: 像Arbor这样的专业工具非常出色,但它们可能无法捕捉复杂、高度专有设备的每一个细微之处(例如,标准配置空间之外的特定厂商定义寄存器)。对于高级仿真,您可能需要将此信息与捐赠设备驱动程序的逆向工程结合起来。

6. 初始固件定制

在细致地记录了捐赠设备的信息之后,下一个关键阶段是定制您的FPGA固件,以准确仿真捐赠设备。这个过程首先要修改PCIe配置空间中的关键识别寄存器,并确保设备序列号等特定标识符被正确集成。

6.1 修改配置空间

PCIe配置空间是定义设备如何被识别并与主机系统在枚举期间交互的基本组件。精确定制此空间以匹配捐赠设备的配置文件对于成功仿真绝对至关重要,它能让主机操作系统加载正确的驱动程序并按预期交互。

6.1.1 导航到配置文件

PCIe配置空间参数通常在PCILeech-FPGA项目中的特定SystemVerilog(.sv)文件中定义。此文件将综合成配置PCIe IP核并向主机公开设备身份的逻辑。

PCILeech-FPGA(基于Artix-7的板卡,如Squirrel)的常见路径:
找到负责为您特定板卡配置PCIe参数的文件。对于许多Artix-7 PCILeech变体,这将是:

1
pcileech-fpga/<your_board_variant>/src/pcileech_pcie_cfg_a7.sv
  • 示例(对于Squirrel 35T)
    1
    pcileech-fpga/pcileech-squirrel-35t/src/pcileech_pcie_cfg_a7.sv
    注意:实际的文件夹名称,如pcileech-squirrel-35t,可能会根据您克隆的PCILeech-FPGA的具体版本或分支略有不同。克隆主仓库后,请始终导航到相关的板卡特定子目录。

6.1.2 在Visual Studio Code中打开文件

编辑配置文件需要一个合适的代码编辑器,该编辑器支持SystemVerilog(或Verilog)的语法高亮,使代码更易于阅读和修改。

步骤:

  1. 启动Visual Studio Code:
    • 点击VS Code图标或通过“开始”菜单找到它。
  2. 打开文件:
    • 使用 文件 > 打开文件 或按下 Ctrl + O(macOS上为 Cmd + O)。
    • 导航到第6.1.1节中确定的配置文件路径(例如,pcileech-fpga/pcileech-squirrel-35t/src/pcileech_pcie_cfg_a7.sv)。
    • 选择文件并点击 打开
  3. 验证语法高亮:
    • 确保编辑器识别 .sv 文件扩展名并应用正确的SystemVerilog语法高亮。如果不行,请返回第4.3.2节,确保您已安装推荐的Verilog/SystemVerilog扩展程序。
  4. 熟悉文件结构:
    • 滚动浏览文件。您通常会发现使用localparamreg赋值定义的参数,通常附有解释其目的的注释。查找定义和赋值标准PCIe配置寄存器(厂商ID、设备ID等)的部分。

6.1.3 修改设备ID和厂商ID

更新这些基本标识符是主机系统正确将仿真设备识别为您的捐赠设备的最关键步骤。操作系统严重依赖 Vendor IDDevice ID 对来识别连接的硬件并加载适当的设备驱动程序。

步骤:

  1. 搜索 cfg_deviceid
    • 在VS Code中使用搜索功能(Ctrl + FCmd + F)。
    • 找到定义cfg_deviceid的行。它通常看起来像这样:
      1
      reg [15:0] cfg_deviceid = 16'hAAAA; // 默认或占位符设备ID
  2. 更新设备ID:
    • AAAA替换为您使用Arbor从捐赠设备中提取的16位十六进制设备ID(例如,0x1234)。
    • 示例:
      如果捐赠设备的设备ID是0x1234,则将该行更新为:
      1
      reg [15:0] cfg_deviceid = 16'h1234; // 更新为捐赠设备的设备ID(例如,来自网卡)
  3. 搜索 cfg_vendorid
    • 找到定义cfg_vendorid的行。其格式将类似于cfg_deviceid
      1
      reg [15:0] cfg_vendorid = 16'hBBBB; // 默认或占位符厂商ID
  4. 更新厂商ID:
    • BBBB替换为您从捐赠设备中提取的16位十六进制厂商ID(例如,0xABCD)。
    • 示例:
      如果捐赠设备的厂商ID是0xABCD,则将该行更新为:
      1
      reg [15:0] cfg_vendorid = 16'hABCD; // 更新为捐赠设备的厂商ID(例如,Intel Corporation)
  5. 确保格式正确:
    • 验证十六进制值是否正确以16'h为前缀(表示一个16位十六进制数)。
    • 保持一致的缩进和注释风格以提高可读性。

6.1.4 修改子系统ID和修订ID

这些标识符提供了关于设备变体、特定产品型号或硬件修订的额外详细信息。虽然通常是可选的,但匹配它们能增强仿真的真实性,并且对于执行细粒度检查的驱动程序可能至关重要。

步骤:

  1. 搜索 cfg_subsysid

    • 找到定义cfg_subsysid的行。
    1
    reg [15:0] cfg_subsysid = 16'hCCCC; // 占位符子系统ID
  2. 更新子系统ID:

    • CCCC替换为您捐赠设备的16位十六进制子系统ID(例如,0x5678)。
    • 示例:
      1
      reg [15:0] cfg_subsysid = 16'h5678; // 设置为捐赠设备的子系统ID
  3. 搜索 cfg_subsysvendorid

    • 找到定义cfg_subsysvendorid的行。
    1
    reg [15:0] cfg_subsysvendorid = 16'hDDDD; // 占位符子系统厂商ID
  4. 更新子系统厂商ID(如果适用):

    • DDDD替换为您捐赠设备的16位十六进制子系统厂商ID(例如,0x9ABC)。如果您的捐赠设备没有唯一的子系统厂商ID(即与主厂商ID相同),您仍应将其设置为该值。
    • 示例:
      1
      reg [15:0] cfg_subsysvendorid = 16'h9ABC; // 设置为捐赠设备的子系统厂商ID
  5. 搜索 cfg_revisionid

    • 找到定义cfg_revisionid的行。
    1
    reg [7:0] cfg_revisionid = 8'hEE; // 占位符修订ID
  6. 更新修订ID:

    • EE替换为您捐赠设备的8位十六进制修订ID(例如,0x01)。
    • 示例:
      1
      reg [7:0] cfg_revisionid = 8'h01; // 设置为捐赠设备的修订ID

6.1.5 更新类别代码

类别代码通知主机操作系统设备的通用类型和功能(例如,网络控制器、存储设备)。这对于操作系统加载通用类别驱动程序至关重要,即使没有安装特定厂商驱动程序。

步骤:

  1. 搜索 cfg_classcode

    • 找到定义cfg_classcode的行。
    1
    reg [23:0] cfg_classcode = 24'hFFFFFF; // 默认或占位符类别代码
  2. 更新类别代码:

    • FFFFFF替换为您从捐赠设备中提取的24位十六进制类别代码(例如,0x020000用于以太网控制器)。请记住格式:基本类别、子类别、编程接口。
    • 示例:
      如果捐赠设备的类别代码是0x020000(表示基本类别:0x02 - 网络控制器,子类别:0x00 - 以太网控制器,编程接口:0x00),则更新为:
      1
      reg [23:0] cfg_classcode = 24'h020000; // 设置为捐赠设备的类别代码(例如,以太网控制器)
  3. 验证正确的位宽:

    • 确保类别代码使用24'h前缀正确表示为24位十六进制值。

6.1.6 保存更改

在对配置参数进行所有修改后,保存和审查更改至关重要。

步骤:

  1. 保存文件:
    • 在VS Code中点击 文件 > 保存,或按下 Ctrl + S(macOS上为 Cmd + S)。
  2. 审查更改:
    • 在关闭之前,快速重新阅读修改过的行,以根据您的捐赠设备信息文档确认其准确性。
    • 检查是否有任何明显的语法错误或拼写错误(VS Code的扩展可能会高亮显示这些)。
  3. 可选 - 使用版本控制:
    • 如果您正在使用Git(强烈推荐用于任何代码项目,尤其是固件),请以清晰且有意义的消息提交您的更改。这将创建您的修改历史记录。
    • 示例Git命令:
      1
      2
      git add pcileech_pcie_cfg_a7.sv
      git commit -m "更新PCIe配置寄存器(VID, DID, SubIDs, Revision, Class Code)以匹配捐赠设备:[捐赠设备名称]"

6.2 插入设备序列号(DSN)

设备序列号(DSN)是一些PCIe设备(特别是那些具有高级功能或特定驱动程序的设备)可能使用的独特64位标识符。包含它能增强仿真的真实性,并有助于绕过明确查询此值的驱动程序中的检查。

6.2.1 定位DSN字段

DSN(如果由捐赠设备实现)是PCIe扩展能力的一部分。在PCILeech-FPGA框架中,DSN字段通常作为您一直在编辑的同一配置文件中的可配置参数公开。

步骤:

  1. 搜索 cfg_dsn
    • pcileech_pcie_cfg_a7.sv(或您的板卡等效配置文件)中,使用搜索功能(Ctrl + FCmd + F)查找 cfg_dsn
  2. 理解现有赋值:
    • DSN可能被设置为默认值(通常是全零)或被注释掉。它通常看起来像这样:
      1
      reg [63:0] cfg_dsn = 64'h0000000000000000; // 默认DSN(如果未使用,通常为0)

6.2.2 插入DSN

更新DSN涉及将其设置为从捐赠设备捕获的精确64位十六进制值。

步骤:

  1. 更新 cfg_dsn
    • 将现有的十六进制值替换为您使用Arbor从捐赠设备中提取的64位DSN。
    • 示例:
      如果捐赠设备的DSN是0x0011223344556677,则更新为:
      1
      reg [63:0] cfg_dsn = 64'h0011223344556677; // 捐赠设备序列号
  2. 处理DSN不可用或不相关的情况:
    • 如果您的捐赠设备没有DSN,或者您已确定它不是您目标驱动程序所需的参数,您可以简单地将其保留为零:
      1
      reg [63:0] cfg_dsn = 64'h0000000000000000; // 捐赠设备没有特定DSN,保留为默认0
    • 注意:对于关键仿真,如果捐赠设备有DSN,最好准确仿真它。
  3. 确保格式正确:
    • DSN是64位值;确保它以64'h前缀正确格式化为十六进制值。

6.2.3 保存更改

通过保存和审查文件来完成DSN修改。

步骤:

  1. 保存文件:
    • 在VS Code中点击 文件 > 保存,或按下 Ctrl + S
  2. 验证语法:
    • 检查VS Code的语法检查器是否有任何红色下划线或错误指示。立即纠正任何问题。
  3. 记录更改:
    • 如果使用版本控制,请使用适当的消息提交更新。
    • 示例Git命令:
      1
      git commit -am "在PCIe配置中插入捐赠设备序列号(DSN)"

7. Vivado项目设置与定制

在固件文件更新以反映捐赠设备的关键识别和配置数据后,下一个关键步骤是将这些更改集成到Vivado项目中。这包括为您的特定FPGA板卡生成项目文件,定制嵌入式PCIe IP核,并准备整个设计以进行综合和实现阶段。

7.1 生成Vivado项目文件

Vivado是Xilinx(AMD)开发套件,使用Tcl(工具命令语言)脚本来自动化项目创建、添加源文件和配置项目设置。通过运行PCILeech-FPGA框架提供的这些脚本,您可以确保您的Vivado项目已为目标FPGA板卡正确设置。

7.1.1 打开Vivado

启动Vivado的新会话可确保之前会话中没有残留设置或打开项目干扰当前工作。

步骤:

  1. 启动Vivado:
    • 在“开始”菜单(Windows)或“应用程序”文件夹(Linux/macOS)中找到Vivado应用程序图标。
    • 点击打开。
  2. 选择正确的版本:
    • 如果您安装了多个Vivado版本,请确保您启动的是与您的FPGA板卡和PCILeech-FPGA项目兼容的版本(如第4.3.1节所述,建议使用Vivado 2023.x等最新稳定版本)。
  3. 等待启动界面:
    • 让Vivado完全初始化并显示欢迎界面或项目仪表板,然后才能继续。

7.1.2 访问Tcl控制台

Vivado内的Tcl控制台是您执行脚本和直接命令的主要界面。您将在此处运行项目生成脚本。

步骤:

  1. 打开Tcl控制台:
    • 在Vivado界面中,导航到菜单栏。
    • 单击 Window > Tcl Console
    • Tcl控制台窗格通常会出现在Vivado窗口的底部。
  2. 调整控制台大小(可选):
    • 您可以拖动控制台的顶部边框来调整其大小,使其更高以便更好地查看命令和输出。
  3. 清除先前命令(可选但推荐):
    • 如果存在任何先前的命令或消息,您可以在控制台内右键单击并选择“Clear Console”以获得一个干净的开始。

7.1.3 导航到项目目录

在运行Tcl脚本之前,您必须确保Tcl控制台的当前工作目录已设置为您的板卡特定PCILeech-FPGA项目脚本所在的正确位置。

对于Squirrel DMA (Artix-7 35T) 或类似板卡:

典型路径(克隆pcileech-fpga并导航到您的板卡变体后):

1
2
C:/Users/YourUsername/Documents/pcileech-fpga/pcileech-squirrel-35t/  # 在Windows上
~/Projects/pcileech-fpga/pcileech-squirrel-35t/ # 在Linux/macOS上

注意:将<your_board_variant>替换为您的板卡子目录的实际名称(例如,pcileech-squirrel-35tpcileech-artix-7-50t)。

步骤:

  1. 在Tcl控制台中设置工作目录:
    • 在Vivado Tcl控制台中,输入cd命令,后跟您的板卡项目目录的完整路径。
    • 示例(Windows):
      1
      cd C:/Users/YourUsername/Documents/pcileech-fpga/pcileech-squirrel-35t/
    • 示例(Linux/macOS):
      1
      cd ~/Projects/pcileech-fpga/pcileech-squirrel-35t/
    • 自我纠正提示:即使在Windows上,Tcl路径也使用正斜杠(/)。
  2. 验证目录更改:
    • 要确认您处于正确的目录中,请在Tcl控制台中输入pwd(打印工作目录)。
    • 控制台应显示您刚刚设置的完整路径,确认更改。

7.1.4 生成Vivado项目

运行适用于您的FPGA板卡的相应Tcl脚本将自动化Vivado内部的整个项目设置过程。这包括创建项目、添加所有必要的源文件(HDL、约束)以及配置核心项目设置。

步骤:

  1. 运行Tcl脚本:
    • 输入source命令,后跟您的板卡的项​​目生成脚本的名称。PCILeech-FPGA项目通常在主板卡目录中提供这些脚本。
    • 对于Squirrel (Artix-7 35T)(以及类似的Artix-7板卡):
      1
      source vivado_generate_project_squirrel.tcl -notrace
    • 对于Enigma-X1 (Artix-7 75T):
      1
      source vivado_generate_project_enigma_x1.tcl -notrace
    • 对于ZDMA (Artix-7 100T):
      1
      source vivado_generate_project_100t.tcl -notrace
    • -notrace选项可防止每个Tcl命令的详细输出,使控制台更整洁。
  2. 等待脚本完成:
    • 脚本将按顺序执行许多命令。此过程可能需要几分钟,具体取决于您的系统性能和项目的复杂性。
    • 监控Tcl控制台的进度消息。脚本将:
      • 在当前目录中创建一个新的Vivado项目(.xpr文件)。
      • 添加所有SystemVerilog/Verilog源文件(.sv.v)。
      • 添加Xilinx IP核配置(.xci)。
      • 添加XDC(Xilinx设计约束)文件。
      • 可能配置各种项目设置。
    • 处理任何错误:如果发生任何错误(例如,“文件未找到”、“无效命令”),脚本通常会停止。检查错误消息,纠正底层问题(例如,路径不正确、文件丢失),然后重新运行脚本。
  3. 确认项目生成:
    • 成功完成后,Tcl控制台通常会指示项目已创建,并且您应该在项目目录中看到新生成的项目文件(例如,pcileech_squirrel_top.xpr)和相关目录(例如,pcileech_squirrel_top.runspcileech_squirrel_top.ip)。

7.1.5 打开生成的项目

现在Vivado项目文件已成功由Tcl脚本生成,您可以在Vivado GUI中打开该项目以进行进一步检查和定制。

步骤:

  1. 打开项目:
    • 在Vivado中,点击 文件 > 打开项目
    • 导航到您的项目目录(与您在第7.1.3节中在Tcl控制台中设置的目录相同)。
  2. 选择项目文件:
    • 找到并选择与您的板卡对应的Vivado项目文件(.xpr扩展名)。
    • 对于Squirrel: 文件名通常为 pcileech_squirrel_top.xpr
    • 点击 .xpr 文件以选择它。
  3. 点击打开:
    • Vivado将加载项目,显示设计层次结构、源文件、IP集成器块设计(如果使用)和各种设计视图。这可能需要一些时间。
  4. 验证项目内容:
    • 项目管理器 窗口(通常在左侧)中,展开 源文件 窗格。
    • 确保所有预期的源文件(Verilog/SystemVerilog、XDC、IP核)都已列出,并且设计层次结构看起来正确。
    • 检查 消息 窗格(底部)中打开项目时出现的任何警告或严重警告,因为这些可能表明潜在问题。

7.2 修改IP核

PCIe IP核是设备PCIe接口的核心。它是一个经过Xilinx(AMD)预验证、可配置的模块,用于处理复杂的PCIe协议层。尽管某些配置空间值在SystemVerilog文件中处理(第6.1节),但其他核心PCIe参数,特别是与链路能力和BAR结构相关的参数,是在Vivado中直接通过PCIe IP核的定制设置进行配置的。定制IP核可确保您的FPGA在PCIe协议级别上与捐赠硬件的行为完全一致。

7.2.1 访问PCIe IP核

PCIe IP核在您的Vivado项目中被实例化为一个IP块。您需要打开其定制GUI来修改其参数。

步骤:

  1. 定位PCIe IP核:
    • Sources(源文件)窗格(位于 Project Manager(项目管理器)窗口内)中,确保已选择 Hierarchy(层次结构)选项卡。
    • 展开设计层次结构,直到找到PCIe IP核的实例。
    • 对于7系列FPGA(如Squirrel中使用的Artix-7),它通常被命名为 pcie_7x_0.xci 或类似名称,通常位于项目源文件的 ip 子目录中。
  2. 打开IP定制窗口:
    • 右键单击 pcie_7x_0.xci 文件。
    • 从上下文菜单中选择 Customize IP(定制IP)。
    • 将打开 IP Configuration(IP配置)窗口(或类似名称,如“Customize IP”或“Re-customize IP”),显示带有各种选项卡和选项的图形界面,用于配置PCIe核。
  3. 等待IP设置加载:
    • IP定制界面可能需要几分钟才能初始化并填充所有设置。在您开始进行更改之前,请确保所有选项和选项卡都已完全加载并响应。

7.2.2 在IP核内部定制设备ID和BAR

尽管某些设备标识符在pcileech_pcie_cfg_a7.sv中设置,但PCIe IP核本身也包含设备ID、厂商ID以及至关重要的基地址寄存器(BARs)的定义参数。您必须确保这些参数保持一致。.sv文件中的某些值可能会覆盖或输入到IP核中,但在此处也确保一致性是一个好习惯。IP核中的BAR设置尤其重要,因为它们决定了内存映射的硬件实现。

步骤:

  1. 导航到基本/识别参数:
    • 在IP定制窗口中,查找与 基本设备和厂商标识符通用PCIe能力 相关的选项卡或部分。这是定义基本ID和初始链路设置的地方。
  2. 验证/输入设备ID、厂商ID、子系统ID、修订ID、类别代码:
    • 至关重要:请确认这些值与您在pcileech_pcie_cfg_a7.sv中设置的以及从捐赠设备中获取的值相匹配。
    • 查找以下字段:
      • 设备ID:输入0xXXXX(例如,0x1234)。
      • 厂商ID:输入0xYYYY(例如,0xABCD)。
      • 子系统ID:输入0xZZZZ(例如,0x5678)。
      • 子系统厂商ID:输入0xWWWW(例如,0x9ABC)。
      • 修订ID:输入0xRR(例如,0x01)。
      • 类别代码:输入0xCCCCCC(例如,0x020000)。
    • 重要提示:某些IP核版本或特定配置可能会直接从用户逻辑(如pcileech_pcie_cfg_a7.sv)拉取这些值,或者可能允许直接在此处设置它们。最可靠的方法是,如果IP GUI中提供此选项,则在两个位置都保持一致设置。
  3. 导航到基地址寄存器(BARs)选项卡:
    • 在IP定制窗口中,找到并选择 BARs 选项卡或部分。这是您定义PCIe设备暴露的内存区域的地方。
  4. 配置每个BAR:
    • 对于您的捐赠设备使用的每个BAR(BAR0到BAR5),根据您使用Arbor提取的信息,仔细配置以下参数:
      • 启用BAR:仅当捐赠设备使用此特定BAR时才选中此框。禁用(取消选中)捐赠设备不使用的任何BAR。
      • BAR大小:从下拉列表中选择精确的大小(例如,256 MB64 KB4 KB)。这对于主机操作系统分配正确数量的内存至关重要。
      • BAR类型:选择适当的类型:
        • Memory (32-bit Addressing) (内存(32位寻址)): 用于32位地址可访问的内存映射区域。
        • Memory (64-bit Addressing) (内存(64位寻址)): 用于可以驻留在64位地址空间中任何位置的内存映射区域(对于大内存区域或如果捐赠设备使用它,则需要)。
        • I/O: 用于传统I/O端口区域(在现代PCIe中较不常见,但仍然可能)。
      • 可预取:如果捐赠设备的BAR被标记为可预取,则选中此框。此属性允许主机系统从此区域缓存或预取数据以提高性能。
    • 示例配置(基于您的捐赠设备):
      • BAR0
        • 启用:是
        • 大小:256 MB
        • 类型:Memory (64-bit Addressing)
        • 可预取:是
      • BAR1
        • 启用:否(如果捐赠设备不使用BAR1)
      • 继续配置BAR2-BAR5,镜像捐赠设备的配置。
  5. 确保对齐和非重叠空间
    • Vivado IP核通常会根据您选择的大小自动处理对齐。但是,请注意PCIe规范要求BAR大小是2的幂,并且BAR必须对其大小进行对齐。
    • 确保所有活动BAR映射的总内存不超过FPGA可用的块RAM(BRAM)或外部内存容量。

7.2.3 完成IP定制

在IP核定制窗口中配置所有必要的设置后,您必须应用这些更改,使其在Vivado项目中生效。

步骤:

  1. 审查所有设置:
    • 在应用之前,花点时间快速最后一次审查IP定制窗口中的每个选项卡。
    • 确认所有条目都与您捐赠设备的文档规范精确匹配。这里的一个小错误可能导致设备检测或功能问题。
  2. 应用更改:
    • 点击IP定制窗口底部的 OKGenerate 按钮(标签可能不同)。
    • 如果Vivado提示您确认是否继续更改并重新生成IP输出产品,请点击 Yes 确认。
  3. 重新生成IP核:
    • Vivado现在将重新生成IP核的输出产品(例如,网表、仿真模型、新的.xci配置文件),以反映您的新配置。
    • 监控 消息 窗格(Vivado窗口底部),查看在此重新生成过程中可能出现的任何错误、警告或严重警告。立即解决任何严重警告。
  4. 更新项目中的IP:
    • 在IP核重新生成后,Vivado可能会自动更新或提示您更新项目中的任何IP依赖项。允许它这样做,以确保在整个设计中使用最新的配置。

7.2.4 锁定IP核

锁定IP核是Vivado中推荐的最佳实践,可防止在后续综合和实现运行期间意外修改或重新定制,这可能会潜在地恢复您精心配置的设置。

锁定的目的:

  • 防止覆盖: 确保您在IP核GUI中进行的手动配置得以保留,不会因Vivado自动化或IP因细微项目更改而被检测为“过时”而意外覆盖。
  • 保持一致性: 在整个构建过程中保持IP核处于已知、稳定的状态,这对于PCIe接口等关键组件尤其重要。

步骤:

  1. 打开Tcl控制台:

    • 在Vivado中,如果Tcl控制台尚未打开,请转到 Window > Tcl Console
  2. 执行锁定命令:

    • 在Tcl控制台中,精确输入以下命令。此命令将PCIe IP核实例(pcie_7x_0)的IP_LOCKED属性设置为true
    1
    set_property -name {IP_LOCKED} -value true -objects [get_ips pcie_7x_0]
    • Enter 执行命令。
  3. 验证锁定:

    • 检查 消息 窗格。您应该会看到一条确认属性已设置的消息。
    • 您还可以右键单击源文件窗格中的 pcie_7x_0.xci,选择“IP Properties”(IP属性),并验证 IP_LOCKED 是否设置为 true。您可能还会注意到“Customize IP”(定制IP)选项现在已灰显,或者只允许“Re-customize IP”(重新定制IP),然后会警告您关于锁定。
  4. 解锁(如果需要):

    • 如果您将来需要对PCIe IP核的设置进行进一步修改,则必须先将其解锁。使用以下Tcl命令:
    1
    set_property -name {IP_LOCKED} -value false -objects [get_ips pcie_7x_0]
    • 请记住在进行和应用更改后重新锁定它。
  5. 记录操作:

    • 在您的项目文档(例如,README文件、项目说明)中注明PCIe IP核已锁定是一个好习惯。这有助于项目中其他人理解其配置状态并避免混淆。

第二部分:中级概念与实现


8. 高级固件定制

为了实现对捐赠设备精确且令人信服的仿真,除了基本识别之外,还需要对FPGA固件进行更深入的定制。这包括调整PCIe参数(例如链路速度和事务大小),细致地调整基地址寄存器(BARs)及其相关的内存映射,以及准确仿真电源管理和中断机制。这些步骤确保仿真设备不仅在主机系统看来与原始硬件相同,而且在协议和功能级别上行为也完全一致。

8.1 配置PCIe参数以进行仿真

精确仿真要求您的FPGA设备的PCIe操作参数经过细致配置,以匹配捐赠设备的参数。这包括PCIe链路速度、链路宽度、能力指针和最大有效载荷大小等设置。正确的配置可确保与主机系统的兼容性、驱动程序和应用程序与设备交互的正确操作以及数据传输的最佳性能。

8.1.1 匹配PCIe链路速度和宽度

PCIe链路速度(例如,Gen1、Gen2、Gen3)和链路宽度(例如,x1、x4、x8)是决定设备最大理论数据吞吐量和性能的关键参数。将这些设置与捐赠设备匹配对于精确仿真至关重要,因为驱动程序或系统组件可能期望特定的链路能力。

步骤:

  1. 访问PCIe IP核设置:

    • 打开您的Vivado项目: 启动Vivado并打开您之前创建或修改的项目(例如,pcileech_squirrel_top.xpr)。确保所有源文件都已正确添加到项目中。
    • 定位PCIe IP核:Sources(源文件)窗格(通常在左侧)中,展开设计层次结构以找到PCIe IP核实例。对于Xilinx 7系列设计(如Squirrel中使用的Artix-7),这通常被命名为 pcie_7x_0.xci
    • 定制IP核: 右键单击 pcie_7x_0.xci 并选择 Customize IP(定制IP)。IP定制窗口将打开,显示多个选项卡中的各种配置选项。
  2. 设置最大链路速度:

    • 导航到链路参数: 在IP定制窗口中,点击 PCIe Capabilities(PCIe功能)选项卡(有时是“PCIe Configuration”或“General”)。在此选项卡内,查找与 Link Parameters(链路参数)或 Link Capability Register(链路能力寄存器)相关的部分。
    • 配置最大链路速度: 找到标有 Maximum Link Speed(最大链路速度)的选项(或“Target Link Speed”)。
    • 将其设置为与您的捐赠设备支持和广告的最大链路速度相匹配。
      • 示例:
        • 如果捐赠设备在 PCIe Gen2 (5.0 GT/s) 下运行,选择 5.0 GT/s
        • 如果它在 PCIe Gen1 (2.5 GT/s)PCIe Gen3 (8.0 GT/s) 下运行,请选择相应的选项。
    • 注意:确保您的FPGA的收发器和物理硬件(主板PCIe插槽)支持所选的链路速度。FPGA只会协商到其配置的最大速度。
  3. 设置链路宽度:

    • 配置链路宽度: 在相同的 Link Parameters(链路参数)部分中,找到 Link Width(链路宽度)设置(或“PCIe Link Width”、“Target Link Width”)。
    • 将其设置为与您的捐赠设备广告的最大链路宽度相匹配。
      • 示例:
        • 如果捐赠设备使用 x4 链路,将 Link Width 设置为 4
        • 选项通常包括 124816 通道。
    • 注意:物理PCIe插槽和FPGA的封装必须支持所选的链路宽度。尝试配置大于物理连接的宽度将导致链路协商问题。
  4. 保存并重新生成:

    • 应用更改: 配置链路速度和宽度后,点击 OK 以在IP定制窗口中应用更改。
    • 重新生成IP输出产品: Vivado很可能会提示您由于所做的更改而重新生成IP核的输出产品。确认并允许重新生成过程完成。这可能需要一些时间。
    • 验证设置: 一旦重新生成完成,您可以选择性地重新访问IP核设置,以确保配置已正确应用。检查Vivado中 Messages(消息)窗口中是否有任何警告或错误。

8.1.2 设置能力指针

PCIe配置空间中的能力指针是8位寄存器,它们形成一个链表,指向各种能力结构(例如,电源管理、MSI/MSI-X、PCIe Express能力)。正确设置这些指针可确保主机系统能够遍历能力列表并定位和利用设备广告的功能。

步骤:

  1. 在固件中定位能力指针:

    • 打开配置文件: 在Visual Studio Code中,打开您的板卡的主配置文件,通常是pcileech_pcie_cfg_a7.sv,位于pcileech-fpga/<your_board_variant>/src/pcileech_pcie_cfg_a7.sv
    • 理解能力指针: 此文件中的能力指针(cfg_cap_pointer)指向PCIe配置空间中的第一个能力结构,通常从标准64字节配置头之后开始。后续的能力通过其“下一个能力指针”字段链接起来。
  2. 设置能力指针值:

    • 找到cfg_cap_pointer的赋值: 在代码中搜索定义cfg_cap_pointer的行。
      1
      reg [7:0] cfg_cap_pointer = 8'hXX; // 当前值(例如,默认的8'h40)
    • 更新能力指针:XX替换为您使用Arbor从捐赠设备观察到的8位十六进制能力指针值。此值通常指向设备特定配置空间(通常在偏移量0x3F结束)之后第一个能力结构的偏移量。能力常见的起始点是0x400x60
      • 示例:
        • 如果捐赠设备的第一个能力指针是0x60(表示其第一个能力结构在配置空间中从偏移量0x60开始),将该行更新为:
          1
          reg [7:0] cfg_cap_pointer = 8'h60; // 更新以匹配捐赠设备的第一个能力偏移量
    • 确保正确对齐: 能力结构必须对齐到4字节边界。能力指针应始终指向配置空间中有效的4字节对齐偏移量。
  3. 保存更改:

    • 保存配置文件: 进行更改后,点击 文件 > 保存 或按下 Ctrl + S 保存文件。
    • 验证语法: 确保更改未引入任何语法错误(VS Code通常会高亮显示这些错误)。
    • 添加注释以清晰说明: 添加注释解释更改,以便将来参考和维护。
      1
      reg [7:0] cfg_cap_pointer = 8'h60; // 设置为捐赠设备的能力指针(例如,PCIe能力位于0x60)

8.1.3 调整最大载荷和读取请求大小

这些参数定义了单个PCIe事务层数据包(TLP)中可以传输的最大数据量,以及非posted内存读取请求TLP的最大大小。将这些设置与捐赠设备匹配可确保兼容性并优化数据传输操作的性能。不匹配可能导致吞吐量降低或通信错误。

步骤:

  1. 设置支持的最大载荷大小(IP核):

    • 访问设备功能: 在PCIe IP核定制窗口(Vivado中的pcie_7x_0.xci)中,导航到 PCIe Capabilities(PCIe功能)选项卡。
    • 配置支持的最大载荷大小: 找到标有 Max Payload Size Supported(支持的最大载荷大小)的设置(或类似名称)。
    • 将其设置为与您的捐赠设备支持和广告的值相匹配(例如,128字节、256字节、512字节、1024字节、2048字节、4096字节)。
      • 示例: 如果捐赠设备支持的最大载荷大小为 256字节,请从下拉列表中选择 256字节
  2. 设置支持的最大读取请求大小(IP核):

    • 配置支持的最大读取请求大小: 在同一选项卡中,找到 Max Read Request Size Supported(支持的最大读取请求大小)设置。
    • 将其设置为与捐赠设备的能力相匹配。这指定了设备在单个读取事务中可以请求的最大数据量。
      • 示例: 如果捐赠设备支持的最大读取请求大小为 512字节,请选择 512字节
  3. 调整固件参数(匹配IP核):

    • 打开 pcileech_pcie_cfg_a7.sv 确保配置文件在Visual Studio Code中打开。
    • 更新固件常量: 找到定义max_payload_size_supportedmax_read_request_size_supported的行。这些通常是与您在IP核中选择的字节大小对应的位编码值。
      1
      2
      reg [2:0] max_payload_size_supported = 3'bZZZ;   // 当前值
      reg [2:0] max_read_request_size_supported = 3'bWWW; // 当前值
    • 设置适当的值:ZZZWWW替换为与所选大小对应的3位二进制表示。
      • 映射(根据PCIe规范):
        • 128字节3'b000
        • 256字节3'b001
        • 512字节3'b010
        • 1024字节3'b011
        • 2048字节3'b100
        • 4096字节3'b101
      • 示例:
        • 对于 256字节 载荷大小:
          1
          reg [2:0] max_payload_size_supported = 3'b001; // 支持最大256字节 (0x100)
        • 对于 512字节 读取请求大小:
          1
          reg [2:0] max_read_request_size_supported = 3'b010; // 支持最大512字节 (0x200)
    • 原因:这些固件参数通常决定了与PCIe核接口的用户逻辑的行为,确保您的逻辑遵循配置的最大值。
  4. 保存更改:

    • 保存文件: 更新pcileech_pcie_cfg_a7.sv中的值后,保存文件。
    • 验证一致性: Vivado PCIe IP核GUI中配置的值必须与您的HDL配置文件中设置的值匹配。任何不匹配都可能导致意外行为或链路训练问题。
    • 添加注释: 在您的代码中清晰地记录这些更改,以便将来参考。

8.2 调整BARs和内存映射

基地址寄存器(BARs)是PCIe设备向主机系统公开其内部内存和寄存器的基本方式。正确配置BARs并在FPGA的BRAMs(块RAMs)和逻辑中定义它们的内存映射对于精确仿真和主机端设备驱动程序的正常运行至关重要。

8.2.1 设置BAR大小和类型(IP核和BRAM)

配置BAR大小和类型可确保您的仿真设备在枚举期间向主机请求正确的地址空间量,并且主机适当地分配和映射这些区域。这还涉及将这些地址区域与FPGA内的物理内存块关联起来。

步骤:

  1. 访问BAR配置(PCIe IP核):

    • 定制PCIe IP核: 在Vivado中,右键单击 pcie_7x_0.xci 并选择 Customize IP(定制IP)以打开其配置GUI。
    • 导航到BARs选项卡: 在IP定制窗口中,点击 Base Address Registers (BARs) (基地址寄存器(BARs))选项卡。
  2. 配置每个BAR(IP核):

    • 匹配捐赠设备的BARs: 对于每个BAR(BAR0到BAR5),根据您使用Arbor从捐赠设备中提取的信息,细致地设置大小、类型和可预取状态。
    • 启用/禁用BARs: 确保只启用捐赠设备实际使用的BARs。禁用(取消选中)任何未使用的BARs。
    • 设置BAR大小: 为每个已启用的BAR从下拉列表中选择适当的大小。这将是2的幂次(例如,4KB、8KB、64KB、1MB、256MB、1GB)。
      • 示例:
        • 如果 BAR064 KB,将 BAR0 Size 设置为 64 KB
        • 如果 BAR1128 MB,将 BAR1 Size 设置为 128 MB
    • 设置BAR类型:
      • 如果BAR是内存映射的,选择 Memory (32-bit Addressing) (内存(32位寻址))或 Memory (64-bit Addressing) (内存(64位寻址))。如果捐赠设备的BAR是64位或您需要访问4GB以上的地址,请选择 64-bit Addressing
      • 如果BAR用于I/O端口空间(现代PCIe设备较少见),选择 I/O
    • 设置可预取状态:如果捐赠设备的BAR被识别为可预取,请选中“Prefetchable”(可预取)框。此位允许主机预取该区域的数据,可能提高性能。
  3. 更新BRAM配置(如果适用):

    • 许多PCILeech-FPGA项目使用Xilinx块RAM(BRAM)IP核来表示BARs暴露的内存区域。这些BRAM提供仿真设备内存的物理存储。
    • 定位BRAM IP核: 在您的Vivado项目 Sources(源文件)窗格中,在ip子目录(或类似目录)中,您可能会找到BRAM的.xci文件,名称可能类似:
      1
      2
      3
      pcileech-fpga/<your_board_variant>/ip/bram_bar_zero4k.xci
      pcileech-fpga/<your_board_variant>/ip/bram_pcie_cfgspace.xci
      # 可能还有BAR1、BAR2等的其他文件
    • 修改BRAM大小: 对于与已启用BAR关联的每个BRAM IP核,您可能需要 Customize IP(定制IP)(右键单击.xci文件)并调整其内存大小配置,以精确匹配相应的BAR大小。
      • 示例: 如果BAR0是256MB,请确保连接到BAR0的BRAM大小为256MB。
      • 注意:确保所有活动BAR所需的总内存不超过您的FPGA设备的物理BRAM容量。超出容量将导致实现失败。
  4. 保存并重新生成:

    • 应用更改(IP核): 在PCIe IP核中配置BAR后,点击IP定制窗口中的 OK
    • 重新生成IP核: Vivado将提示您由于所做的更改而重新生成PCIe IP核和任何相关的BRAM IP核。允许重新生成完成。这可确保硬件网表反映您的新BAR定义。
    • 检查错误: 检查 Messages(消息)窗口中是否有与BAR配置或BRAM实例化相关的任何警告或错误。

8.2.2 在固件中定义BAR地址空间

尽管PCIe IP核配置了BAR的硬件方面,但您的定制固件(SystemVerilog代码)需要定义当主机CPU对这些BAR区域内的地址执行读写操作时,仿真设备如何响应的逻辑。这涉及地址解码和实现寄存器/内存访问逻辑。

步骤:

  1. 打开BAR控制器文件:

    • 在Visual Studio Code中,打开负责处理BAR访问的SystemVerilog文件。对于PCILeech-FPGA,这通常是:
      1
      pcileech-fpga/<your_board_variant>/src/pcileech_tlps128_bar_controller.sv
      此模块通常接收PCIe内存读/写TLP,并解码地址以确定正在访问哪个BAR(以及该BAR内的哪个偏移量)。
  2. 实现地址解码逻辑:

    • pcileech_tlps128_bar_controller.sv模块中,您会找到确定传入事务目标是哪个BAR的逻辑。这通常涉及根据配置的BAR大小检查地址位。
    • 您需要定义传入地址req_addr(来自TLP)如何映射到您的特定BAR内的偏移量。
    • 概念示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      // 示例:BAR0的逻辑(假设它是一个256MB的64位内存BAR,用于寄存器/数据)
      // 'bar_hit[0]'是一个指示命中BAR0的输入信号,通常来自PCIe核。
      // 'req_addr'是传入的PCIe地址。
      // 'req_be'是来自TLP的字节使能。
      // 'req_data'是传入的写入数据。
      // 'rsp_data'是传出的读取数据。

      // 假设BAR0是256MB (2^28字节),地址位 [27:0] 在BAR范围内。
      localparam BAR0_SIZE_BITS = 28; // 2^28 = 256MB

      reg [31:0] internal_register_0; // BAR0内的示例寄存器
      reg [31:0] internal_register_1; // 另一个示例寄存器

      assign bar0_offset = req_addr[BAR0_SIZE_BITS-1:0]; // 提取BAR0内的偏移量

      always_comb begin
      // 默认响应
      rsp_data = 32'hFFFFFFFF; // 默认值为全F或类似值,表示未映射区域

      if (bar_hit[0]) begin // 如果事务目标是BAR0
      if (req_write) begin // 这是写入操作
      case (bar0_offset)
      // 示例:将偏移量0x0映射到internal_register_0
      32'h0000_0000: begin
      if (req_be[3]) internal_register_0[31:24] = req_data[31:24];
      if (req_be[2]) internal_register_0[23:16] = req_data[23:16];
      if (req_be[1]) internal_register_0[15:8] = req_data[15:8];
      if (req_be[0]) internal_register_0[7:0] = req_data[7:0];
      end
      // 示例:将偏移量0x4映射到internal_register_1
      32'h0000_0004: begin
      if (req_be[3]) internal_register_1[31:24] = req_data[31:24];
      if (req_be[2]) internal_register_1[23:16] = req_data[23:16];
      if (req_be[1]) internal_register_1[15:8] = req_data[15:8];
      if (req_be[0]) internal_register_1[7:0] = req_data[7:0];
      end
      // 添加更多寄存器映射或内存访问(例如,BRAM访问)
      default: begin
      // 处理BAR0内未映射的写入,例如,忽略或记录
      end
      endcase
      end else if (req_read) begin // 这是读取操作
      case (bar0_offset)
      // 示例:从internal_register_0读取
      32'h0000_0000: rsp_data = internal_register_0;
      // 示例:从internal_register_1读取
      32'h0000_0004: rsp_data = internal_register_1;
      // 添加更多寄存器映射或内存访问(例如,BRAM访问)
      default: begin
      rsp_data = 32'h0; // 对于未映射的读取返回0或特定错误值
      end
      endcase
      end
      end
      end
    • 处理数据传输: always_comb块(或always_ff用于时序逻辑)应定义如何为读取生成rsp_data,以及如何根据bar0_offset和字节使能(req_be)更新内部寄存器/内存。
  3. 实现BRAM访问(如果BAR映射到BRAM):

    • 如果BAR映射到大块内存(例如,256MB),您通常会实例化一个BRAM IP核(如8.2.1所述)并将其bar_controller逻辑与它连接。bar_controller将向BRAM提供地址和控制信号。
    • 概念性BRAM集成(简化):
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      // 在pcileech_tlps128_bar_controller.sv或子模块中
      // BRAM接口
      wire [BAR0_SIZE_BITS-1:0] bram_addr;
      wire [31:0] bram_wr_data;
      wire [3:0] bram_wr_en; // BRAM的字节使能
      wire bram_wr_ce;
      wire bram_rd_ce;
      wire [31:0] bram_rd_data;

      // 将TLP信号映射到BRAM接口
      assign bram_addr = bar0_offset;
      assign bram_wr_data = req_data;
      assign bram_wr_en = req_be;
      assign bram_wr_ce = bar_hit[0] && req_write;
      assign bram_rd_ce = bar_hit[0] && req_read;

      // 实例化BRAM IP核
      bram_bar_zero bram_inst ( // 假设'bram_bar_zero'是您的BRAM IP模块
      .clka(clk),
      .ena(1'b1),
      .wea(bram_wr_en),
      .addra(bram_addr),
      .dina(bram_wr_data),
      .douta(bram_rd_data)
      );

      // 对于从BAR0的读取,输出BRAM的数据
      if (bar_hit[0] && req_read) begin
      rsp_data = bram_rd_data;
      end
  4. 保存更改:

    • 实现每个BAR的逻辑后,保存pcileech_tlps128_bar_controller.sv文件。
    • 验证功能: 此逻辑很复杂。彻底的仿真(使用测试平台)和后续的硬件测试对于确保正确行为至关重要。

8.2.3 处理多个BAR

正确管理多个BAR对于暴露多个独立内存或I/O区域的设备至关重要。bar_controller模块通常处理所有BAR。

步骤:

  1. 实现每个BAR的逻辑:

    • pcileech_tlps128_bar_controller.sv内部,扩展逻辑以处理您的捐赠设备使用的所有已启用BAR(BAR0、BAR1、BAR2等)。
    • 独立逻辑块: 为清晰和可维护性,创建独立的if/else if块或case语句,根据哪个bar_hit信号被断言而激活。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // BAR0处理
      if (bar_hit[0]) begin
      // BAR0特定读/写逻辑,用于其寄存器/内存
      end else if (bar_hit[1]) begin
      // BAR1特定读/写逻辑,用于其寄存器/内存
      end else if (bar_hit[2]) begin
      // BAR2特定逻辑
      end
      // ... 继续其他BAR
    • 定义寄存器和内存: 根据需要为每个BAR分配独立的寄存器集或连接不同的BRAM实例。
  2. 确保非重叠地址空间:

    • 虽然PCIe IP核处理与主机操作系统的每个BAR的不同地址空间的协商,但您的内部固件逻辑必须假定这些空间是独立的且不重叠的。
    • 验证地址范围:仔细检查PCIe IP核中的BAR大小配置,以确保它们是独立的,并且根据PCIe规范正确地对齐到2的幂次方边界。
    • 更新地址解码:您的bar_controller逻辑依赖于PCIe IP核生成的bar_hit信号。确保这些信号被正确解释并导致每个BAR的独特处理逻辑。
  3. 测试BAR访问:

    • 仿真测试: 在硬件部署之前,使用仿真工具(例如Vivado仿真器)和全面的测试平台来验证对每个BAR的所有读写操作。
      • 向每个BAR内的特定偏移量发送内存写入TLP。
      • 向每个BAR内的特定偏移量发送内存读取TLP并验证返回的数据。
    • 硬件测试: 编程FPGA后,使用主机端软件工具(如PCILeech客户端软件或定制C/Python脚本)访问和验证每个BAR。
      • Linux:使用lspci -vvv检查BAR映射(Memory at XXXX (64-bit, prefetchable) [size=YYYY])。然后可以使用devmem2或自定义内核模块来读/写这些映射地址。
      • Windows:使用“RW-Everything”等工具或自定义用户模式应用程序来检查和与映射的内存区域交互。
      • 执行各种读/写模式以确保所有BAR之间的数据完整性和正确寻址。

8.3 仿真设备电源管理和中断

实现电源管理功能和中断对于需要与主机操作系统的电源管理和中断处理机制密切高效交互的设备至关重要。没有这些,仿真设备可能无法完全正常工作,或者性能可能不理想。

8.3.1 电源管理配置

实现电源管理允许仿真设备支持各种电源状态(例如D0、D3hot),有助于系统范围的电源效率并符合操作系统的预期。主机操作系统将查询设备的功能并发送命令以在这些状态之间转换。

步骤:

  1. 在PCIe IP核中启用电源管理:

    • 访问功能: 在PCIe IP核定制窗口(pcie_7x_0.xci)中,导航到 PCIe Capabilities(PCIe功能)选项卡。
    • 启用电源管理: 查找与 Power Management Capability(电源管理功能)相关的部分或选项。确保已选中或启用此选项,以便在设备的配置空间中包含电源管理(PM)功能结构。
  2. 设置支持的电源状态:

    • 配置支持的状态: 在IP核的电源管理功能部分,指定设备支持的电源状态。这些通常是复选框或下拉菜单。将这些设置与您通过Arbor观察到的捐赠设备的能力相匹配。
      • D0(完全开启/运行) :始终支持。
      • D1、D2(中间状态) :可选,用于低功耗空闲状态。
      • D3hot(断电,辅助电源存在) :设备逻辑关闭,但可以响应PM事件。
      • D3cold(完全断电) :设备没有电源。
    • 示例:如果捐赠设备仅支持D0和D3hot,则只启用它们。
  3. 在固件中实现电源状态逻辑:

    • 打开 pcileech_pcie_cfg_a7.sv(或相关控制模块): 您通常需要修改固件以反映并可能响应主机命令的电源状态转换。PCIe核本身处理大部分协议,但您的用户逻辑需要知道当前状态。
    • 处理电源管理控制和状态寄存器(PMCSR)写入: 主机操作系统通过写入PMCSR中的特定位来改变设备的电源状态,PMCSR是PM能力结构的一部分。您的固件理想情况下应有逻辑来读取这些位并调整设备行为(例如,暂停/恢复操作,启用/禁用时钟)。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      // 示例:pcileech_pcie_cfg_a7.sv或专用PM模块的一部分
      // 假设'cfg_write'在配置写入时被断言,'cfg_address'是偏移量,'cfg_writedata'是数据。
      // D状态位位于PM能力结构中偏移量0x04处,位[1:0]。

      // PMCSR寄存器(内部表示)
      reg [15:0] pmcsr_reg = 16'h0000; // 初始化为D0

      // 用户逻辑信号,指示当前电源状态
      reg [1:0] current_d_state = 2'b00; // 00 = D0, 01 = D1, 10 = D2, 11 = D3hot

      always @(posedge clk) begin
      if (reset) begin
      pmcsr_reg <= 16'h0000;
      current_d_state <= 2'b00; // 重置为D0
      end else begin
      // 示例:捕获对PMCSR的写入(如果直接在用户逻辑中处理)
      // 注意:PCIe IP核管理大部分内容,但您的用户逻辑可能需要从中读取值。
      // 假设PCIe核提供一个反映当前D状态的输出:
      // assign current_d_state = pcie_core_d_state_output;

      // 如果用户逻辑*需要*写入PMCSR(较少见,通常是只读状态)
      // 或者它需要处理命令
      // if (cfg_write && (cfg_address == PM_CAP_OFFSET + 2'h04)) begin // PMCSR在PM Cap基础地址+0x04
      // pmcsr_reg[1:0] <= cfg_writedata[1:0]; // 捕获新D状态
      // // current_d_state <= cfg_writedata[1:0]; // 更新内部状态
      // end

      // 在PCILeech中,PCIe核管理PMCSR。您可能会从核读取信号。
      // 为了演示,假设'pcie_d_state'是来自IP核的输入。
      current_d_state <= pcie_d_state; // 根据PCIe核的状态更新
      end
      end

      // 示例:响应D状态变化的逻辑
      always @(*) begin
      if (current_d_state == 2'b11) begin // D3hot状态
      // 禁用非必要模块的电源,暂停操作,
      // 断言信号给主DMA逻辑以停止活动。
      // 例如:dma_engine_enable = 1'b0;
      end else if (current_d_state == 2'b00) begin // D0状态
      // 启用全部功能
      // 例如:dma_engine_enable = 1'b1;
      end
      end
    • 管理电源状态效果: 实现逻辑以根据current_d_state更改设备的内部行为(例如,启用/禁用时钟,将子模块置于低功耗模式)。这对于精确的功耗仿真以及确保设备正确响应操作系统命令至关重要。
  4. 保存更改:

    • 保存任何修改过的固件文件。
    • 通过仿真或硬件测试(例如,Windows“睡眠”或“休眠”功能,或Linux poweroff命令)彻底测试电源管理功能,以查看设备是否正确转换。

8.3.2 MSI/MSI-X配置

实现消息信号中断(MSI)或其扩展版本(MSI-X)允许仿真设备使用基于消息的中断。这些中断比传统的引脚中断(INTx)效率更高、可扩展性更强,是现代PCIe设备的优选方法。MSI/MSI-X允许设备通过向特定内存地址写入特殊TLP来通知CPU。

步骤:

  1. 在PCIe IP核中启用MSI/MSI-X:

    • 访问中断配置: 在PCIe IP核定制窗口(pcie_7x_0.xci)中,导航到 Interrupts(中断)选项卡或专门标记为 MSI/MSI-X Capabilities(MSI/MSI-X功能)的部分。
    • 选择中断类型: 根据捐赠设备的功能,选择 MSIMSI-X。MSI-X通常因其灵活性(更多向量,每个向量可屏蔽)而受到青睐。
    • 配置支持的向量数量: 设置设备将支持的中断向量(消息)数量。这应与捐赠设备匹配。
      • MSI 支持最多32个向量(通常是1、2、4、8、16或32)。
      • MSI-X 支持最多2048个向量,允许更细粒度的中断源。
    • 启用功能: 确保MSI或MSI-X功能结构已明确启用,以便包含在设备的配置空间中。这是主机操作系统发现设备中断能力的方式。
  2. 在固件中实现中断逻辑:

    • 打开 pcileech_pcie_tlp_a7.sv(或用户逻辑模块): 此文件通常负责用户定义的TLP生成,并且可能是启动MSI/MSI-X消息的合适位置。但是,中断的触发将来自您的自定义逻辑。
    • 定义中断信号: 声明内部信号,指示何时需要生成中断。
      1
      2
      // 在自定义模块中(例如,'my_device_logic.sv'),该模块与TLP生成逻辑接口
      reg msi_trigger_signal; // 当发生中断条件时断言此信号
    • 实现中断生成逻辑: 定义应该触发中断的条件。这通常涉及在仿真设备的逻辑中检测事件。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // 在'my_device_logic.sv'内部
      input wire clk;
      input wire reset;
      input wire event_data_ready; // 示例:当数据就绪时来自您的逻辑的输入

      always @(posedge clk or posedge reset) begin
      if (reset) begin
      msi_trigger_signal <= 1'b0;
      end else if (event_data_ready) begin // 当特定事件发生时
      msi_trigger_signal <= 1'b1; // 触发MSI
      end else begin
      msi_trigger_signal <= 1'b0; // 一个周期后或被确认后清除
      end
      end
    • 连接到PCIe核的MSI接口: msi_trigger_signal(或您的自定义逻辑的类似输出)需要连接到PCIe IP核的适当输入(例如,如果使用AXI-Stream接口进行MSI TLP,则连接到s_axis_tdata_treadys_axis_tdata_tvalids_axis_tdata_tlast;或者连接到IP核提供的专用MSI请求端口)。然后PCIe核会形成并发送实际的MSI/MSI-X TLP。有关精确的接口详细信息,请查阅Xilinx PCIe IP核文档。
  3. 保存更改:

    • 实现中断逻辑后,保存所有修改过的固件文件。
    • 检查时序约束: 新逻辑,特别是中断路径,可能对时序很敏感。确保综合和实现工具不会报告与您的中断生成逻辑相关的任何时序违规。

8.3.3 实现中断处理逻辑(设备端)

除了启用功能之外,定义仿真设备何时以及如何生成中断对于其与主机中断处理机制和驱动程序行为的正确交互至关重要。这涉及创建断言中断请求的内部逻辑。

步骤:

  1. 定义中断条件:

    • 识别触发事件: 根据您的捐赠设备的行为,确定哪些特定的内部事件应导致您的仿真设备生成中断。
      • 示例:数据传输完成、接收缓冲区中有新数据、内部错误条件、特定命令完成、链路状态更改。
    • 实现条件逻辑: 在您的自定义SystemVerilog模块中使用组合逻辑或时序逻辑,精确检测这些事件并生成一个短脉冲或电平信号,指示中断请求。
  2. 创建中断生成模块(模块化设计):

    • 将中断生成逻辑封装到一个单独的专用模块中是一个好习惯,这样可以提高清晰度、可重用性并方便调试。此模块将内部事件作为输入,并产生一个连接到PCIe核的msi_req(或类似)输出。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      // 文件:interrupt_generator.sv
      module interrupt_generator (
      input wire clk,
      input wire reset,
      input wire event_trigger, // 来自您的自定义逻辑的输入信号(例如,data_ready, error_flag)
      output reg msi_req_o // 输出:断言此信号以请求MSI/MSI-X
      );

      // MSI的简单脉冲发生器(一次性中断)
      reg event_trigger_d1;

      always @(posedge clk or posedge reset) begin
      if (reset) begin
      msi_req_o <= 1'b0;
      event_trigger_d1 <= 1'b0;
      end else begin
      event_trigger_d1 <= event_trigger;
      // 当event_trigger从低到高跳变时,生成一个单周期脉冲
      if (event_trigger && !event_trigger_d1) begin
      msi_req_o <= 1'b1; // 断言MSI请求
      end else begin
      msi_req_o <= 1'b0; // 一个周期后取消断言
      end
      end
      end

      endmodule // interrupt_generator
    • 与主固件集成: 在您的顶层用户逻辑中(例如,在pcileech_squirrel_top.sv或其实例化的模块中)实例化此interrupt_generator模块,并将其msi_req_o输出连接到PCIe IP核的MSI输入。
  3. 确保正确的时序和序列:

    • 遵守PCIe规范: MSI/MSI-X消息是TLP。确保这些消息的生成符合PCIe TLP格式、流控制和时序要求。PCIe IP核处理大部分内容,但您提供给它的输入信号必须稳定且时序正确。
    • 管理中断延迟: 优化您的逻辑,以最大限度地减少内部事件发生与msi_req_o信号断言之间的任何不必要延迟。
  4. 测试中断传递:

    • 仿真: 使用全面的测试平台模拟应生成中断的场景。验证您的msi_req_o信号是否按预期工作,以及PCIe核是否生成正确的MSI/MSI-X TLP。
    • 硬件测试:
      • 用更新的固件编程FPGA。
      • 使用主机端软件触发应引起中断的事件(例如,启动完成的DMA传输)。
      • 确认主机操作系统接收到中断。在Linux上,dmesg可以显示中断消息。在Windows上,您可以使用特定的驱动程序调试工具或事件查看器。
      • 调试工具: 利用Vivado的集成逻辑分析仪(ILA)核(如第12节所述)实时监控event_triggermsi_req_o和PCIe核的TLP输出信号,以验证正确的中断生成。
  5. 保存更改:

    • 完成所有代码修改并保存相关固件文件。
    • 根据测试结果审查并改进您的中断逻辑,以确保可靠性。

9. 仿真设备特定功能

除了标准的PCIe配置空间和通用DMA功能之外,许多捐赠设备还具有独特的功能、自定义寄存器或厂商特定功能,这些对于其完整功能或与其专有驱动程序交互至关重要。精确的仿真需要理解和复制这些细微之处。本节将深入探讨如何实现这些高级功能,从而实现更忠实和功能更全面的仿真。

9.1 实现高级PCIe功能

PCIe规范包含除了基本配置空间之外的各种扩展功能。这些功能提供了高级错误报告、电源管理、虚拟化等特性。实现这些功能有助于您的仿真设备显得更合法,并与现代主机系统正确交互。

步骤:

  1. 识别所需的扩展功能:

    • 在使用Arbor等工具收集捐赠设备信息时,请仔细查找并记录捐赠设备配置空间中存在的任何扩展功能。这些通常在标准配置空间的初始256字节之外找到。
    • 常见示例:
      • 高级错误报告(AER) :为PCIe链路提供强大的错误检测、日志记录和报告机制。
      • 设备序列号(DSN) :(已在第6.2节中介绍)。
      • 电源管理(PM) :(已在第8.3.1节中介绍)。
      • PCI Express(PCIe)功能结构:(已在第8.1节中介绍链路速度/宽度、最大载荷/读取请求,但也包括其他字段,如设备控制/状态、链路控制/状态)。
      • 虚拟通道(VC)/多功能虚拟通道(MFVC) :用于服务质量(QoS)和流量管理。
      • 精确时间测量(PTM) :用于设备之间的时间同步。
      • 延迟容忍报告(LTR) :基于延迟要求进行电源管理。
      • 可重置FPC(功能级重置) :用于更细粒度的重置。
  2. 在Vivado PCIe IP核中启用功能:

    • 访问Vivado中的PCIe IP核定制窗口(pcie_7x_0.xci)。
    • 导航到各个选项卡(例如,“PCIe Capabilities”、“Extended Capabilities”、“Advanced Options”)。
    • 查找复选框或下拉菜单,以启用和配置从捐赠设备中识别出的特定扩展功能。
    • 示例(AER): 您会找到一个“高级错误报告”部分,您可以在其中启用它并配置其寄存器(例如,严重性掩码)。
    • 注意: Xilinx PCIe IP核为许多标准和扩展功能提供了高度可配置性。通常只需在GUI中启用正确的选项即可。
  3. 实现功能寄存器的固件逻辑(如果需要):

    • 虽然PCIe IP核处理这些功能的存在和大部分协议,但某些功能会暴露您的自定义固件可能需要读写或其值需要固件响应的寄存器。
    • 示例(AER): 如果您的仿真设备检测到应通过AER报告的内部错误,您的固件需要写入特定的AER错误状态寄存器(这些寄存器可能作为BAR的一部分暴露,或由PCIe核内部处理,然后反映到用户逻辑)。然后您的用户逻辑将向PCIe核断言错误输入。
    • 示例(电源管理): 如8.3.1节所述,您的固件需要响应PCIe核发出的D状态变化。
    • 流程:
      • 识别您的捐赠设备驱动程序交互的每个已启用功能结构中的特定寄存器。
      • 在PCILeech-FPGA框架中找到与这些寄存器接口的相应信号或逻辑(通常在pcileech_pcie_cfg_a7.svbar_controller中)。
      • 实现这些寄存器的读写逻辑,确保您的仿真设备的内部状态准确反映驱动程序期望的值。

9.2 仿真厂商特定功能

这是真正的“全设备仿真”变得高度专业化的部分。许多实际设备具有独特的寄存器、未文档化的命令、自定义数据格式或专有控制流程,这些都使其与众不同。复制这些需要更深入的分析和定制HDL开发。

步骤:

  1. 逆向工程厂商特定行为:

    • 这通常是最具挑战性的部分。
    • 静态分析(驱动程序/固件): 反汇编捐赠设备的官方驱动程序(Windows .sys,Linux .ko)或设备的原始固件(如果可用)。查找独特的I/O或MMIO访问模式、魔术值或寄存器写入序列。Ghidra、IDA Pro或objdump等工具会非常有价值。
    • 动态分析(驱动程序执行): 运行捐赠设备及其驱动程序,并使用PCIe协议分析仪(例如,Teledyne LeCroy,Keysight,如第12.2节所述)监控PCIe流量。这是理解实际TLP交换,包括厂商定义消息和寄存器访问序列的黄金标准。请注意:
      • BARs中访问的特定内存地址。
      • 对这些地址的读/写模式。
      • 写入或读取特定寄存器的值。
      • 命令和响应之间的时序关系。
    • 系统调用/API监控:在主机上,使用Procmon(Windows)或strace(Linux)等工具查看驱动程序如何与操作系统交互以及它使用了哪些特定的设备I/O控制(IOCTL)代码,这些代码可能对应于特定的硬件操作。
    • 硬件嗅探:如果可能,使用硬件嗅探器(如Saleae逻辑分析仪)捕获设备内部总线(例如SPI,I2C)上的信号,如果它有外部闪存或组件。
  2. 在BARs中实现自定义寄存器和逻辑:

    • 一旦您识别出厂商特定的寄存器或命令协议,您将需要将这些定义到您的FPGA固件中,通常作为可通过您的某个BAR访问的内存映射寄存器。
    • 创建内部寄存器: 在您的SystemVerilog代码中声明reg变量来表示这些自定义寄存器。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      // 在pcileech_tlps128_bar_controller.sv或子模块中
      reg [31:0] custom_control_reg;
      reg [31:0] custom_status_reg;
      reg [31:0] custom_data_reg;

      // 示例:将它们映射到BAR0中的特定偏移量(假设BAR0足够大)
      // 调整'bar0_offset' case语句(来自第8.2.2节)
      // ...
      if (bar_hit[0]) begin
      if (req_write) begin
      case (bar0_offset)
      32'h0000_1000: custom_control_reg <= req_data; // 自定义控制寄存器
      32'h0000_1004: custom_data_reg <= req_data; // 自定义数据写入寄存器
      // ... 其他映射
      endcase
      end else if (req_read) begin
      case (bar0_offset)
      32'h0000_1000: rsp_data = custom_control_reg; // 读取控制寄存器
      32'h0000_1008: rsp_data = custom_status_reg; // 自定义状态寄存器
      // ... 其他映射
      endcase
      end
      end
      // ...
    • 实现行为逻辑: 创建SystemVerilog逻辑(状态机、组合逻辑),用于:
      • 响应对custom_control_reg的写入。例如,此寄存器中的某个特定位可能触发DMA传输、清除状态标志或启动内部操作。
      • 根据仿真设备的内部状态更新custom_status_reg(例如,“操作完成”、“发生错误”、“数据可用”)。
      • 处理写入custom_data_reg的数据,或在读取时从中提供数据,模仿捐赠设备的数据路径。
  3. 仿真厂商特定消息(如果适用):

    • 一些复杂设备可能通过PCIe使用“厂商定义消息”(VDM)进行特定控制或通信。如果您的分析揭示了此类消息,您将需要:
      • 在PCIe IP核中启用VDM支持(如果可用)。
      • 实现TLP生成逻辑(如第10节所述)来制作和发送这些VDM。
      • 实现TLP接收和解析逻辑来解释来自主机的传入VDM。
  4. 验证仿真行为:

    • 迭代测试: 这是一个高度迭代的过程。进行小修改,编译,烧录,然后测试。
    • 驱动程序加载: 捐赠设备的驱动程序是否正确加载而没有错误?
    • 功能测试: 驱动程序能否启动基本操作?它是否从您的仿真寄存器获得预期的响应?
    • 应用程序测试: 依赖捐赠设备的应用程序能否在您的仿真版本下正常运行?
    • 调试: 广泛使用ILA和PCIe协议分析仪来比较您的仿真设备的行为与真实捐赠设备捕获的行为。寻找TLP时序、寄存器值和总体协议流中的差异。

10. 事务层数据包(TLP)仿真

事务层数据包(TLP)是PCIe架构中通信的基本单位。主机系统与PCIe设备之间的每一次交互,从配置读取到数据传输,都被封装在一个或多个TLP中。精确的TLP仿真不仅重要;它对于您的仿真设备与主机系统正确交互,确保驱动程序正常运行和数据按预期移动,是至关重要的。

10.1 理解和捕获TLP

在您能够制作自定义TLP之前,您必须深入理解它们的结构和常见类型。从您的捐赠设备捕获真实世界的TLP可提供最精确的蓝图。

  • TLP结构的学习
    TLP通常由报头、可选数据载荷和可选的端到端CRC(ECRC)组成。报头至关重要,它定义了TLP的类型、事务细节和路由信息。

    • TLP的组成部分
      • 报头(Header) :最重要的部分,通常是3或4个双字(Dword = 4字节)。它包含定义TLP目的和处理方式的关键字段:
        • Fmt(格式)和 Type(类型) :定义TLP的格式(3DW/4DW,带/不带数据)及其特定目的(例如,内存读取请求、内存写入、完成、配置读取/写入)。
        • Length(长度) :指定数据载荷的长度(以双字为单位)。
        • Requester ID(总线、设备、功能) :标识发起请求的PCIe功能。对于将完成数据路由回正确的源头至关重要。
        • Tag(标签) :由请求者分配给事务的唯一标识符,允许完成者将完成TLP与其原始请求TLP匹配。
        • Address(地址) :对于内存/IO事务,这是目标内存或I/O地址。
        • First DW Byte Enable (FBE)Last DW Byte Enable (LBE) :指定数据载荷的第一个和最后一个双字中哪些字节对于写入操作有效,或哪些字节正在请求读取完成。
        • Traffic Class (TC)Transaction ID (TID) :用于QoS和排序规则。
      • 数据载荷(可选) :存在于内存写入、配置写入和读取完成等TLP中。它包含实际传输的数据。
      • 端到端CRC (ECRC)(可选) :一个32位CRC,覆盖整个TLP,确保从源到目的地的数据完整性,通常由软件生成/检查。
  • 理解常见的TLP类型:您的固件将主要处理这些类型:

    • Memory Read Request (MRd) :由请求者(例如,主机CPU,或您的FPGA作为DMA主设备)发送的TLP,用于从特定内存地址读取数据。
    • Memory Read Completion (CplD) :由完成者(例如,您的FPGA响应主机MRd)发送的TLP,携带请求的数据。
    • Memory Write (MWr) :由请求者(例如,主机CPU,或您的FPGA)发送的TLP,用于向特定内存地址写入数据。
    • Completion Without Data (Cpl) :由完成者发送的TLP,用于确认不返回数据的请求(例如,成功的MWr)。
    • Configuration Read Request (CfgRd) :来自主机的TLP,用于读取设备配置空间中的寄存器。
    • Configuration Read Completion (CplD) :来自设备返回CfgRd数据数据的TLP。
    • Configuration Write Request (CfgWr) :来自主机的TLP,用于写入设备配置空间中的寄存器。
    • Vendor-Defined Messages (VDM) :特定厂商用于专有通信的自定义TLP。

10.1.2 从捐赠设备捕获TLP

从您的捐赠设备捕获真实的PCIe流量是无价的。它提供了TLP结构、序列和时序的具体示例,使您能够精确地复制它们。

  • 步骤
    1. 设置PCIe协议分析仪
      • 最有效的方法是使用专用的硬件工具,通常称为“PCIe协议分析仪”。这些设备位于主机和捐赠PCIe卡之间,被动捕获所有流量。
      • 示例
        • Teledyne LeCroy PCIe 分析仪:行业标准,功能强大,但投资巨大。
        • Keysight PCIe 分析仪:另一个领先的供应商。
        • (对于基本调试,一些带PCIe解码器的高端逻辑分析仪可能提供有限的TLP查看功能,但真正的协议分析仪更优越)。
    2. 捕获事务
      • 在连接了协议分析仪的测试系统中安装捐赠设备。
      • 运行捐赠设备的驱动程序和任何相关应用程序。
      • 在正常操作期间,尤其是关键阶段,监控和记录PCIe事务,例如:
        • 设备枚举(操作系统首次检测到它时)。
        • 驱动程序加载和初始化。
        • 典型数据传输操作(例如,存储设备的大文件复制,网卡的网络流量)。
        • 设备特定命令或诊断。
    3. 分析捕获的TLP
      • 使用协议分析仪的先进软件解剖捕获的TLP。软件将解码字段,提供时间顺序视图,并允许过滤和搜索。
      • 密切关注:
        • 精确的FmtType字段。
        • Requester IDTag值(特别是对于完成)。
        • 内存事务的AddressLength
        • 写入和读取完成的Data Payload内容。
        • 任何厂商特定字段或自定义TLP。

10.1.3 记录关键TLP事务

对捕获的TLP进行结构化文档创建了一个用于您的仿真的蓝图。

  • 步骤
    1. 识别关键事务
      • 重点关注对设备核心功能至关重要的TLP。这包括:
        • 初始化序列:操作系统在枚举期间执行的一系列配置读/写。
        • 驱动程序初始化:驱动程序启动时交换的命令和数据。
        • 主要数据传输MWrMRd TLP如何为设备的主要功能构建和完成。
        • 错误处理:设备如何报告错误(例如,带有Completer Abort (CA)、Unsupported Request (UR)的Completion)。
        • 电源管理转换:与D状态变化相关的TLP。
        • 中断生成:MSI/MSI-X消息如何发送。
      • 协议分析仪的截图在这里会非常有帮助。
    2. 创建详细文档
      • 对于每个关键TLP序列,记录:
        • TLP类型(例如,MWr、MRd、CplD)。
        • 报头字段(Fmt、Type、Requester ID、Tag、Length、Address、Byte Enables)。
        • 数据载荷(如果适用)。
        • 事务中的序列号或顺序。
        • 发送它的条件(例如,“主机在驱动程序初始化时发送”,“设备在DMA完成时发送”)。
        • 任何预期的响应或后续TLP。
      • 协议分析仪的截图在这里会非常有帮助。
    3. 理解时序和序列
      • 除了TLP内容,TLP的时序序列至关重要。PCIe有严格的排序规则和流控制机制。注意:
        • 请求和完成之间的延迟:真实设备响应的速度。
        • 流控制信用:设备如何管理其传入/传出TLP的缓冲区空间。虽然Xilinx PCIe IP核处理基本的流控制,但对于高级仿真,了解捐赠设备的典型信用使用情况会有所帮助。
        • 事务层数据包排序:理解posted(写入)和non-posted(读取、完成)事务如何排序。

10.2 制作用于特定操作的定制TLP

一旦您理解了蓝图,您就可以将这些知识转化为您的FPGA固件(SystemVerilog),以主动生成和响应TLP。PCILeech-FPGA框架提供了抽象层,但对于深度仿真,您可能需要直接与TLP生成/解析逻辑交互。

10.2.1 在固件中实现TLP处理

您的固件需要逻辑来发送和接收TLP。PCIe IP核处理物理层和数据链路层,向您的用户逻辑暴露一个事务层接口(通常是AXI-Stream)。

  • 要修改的文件(主要)

    • pcileech-fpga/<your_board_variant>/src/pcileech_pcie_tlp_a7.sv(或类似文件,取决于板卡变体)
      • 此文件通常包含将用户请求转换为出站TLP并将传入TLP解析为用户逻辑信号的核心逻辑。
    • pcileech-fpga/<your_board_variant>/src/pcileech_tlps128_bar_controller.sv
      • 此模块专门处理解析目标为设备BAR的传入内存读/写TLP,并生成相应的完成TLP。
  • 步骤

    1. 理解PCIe IP核接口

      • 在编写TLP逻辑之前,请彻底阅读Xilinx PCIe IP核用户指南(特别是关于用户应用接口或AXI4-Stream接口的部分)。这定义了您的SystemVerilog逻辑如何连接到PCIe核以发送和接收TLP。您通常会与s_axis_rx_tdata(接收到的TLP数据)、s_axis_rx_tvalid(接收到有效TLP)、m_axis_tx_tdata(传出TLP数据)、m_axis_tx_tready(核已准备好接受TLP)等进行交互。
    2. 创建TLP生成函数(用于出站TLP)

      • pcileech_pcie_tlp_a7.sv(或与m_axis_tx_*接口的模块)中,您将编写逻辑来组装具有所需报头和载荷的TLP。这通常涉及将各种字段组合成一个[127:0](对于128位接口)或[63:0](对于64位接口)总线,该总线馈送PCIe核。
      • 示例(概念性,简化函数,用于3DW TLP报头):
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        // 这是一个概念性辅助函数。实际上,您将构建一个状态机
        // 通过AXI-Stream接口发送TLP,可能使用FIFO。
        function automatic [95:0] create_3dw_tlp_header; // 假设3个双字 = 96位
        input logic [7:0] tlp_type_fmt; // 格式和类型字段
        input logic [15:0] requester_id; // BDF
        input logic [7:0] tag;
        input logic [7:0] lower_address_bits; // 或更复杂的地址
        input logic [7:0] byte_enables; // 第一个双字字节使能

        begin
        create_3dw_tlp_header = {
        tlp_type_fmt, // Fmt[6:4], Type[3:0]
        8'b0, // 保留
        4'b0, // TC[3:0] (流量类别)
        3'b0, // Attr[2:0]
        1'b0, // TH (TLP提示)
        2'b0, // D(igest) (ECRC存在)
        1'b0, // EP (Poisoned)
        1'b0, // TD (类型依赖)
        // DW0: Fmt, Type, TC, Attr, TH, D, EP, TD, Length (不在3DW中, 4DW有)

        requester_id, // 请求者ID (Bus[7:0], Device[4:0], Function[2:0])
        tag, // 标签
        lower_address_bits, // 示例:地址的低位或数据的一部分
        byte_enables, // 第一个双字字节使能
        4'b0, // 保留
        4'b0 // 最后一个双字字节使能 (通常用于MWr)
        // DW1, DW2... 字段
        };
        end
        endfunction

        // 示例:在状态机中生成带数据的完成(CplD)
        // 这只是一个片段,不是完整实现
        localparam CPLD_3DW_FMT = 8'h4A; // Fmt=100 (4DW, 带数据), Type=1010 (Cpl)
        localparam CPL_D_FMT_TYPE_LEN = 8'h4A; // 根据PCIe规范调整。(带数据的4DW报头)

        // ... 发送TLP的状态机
        // 在准备发送CplD的状态下
        if (tx_ready_from_pcie_core) begin
        // 构建报头和载荷
        // 对于CplD,您需要Compliter ID, Status, Byte Count, Requester ID, Tag, Completion ID, Lower Address
        // 然后是实际的读取数据载荷
        m_axis_tx_tdata_reg = {
        CPL_D_FMT_TYPE_LEN, // 字节0: Fmt/Type
        tlp_length_dw_minus_one, // 字节1: TLP长度 (以双字为单位) - 1
        status_completion_bits, // 字节2: Cpl Status, BCM, Rsvd
        byte_count_dws_upper, // 字节3: 字节计数 (高位)
        requester_id, // 字节4-5: 请求者ID (来自原始MRd)
        tag, // 字节6: 标签 (来自原始MRd)
        byte_count_dws_lower, // 字节7: 字节计数 (低位)
        completion_id, // 字节8-9: 完成ID (您的BDF)
        lower_address_from_request // 字节10-11: 请求的低位地址
        // ... 接着是实际的数据载荷
        };
        m_axis_tx_tvalid_reg = 1'b1;
        m_axis_tx_tlast_reg = 1'b1; // 最后一个TLP片段
        // ... 状态转换以等待tready
        end
      • 注意:实际实现涉及状态机、FIFO,以及遵循PCIe IP核的AXI-Stream协议。PCILeech-FPGA框架已经为此提供了良好的基础,但您可能需要为非常特定的TLP行为进行扩展或修改。
    3. 处理TLP接收(用于入站TLP)

      • 实现逻辑以解析来自PCIe核接收接口的传入TLP(例如,s_axis_rx_tdatas_axis_rx_tvalid)。
      • 此解析包括:
        • 检查s_axis_rx_tvalid以判断是否存在TLP。
        • 从TLP报头中读取FmtType字段以确定其目的。
        • 提取Requester IDTagAddressLengthData Payload等相关字段。
      • 使用case语句或if/else if块,根据TLP类型将信息路由到适当的内部逻辑(例如,用于内存写入的bar_controller,用于配置写入的配置模块)。
      • 示例(概念性,简化解析):
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        // 在pcileech_pcie_tlp_a7.sv或TLP解析模块中
        input wire [127:0] s_axis_rx_tdata;
        input wire s_axis_rx_tvalid;
        output wire s_axis_rx_tready; // 需要断言此信号以接受更多数据

        reg [7:0] received_tlp_fmt_type;
        reg [15:0] received_requester_id;
        // ... 声明其他已解析的字段

        assign s_axis_rx_tready = 1'b1; // 为简化起见始终准备好接收,在实际设计中管理反压

        always @(posedge clk) begin
        if (s_axis_rx_tvalid) begin
        received_tlp_fmt_type = s_axis_rx_tdata[127:120]; // 假设最高位
        received_requester_id = s_axis_rx_tdata[111:96]; // 示例偏移量

        // 根据TLP类型解码
        case (received_tlp_fmt_type[3:0]) // 仅TLP类型位
        4'h0: // 内存写入 (3DW或4DW取决于Fmt)
        // 提取地址、长度、载荷并传递给BAR控制器
        begin
        // 传递给BAR控制器,用于写入仿真内存
        // bar_write_enable = 1'b1;
        // bar_write_address = s_axis_rx_tdata[...];
        // bar_write_data = s_axis_rx_tdata[...];
        end
        4'h1: // 内存读取
        // 提取地址、长度,并传递给BAR控制器进行读取
        begin
        // bar_read_enable = 1'b1;
        // bar_read_address = s_axis_rx_tdata[...];
        // (完成将由BAR控制器生成)
        end
        // ... 其他TLP类型
        default: begin
        // 处理不支持或保留的TLP类型(例如,日志记录、错误)
        end
        endcase
        end
        end
    4. 确保符合性

      • 严格验证您生成和解析的TLP是否符合PCIe规范的格式、字段定义和时序。偏差将导致通信失败。
    5. 实现完成处理

      • 对于从主机接收到的内存读取请求(MRd)和配置读取请求(CfgRd),您的设备必须在指定的时间内返回适当的完成TLP(CplD表示数据,Cpl表示无数据)。bar_controller模块(第8.2.2节)是此BAR读取逻辑所在的位置。
    6. 保存更改

      • 保存文件(pcileech_pcie_tlp_a7.svpcileech_tlps128_bar_controller.sv或任何自定义模块)后,实现更改。

10.2.2 处理不同TLP类型

每种TLP类型都有特定的报头格式和行为。您的固件必须擅长处理与您的捐赠设备相关的那些类型。

  • 内存读取请求(MRd)

    • 实现
      • 当接收到MRd TLP(由pcileech_pcie_tlp_a7.sv解析并路由到bar_controller)时,bar_controller需要:
        • 解析请求的地址和长度。
        • 从适当的内部内存位置(例如,连接到BAR的BRAM)或内部寄存器中获取数据。
        • 组装一个带数据的完成(CplD) TLP。关键是,此TLP必须包含来自MRd请求的原始Requester IDTagCompletion ID(您的设备BDF),以及获取的数据载荷。
        • 通过PCIe IP核的传输接口将CplD TLP发送回主机。
  • 内存写入请求(MWr)

    • 实现
      • 当接收到MWr TLP时,bar_controller需要:
        • 解析目标地址、长度和Byte Enables(FBE/LBE)。
        • 提取数据载荷
        • 将数据写入仿真设备内的指定内存位置(例如,BRAM或内部寄存器),并遵循字节使能。
      • 内存写入是“posted事务”,这意味着它们不需要完成TLP进行确认,除非发生错误。
  • 配置读/写请求(CfgRd/CfgWr)

    • 实现
      • 这些TLP针对设备的配置空间(厂商ID、设备ID、BAR、功能等)。Xilinx PCIe IP核根据其配置自动处理大部分标准配置空间访问。
      • 但是,如果您的配置空间中存在非标准的自定义寄存器或扩展功能,您可能需要特定的逻辑来:
        • 对于CfgRd:从您的内部cfg_寄存器返回请求的数据。
        • 对于CfgWr:更新您的内部cfg_寄存器或根据写入的数据触发操作。
      • 配置读取需要带数据的完成(CplD) ,而配置写入需要不带数据的完成(Cpl)
  • 厂商定义消息(VDM)

    • 实现
      • 如果您的捐赠设备使用VDM,这将需要专门的解析和响应逻辑。
      • 解析传入VDM:根据其FmtType字段识别VDM。提取厂商特定数据并根据您的逆向工程发现进行解释。
      • 制作出站VDM:当您的仿真设备需要发送VDM时,创建逻辑来组装具有精确厂商特定报头和载荷格式的VDM。

10.2.3 验证TLP时序和序列

即使TLP格式完美,不正确的时序或序列也会导致设备故障或被检测为不兼容。

  • 步骤

    1. 使用仿真工具

      • 测试平台:为您的TLP生成和解析模块开发全面的SystemVerilog测试平台。
      • 模拟各种场景(例如,主机发送MRd,您的设备发送CplD;主机发送MWr;主机枚举设备),以验证TLP是否正确形成、传输、接收和处理。
      • 验证TLP的序列,并确保在合理的时间内发送完成。
    2. 使用ILA监控(集成逻辑分析仪)

      • 如第12.1节所述,在您的Vivado设计中插入一个ILA核。
      • 将ILA探头连接到PCIe IP核的AXI-Stream接口(例如,s_axis_rx_tdatas_axis_rx_tvalidm_axis_tx_tdatam_axis_tx_tready)。
      • 设置触发器以捕获特定TLP(例如,在m_axis_tx_tvalid上针对某种TLP类型触发)。
      • 这使您可以在硬件操作期间实时查看FPGA上的实际TLP位,验证您的固件是否向/从PCIe IP核发送/接收正确的数据和控制信号。
    3. 检查时序约束

      • PCIe IP核对其AXI-Stream接口有严格的时序要求。确保您的用户逻辑向m_axis_tx_tdata提供数据和处理s_axis_rx_tdata满足这些时序约束。
      • Vivado的时序分析报告(综合和实现后)将标记任何违规。通过优化您的逻辑或在可能的情况下调整时钟来解决这些问题。
    4. 符合性测试(高级)

      • 对于高保真仿真,请考虑使用专用的PCIe符合性测试套件(通常与高端协议分析仪集成)。这些测试系统地检查是否符合PCIe规范,揭示细微的协议违规。
    5. 保存更改

      • 在彻底测试和验证后保存所有修改过的文件。迭代是TLP级调试的关键。

第三部分:高级技术与优化


11. 构建、烧录与测试

完成所有定制后,就到了验证的时刻:构建固件,将其编程到您的FPGA上,并严格测试其功能,以确保它与捐赠设备的行为完全一致。此阶段将您的设计从代码转换为可工作的硬件仿真。

11.1 综合与实现

这是FPGA设计流程中的核心步骤,您的SystemVerilog高级代码将被转换为可以加载到FPGA上的低级硬件配置。

11.1.1 运行综合

综合是Vivado将您的HDL代码转换为门级网表(逻辑门及其互连的描述)的过程。它还执行初步的时序分析和资源估算。

  • 步骤
    1. 开始综合
      • 在Vivado GUI中,在 Flow Navigator(流程导航器)窗格(通常在左侧)中,在“Synthesis”(综合)下,点击 Run Synthesis(运行综合)。
    2. 监控进度
      • Vivado将打开一个“Launch Runs”(启动运行)对话框。您通常只需点击“OK”。
      • 监控Vivado窗口底部的 Messages(消息)选项卡。它将显示综合运行的进度。
      • 常见警告/错误关注点
        • [Synth 8-327]​ ** Unconnected Ports / Unused Inputs(未连接端口/未使用的输入)** :这表明您设计中的信号或端口未连接到任何东西。虽然有时是故意的(例如,FPGA上未使用的引脚),但它们也可能指向端口名称中的拼写错误或遗忘的连接。检查每个警告以确保这不是功能问题。
        • [Synth 8-256]​ ** Registers/Wires Not Optimized(寄存器/线未优化)** :这可能表明逻辑推断不正确,或者您有冗余逻辑可以优化。
        • Syntax Errors(语法错误) :如果您的SystemVerilog代码中有致命的语法错误,综合将立即失败。请在Visual Studio Code中修复这些错误。
    3. 审查综合报告
      • 成功完成后,Vivado将询问您下一步要做什么。选择 Open Synthesized Design(打开综合设计)或 Open Report(打开报告)。
      • 最重要的是,查看综合报告中的 Utilization Summary(资源利用率摘要)。这显示了您的设计消耗了FPGA多少资源(LUT、触发器、BRAM、DSP)。确保设计适合您的目标FPGA的容量(例如,对于Artix-7 35T,您应该在其限制内)。

11.1.2 运行实现

实现是最耗时的一步。它接收综合后的网表并将其物理映射到FPGA的资源上(放置逻辑块,布线连接),然后执行详细的时序分析,以确保设计能够以指定的时钟频率运行。

  • 步骤
    1. 开始实现
      • 成功综合后,在 Flow Navigator(流程导航器)中,在“Implementation”(实现)下,点击 Run Implementation(运行实现)。
      • 确认“Launch Runs”(启动运行)对话框。
    2. 监控进度
      • 实现包括几个阶段:Opt Design(优化设计)、Power Opt Design(功耗优化设计)、Place Design(放置设计)、Post-Placement Phys Opt Design(后放置物理优化设计)、Route Design(布线设计)、Post-Route Phys Opt Design(后布线物理优化设计)。每个阶段都可能需要大量时间。
      • 监控 Messages(消息)选项卡以了解进度和潜在问题。
    3. 分析时序报告
      • 这是实现后最关键的步骤。完成后,Vivado会再次询问下一步做什么。选择 Open Implemented Design(打开已实现设计),或者更重要的是,选择 Open Report(打开报告),然后选择 Report Timing Summary(报告时序摘要)。
      • 确保所有时序约束都得到满足。 查找“WNS (Worst Negative Slack)”值。
        • 正WNS:表示所有时序路径都满足其要求(有余量)。这是您想要的结果。
        • 负WNS:表示时序违规,这意味着您的设计无法在所需的时钟频率下运行,或者数据可能不稳定。这是一个必须解决的关键问题。
      • 解决违规
        • 如果您有负余量,请调查失败的具体路径。Vivado的时序报告将显示失败路径的源、目标和组件。
        • 解决方案可以包括:
          • 优化HDL代码以减少逻辑深度或关键路径延迟。
          • 添加流水线级(寄存器)以打断长组合路径。
          • 改进XDC(约束)文件,确保所有时钟都正确定义和传播。
          • 调整时钟频率(如果应用程序允许)。
          • 在Vivado中使用更快的时序收敛策略。
          • 确保您的自定义逻辑与PCIe核的AXI-Stream接口时序要求正确接口。
    4. 验证布局(可选)
      • 在已实现的设计中,您可以打开“Device”(器件)视图,查看您的逻辑如何在FPGA上布局。这通常适用于高级用户,以确认关键组件是否最佳布局(例如,靠近PCIe收发器)。

11.1.3 生成比特流

比特流是最终的二进制配置文件(.bit扩展名),将被加载到您的FPGA上。它是综合和实现的成果。

  • 步骤
    1. 生成比特流
      • 成功实现后(没有严重的时序违规),在 Flow Navigator(流程导航器)中,在“Program and Debug”(编程和调试)下,点击 Generate Bitstream(生成比特流)。
    2. 等待完成
      • 此过程通常比实现花费的时间少,但仍可能因设计复杂性而异。
    3. 审查比特流生成日志
      • 完成后,Vivado会指示成功。审查日志中是否有任何警告,但通常如果实现顺利通过,比特流生成也会顺利通过。
      • .bit文件将生成在您的项目目录pcileech_squirrel_top.runs/impl_1/(或您的板卡类似路径)中。

11.2 烧录比特流

编程(烧录)比特流会将您编译的设计加载到FPGA上,使您的仿真设备激活。

11.2.1 连接FPGA设备

  • 步骤
    1. 准备硬件
      • 确保您的基于FPGA的DMA板卡已正确插入主机系统的兼容PCIe插槽。
      • 将JTAG编程器(例如,Digilent HS3,Xilinx Platform Cable)连接到FPGA板卡上的JTAG接口和开发PC的USB端口。
      • 打开主机系统电源。
      • 请参阅您的特定FPGA板卡手册,了解精确的电源、JTAG和PCIe连接说明。
    2. 打开硬件管理器
      • 在Vivado中,导航到 Flow Navigator > Program and Debug > Open Hardware Manager(流程导航器 > 编程和调试 > 打开硬件管理器)。
      • 如果Vivado未运行,您可以作为独立应用程序启动硬件管理器。

11.2.2 编程FPGA

  • 步骤
    1. 连接到目标
      • 在硬件管理器窗口中,点击 Open Target(打开目标)(通常是一个大按钮或链接),然后选择 Auto Connect(自动连接)。
      • Vivado应该自动检测到您的JTAG编程器,然后检测到JTAG链上连接的FPGA设备。如果检测失败,请检查JTAG电缆连接、板卡电源以及PC上的JTAG驱动程序。
    2. 编程设备
      • 一旦您的FPGA设备在硬件窗口中被检测并显示,右键单击 您的FPGA设备(例如,xc7a35t_0)并选择 Program Device(编程设备)。
      • 将出现一个对话框。点击“Bitstream file”(比特流文件)字段旁边的“…”按钮,导航到您生成的比特流文件(例如,pcileech_squirrel_top.runs/impl_1/pcileech_squirrel_top.bit)。
      • 点击 Program(编程)开始将固件烧录到FPGA上。
      • 等待编程过程完成。您将看到一个进度条。

11.2.3 验证编程

  • 步骤
    1. 检查状态
      • 确保编程在Vivado的硬件管理器中无错误地完成。Vivado将在完成后显示“Program Device”成功消息。
    2. 观察LED或指示灯
      • 许多FPGA板卡都有状态LED。成功的编程操作通常会导致特定LED亮起或改变状态(例如,“DONE”LED)。这是一个快速的视觉确认。
    3. 主机系统重启(有时需要)
      • 为了让主机操作系统正确识别新编程的PCIe设备,通常需要系统重启,尤其是在Windows上,以触发完整的PCIe枚举过程。

11.3 测试与验证

编程完成后,关键一步是验证您的仿真设备是否被主机正确检测,并且其功能是否按预期工作,模仿捐赠设备。

11.3.1 验证设备枚举

这证实主机操作系统根据您编程的ID将您的FPGA识别为捐赠设备。

  • Windows
    • 步骤
      1. 打开设备管理器:按下 Win + X 并从快速链接菜单中选择 Device Manager(设备管理器)。
      2. 检查设备属性
        • 在适当的设备类别下查找(例如,Network Adapters(网络适配器)、Storage Controllers(存储控制器)、System devices(系统设备))。
        • 找到您的仿真设备。它现在应该显示为捐赠设备的名称(例如,“Intel(R) Ethernet Connection…”)。
        • 右键单击该设备,选择 Properties(属性),然后转到 Details(详细信息)选项卡。
        • 在“Property”(属性)下拉菜单中,选择“Hardware Ids”(硬件ID)。确认 设备ID(DID)厂商ID(VID) (例如,PCI\VEN_ABCD&DEV_1234)与您编程到固件中的值匹配。
        • 还应检查“Class Code”(类别代码)和“Subsystem ID”(子系统ID)以进行进一步验证。
  • Linux
    • 步骤
      1. 使用 lspci:打开终端并使用lspci命令。
        1
        2
        lspci -nn # 显示厂商ID:设备ID
        lspci -vvv # 显示包括BAR、功能等详细信息
      2. 验证设备列表
        • 检查仿真设备是否在lspci输出中显示了正确的厂商ID、设备ID和类别代码。
        • 示例输出(仿真Intel NIC)
          1
          03:00.0 Network controller [0280]: Intel Corporation Ethernet Connection I219-V [8086:1570] (rev 21)
          8086是Intel的厂商ID,1570是I219-V的设备ID,0280是网络控制器类别代码)。
        • 使用lspci -vvv确认BAR是否以正确的大小和类型枚举,与您的捐赠设备配置匹配。

11.3.2 测试设备功能

一旦设备被枚举,最终的测试是它是否像原始设备一样工作。

  • 步骤
    1. 安装必要的驱动程序
      • 如果主机操作系统未自动加载合适的驱动程序,您将需要手动安装捐赠设备的官方驱动程序。从制造商网站下载它们。
      • 按照制造商的说明进行安装。如果仿真成功,驱动程序应该安装并识别您的FPGA为真实硬件。
    2. 执行功能测试
      • 运行通常与捐赠设备交互的应用程序或实用程序。
      • 示例
        • 网卡:执行ping测试、浏览网页或启动大文件传输以测试吞吐量。
        • 存储控制器:尝试格式化模拟驱动器(如果您的仿真包括存储功能),执行读/写操作,或运行磁盘基准测试。
        • USB控制器:连接USB设备(如果您的仿真包括USB主机功能)并测试它们的检测和操作。
      • 监控主机系统以获取预期的行为和性能特征。
    3. 监控系统行为
      • 检查系统稳定性(Windows上没有蓝屏,Linux上没有内核崩溃)。
      • 在系统日志中查找设备特定错误(Windows上的事件查看器,Linux上的dmesgjournalctl)。
      • 确保仿真设备在各种工作负载下(包括大数据传输或压力测试)按预期运行。

11.3.3 监控错误

主动的错误监控对于识别可能不会立即导致崩溃的细微仿真问题至关重要。

  • Windows
    • 步骤
      1. 检查事件查看器:按下 Win + X 并选择 Event Viewer(事件查看器)。
      2. 查找与PCIe相关的错误:导航到 Windows Logs > System(Windows 日志 > 系统)。筛选或搜索与“PCIe”、“PCI Express”相关的警告、错误或关键事件,或源自特定设备驱动程序的事件(查找与您的仿真设备驱动程序匹配的源名称)。
        • 常见错误包括资源冲突、驱动程序初始化失败或意外的设备响应。
  • Linux
    • 步骤
      1. 检查 dmesg​ ** 日志**:打开终端并输入:
        1
        2
        dmesg | grep -i pci # 不区分大小写地搜索pci消息
        dmesg | grep -i <VendorID> # 过滤您的设备的厂商ID
      2. 识别问题:查找指示PCIe链路训练问题、设备初始化问题、内存分配失败或意外DMA活动的消息。Linux内核的PCIe子系统非常详细。
    • Systemd Journal (现代Linux)
      1
      journalctl -b | grep -i pci # 当前引导日志

12. 高级调试技术

当问题出现时,特别是在复杂的PCIe设备仿真中,基本的故障排除可能不足以解决问题。高级调试工具和技术提供对FPGA内部逻辑和PCIe总线的深入可见性,使您能够高效地识别和解决问题。

12.1 使用Vivado的集成逻辑分析仪(ILA)

集成逻辑分析仪(ILA)是Xilinx提供的一种强大、可配置的调试IP核,您可以直接将其嵌入到FPGA设计中。它允许您监控内部FPGA信号(线和寄存器)的实时行为,而无需外部探测硬件,其功能类似于一个强大的内部示波器或逻辑分析仪。

12.1.1 插入ILA核

  • 步骤
    1. 规划您的探头:确定您需要观察的关键信号。对于PCIe仿真,这些信号通常包括:
      • PCIe IP核的AXI-Stream接口(例如,s_axis_rx_tdatas_axis_rx_tvalidm_axis_tx_tdatam_axis_tx_tready)。
      • 内部状态机信号(current_statenext_state)。
      • BAR地址解码输出(bar_hit[0]bar_hit[1])。
      • 自定义寄存器值(custom_control_regcustom_status_reg)。
      • 中断请求信号(msi_trigger_signal)。
    2. 添加ILA IP核
      • 在Vivado中,打开 IP Catalog(IP目录)(通常在 Flow Navigator(流程导航器)窗格中)。
      • 搜索“ILA”(Integrated Logic Analyzer)。
      • 双击“Debug Bridge”(用于基本ILA)或“Integrated Logic Analyzer (ILA)”以打开其定制GUI。
      • 配置ILA:
        • 设置您需要的捕获数据端口数量(探头)。
        • 设置每个探头的宽度以匹配您计划连接的信号。
        • 配置采样深度(在触发前/后存储多少个样本)。更深的深度会消耗更多BRAM。
        • 点击“OK”并让Vivado生成IP。
    3. 实例化并连接信号
      • Vivado将生成ILA的.xci文件。您可以将其直接实例化在您的顶层SystemVerilog文件(例如,pcileech_squirrel_top.sv)中,或在可用信号的模块中。
      • 示例(在pcileech_squirrel_top.sv或子模块中):
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        // 假设您已从IP Catalog生成了ila_0
        // 连接到您设计的时钟和感兴趣的信号
        ila_0 your_ila_instance (
        .clk(clk_125mhz), // 连接到您设计中稳定的时钟,通常是PCIe用户时钟
        .probe0(pcie_s_axis_rx_tdata), // 示例:PCIe入站TLP数据
        .probe1(pcie_s_axis_rx_tvalid), // 示例:PCIe入站TLP有效
        .probe2(pcie_m_axis_tx_tdata), // 示例:PCIe出站TLP数据
        .probe3(my_bar_controller_state), // 示例:您的BAR逻辑状态
        .probe4(my_custom_register), // 示例:自定义寄存器的值
        // 根据需要添加更多探头
        .probeN(signal_to_monitor_N)
        );
      • 替代方法(标记用于调试): 对于更简单的信号,有时可以直接在HDL代码中标记它们用于调试。使用(* mark_debug = "true" *) wire my_signal;(* mark_debug = "true" *) reg my_register;。Vivado随后会自动建议将它们添加到ILA中。

12.1.2 配置触发条件

当您配置智能触发条件以精确地在感兴趣的事件发生时(例如,错误、特定TLP类型、状态转换)捕获数据时,ILA最强大。

  • 步骤
    1. 生成带有ILA的比特流:插入并连接ILA后,您必须运行综合、实现并生成新的比特流。ILA核消耗FPGA资源,并将嵌入到您的设计中。
    2. 打开硬件管理器:使用支持ILA的比特流编程您的FPGA(第11.2节)。然后,在Vivado中,打开硬件管理器并连接到您的目标。
    3. 访问ILA仪表板:在硬件管理器中,选择您的ILA实例(例如,hw_ila_1)。这将打开ILA仪表板。
    4. 定义触发器
      • 选择要用作触发输入的探头。
      • 设置特定的触发模式(例如,pcie_s_axis_rx_tdata0x4A以触发完成TLP)。
      • 配置触发条件(例如,“等于”、“不等于”、“上升沿”、“下降沿”)。
      • 设置触发位置(在触发事件之前捕获多少样本,用于预触发可见性)。
      • 您可以设置多个触发序列以检测复杂事件。
      • 触发器示例场景
        • 在收到的TLP中触发特定的Fmt/Type以分析传入命令。
        • 当特定寄存器(my_custom_register)达到某个值时触发。
        • pcie_m_axis_tx_tvalid断言 AND pcie_m_axis_tx_tdata[3:0] == 4'hC(用于内存写入TLP)时触发,以分析出站写入。
        • 在错误信号断言时触发。

12.1.3 捕获和分析数据

  • 步骤
    1. 运行设计:让您的主机系统与已编程的FPGA交互,从而引发您要调试的事件。
    2. 布防ILA:在ILA仪表板中,点击 Run Trigger(运行触发器)按钮(通常是绿色的“播放”图标)。ILA将等待定义的触发条件。
    3. 捕获数据:一旦满足触发条件,ILA将把信号快照捕获到其内部内存缓冲区中。
    4. 分析波形
      • 捕获的数据将出现在波形查看器中。
      • 检查信号随时间的变化行为。放大、添加光标并解码值。
      • 寻找:
        • 意外的跳变:信号在错误的时间改变。
        • 不正确的值:寄存器中保存了错误的数据。
        • 协议违规:您的逻辑在PCIe接口上发送了不正确的数据。
        • 时序问题:如果信号在预期时不稳定(尽管完整的时序分析在实现中完成,但ILA显示运行时行为)。
      • 将捕获到的行为与您的预期设计逻辑和捐赠设备的观察行为(如果您使用协议分析仪捕获了它)进行比较。

12.2 PCIe流量分析工具

虽然ILA提供了FPGA内部可见性,但外部PCIe流量分析工具提供了对您的仿真设备和主机之间PCIe总线上实际通信的无与伦比的视图。这对于验证协议符合性和调试链路级问题至关重要。

12.2.1 PCIe协议分析仪(硬件)

  • 示例
    • Teledyne LeCroy PCIe 分析仪:深度分析的黄金标准,完整的协议解码,高级触发,以及错误注入功能。
    • Keysight PCIe 分析仪:另一个领先的供应商,具有类似的高端功能。
  • 步骤
    1. 设置分析仪:将硬件分析仪串联连接在主机系统的PCIe插槽和您的基于FPGA的DMA设备之间。这通常涉及一个特殊的中间卡。
    2. 配置捕获设置:使用分析仪的软件定义要捕获的流量。您可以按TLP类型、地址、请求者ID、错误条件等进行过滤,以关注相关事件。
    3. 捕获流量:在主机上运行您的仿真设备。分析仪将被动记录所有PCIe事务。
    4. 分析结果
      • 使用分析仪强大的软件查看解码的TLP、事务列表和波形视图。
      • 检查TLP的符合性和正确性:所有字段都正确吗?序列是否正确?
      • 识别任何协议违规或意外行为:这是您发现驱动程序可能失败的原因(例如,您的设备发送了带数据的完成,而规范要求不带数据的完成,或者响应太慢)。
      • 与捐赠设备捕获进行比较:直接比较您的仿真设备捕获的流量与您从真实捐赠设备捕获的流量。这是仿真准确性的最终测试。

12.2.2 基于软件的工具

对于基本的PCIe总线检查,或者在没有专用硬件分析仪的情况下,一些软件工具可以提供有限的洞察。

  • 示例
    • Wireshark with PCIe Plugins:虽然Wireshark主要用于网络流量,但通过专用硬件(例如,将PCIe跟踪暴露给操作系统的网卡,或特定的捕获硬件/驱动程序),它有时可以捕获和解码PCIe数据包。这高度依赖于系统。
    • ChipScope Pro(传统Xilinx,现已集成到Vivado中) :Integrated Logic Analyzer (ILA) 是现代的等效工具,但ChipScope曾是一个独立工具。
    • lspci​ ** (Linux)** :如第11.3.1节所述,lspci -vvv提供了广泛的静态配置空间信息。您可以将其与watch或脚本结合使用来监控随时间的变化。
    • pcileech客户端(来自PCILeech框架)pcileech客户端软件本身可以通过您的FPGA执行内存和配置空间的读写操作,并可用于测试基本的DMA功能。虽然不是“流量分析仪”,但它对于测试功能接口至关重要。
  • 步骤
    1. 安装必要的工具/插件:确保工具已安装并配置了任何所需的驱动程序或插件。
    2. 监控PCIe总线:运行软件工具以捕获和显示PCIe相关信息。
    3. 分析通信
      • 查找设备配置中的差异。
      • 如果工具支持,分析捕获数据包的结构是否存在异常或错误。
      • 验证您的仿真设备是否正确响应了配置请求。

13. 故障排除

本节提供了在PCIe设备仿真定制固件开发、比特流编程和硬件测试过程中可能遇到的常见问题的解决方案。固件调试可能具有挑战性,因此采用系统方法是关键。

13.1 设备检测问题

问题:您的基于FPGA的DMA设备在编程后未被主机系统识别,或者在设备管理器/lspci中显示为不正确的ID(例如,“未知设备”)或错误符号。

可能原因及解决方案

  1. 设备ID、厂商ID、子系统ID或类别代码不正确

    • 原因:最常见的原因。您编程到FPGA固件中的识别值与主机操作系统预期或您打算仿真的值不匹配。
    • 解决方案
      • 验证:仔细检查pcileech_pcie_cfg_a7.sv(或等效文件)中的所有cfg_deviceidcfg_vendoridcfg_subsysidcfg_subsysvendoridcfg_revisionidcfg_classcode参数,与您精心记录的捐赠设备信息(来自第5节)进行比对。
      • 一致性:确保这些值在Vivado PCIe IP核定制GUI(第7.2.2节)中也保持一致设置。
      • 重新构建并重新烧录:进行任何更改后,始终重新综合、重新实现、生成新的比特流并重新烧录FPGA(第11.1、11.2节)。
      • 重启主机:烧录后务必重启主机系统,因为Windows通常需要完全重启才能正确重新枚举PCIe设备。
  2. PCIe链路训练失败

    • 原因:主机根联合体与您的FPGA卡之间的基本PCIe链路未能建立。这发生在任何配置空间读取之前。症状包括设备完全不出现(lspci在该总线/插槽上没有任何显示,或设备管理器显示“PCI Express Root Port”错误)。
    • 解决方案
      • 物理连接:确保FPGA板卡牢固地插入PCIe插槽,并且所有电源连接都牢固。如果可能,尝试不同的PCIe插槽。
      • 电源:验证FPGA板卡是否获得足够的电源。某些板卡需要辅助PCIe电源连接器。
      • 链路速度/宽度
        • 检查Vivado PCIe IP核中的Max Link SpeedLink Width设置(第8.1.1节)。
        • 尝试将链路速度设置为较低的代数(例如,Gen1 / 2.5 GT/s)并将宽度设置为x1,即使您的板卡支持更高。有时,在较高速度下与特定主板会产生兼容性问题。
        • 检查主板BIOS设置中的PCIe插槽速度选项。
      • 复位:确保FPGA的复位逻辑正确实现(例如,与PCIe参考时钟同步),并在上电/重启时正确断言/去断言。
      • PCIe IP核:确保PCIe IP核正确实例化,并且其时钟和复位在您的顶层设计中正确连接。
  3. 电源问题(电源不足或不稳定)

    • 原因:FPGA板卡未获得足够的稳定电源,或电源供应不稳定,导致操作不可靠。
    • 解决方案
      • 验证连接:仔细检查所有电源线(主PCIe插槽电源、辅助PCIe电源、如果使用则包括外部直流插孔)。
      • 电源供应:确保您的主机系统电源(PSU)具有足够的瓦数和稳定的12V电压轨。对于高功耗FPGA,弱电源可能导致问题。
      • 外部电源:如果板卡有外部电源插孔,请确保使用正确电压和电流额定值的电源。
  4. 固件错误(早期阶段)

    • 原因:SystemVerilog代码中的逻辑错误,特别是顶层模块或PCIe核的包装器中,导致PCIe核无法初始化或正确呈现自身。
    • 解决方案
      • Vivado消息:仔细检查Vivado的综合和实现日志中与PCIe IP核相关的严重警告错误。这些通常是配置错误或连接不当的指示。
      • ILA调试:如果链路尝试训练但失败,请使用连接到PCIe IP核的状态信号(例如,link_uplink_speedlink_width)和AXI-Stream接口的ILA(第12.1节),以查看链路协商在哪个点失败,或者核是否生成了意外流量。

13.2 内存映射和BAR配置错误

问题:仿真设备已检测到,但当主机操作系统或驱动程序尝试通过BAR访问其内存映射寄存器或缓冲区时,系统崩溃、冻结或报告错误。

可能原因及解决方案

  1. BAR大小或类型不正确(IP核和固件)

    • 原因:您在Vivado PCIe IP核(第7.2.2节)中配置的BAR大小或类型(32位/64位、内存/I/O、可预取/不可预取)和/或在pcileech_tlps128_bar_controller.sv中处理的值与捐赠设备实际提供的值不匹配。这可能导致主机分配不正确的地址空间或尝试不支持的访问。
    • 解决方案
      • 交叉验证:返回到您的Arbor/协议分析仪数据(第5节),重新验证每个BAR配置(大小、类型、可预取)。
      • 一致性:确保这些值在PCIe IP核定制中完全匹配,并且您的bar_controller逻辑正确处理每个BAR的大小(地址解码范围)和类型。
      • BRAM大小:如果您的BAR映射到BRAM,请确认BRAM IP核的大小(第8.2.1节)与BAR大小完全匹配。
  2. 固件中的地址解码错误

    • 原因:您的pcileech_tlps128_bar_controller.sv(或自定义BAR逻辑)错误地解释了传入的PCIe地址,导致访问了不正确的内部寄存器或内存位置。
    • 解决方案
      • 审查逻辑:仔细审查bar_controller中的case语句和地址计算。
      • 仿真:在您的SystemVerilog测试平台中开发特定的测试用例,模拟主机对每个BAR中不同偏移量的读写访问。验证内部bar_hit信号是否正确,以及数据是否正确路由到/从正确的内部寄存器/BRAM。
      • ILA调试:在req_addrreq_writereq_readreq_datarsp_data以及bar_controller中与您的地址解码和寄存器访问相关的内部信号上放置ILA探头。实时观察地址如何解码以及正在读/写的数据。
  3. 内部地址空间重叠

    • 原因:虽然PCIe标准确保不同设备的BAR在主机的内存映射中不重叠,但在FPGA内部,您可能会意外地将不同的逻辑组件映射到单个BAR中的相同物理地址空间。
    • 解决方案
      • 仔细映射:在BAR中定义内部寄存器和内存块时,显式为每个寄存器和内存块分配唯一的、不重叠的偏移量。使用localparam来定义这些偏移量以防止错误。
      • 设计审查:需要对您的bar_controller进行彻底的设计审查,以确保每个地址范围都得到唯一处理。
  4. BRAM访问问题

    • 原因:您的逻辑与BRAM IP核接口存在问题(例如,不正确的BRAM时钟、异步复位、错误的字节使能或不正确的写入使能逻辑)。
    • 解决方案
      • BRAM文档:查阅Xilinx BRAM IP核文档,了解正确的实例化和接口信号。
      • ILA:在BRAM接口信号(地址、写入使能、数据输入、数据输出)上放置ILA探头,以验证您的逻辑是否向BRAM发送了正确的控制信号。

13.3 DMA性能和TLP错误

问题:设备已检测到并功能上看起来正常,但在大型DMA操作期间,数据传输速率缓慢,或者系统间歇性崩溃、挂起或报错。PCIe协议分析仪报告TLP格式错误或流控制问题。

可能原因及解决方案

  1. TLP格式错误(报头/载荷)

    • 原因:您的固件生成的TLP(特别是您的FPGA作为DMA主设备时发送的完成或出站内存写入)具有不正确的报头、长度、字节使能或载荷。主机系统的PCIe核或驱动程序将其检测为违规。
    • 解决方案
      • PCIe协议分析仪:这是最好的工具(第12.2.1节)。捕获流量并仔细比较您生成的TLP与PCIe规范,更重要的是,与您真实捐赠设备的捕获进行比较。
      • TLP生成逻辑:审查您的TLP组装代码(pcileech_pcie_tlp_a7.sv及相关模块)。确保所有字段(Fmt、Type、Requester ID、Tag、Completion ID、Length、Byte Enables、Address)都正确派生并打包到TLP结构中。
      • 错误检查:在固件中实现基本的错误检查(例如,检查是否存在意外的req_valid而没有req_ready,反之亦然)。
  2. 流控制问题

    • 原因:PCIe使用基于信用的流控制机制。如果您的固件(或PCIe IP核与其的交互)错误地管理信用,可能导致死锁、超时或丢包。症状包括PCIe链路“停滞”、超时或低吞吐量。
    • 解决方案
      • PCIe IP核配置:确保Vivado PCIe IP核定制中的流控制设置适用于您预期的流量模式。默认设置通常是健壮的。
      • 用户逻辑反压:您的用户逻辑向PCIe IP核发送TLP(m_axis_tx_*接口)必须遵守来自IP核的m_axis_tx_tready信号。如果tready被去断言,您必须暂停发送数据。否则将导致核的缓冲区溢出。
      • ILA调试:将ILA探头连接到PCIe IP核的流控制接口信号和您的用户逻辑,以观察tvalid/tready握手是否正常工作。
  3. DMA逻辑效率低下/缓冲问题

    • 原因:FPGA内部的DMA引擎实现(读取/写入主机内存数据的部分)未优化,导致瓶颈。这可能涉及:
      • 缺少流水线。
      • BRAM使用效率低下。
      • 外部内存访问延迟导致的停滞。
      • 突发大小过小。
    • 解决方案
      • 流水线:将长组合路径分解为更小、更连续的阶段,使用寄存器。这允许更高的时钟频率和更好的吞吐量。
      • 缓冲:使用FIFO(先进先出缓冲区)来解耦发送方和接收方逻辑,平滑数据流并防止停滞。
      • 突发传输:利用PCIe执行突发读/写的能力以提高效率。确保您的DMA逻辑以适当的突发大小请求和处理数据。
      • 内存带宽:确保您的BRAM或外部DDR内存接口能够足够快地提供/消耗数据,以满足您所需的DMA速率。
      • ILA:监控您的DMA引擎的内部状态、读写指针和数据路径信号,以识别瓶颈。
  4. 完成超时/不支持的请求

    • 原因:主机发送请求(例如MRd、CfgRd),但您的FPGA设备未在允许的超时时间内响应完成TLP,或者它以错误状态(例如,带有Unsupported Request (UR) 或 Completer Abort (CA) 的完成)进行响应。
    • 解决方案
      • 响应逻辑:验证您的bar_controller(用于MRd)和pcileech_pcie_cfg_a7.sv(用于自定义配置空间的CfgRd)是否正确识别请求并生成适当的完成。
      • 超时值:审查您的捐赠设备预期的完成延迟。虽然PCIe定义了默认超时,但某些驱动程序可能对此敏感。
      • ILA/协议分析仪:对于查明为什么未发送完成或完成格式错误至关重要。请求TLP是否甚至到达了您的用户逻辑?您的逻辑是否生成了响应?PCIe核是否成功发送了响应?

14. 仿真精度与优化

实现真正令人信服的仿真意味着让您的基于FPGA的设备与捐赠设备难以区分,不仅在ID上,而且在行为上。这需要对时序、响应速度和微妙的操作细节进行细致的关注。

14.1 精确定时仿真技术

精确的时序在硬件中至关重要,特别是对于PCIe这样的高速接口。不匹配可能导致驱动程序超时、数据解释不正确或系统不稳定。

  • 实现时序约束(XDC文件)

    • 目的:时序约束是 Vivado 综合和实现工具的指令,告诉它们您的设计需要运行多快。它们定义了时钟周期、输入/输出延迟和路径延迟。
    • 用法:PCILeech-FPGA项目包含 XDC 文件(例如,pcileech_squirrel_top.xdc),它们定义了主时钟(例如,create_clock -name sys_clk_p -period 8.0 [get_ports sys_clk_p])。
    • 优化:如果您的仿真需要非常特定的内部时序或对时间敏感的命令做出反应,您可能需要在自定义逻辑中添加进一步的约束(set_max_delayset_input_delayset_output_delay)到关键路径。
    • 目标:确保 Vivado 在实现后报告所有路径的 正 WNS(最差负余量) ,表明设计满足其时序要求。
  • 使用时钟域交叉(CDC)技术

    • 目的:PCIe设计通常涉及多个时钟域(例如,125MHz PCIe用户时钟,自定义逻辑的单独时钟)。在这些域之间异步移动信号(没有适当的同步)可能导致亚稳态,从而导致不可靠的行为。
    • 实现:对于跨时钟域的信号,始终使用适当的CDC电路:
      • 双触发器同步器:用于单比特控制信号。
      • 异步FIFO(先进先出) :用于多比特数据路径,提供时钟域之间的数据缓冲和流控制。
      • 格雷码编码器/解码器:用于跨域的计数器或地址,以确保每次只有一个比特发生变化。
    • Vivado 工具:Vivado 包含 CDC 分析工具(例如,report_cdc),可以识别潜在的亚稳态问题。
  • 使用时间精确模型仿真设备行为

    • 高级测试平台:使用 SystemVerilog 测试平台,其中包含真实的定时延迟,甚至提供时间精确的 PCIe 总线功能模型(BFM)。
    • 验证:这使您可以观察您的仿真设备的内部状态以及外部 TLP 生成/响应时序在各种条件下如何表现,确保它们与您捕获的捐赠设备行为相匹配。

14.2 对系统调用的动态响应

真正精确的仿真不仅能呈现正确的ID;它还能智能且动态地响应主机系统的命令和查询,模仿真实、活动设备的行为。

  • 实现设备控制的状态机

    • 目的:设计健壮的 SystemVerilog 状态机来管理设备的操作模式、命令处理和数据流。
    • 响应性:确保状态机能够逻辑地、快速地响应传入命令(例如,写入 BAR 中的控制寄存器,特定的 TLP)。
    • 优雅处理:状态机应能够优雅地处理意外或无序的请求,可能返回错误 TLP 或仅仅忽略无效命令,而不是崩溃或冻结。
  • 监控和响应主机命令(超越简单的读写)

    • 配置写入:除了初始枚举之外,驱动程序通常会写入配置空间寄存器以启用功能、设置阈值或清除状态位。您的固件必须处理这些写入并相应地更新内部状态。
    • 厂商特定命令:如第9.2节所述,如果捐赠设备具有专有命令(通过自定义寄存器或厂商定义消息访问),您的固件必须解析这些命令并触发适当的仿真行为。
    • 电源管理命令:通过启用/禁用内部逻辑并确认状态更改来响应主机发起的电源状态转换(D0、D1、D3hot 等)。
    • 中断确认:如果主机驱动程序通过写入特定寄存器来确认中断,请确保您的固件能够检测到此并清除内部中断请求。
  • 优化固件逻辑以提高响应性

    • 降低延迟:关键数据路径和控制路径应优化,以最小化组合逻辑深度和流水线停顿。
    • 并行性:利用 FPGA 固有的并行性来同时执行多个操作,提高吞吐量和响应时间。
    • 高效内存访问:优化对内部 BRAM 或外部 DDR 内存的访问,以确保在需要时为 DMA 传输或寄存器读取提供数据。
    • 硬件加速:对于捐赠设备执行的复杂计算或数据操作,请考虑在 FPGA 上实现专用的硬件加速器,而不是尝试以缓慢、类似软件的方式执行它们。

15. 固件开发最佳实践

在定制固件开发中遵循最佳实践对于保持代码质量、促进协作(如果团队合作)、简化调试以及确保项目的长期可维护性和可靠性至关重要。这对于安全敏感的应用尤为如此。

15.1 持续测试与文档

  • 定期、增量测试

    • 单元测试:使用专用测试平台隔离测试小型独立模块(例如,TLP解析器、寄存器块)。
    • 集成测试:验证不同模块是否协同工作。
    • 系统测试:烧录后,与主机系统执行端到端测试,确保整体功能。
    • 尽早测试,经常测试:在每次重大更改后,无论多小,都要测试固件,以便尽早发现问题,此时问题更容易调试。
  • 自动化测试(高级)

    • 对于复杂项目,在主机端实现自动化测试脚本(例如,使用Python和硬件抽象层)以重复验证功能和性能。
    • 在团队环境中,考虑与持续集成(CI)工具(例如,Jenkins、GitLab CI)集成,以自动化每次代码提交的构建、测试和静态分析。
  • 维护全面的文档

    • 设计文档:创建并更新描述固件架构的文档,包括:
      • 框图:说明主要模块及其互连。
      • 状态机图:适用于所有有状态逻辑。
      • 接口规范:详细说明模块之间的输入/输出信号、时序和协议。
      • 内存映射:针对所有BAR,定义寄存器地址、位域及其功能。
    • 代码注释:在SystemVerilog代码中使用清晰、简洁的注释来解释复杂的逻辑、信号的目的以及任何不明显的设计选择。
    • 更改日志/提交消息:维护更改日志或使用详细的Git提交消息来跟踪所有修改、错误修复和功能添加,解释为什么进行更改。
    • 用户指南:对于您的定制固件,一个简单的用户指南,解释如何从主机端构建、烧录和与仿真设备交互,是无价的。

15.2 管理固件版本

正确的版本控制对于跟踪更改、有效协作和管理发布至关重要。

  • 使用版本控制系统(VCS)

    • Git:强烈推荐。使用Git管理您的HDL源代码、约束文件和项目脚本。
    • 组织仓库:保持清晰的目录结构(例如,为srcxdcipscriptsdoc等设置单独的文件夹)。
    • 分支:使用功能分支开发新功能或进行重大更改。在彻底测试后合并回maindevelop分支。
    • 定期提交:频繁提交,提交内容原子化,提交消息有意义。
  • 标记发布和里程碑

    • 稳定版本:使用Git标签(例如,v1.0.0v1.0.1_bugfix)标记固件的稳定、经过测试的版本。这使得回溯或部署已知良好状态变得容易。
    • 里程碑:标记重要的开发里程碑(例如,“基本枚举工作正常”、“DMA读/写功能正常”)。
  • 备份和恢复策略

    • 基于云的仓库:将您的Git仓库托管在GitHub、GitLab或Bitbucket等平台上。这提供了异地备份并促进了协作。
    • 本地备份:即使有云仓库,也要定期对整个Vivado项目目录进行本地备份(由于生成的文件,它可能非常大)。

15.3 安全注意事项

开发用于PCIe设备仿真(特别是能够直接内存访问的设备)的定制固件具有重要的安全影响。这项技术本质上是一种“两用”能力,意味着它既可以用于合法目的(例如,硬件测试、安全研究),也可以用于恶意目的(例如,DMA攻击、安全绕过)。理解并负责任地管理这些风险至关重要。

  • 两用性质与道德影响

    • 道德黑客行为与恶意使用:明确区分将这些知识用于授权安全测试(红队演练、渗透测试)和未经授权的非法活动。
    • 负责任的披露:如果您使用这些技术发现漏洞,请遵循负责任的披露准则。
    • 法律和许可合规性:了解并遵守所有与硬件逆向工程和设备修改相关的法律、法规和许可协议(例如,PCIe-SIG规范、Xilinx EULA)。
    • “武器化” :认识到精确仿真受信任硬件的能力可以被武器化用于高级持久威胁(APTs)或复杂恶意软件。
  • 理解攻击向量(攻击视角)

    • 内存窃取:恶意仿真设备可以执行DMA读取,以访问任何物理内存地址,包括内核、用户进程中的敏感数据、加密密钥或网络缓冲区。
    • 内存注入/修改:恶意仿真设备可以执行DMA写入以任意修改内存,从而实现:
      • 权限提升:修改内核数据结构(例如,进程令牌、SID)以获得管理员或系统权限。
      • 代码注入:将恶意代码注入正在运行的进程或内核,然后触发其执行。
      • 安全软件绕过:通过直接修改内存来禁用或颠覆端点检测和响应(EDR)、防病毒或防火墙软件。
    • 模糊测试和崩溃:发送格式错误或不符合规范的TLP/命令,以触发驱动程序漏洞,导致系统崩溃(蓝屏死机)或潜在的可利用内存损坏。
    • 固件/BIOS操作:在某些高级场景中,DMA设备可能能够与包含BIOS/UEFI的主机SPI闪存进行交互,可能用于持久性修改。
  • 防御措施和缓解策略(防御视角)

    • IOMMU/VT-d/AMD-Vi:如第3.2节所述,这些技术旨在通过为外设提供内存保护来缓解DMA攻击。对于合法测试,您会禁用它们,但在生产系统中,它们应始终启用。 它们阻止外设未经授权的内存访问。
    • 内核DMA保护(Windows)/ Thunderbolt安全(Linux) :现代操作系统功能专门解决“冷启动”DMA攻击(攻击者在系统关闭或锁定时连接恶意设备)。在生产系统上保持这些功能启用。
    • 安全启动:虽然不是直接的DMA保护,但安全启动有助于确保只加载受信任的引导加载程序和内核模块,从而减少攻击者注入恶意内核组件以绕过DMA保护的机会。
    • 物理安全:最基本但最关键的防御。如果攻击者可以物理访问PCIe插槽或Thunderbolt端口,他们可以绕过许多软件保护。保护对关键系统的物理访问。
    • 驱动程序强化:驱动程序应以防御性方式编写,严格验证来自硬件的所有输入并在严格的内存边界内操作。
    • 内存强化:操作系统级的内存保护(例如KASLR、DEP、SMAP/SMEP)有助于减少内存损坏的影响,但直接DMA攻击会绕过这些保护。
    • 监控和日志记录:虽然在硬件层面很难,但异常的DMA活动或未知PCIe设备的枚举应在安全监控系统中触发警报。
  • 固件安全编码实践

    • 输入验证:如果您的固件接受任何输入(例如,通过UART调试接口,或由主机写入的内部寄存器),请严格验证它们,以防止缓冲区溢出、整数溢出或意外行为。
    • 最小权限:设计您的固件逻辑,使其仅执行其功能绝对必要的操作。避免授予不必要的功能。
    • 状态管理:实现健壮的状态机,以防止由于无效状态转换而导致的意外行为。
    • 无硬编码秘密:避免直接在固件中嵌入敏感信息(例如,加密密钥、硬编码凭据),如果它们可以轻易被提取。
    • 篡改检测:对于生产固件,考虑实现检测固件本身是否已被篡改或是否加载了未经授权配置的机制。

16. 其他资源

为了加深您对FPGA开发、PCIe和硬件安全等动态领域的理解并保持更新,请查阅以下资源:

  • Xilinx (AMD) 文档:您获取Vivado和Xilinx FPGA所有信息的主要来源。

    • 主文档门户https://docs.amd.com/(原Xilinx.com/support/documentation)。
    • Vivado 设计套件用户指南
      • UG900 - 入门指南:Vivado新用户必备。
      • UG901 - 逻辑综合:深入了解综合。
      • UG904 - 实现:关于放置和布线的详细指南。
      • UG912 - Tcl 命令参考指南:对于脚本编写价值巨大。
      • UG939 - 调试:ILA和其他调试功能的综合指南。
    • PCI Express IP 核用户指南:理解Xilinx PCIe IP至关重要(例如,PG054 for 7 Series Integrated Block for PCI Express)。在文档门户上搜索“PCI Express”。这详细介绍了核的配置、接口和限制。
  • PCI-SIG 规范:PCIe 标准的权威来源。

    • PCI Express Base Specification:基础文档。虽然不公开免费,但基于它的摘要和教育材料广泛可用。您通常可以在其网站上找到信息:https://pcisig.com/specifications(注意:完整规范通常需要PCI-SIG会员资格)。
  • FPGA 教程和学习平台

  • PCIe 协议分析工具

  • PCILeech 社区和资源

    • ufrisk/pcileech GitHub 仓库是核心。积极关注其更新和问题。
    • 寻找致力于PCILeech或类似开源DMA项目的社区论坛或Discord服务器。
  • 硬件安全与逆向工程

    • 关于硬件黑客、逆向工程和低级系统利用的书籍。
    • Black Hat、DEF CON、Recon 和 Troopers 等会议通常会举办关于 PCIe 和 DMA 攻击的讲座。
    • 专注于硬件的安全研究人员的博客和研究论文。

17. 联系方式

如果您需要帮助、有疑问或希望就本指南、固件开发或硬件安全相关主题进行合作,请随时联系。我乐意提供指导、解决复杂问题或详细讨论想法。

Discord


18. 支持与贡献

您的支持有助于维护和改进本指南及相关项目。创建和更新全面的技术文档以及开源硬件项目需要大量时间和精力。

捐赠

如果您觉得本指南有帮助并希望支持正在进行的工作,请考虑捐赠。每一笔捐款,无论大小,都有助于我们继续通过进一步的研究、开发和文档工作来创建、分享和支持社区。

  • 加密货币捐赠(LTC - 莱特币)
    • 地址MPMyQD5zgy2b2CpDn1C1KZ31KmHpT7AwRi

特别奖励:如果您捐赠,请随时在Discord上(VCPU)与我联系,以获得个人感谢,并可能获得额外资源、新内容的早期访问或项目上的个性化帮助。

注意:如果您需要我审查您实现的特定部分、解决问题或提供详细的代码反馈,请在您的代码中用//VCPU-REVIEW//注释标记相关部分,并提供您遇到的问题或疑问的详细说明。这有助于我集中精力并提供最有效的支持。

愿上帝保佑您的灵魂。


指南结束


文章作者: Damonny
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Damonny !
 本篇
全设备仿真的定制固件开发指南 全设备仿真的定制固件开发指南
**《全设备仿真的定制固件开发指南》摘要** 本指南系统介绍了基于FPGA的PCIe设备固件开发全流程,涵盖基础概念到高级优化技术。作者在个人困境中坚持完成这一开源项目,并特别纪念FPGA之父Ross Freeman的贡献。 **核心内
下一篇 
mysql后缀匹配问题 mysql后缀匹配问题
MySQL 后缀匹配手机号后四位的几种方法及优化建议: 1. **基础方法**: - `RIGHT(phone_number, 4)` 或 `LIKE '%5678'` 简单易用,但无法利用索引,适合小数据量。 -
  目录