
折腾了差不多两个礼拜终于把紫光同创PGL50H开发板的PCIe链路完整跑通从PDS里导入IP核、配置PCIE硬核、生成bitstream到把板卡插进电脑最后在Windows设备管理器里看到那个设备出现。这个过程比我预想的要顺利但也踩了不少文档里没有细写的坑。这篇教程我会把操作顺序和背后原理一起讲清楚适合手头已经有PGL50H开发板、正准备做PCIe通信的朋友参考如果你是从Xilinx的Vivado转过来想快速上手国产工具链直接照着做也完全行。先说清楚这玩意儿到底能干什么。PGL50H是紫光同创Logos系列里经常被拿来当原型验证板的一颗FPGA自带PCIe硬核可以直接在板卡上做成一个标准的PCIe端点设备。硬核帮你把物理层、数据链路层、事务层大部分事情都做了作为使用者我们真正要操作的是配置空间、寄存器接口和数据通路这一层。相比用逻辑自己搭收发器这个难度已经是友好太多了。文章最后做到的效果就是Windows设备管理器能正确识别到这块FPGA并且配置空间能被正常读取说明链路从物理层到事务层全部打通后续做DMA、数据采集或者寄存器读写都有了一个可靠基础。1. 项目全貌与准备工作1.1 PGL50H的PCIe能力定位在开始操作之前我建议你先搞清楚手上的开发板到底把PCIe引脚引到了哪里、金手指是x1还是x4、参考时钟怎么供给。紫光同创PGL50H这颗器件本身带有PCIe硬核理论上可以配置成Endpoint模式支持的链路宽度和速率取决于具体封装、板卡设计和IP核版本。我这次调试用的是x1链路Gen2速率也就是单通道5GT/s对验证通信链路来说已经完全够用。这里多说一句PCIe和普通逻辑调试最大的区别是它不是上电就能用的。FPGA内部逻辑跑起来之后PCIe硬核还要经过复位释放、训练序列、配置空间枚举这一整套流程电脑主板才会认为你插了一个正常的PCIe设备。任何一个环节断掉Windows设备管理器里就什么都看不到而且不会给你任何报错。所以做这个项目要有心理准备很多时候问题不在代码而在链路建立的时序和硬件环境上。1.2 软硬件清单与开发环境我这次实际用的环境如下供你对照自己的情况参考。硬件方面一块PGL50H开发板带PCIe金手指那种不是拿普通排线引出来的一台有PCIe插槽的Windows电脑最好是有人用过的旧机器避免BIOS安全启动之类的东西干扰一个配套的JTAG调试器用来下载bitstream另外我推荐准备一个PCIe转接卡不是所有开发板都能刚好插进机箱很多时候需要竖起来放转接卡能省掉不少物理上的麻烦。软件方面紫光同创的FPGA开发软件PDS是必须的注意装的时候路径不要有中文建议直接装到磁盘根目录下的某个英文文件夹里。仿真用的ModelSim或Questa我也装了虽然这个项目不写复杂逻辑但IP核生成之后做一遍行为仿真的价值很大能帮你在上板前就排除掉时钟和复位的问题。还有一个容易被忽略的是串口调试终端用来看FPGA内部运行状态后面排查问题会用到。PDS这个工具如果你之前只用过Vivado第一感觉会有点熟悉又有点陌生。它也有工程管理、综合、布局布线、bitstream生成这一整套流程但一些操作入口和叫法不太一样。建议先花半小时新建一个点亮LED的简单工程跑通下载流程确保JTAG链路和板卡配置流程是好的再进入PCIe的调试。跳过这一步直接搞PCIe的话出了问题很难判断是工具问题还是硬件问题。1.3 让License顺利生效紫光同创PDS的License是很容易卡住人的一个地方。软件装好后第一次启动会要求加载License文件。这个文件需要去官网注册账号申请一般发给FAE或者下载中心填个表格就能拿到。不要试图自己找许可证生成器之类的东西国产FPGA工具链的License服务经常更新老老实实申请才是正路。拿到License文件之后在PDS启动界面选择“使用License文件”把路径指过去就行。注意License文件和PDS版本要匹配我用的是一个较新的PDS版本搭配官方对应版本的License才识别正常。另外License有时效性申请的时候看清有效期如果调试到一半License过期IP核生成会直接报错你可能会以为是配置问题其实是授权问题。实践经验把License文件放在PDS安装目录下的一个固定文件夹里并且手动添加环境变量LM_LICENSE_FILE指向它这样以后启动不用每次手动选。还有一种情况是装的杀毒软件把License读取拦截了如果启动PDS提示找不到License且路径没问题关掉杀毒软件再试一次。2. PCIe IP核安装与参数配置2.1 三步把IP核装进PDSPDS安装完成后自带的IP库里是有PCIe IP的但具体到某个器件型号有时需要单独下载对应版本的IP包导入。这个步骤在Vivado里非常顺滑因为Xilinx把IP都集成在软件里了紫光同创目前的流程稍微绕一点。我这次实际走的流程大致可以总结成三步。第一步去官网下载中心找PGL50H对应的PCIe IP包注意IP包有版本号和工具版本对应关系下载时看清说明。第二步解压IP包你会看到里面有一个类似.pso或者.zip格式的发布文件这个文件就是给PDS导入用的。第三步在PDS中打开菜单栏里的IP相关管理入口选择导入IP包指定刚才解压出来的文件系统会自动帮你安装到当前版本的IP库里。导入完成之后新建工程的时候或者在IP Compiler里就能看到PCIe IP了。如果没有看到多半是版本不匹配或者导入操作没有真正成功。我遇到过一种情况是解压路径有中文导致导入一直失败把文件换个目录重新解压就好了。导入IP包这一步对使用PDS的人来说属于基础操作但坑就在版本匹配上一定不要拿别的器件型号的IP包硬套。2.2 配置PCIe IP核的六个关键点打开IP Compiler选择PCIe IP命名之后进入配置界面。这个界面里的选项有点多但不是每个都要动。我这次实际配置的时候重点关注下面这几个位置你照着设置一遍基本不会走弯路。链路宽度选择x1还是x4取决于你板卡金手指实际引出了几对收发器。这个不是想设多少就设多少的设多了可能因为硬件上没接线导致训练失败设少了浪费性能。我第一次就是图省事配置成x4结果板子是x1的引线训练一直失败后来才发现问题。速率方面我选了Gen2。如果你参考时钟质量一般或者走线比较长可以先用Gen1验证链路稳定后再上Gen2。这个改动只是IP核配置里一个下拉框的事但影响的是整个SerDes的工作状态建议从低速率开始。参考时钟配置上PCIe标准要求100MHz差分时钟。IP核界面里通常会让你选择参考时钟来源和频率注意如果板子上是125MHz或者别的频率一定要改过来。还有弹性缓存那块物理层会自动处理两端的时钟频率偏差不需要手动干预你只需要保证给到IP核的参考时钟频率正确就好。端点模式必须设为Endpoint如果你是做根复合体实验才选Root Complex。BAR空间这里我开了BAR0大小设置为4KB地址空间映射方式保持默认。BAR空间是Windows枚举后给设备分配的内存窗口后续读写寄存器都靠它。中断方式我这次直接选了MSI比传统INTx中断在Windows下好使很多。数据接口部分PCIe IP核一般会引出类似AXI-Stream或者自定义接口。紫光同创这个IP核生成的接口形式和Xilinx的XDMA思路有相似之处但寄存器定义和信号名是各家自己定的。我这次用了最简单的寄存器读写接口先不上DMA等链路稳定再考虑大数据传输。这一点后面例化的时候还要再提。2.3 IP核生成后先做这两件事IP核配置完成后点生成PDS会生成一个IP核的例化模板和一套配套文件。我建议此刻不要着急写代码先做两件事。第一件打开生成的例化模板把顶层端口名大致扫一遍确认时钟、复位、收发器引脚、配置接口这些信号都在。很多IP核的端口会在名字后面标注方向仔细看看哪些信号是需要你从顶层引下来的。第二件事找到IP核生成的时序约束文件。PCIe硬核的时序约束通常已经由IP核自身带出来了不需要你在用户约束文件里重复写。但如果你看到IP核目录里有约束文件记得确认它被加到了工程约束列表里这个很容易忽略。我在第一次做的时候就没留意导致综合完之后时序报告一团糟排查到最后发现是约束文件被PDS默认排除了。如果你准备做仿真IP核生成目录下还会带仿真模型和测试bench模板。把这些文件加到仿真工程里直接跑一下默认仿真能很快确认硬核的复位时钟链路是否正常。这步虽然不能验证板级链路但至少能验证IP核配置本身没有逻辑错误。3. 板级设计与仿真验证要点3.1 耦合电容、阻抗与等长这里要说的是很多人容易忽视的硬件问题。如果你用的是现成开发板这部分通常不用你操心但如果你想自己画板子加PCIe接口或者做一个小转接板那耦合电容摆放位置、阻抗控制、差分对等长这三个问题会在联调时给你狠狠上一课。PCIe的收发差分信号需要AC耦合电容标准做法是在发送端那一侧串接一个电容。这个电容的摆放位置很有讲究要尽量靠近发送端的引脚而不是靠近连接器。原因很简单AC耦合电容会带来阻抗不连续点把它放在走线靠近源端的位置后面那段走线可以帮忙恢复一部分阻抗信号完整性会比放在连接器附近好很多。容值方面PCIe Gen1和Gen2常用0.1uFGen3推荐0.22uF选择容值要考虑低速信号比如参考时钟的频率分量。摆放时还要注意差分对的两颗电容要对称焊盘尺寸尽量一致不然两个方向的寄生参数不相同差分转换会引入共模噪声。还有一点容易被忽略耦合电容附近的地平面要完整不要在电容下方穿线否则这条走线会成为整个链路里最严重的阻抗破坏点。阻抗控制上PCIe差分阻抗要求是85欧姆左右老版本规范写的是100欧姆新设计一般都按85欧姆去控制。做板子的时候告诉板厂“PCIe走线差分阻抗85”他们就知道该怎么调线宽和间距了。另外在布线时一对差分线内部要保持等长误差控制在5mil以内通道与通道之间的误差可以放宽到几十个mil这个要求并不苛刻。3.2 参考时钟、电源与复位PCIe链路能不能训练成功参考时钟质量是第一位。100MHz差分参考时钟要由连接器或者板载晶振提供抖动指标得符合PCIe规范。有人想用普通有源晶振单端输出直接接PCIe参考时钟引脚这里要提醒一下PCIe参考时钟是差分形式单端转差分电路如果设计不好出来的时钟抖动可能完全不合格。电源方面PCIe硬核通常需要独立的模拟电源和数字电源。我这次用的开发板是官方设计电源树不用我操心但如果你自己设计板卡一定要参考芯片手册里的上电时序要求尤其是SerDes那块供电。上电顺序不对轻则链路训练不稳重则直接枚举不到设备而且这种问题非常难查。复位信号也值得多花两句话。PCIe设备的复位一般来自金手指上的PERST#信号或者板载复位控制。FPGA内部逻辑需要在复位释放之后再给PCIe硬核一个确定的复位释放时间。不要用FPGA内部的全局复位按键直接控制PCIe硬核因为你按键释放的时机跟PCIe规范要求的复位时序很可能对不上。我这次在顶层里加了一个简单的复位同步逻辑用系统复位信号打两拍之后再去复位PCIe硬核实测稳定很多。3.3 用仿真确认链路基础状态上板之前先跑一遍仿真能省掉很多在现场抓头的时间。PDS和ModelSim联合仿真步骤不复杂关键是IP核自带的测试bench能不能正常编译。我把IP核生成的仿真模型和testbench加入ModelSim跑了一遍主要看三个东西参考时钟有没有正常起振、复位信号是否正确释放、AXI接口的寄存器读写事务能不能执行。这里有个经验如果仿真阶段AXI寄存器读写的状态机就不是预期的样子那么上板之后大概率也读不到正确数据。仿真能发现的问题一般是逻辑逻辑层面的比如某个信号打拍顺序不对、复位周期不够长等等。这类问题在真机上调试非常痛苦因为没有波形给你看而仿真里几秒钟就能定位。不过仿真的局限性也要清楚它验证不了物理层的实际训练结果因为那个需要真正的外部对端设备。仿真过了只说明IP核配置正确、内部逻辑自洽不代表插到电脑上就一定能识别。真正决定能否识别的还是硬件链路状态。4. 顶层例化与比特流生成全流程4.1 例化PCIe硬核与AXI接口对接IP核生成之后要写一个顶层模块把它例化出来。这一步其实是整个流程里最直接的部分照着IP核的例化模板改就行但有几个信号名要特别注意。下面这段是我这次简化后的例化示意端口名不是每个版本都完全一样关键是把对应关系理清最终以你生成的模板为准。pcie_ep_0 pcie_ep_inst ( .ref_clk (pcie_ref_clk), // 100MHz 差分参考时钟 .perst_n (pcie_perst_n), // PCIe复位信号 .rxp (pcie_rxp), // 接收差分对正 .rxn (pcie_rxn), // 接收差分对负 .txp (pcie_txp), // 发送差分对正 .txn (pcie_txn), // 发送差分对负 .user_clk (user_clk), // 用户时钟输出 .user_reset (user_reset), // 用户复位输出 .reg_wr_en (reg_wr_en), // 寄存器写使能 .reg_wr_addr (reg_wr_addr), // 寄存器写地址 .reg_wr_data (reg_wr_data), // 寄存器写数据 .reg_rd_en (reg_rd_en), // 寄存器读使能 .reg_rd_addr (reg_rd_addr), // 寄存器读地址 .reg_rd_data (reg_rd_data) // 寄存器读数据 );这里想多说一下user_clk和user_reset这两个信号。PCIe硬核输出的用户时钟是链路训练完成后才稳定输出的所以不要在顶层里用你自己的时钟去驱动这些AXI接口逻辑要用IP核输出的用户时钟作为整个用户逻辑的主时钟。我当时刚开始做的时候偷懒直接用板载时钟管用户逻辑结果寄存器读写时好时坏很难稳定复现。如果你只是做到“设备管理器识别”这一步其实不需要写太多逻辑把寄存器接口挂到一组简单的读回逻辑上就行比如读写寄存器0x00存一个固件版本号Windows端读回来能对得上就说明通路通了。这样设计的最小验证逻辑既不会引入复杂状态机又能验证配置空间之外的用户逻辑链路。4.2 约束文件只需要管三件事PCIe收发器引脚是IP核内部绑定的硬核引脚普通用户约束文件里不需要也不能手动分配。这一点和普通GPIO完全不同千万别试图在PDS的引脚约束里手动指定txp/rxp的位置那是无效的。用户约束文件里实际上只需要关注三件事。第一是参考时钟或者用户时钟的时序约束对于PCIe硬核参考时钟IP核生成文件里已有时钟约束但如果你在逻辑中额外使用了一个板载时钟要把它约束成主时钟避免时序报告完全没法看。第二是复位按键和LED这些普通IO的引脚分配以及它们的电平标准。第三是如果用了串口把UART引脚分配好。时钟约束其实很简单在PDS约束编辑器里选中时钟信号设置周期和占空比就行。不用做太复杂的多周期约束先把基础约束配好保证综合之后时序不红即可。PCIe内部的高速时序由IP核自己的约束保证跟你写的用户约束基本不相关。4.3 综合、布线到烧写的完整顺序约束配好之后回到PDS流程界面依次执行综合、布局布线、生成比特流。PDS的综合速度比Vivado慢一些PGL50H这种规模一个简单设计大约几分钟到十几分钟别着急。如果综合阶段报错优先看是不是IP核版本和工具版本不匹配其次是代码里的语法问题。布局布线完成之后打开时序报告重点看有没有建立时间违例。如果出现红字先去检查user_clk是否真的用了IP核输出的时钟很多时候时序违例都是因为跨时钟域没有做同步处理造成的。PCIe硬核本身因为IP核约束完善时序一般不会有问题问题都出在用户逻辑跨时钟域处理上。比特流生成之后用JTAG下载器把它烧到开发板。这里我强烈建议再准备一个烧写到配置Flash的操作因为JTAG在线配置只对当前会话有效拔掉调试器或者重新上电之后FPGA又回到空白状态。而Windows识别PCIe设备通常要求设备在开机时就存在所以最好把bitstream转成烧写文件刻到配置Flash里这样FPGA上电自动加载PCIe链路在上电后自动建立。这个烧写Flash的操作和普通FPGA配置完全一样PDS界面里有专门的工具入口。5. Windows设备管理器识别与驱动处理5.1 让PC在枚举阶段就能发现FPGA这一步是整个项目里最考验耐心的阶段也是很多人卡住的地方。先说结论如果想在设备管理器里看到设备最好让FPGA在上电时就已经配置好也就是烧到配置Flash里让Windows开机时直接跑PCIe枚举。如果FPGA里没有配置Windows开机时主板根本发现不了这个PCIe设备之后再通过JTAG在线配置Windows也不会自动重新扫描PCIe总线。实际过程我用两种方式都试过。第一种是把bitstream烧进配置Flash然后关机插入PCIe开发板开机进入Windows设备管理器刷新设备出现了。第二种是把开发板先插在PCIe插槽上开机进入Windows再通过JTAG下载配置然后从设备管理器菜单里选“扫描检测硬件改动”。这种方式有时有效有时无效取决于主板对PCIe热插拔事件的支持程度。所以我最后的建议是老老实实烧Flash别偷懒。还有一个容易忽略的地方开发板插到电脑上之后即使FPGA配置正确如果PCIe链路因为某些原因没有训练成功Windows同样看不到设备。判断链路是否建立有一个很实用的方法在系统POST阶段看板卡上有没有一个LED在闪烁如果有专门指示PCIe link up的LED最好没有的话可以用逻辑分析仪看用户时钟是否输出。我这次在顶层逻辑里加了一个LED当user_clk正常锁定时点亮这样一眼就能判断链路是否link up。5.2 设备管理器里看到设备后的驱动问题当你打开Windows设备管理器在“其他设备”分类下看到一个带黄色感叹号的“PCI Device”或者“未知设备”恭喜这说明PCIE枚举已经成功链路已经训练到L0状态Windows成功读到了FPGA的Vendor ID和Device ID。到这个阶段FPGA端的PCIe物理层、事务层以及配置空间都没有问题。接下来是驱动。如果你只是验证链路看到这个黄色感叹号已经算达标了。但如果想进一步做数据通信就需要给设备安装一个驱动。驱动这块有几个路线。第一个路线用紫光同创或者开发板厂商提供的参考驱动有些开发板会配套一个上位机测试工具安装工具时会自动安装驱动。第二个路线用WinDriver这类工具生成一个通用驱动WinDriver有评估版可以生成一个INF文件安装之后设备管理器里的设备名会变成WinDriver设备名做寄存器读写测试完全够用。第三个路线自己用WDF框架写一个驱动这个门槛较高项目需要的时候再深挖本次验证用不到。Windows在驱动签名方面比较严格如果系统提示驱动未签名无法安装可以临时进入高级启动选项选择“禁用驱动强制签名”或者给测试模式开起来。我这边验证的时候WinDriver生成的驱动是签过名的所以没遇到这个阻碍但如果你用自编译驱动就一定会有这一关。5.3 用配置空间验证链路真正可用看到设备管理器里出现设备之后我建议再用工具读取PCIe配置空间确认设备信息到底对不对。这个验证有两个作用一是确认Windows枚举出来的Vendor ID和Device ID跟IP核配置里写的是否一致二是看看BAR0有没有被分配地址空间。我在Windows下用过两款工具一个是RWEverything一个是PCITree。这两款工具都能枚举当前系统里的PCIe设备显示配置空间的前256字节。打开之后找到设备名对应的条目看Vendor ID、Device ID、Class Code这些字段。PCIe设备的Class Code一般是0x05或者其他值具体是内存控制器、数据传输控制器还是串行控制器取决于IP核配置。默认情况下如果配置空间里所有字节都是FF说明链路实际上没训练成功Windows只是拿到了一个空壳设备的占位信息不过这种情况很少见。确认BAR0有地址之后可以尝试通过工具直接读写BAR0映射的寄存器。比如我设计里的固化版本寄存器地址是0x00在工具里往这个地址写一个值再读回来数据一致说明不光是PCIe链路通了用户逻辑数据通路也是通的。这一步做完整条链路从FPGA内核到Windows用户态就算彻底验证完毕。6. 常见问题与排查技巧实录6.1 设备管理器里完全没有PCIe设备这是最令人头疼的问题因为Windows给你一个没有任何线索的“没有”。一旦遇到这个情况不要先怀疑代码按照下面的顺序去排查。第一步看开发板配置是否成功确认bitstream下载过程中没有报错再加一个LED翻转逻辑来确认FPGA确实在运行。第二步看PCIe链路是否link up检查参考时钟是否起振、复位释放是否正常有逻辑分析仪的话抓一下IP核输出的LTSSM状态寄存器的值。这里给一个具体的参考PCIe链路训练有一个有限状态机大致从Detect到Polling到Configuration到L0。如果抓到的状态一直停留在Detect说明物理层信号有问题如果停在Configuration说明链路宽度协商或者速率协商出了问题。PDS自带调试核类似Xilinx的ILA可以在线抓取这些信号。调试核的使用需要额外占一些LUT和BRAM约束加好后布线会稍微变慢但排查问题时非常值。另外一个很实际的建议是把PCIe速率降级到Gen1再试。很多时候主板BIOS对Gen2的兼容性一般或者板卡走线质量差了一点Gen2速率训练不上降级到Gen1之后一切正常。我这次调试中途就被这个问题坑过后来在IP核配置里把最大速率改成Gen1训练一次成功然后再改回Gen2前后花了很长时间。下次我会直接从Gen1开始稳定后再提升速率。6.2 链路训练不上时的排查顺序链路训练不上可以从信号质量、配置空间、平台三方面排查。信号质量方面用示波器看参考时钟是否干净看差分信号有没有幅度和振铃。如果没有示波器就默认参考时钟有问题检查晶振和端接电阻。配置空间方面确认Vendor ID和Device ID是不是非零且合理确认BAR配置是否正确。平台方面要特别说一下不是所有电脑主板都适合做PCIe调试。有的主板BIOS会关闭未识别的PCIe插槽的枚举或者某些插槽和M.2共享带宽导致设备插上后被禁用。我建议调试时用台式机上PCIe x16插槽即使你的设备是x1也可以插到x16插槽里物理上没问题。尽量不要用PCIe转接卡接出来的插槽除非确定它供电充足。PCIe设备需要3.3V和12V电源PCIe x16插槽能提供更高功率比一些转接方案稳定。还有一个容易被忽略的是PCIe插槽的卡扣和固定如果板卡插入不到位金手指接触不良也会导致链路训练失败而且这种问题是间歇性的最难查。我最后一次调试时发现链路时通时断排查了半天最后重新插拔了几次才解决。6.3 识别正常但驱动安装总失败设备管理器能看到设备但安装驱动时总是报错这个问题通常跟驱动签名、设备实例ID和厂商提供的INF文件有关。先确认驱动是针对你这个设备写的检查INF文件里的VID和DID是否与设备管理器里显示的一致如果不一致Windows会提示“找不到匹配的设备”。如果INF文件是对的但安装时提示驱动签名问题可以尝试禁用Windows的强制签名驱动功能。重启电脑在开机过程中选择高级启动选项进入“疑难解答-高级选项-启动设置”选择“禁用驱动程序强制签名”然后正常进入系统再安装驱动。这种方式每次重启后需要重新设置但用来临时验证驱动完全没问题。最后如果驱动还是安装不上可以先试试Windows自带的通用PCI驱动右键更新驱动选择“自动搜索驱动程序”让Windows自己找。对某些类别的PCIe设备Windows会装上一个通用驱动虽然功能有限但设备管理器里不会再有黄色感叹号。到了这一步至少后续用工具访问BAR空间就不会被驱动报错打断已经能满足寄存器读写验证了。回想起整个过程我觉得做PCIe通信和调普通FPGA逻辑最大的不同在于链路问题不是看波形就能直接定位的它需要你把硬件设计、IP核配置、驱动枚举这三个层面串起来总体思考。我的习惯是每改动一个变量就固定其他变量比如先固定Gen1速率固定x1宽度排除掉变量之后再一个一个放开。这个习惯让我少走很多弯路。如果你正准备开始这个项目希望这篇东西能帮你把前期的坑提前填上早点看到设备管理器里那个期待已久的设备出现。