目录
前言
记录一下对于PCIE RC工程搭建、裸机测试、编写PCIE RC Device Tree过程。
一、参考文档和示例工程
注意区分所使用的IP,我们使用的芯片型号是Virtex Ultra系列,所以不支持XDMA IP的RC功能,要使用AXI PCIE IP,所以下文中所有参考的文档以及示例工程均为AXI PCIE IP。
- AXI PCIE IP示例拓扑图(Xilinx Wiki提供)
- Xilinx AXI PCIe Root Port Bridge DT description (DTS编写时变量要求,但是比较简略)
- PCI_Address_Translation(这个文档对于DTS中变量详细解释)
- RC驱动介绍(但是是XDMA IP,可以略微参考)
- 一位博主的分析,但其中有些解释我认为有误,自行判断
- 示例工程,支持各种型号FPGA
二、搭建工程,并裸机测试
1.选择合适IP
只介绍主要IP。
- PCIE RC,需要可提供该功能IP,Xilinx主要提供了两类IP供选择:
- XDMA (PCIE RC)
- AXI PCIE3(PCIE RC)
- CPU:选择MicroBlaze软核,并配置内部支持Linux
2.Block Design
这里将DDR作为了PCIE RC的Slave,整体BD框图如下:
3.Vitis裸机测试
步骤如下:
- Vivado编写xdc,综合并导出XSA硬件文件(包含bitstream)
- 在Vitis导入XSA文件,并打开pcie示例工程
在这里需要注意,Vitis针对AXI PCIE IP的Config参数读取时,TCL脚本存在错误,所以会导致Vitis所读取的Parameter.h文件参数会错误,我们需要对Vitis底层TCL进行修改,该TCL路径如下:
~\Vitis\2022.1\data\embeddedsw\XilinxProcessorIPLib\drivers\axipcie_v3_3\data\axipcie.tcl
打开这个脚本去修改它读的参数类型,都包含一个名为 的定义INCLUDE_RC
。为了确定此定义的值,脚本读取 PCIe IP 的一个名为 的参数CONFIG.INCLUDE_RC
,但该参数仅存在于映射到 PCIe IP 的 AXI 内存中。我们修改后的脚本版本使用正确的参数来确定 的值INCLUDE_RC,修改后的TCL贴到下方:
###############################################################################
# Copyright (C) 2011 - 2020 Xilinx, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
#
# MODIFICATION HISTORY:
#
# Ver Who Date Changes
# -------- ------ -------- ----------------------------------------------------
# 03/22/10 rkv First Release
# 09/06/13 srt Fixed CR 734175:
# C_BASEADDR and C_HIGHADDR configuration parameters are renamed
# to BASEADDR and HIGHADDR in Vivado builds. Modified the tcl
# for this change.
#
# 3.0 adk 10/12/13 Updated as per the New Tcl API's
#
###############################################################################
#uses "xillib.tcl"
proc generate {drv_handle} {
xdefine_pcie_include_file $drv_handle "xparameters.h" "XAxiPcie" \
"NUM_INSTANCES" \
"DEVICE_ID" \
"C_FAMILY" \
"C_BASEADDR" \
"C_HIGHADDR" \
"C_INCLUDE_BAROFFSET_REG"\
"C_AXIBAR_NUM"\
"C_AXIBAR_0"\
"C_AXIBAR_HIGHADDR_0"\
"C_AXIBAR_AS_0"\
"C_AXIBAR2PCIEBAR_0"\
"C_AXIBAR_1"\
"C_AXIBAR_HIGHADDR_1"\
"C_AXIBAR_AS_1"\
"C_AXIBAR2PCIEBAR_1"\
"C_AXIBAR_2"\
"C_AXIBAR_HIGHADDR_2"\
"C_AXIBAR_AS_2"\
"C_AXIBAR2PCIEBAR_2"\
"C_AXIBAR_3"\
"C_AXIBAR_HIGHADDR_3"\
"C_AXIBAR_AS_3"\
"C_AXIBAR2PCIEBAR_3"\
"C_AXIBAR_4"\
"C_AXIBAR_HIGHADDR_4"\
"C_AXIBAR_AS_4"\
"C_AXIBAR2PCIEBAR_4"\
"C_AXIBAR_5"\
"C_AXIBAR_HIGHADDR_5"\
"C_AXIBAR_AS_5"\
"C_AXIBAR2PCIEBAR_5"\
"C_PCIEBAR_NUM"\
"C_PCIEBAR_AS"\
"C_PCIEBAR_LEN_0"\
"C_PCIEBAR2AXIBAR_0"\
"C_PCIEBAR_LEN_1"\
"C_PCIEBAR2AXIBAR_1"\
"C_PCIEBAR_LEN_2"\
"C_PCIEBAR2AXIBAR_2"\
"C_PCIEBAR_LEN_3"\
"C_PCIEBAR2AXIBAR_3"\
"C_PCIEBAR_LEN_4"\
"C_PCIEBAR2AXIBAR_4"\
"C_PCIEBAR_LEN_5"\
"C_PCIEBAR2AXIBAR_5"\
"C_INCLUDE_RC"
::hsi::utils::define_config_file $drv_handle "xaxipcie_g.c" "XAxiPcie" \
"DEVICE_ID" \
"C_BASEADDR" \
"C_AXIBAR_NUM" \
"C_INCLUDE_BAROFFSET_REG" \
"C_INCLUDE_RC"
xdefine_pcie_canonical_xpars $drv_handle "xparameters.h" "AxiPcie" \
"DEVICE_ID" \
"C_FAMILY" \
"C_BASEADDR" \
"C_HIGHADDR" \
"C_INCLUDE_BAROFFSET_REG"\
"C_AXIBAR_NUM"\
"C_AXIBAR_0"\
"C_AXIBAR_HIGHADDR_0"\
"C_AXIBAR_AS_0"\
"C_AXIBAR2PCIEBAR_0"\
"C_AXIBAR_1"\
"C_AXIBAR_HIGHADDR_1"\
"C_AXIBAR_AS_1"\
"C_AXIBAR2PCIEBAR_1"\
"C_AXIBAR_2"\
"C_AXIBAR_HIGHADDR_2"\
"C_AXIBAR_AS_2"\
"C_AXIBAR2PCIEBAR_2"\
"C_AXIBAR_3"\
"C_AXIBAR_HIGHADDR_3"\
"C_AXIBAR_AS_3"\
"C_AXIBAR2PCIEBAR_3"\
"C_AXIBAR_4"\
"C_AXIBAR_HIGHADDR_4"\
"C_AXIBAR_AS_4"\
"C_AXIBAR2PCIEBAR_4"\
"C_AXIBAR_5"\
"C_AXIBAR_HIGHADDR_5"\
"C_AXIBAR_AS_5"\
"C_AXIBAR2PCIEBAR_5"\
"C_PCIEBAR_NUM"\
"C_PCIEBAR_AS"\
"C_PCIEBAR_LEN_0"\
"C_PCIEBAR2AXIBAR_0"\
"C_PCIEBAR_LEN_1"\
"C_PCIEBAR2AXIBAR_1"\
"C_PCIEBAR_LEN_2"\
"C_PCIEBAR2AXIBAR_2"\
"C_PCIEBAR_LEN_3"\
"C_PCIEBAR2AXIBAR_3"\
"C_PCIEBAR_LEN_4"\
"C_PCIEBAR2AXIBAR_4"\
"C_PCIEBAR_LEN_5"\
"C_PCIEBAR2AXIBAR_5"\
"C_INCLUDE_RC"
}
proc xdefine_pcie_include_file {drv_handle file_name drv_string args} {
# Open include file
set file_handle [::hsi::utils::open_include_file $file_name]
# Get all peripherals connected to this driver
set periphs [::hsi::utils::get_common_driver_ips $drv_handle]
set ipname [common::get_property IP_NAME [get_cells -hier $drv_handle]]
# Handle special cases
set arg "NUM_INSTANCES"
set posn [lsearch -exact $args $arg]
if {$posn > -1} {
puts $file_handle "/* Definitions for driver [string toupper [common::get_property NAME $drv_handle]] */"
# Define NUM_INSTANCES
puts $file_handle "#define [::hsi::utils::get_driver_param_name $drv_string $arg] [llength $periphs]"
set args [lreplace $args $posn $posn]
}
# Check if it is a driver parameter
lappend newargs
foreach arg $args {
set value [common::get_property CONFIG.$arg $drv_handle]
if {[llength $value] == 0} {
lappend newargs $arg
} else {
puts $file_handle "#define [::hsi::utils::get_driver_param_name $drv_string $arg] [common::get_property CONFIG.$arg $drv_handle]"
}
}
set args $newargs
# Print all parameters for all peripherals
set device_id 0
foreach periph $periphs {
puts $file_handle ""
puts $file_handle "/* Definitions for peripheral [string toupper [common::get_property NAME $periph]] */"
foreach arg $args {
if {[string compare -nocase "DEVICE_ID" $arg] == 0} {
set value $device_id
incr device_id
} else {
set value [::hsi::utils::get_param_value $periph $arg]
}
if {[llength $value] == 0} {
set value 0
}
# In Vivado, the Gen3 core does not have a parameter called "C_INCLUDE_RC", instead it has "device_port_type"
if { $value == 0 && $arg == "C_INCLUDE_RC" } {
set value [common::get_property CONFIG.device_port_type $periph]
if { $value == "Root_Port_of_PCI_Express_Root_Complex" } {
set value 1
} else {
set value 0
}
}
# For Vivado, C_BASEADDR is renamed to BASEADDR
if { $value == 0 && $arg == "C_BASEADDR" } {
set arg "BASEADDR"
set value [::hsi::utils::get_param_value $periph $arg]
}
# For Vivado, C_HIGHADDR is renamed to HIGHADDR
if { $value == 0 && $arg == "C_HIGHADDR" } {
set arg "HIGHADDR"
set value [::hsi::utils::get_param_value $periph $arg]
}
if { $value == 0 && $arg == "C_PCIEBAR_NUM" && [string match -nocase $ipname "axi_pcie3"]} {
set arg "PCIEBAR_NUM"
set value [::hsi::utils::get_param_value $periph $arg]
}
set value [::hsi::utils::format_addr_string $value $arg]
if {[string compare -nocase "HW_VER" $arg] == 0} {
puts $file_handle "#define [::hsi::utils::get_ip_param_name $periph $arg] \"$value\""
} else {
puts $file_handle "#define [::hsi::utils::get_ip_param_name $periph $arg] $value"
}
}
puts $file_handle ""
}
puts $file_handle "\n/******************************************************************/\n"
close $file_handle
}
proc xdefine_pcie_canonical_xpars {drv_handle file_name drv_string args} {
# Open include file
set file_handle [::hsi::utils::open_include_file $file_name]
set ipname [common::get_property IP_NAME [get_cells -hier $drv_handle]]
# Get all the peripherals connected to this driver
set periphs [::hsi::utils::get_common_driver_ips $drv_handle]
# Get the names of all the peripherals connected to this driver
foreach periph $periphs {
set peripheral_name [string toupper [common::get_property NAME $periph]]
lappend peripherals $peripheral_name
}
# Get possible canonical names for all the peripherals connected to this
# driver
set device_id 0
foreach periph $periphs {
set canonical_name [string toupper [format "%s_%s" $drv_string $device_id]]
lappend canonicals $canonical_name
# Create a list of IDs of the peripherals whose hardware instance name
# doesn't match the canonical name. These IDs can be used later to
# generate canonical definitions
if { [lsearch $peripherals $canonical_name] < 0 } {
lappend indices $device_id
}
incr device_id
}
set i 0
foreach periph $periphs {
set periph_name [string toupper [common::get_property NAME $periph]]
# Generate canonical definitions only for the peripherals whose
# canonical name is not the same as hardware instance name
if { [lsearch $canonicals $periph_name] < 0 } {
puts $file_handle "/* Canonical definitions for peripheral $periph_name */"
set canonical_name [format "%s_%s" $drv_string [lindex $indices $i]]
foreach arg $args {
set lvalue [::hsi::utils::get_driver_param_name $canonical_name $arg]
# The commented out rvalue is the name of the instance-specific constant
# set rvalue [::hsi::utils::get_ip_param_name $periph $arg]
# The rvalue set below is the actual value of the parameter
set rvalue [::hsi::utils::get_param_value $periph $arg]
if {[llength $rvalue] == 0} {
set rvalue 0
}
# In Vivado, the Gen3 core does not have a parameter called "C_INCLUDE_RC", instead it has "device_port_type"
if { $rvalue == 0 && $arg == "C_INCLUDE_RC" } {
set rvalue [common::get_property CONFIG.device_port_type $periph]
if { $rvalue == "Root_Port_of_PCI_Express_Root_Complex" } {
set rvalue 1
} else {
set rvalue 0
}
}
# For Vivado, C_BASEADDR is renamed to BASEADDR
if { $rvalue == 0 && $arg == "C_BASEADDR" } {
set arg "BASEADDR"
set rvalue [::hsi::utils::get_param_value $periph $arg]
}
# For Vivado, C_HIGHADDR is renamed to HIGHADDR
if { $rvalue == 0 && $arg == "C_HIGHADDR" } {
set arg "HIGHADDR"
set rvalue [::hsi::utils::get_param_value $periph $arg]
}
if { $rvalue == 0 && $arg == "C_PCIEBAR_NUM" && [string match -nocase $ipname "axi_pcie3"]} {
set arg "PCIEBAR_NUM"
set rvalue [::hsi::utils::get_param_value $periph $arg]
}
set rvalue [::hsi::utils::format_addr_string $rvalue $arg]
puts $file_handle "#define $lvalue $rvalue"
}
puts $file_handle ""
incr i
}
}
puts $file_handle "\n/******************************************************************/\n"
close $file_handle
}
之后对示例驱动编译,烧录,可以通过串口看到如下打印信息,即为测试成功: