完整设备仿真定制固件开发指南

2025-07-09 15:36:40 2528

完整设备仿真定制固件开发指南

原项目:JPShag/PCILEECH-DMA-FW-Guide-2.0

目录

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

8. 高级固件定制

8.1 为仿真配置PCIe参数

8.2 调整BAR和内存映射

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

9. 仿真设备特定功能

9.1 实现高级PCIe功能

9.2 仿真供应商特定特性

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

10.1 理解和捕获TLP

10.2 为特定操作制作自定义TLP

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

8. 高级固件定制

为了实现对捐赠设备的精确仿真,需要进一步深入定制固件。这包括调整PCIe参数、调整基地址寄存器(BARs)、以及仿真电源管理和中断机制,以匹配捐赠设备的规格。这些步骤确保仿真设备能够与主机系统无缝交互,其行为与原始硬件完全一致。

8.1 为仿真配置PCIe参数

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

8.1.1 匹配PCIe链路速度和宽度

PCIe链路速度和宽度是决定设备数据吞吐量和性能的关键参数。匹配这些设置对于准确仿真至关重要。

步骤:

访问PCIe IP核设置:

打开您的Vivado项目:

启动Vivado并打开您之前创建或修改的项目。

确保所有源文件都已正确添加到项目中。

找到PCIe IP核:

在源文件窗格中,展开层次结构,找到PCIe IP核实例,通常命名为pcie_7x_0。

与IP核关联的文件通常是pcie_7x_0.xci。

定制IP核:

右键点击pcie_7x_0.xci,选择定制IP。

IP配置窗口将打开,显示各种配置选项。

设置最大链路速度:

导航到链路参数:

在IP配置窗口中,点击Link Parameters选项卡或部分。

该部分包含与PCIe链路特性相关的设置。

配置最大链路速度:

如果捐赠设备以Gen2(5.0 GT/s)运行,选择5.0 GT/s。

如果以Gen1(2.5 GT/s)或Gen3(8.0 GT/s)运行,选择相应的选项。

找到Maximum Link Speed选项。

设置为与捐赠设备的链路速度相匹配。

示例:

注意:确保您的FPGA和物理硬件支持所选的链路速度。

设置链路宽度:

配置链路宽度:

如果捐赠设备使用x4链路,将Link Width设置为4。

选项通常包括1、2、4、8、16通道。

在同一Link Parameters部分,找到Link Width设置。

设置为与捐赠设备的链路宽度相匹配。

示例:

注意:物理连接器和FPGA必须支持所选的链路宽度。

保存并重新生成:

应用更改:

配置链路速度和宽度后,点击OK应用更改。

Vivado可能提示您由于更改需要重新生成IP核。

确认并允许重新生成过程完成。

验证设置:

重新生成完成后,重新查看IP核设置,确保配置已正确应用。

检查消息窗口中的任何警告或错误。

8.1.2 设置能力指针

PCIe配置空间中的能力指针指向各种能力结构,如MSI、电源管理等。正确设置这些指针确保主机系统能够找到并利用设备的功能。

步骤:

在固件中找到能力指针:

打开配置文件:

pcileech-fpga/pcileech-wifi-main/src/pcileech_pcie_cfg_a7.sv在Visual Studio Code中,打开pcileech_pcie_cfg_a7.sv文件,位于:

了解能力指针:

能力指针是一个8位寄存器,指向PCIe配置空间中的第一个能力结构,通常从标准配置头之后开始。

设置能力指针值:

找到cfg_cap_pointer的赋值:

cfg_cap_pointer <= 8'hXX; // 当前值搜索代码中cfg_cap_pointer的定义行。

更新能力指针:

如果捐赠设备的能力指针是0x60,则更新为:

将XX替换为捐赠设备的能力指针值。

示例:

cfg_cap_pointer <= 8'h60; // 更新以匹配捐赠设备确保正确对齐:

能力结构必须在4字节边界上对齐。

能力指针应指向配置空间内的有效偏移量。

保存更改:

保存配置文件:

进行更改后,点击文件 > 保存或按Ctrl + S保存文件。

验证语法:

确保更改未引入语法错误。

添加注释以增加清晰度:

cfg_cap_pointer <= 8'h60; // 设置为捐赠设备在偏移量0x60的能力指针添加注释,说明更改的原因,便于将来参考。

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

这些参数定义了在单个PCIe事务中可以传输的最大数据量。与捐赠设备匹配这些设置可确保兼容性和最佳性能。

步骤:

设置最大有效载荷大小:

访问设备功能:

在PCIe IP核定制窗口中,导航到Device Capabilities或Capabilities选项卡。

配置支持的最大有效载荷大小:

如果捐赠设备支持最大有效载荷大小为256字节,请选择256字节。

128字节、256字节、512字节、1024字节、2048字节、4096字节。

找到Max Payload Size Supported设置。

将其设置为捐赠设备支持的值。

选项:

示例:

设置最大读取请求大小:

配置支持的最大读取请求大小:

如果捐赠设备支持最大读取请求大小为512字节,请选择512字节。

在同一选项卡中,找到Max Read Request Size Supported设置。

将其设置为与捐赠设备的能力相匹配。

示例:

调整固件参数:

打开pcileech_pcie_cfg_a7.sv:

确保配置文件在Visual Studio Code中打开。

更新固件常量:

max_payload_size_supported <= 3'bZZZ; // 当前值

max_read_request_size_supported <= 3'bWWW; // 当前值找到定义max_payload_size_supported和max_read_request_size_supported的行。

设置适当的值:

对于256字节的有效载荷大小:

对于512字节的读取请求大小:

128字节:3'b000

256字节:3'b001

512字节:3'b010

1024字节:3'b011

2048字节:3'b100

4096字节:3'b101

将ZZZ和WWW替换为大小的二进制表示。

映射关系:

示例:

max_payload_size_supported <= 3'b001; // 支持高达256字节 max_read_request_size_supported <= 3'b010; // 支持高达512字节

保存更改:

保存文件:

更新值后,保存文件。

验证一致性:

确保固件中的值与PCIe IP核中配置的值匹配。

添加注释:

max_payload_size_supported <= 3'b001; // 根据捐赠设备支持256字节

max_read_request_size_supported <= 3'b010; // 根据捐赠设备支持512字节记录更改,便于将来参考。

8.2 调整BAR和内存映射

基地址寄存器(BARs)定义了设备向主机暴露的内存区域。正确配置BARs和内存映射对于准确仿真和设备驱动程序的正常运行至关重要。

8.2.1 设置BAR大小

配置BAR大小可确保设备在枚举期间请求正确的地址空间,并且主机正确映射这些区域。

步骤:

访问BAR配置:

定制PCIe IP核:

在Vivado中,右键点击pcie_7x_0.xci,选择定制IP。

导航到BARs选项卡:

在IP配置窗口中,点击Base Address Registers (BARs)选项卡。

配置BAR大小和类型:

匹配捐赠设备的BARs:

对于每个BAR(BAR0至BAR5),设置大小和类型以匹配捐赠设备。

设置BAR大小:

如果BAR0是64 KB,将BAR0 Size设置为64 KB。

如果BAR1是128 MB,将BAR1 Size设置为128 MB。

从下拉菜单中为每个BAR选择适当的大小。

示例:

设置BAR类型:

为每个BAR选择32位或64位寻址。

指定BAR的类型是内存还是I/O。

根据捐赠设备设置可预取状态。

启用或禁用BARs:

确保仅启用了捐赠设备使用的BARs。

更新BRAM配置:

调整BRAM IP核:

在ip目录中,找到与BARs对应的BRAM配置。

文件:

pcileech-fpga/pcileech-wifi-main/ip/bram_bar_zero4k.xci

pcileech-fpga/pcileech-wifi-main/ip/bram_pcie_cfgspace.xci修改BRAM大小:

打开每个BRAM IP核,调整内存大小以匹配相应的BAR大小。

确保总内存不超过FPGA的容量。

保存并重新生成:

应用更改:

配置BARs并更新BRAM大小后,点击OK。

重新生成IP核:

Vivado可能会提示由于更改需要重新生成IP核。

允许重新生成完成。

检查错误:

查看消息窗口中的任何与BAR配置相关的警告或错误。

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

设置BAR大小和类型后,您需要定义固件如何处理对这些BAR的访问。

步骤:

打开BAR控制器文件:

找到源文件:

pcileech-fpga/pcileech-wifi-main/src/pcileech_tlps128_bar_controller.sv在Visual Studio Code中,打开:

映射地址范围:

定义地址解码逻辑:

always_comb begin

if (bar_hit[0]) begin

// 处理对BAR0的访问

end else if (bar_hit[1]) begin

// 处理对BAR1的访问

end

// 继续处理其他BAR

end实现逻辑以检测何时访问BAR,基于地址。

实现BAR访问处理:

对于每个BAR,定义如何管理读写操作。

示例:

if (bar_hit[0]) begin

case (addr_offset)

16'h0000: data_out <= reg0;

16'h0004: data_out <= reg1;

// 其他寄存器

default: data_out <= 32'h0;

endcase

end

实现地址解码逻辑:

计算地址偏移量:

addr_offset = incoming_address - bar_base_address[0];处理数据传输:

if (cfg_write) begin

// 将数据写入适当的寄存器

end else if (cfg_read) begin

// 从适当的寄存器读取数据

end

保存更改:

保存文件:

实现逻辑后,保存pcileech_tlps128_bar_controller.sv文件。

验证功能:

确保逻辑正确处理所有可能的访问。

8.2.3 处理多个BAR

正确管理多个BAR对于暴露多个内存或I/O区域的设备至关重要。

步骤:

为每个BAR实现逻辑:

分离逻辑块:

// BAR0处理

if (bar_hit[0]) begin

// BAR0特定逻辑

end

// BAR1处理

if (bar_hit[1]) begin

// BAR1特定逻辑

end为了清晰起见,在控制器内为每个BAR创建单独的代码块。

定义寄存器和内存:

根据需要为每个BAR分配寄存器或内存块。

确保地址空间不重叠:

验证地址范围:

确认每个BAR的地址空间不重叠。

根据PCIe规范要求,将BAR大小对齐到2的幂边界。

更新地址解码:

调整地址解码逻辑,以考虑每个BAR的大小和基地址。

测试BAR访问:

仿真测试:

使用仿真工具测试对每个BAR的读写操作。

验证读取或写入的数据是否正确。

硬件测试:

在Linux上使用lspci检查BAR映射。

编写执行内存映射I/O到BAR的测试程序。

编程FPGA,使用主机上的软件访问每个BAR。

示例:

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

仿真电源管理功能并实现中断对于需要与主机操作系统的电源和中断处理机制紧密交互的设备至关重要。

8.3.1 电源管理配置

实现电源管理允许设备支持各种电源状态,有助于系统范围的电源效率,并符合操作系统的期望。

步骤:

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

访问功能选项:

在PCIe IP核配置窗口中,选择Capabilities选项卡。

启用电源管理:

勾选Power Management选项,以在设备的配置空间中包含此功能。

设置支持的电源状态:

配置支持的状态:

指定设备支持哪些电源状态,例如:

D0(完全开启)

D1、D2(中间状态)

D3hot、D3cold(低功耗状态)

将这些设置与捐赠设备的功能相匹配。

在固件中实现电源状态逻辑:

打开pcileech_pcie_cfg_a7.sv:

修改固件以处理电源状态转换。

处理电源管理控制和状态寄存器(PMCSR):

// PMCSR地址

localparam PMCSR_ADDRESS = 12'h44; // 示例地址

// PMCSR寄存器

reg [15:0] pmcsr_reg;

// 处理PMCSR写操作

always @(posedge clk) begin

if (cfg_write && cfg_address == PMCSR_ADDRESS) begin

pmcsr_reg <= cfg_writedata[15:0];

// 根据pmcsr_reg[1:0]更新电源状态

end

end实现对PMCSR的读写访问。

管理电源状态效果:

实现逻辑,根据当前的电源状态改变设备行为。

保存更改:

保存固件文件:

确保所有修改都已保存。

验证功能:

通过仿真或硬件测试,测试电源管理功能。

8.3.2 MSI/MSI-X配置

实现MSI/MSI-X允许设备使用基于消息的中断,这比传统的基于引脚的中断更高效和可扩展。

步骤:

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

访问中断配置:

在PCIe IP核配置窗口中,导航到Interrupts或MSI/MSI-X选项卡。

选择中断类型:

根据捐赠设备,选择MSI或MSI-X。

配置支持的向量数量:

设置与捐赠设备匹配的中断向量数量。

MSI支持最多32个向量。

MSI-X支持最多2048个向量。

启用功能:

确保MSI或MSI-X功能包含在设备的配置空间中。

在固件中实现中断逻辑:

打开pcileech_pcie_tlp_a7.sv:

修改固件以处理中断生成。

定义中断信号:

reg msi_req;实现中断生成逻辑:

// 示例中断条件

wire interrupt_condition = /* 条件逻辑 */;

// 生成MSI中断

always @(posedge clk) begin

if (interrupt_condition) begin

msi_req <= 1'b1;

end else begin

msi_req <= 1'b0;

end

end连接到PCIe核心:

确保msi_req信号正确连接到PCIe IP核的中断接口。

保存更改:

保存固件文件:

实现中断逻辑后,保存文件。

检查时序约束:

确认新逻辑未引入时序违规。

8.3.3 实现中断处理逻辑

定义何时以及如何生成中断对于设备与主机的中断处理机制的交互至关重要。

步骤:

定义中断条件:

识别触发事件:

数据已准备好处理。

错误条件。

任务完成。

确定应引起中断的特定事件。

示例:

实现条件逻辑:

使用组合逻辑或时序逻辑来检测这些事件。

创建中断生成模块:

模块化设计:

module interrupt_controller(

input wire clk,

input wire reset,

input wire event_trigger,

output reg msi_req

);

always @(posedge clk or posedge reset) begin

if (reset) begin

msi_req <= 1'b0;

end else if (event_trigger) begin

msi_req <= 1'b1;

end else begin

msi_req <= 1'b0;

end

end

endmodule将中断逻辑实现为一个独立的模块,以提高清晰度和可重用性。

与主固件集成:

实例化模块,并将其连接到主固件逻辑。

确保正确的时序和顺序:

遵守PCIe规范:

确保中断按照协议正确生成和清除。

管理中断延迟:

优化逻辑,最小化事件发生和中断生成之间的延迟。

测试中断传递:

仿真:

使用仿真工具验证中断是否正确生成。

硬件测试:

编程FPGA,使用主机端软件确认中断已接收并处理。

调试工具:

利用集成逻辑分析器(ILA)核实时监控信号。

保存更改:

最终确定代码:

确保所有更改都已保存并记录。

审查和改进:

根据测试结果迭代设计。

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

事务层数据包(TLP)是PCIe通信的基本单位。准确的TLP仿真对于设备正确地与主机系统交互至关重要。

10.1 理解和捕获TLP

10.1.1 学习TLP结构

组成部分:

头部:包含字段,如事务层数据包类型(Type)、长度、请求者ID、标签、地址等。

数据负载:存在于内存写入和其他一些TLP中。

CRC:确保数据完整性。

理解TLP类型:

内存读取请求

内存读取完成

内存写入

配置读取/写入

供应商定义的消息

10.1.2 从捐赠设备捕获TLP

步骤:

设置PCIe协议分析仪:

使用如Teledyne LeCroy PCIe分析仪的硬件工具。

捕获事务:

在捐赠设备正常运行期间进行监控,记录TLP。

分析捕获的TLP:

使用分析仪的软件,剖析TLP,理解其结构和序列。

10.1.3 记录关键TLP事务

步骤:

识别关键事务:

关注设备初始化、配置、数据传输和错误处理所必需的TLP。

创建详细文档:

对于每个关键TLP,记录字段值、序列和发送条件。

理解时序和顺序:

注意TLP之间的时序,以及所需的响应时间。

10.2 为特定操作制作自定义TLP

10.2.1 在固件中实现TLP处理

需要修改的文件:

pcileech_pcie_tlp_a7.sv

pcileech-wifi-main/src/pcileech_pcie_tlp_a7.sv步骤:

创建TLP生成函数:

在pcileech_pcie_tlp_a7.sv中,编写函数,组装具有所需头部和负载的TLP。

示例:

function automatic [127:0] generate_tlp;

input [15:0] requester_id;

input [7:0] tag;

input [7:0] length;

input [31:0] address;

input [31:0] data;

begin

generate_tlp = { /* TLP头部和负载 */ };

end

endfunction

处理TLP接收:

实现逻辑,解析接收到的TLP,提取必要信息。

使用状态机管理不同的TLP类型。

确保合规性:

验证TLP符合PCIe规范的格式和时序。

实现完成处理:

对于内存读取请求,生成适当的完成TLP。

保存更改:

实现更改后,保存文件。

10.2.2 处理不同的TLP类型

内存读取请求:

实现:

解析请求头部。

从适当的内存位置获取数据。

组装并发送包含数据的完成TLP。

内存写入请求:

实现:

接收TLP并提取数据负载。

将数据写入指定的内存位置。

配置读取/写入请求:

实现:

访问配置空间寄存器。

对于读取,返回请求的数据。

对于写入,更新寄存器值。

供应商定义的消息:

实现:

根据捐赠设备的协议,实现解析和响应逻辑。

10.2.3 验证TLP时序和序列

步骤:

使用仿真工具:

使用测试平台仿真固件,验证TLP处理。

使用ILA监控:

插入ILA核,捕获TLP相关信号的硬件测试。

检查时序约束:

确保TLP在PCIe标准允许的时序窗口内处理和响应。

合规性测试:

使用PCIe合规性工具验证对标准的遵从。

保存更改:

测试和验证后,保存所有修改的文件。