
1. 项目背景为什么在PG2L100H上折腾DDR3这段时间一直在和紫光同创的PG2L100H打交道具体工作是围绕这块国产FPGA做一套数据采集与缓存方案核心难点之一就是把DDR3这套存储子系统跑稳。以前长期在用Xilinx的MIG系列转到紫光PDS开发环境后很多操作习惯要重新适应。先交代一下平台情况PG2L100H属于紫光Logos-2系列逻辑单元规模在100K LUT级内部带硬核DDR3控制器和PHY最高支持到DDR3-1600数据位宽可选8/16/32bit。对中低端工业场景来说这个规格很实用既能跑视频缓存、数据缓冲也能挂以太网和处理器系统。选这个平台有几个实际考量。第一是项目选型层面的国产化需求这是硬指标第二是成本相比同档次的进口FPGA紫光方案在批量项目里有明显价格优势第三是PDS工具链近两年迭代很快IP种类也在补全DDR3这个核心IP已经相对成熟。当然任何“成熟”都是相对的真正上手调试时踩坑一个都不会少。网上关于PG2L100H DDR3的工程资料很少好多问题只能自己反复测试摸索我觉得这块经验本身是有价值的所以整理成这篇博文。DDR3 IP核内部结构和MIG本质上是一个思路整体可以拆成三层最底下是PHY硬核层负责物理信号、DQS/DQ的收发和训练校准中间是控制器层负责命令调度、刷新管理、时序参数计算把DDR3繁琐的时序折叠成相对简单的读写事务最上面是用户接口层提供AXI4或者native接口。调试过程中我大部分时间都花在用户接口和控制器之间的状态配合上。如果对整个三层结构没有清楚认知遇到问题就会一头雾水不知道是PHY训练失败、控制器拒绝了命令还是自己写的状态机没满足握手条件。这里我想重点说一下接口选择。很多人图省事直接用native接口——确实信号简单类似于读使能、写使能加地址数据处理起来连协议都不用学。但我最终选了AXI4。为什么这么选因为项目后续要做多主设备仲裁比如处理器总线、图像采集模块、定时器DMA三个master要共享这一片DDR3native接口的仲裁逻辑得自己从零写很容易乱。AXI4的通道化设计天然适合多master互连后续挂AXI Interconnect或者自研仲裁器都很方便。项目周期越长、复用性要求越高AXI的优势越明显。1.1 初始方案选型开始规划时我在IP配置界面上首先确认了这几项第一DDR3颗粒型号我这里用的是镁光MT41K128M1616bit位宽两片并联接成32bit第二工作频率PLL输出800MHz的DDR时钟数据速率1600Mbps第三用户接口选择AXI4数据宽度与物理位宽保持一致设32bit第四突发长度AXI侧默认给到16对应DDR侧多个burst这个后续要根据实际需求权衡。一个容易忽略的地方是仲裁机制的选择。IP核里一般会提供读写仲裁的策略选项比如“读写轮流”还是“读优先”。我调试第一版时没细看默认了读优先结果连续写大量数据时被频繁打断写带宽只有理论值的三成。后来改成按比例仲裁写的吞吐量才拉起来。这类参数很容易被忽略但实际跑数据时差别非常大后面章节我会细讲。1.2 为什么状态机设计是关键DDR3存储器本身是一台“必须按规则做事”的机器。它的行激活、列读写、预充电、刷新这些动作都有严格的时间间隔约束。控制器IP会替你做大部分时序管理工作但你在用户层仍然需要设计状态机来控制整个读写流程和应答逻辑。如果状态机考虑不周比如刷新窗口没让路、读写切换太频繁、握手信号漏等一拍轻则性能骤降重则数据错乱甚至挂死。这是整个项目中最需要花精力啃的部分我会在第三章展开讲。2. AXI接口配置与带宽计算2.1 AXI通道与关键信号解读AXI4协议主接口有5个通道写地址AW、写数据W、写响应B、读地址AR、读数据R。很多人刚接触AXI时容易晕其实可以这么理解写事务是一次“下单加送货”AW通道下单W通道送货B通道告诉你“货收到且没问题”读事务是“下单等收货”AR通道下单R通道回货。两个方向完全解耦这正是AXI能天然处理高并发的基础。调试时重点盯几个信号。第一个是READY和VALID的配对。源端拉VALID表示“我手里有数据”目的端拉READY表示“我准备好接收”两个都为高时一个beat传输成立。但VALID拉高后不能随便撤掉必须等到握手成功READY则可以提前拉高等待。很多新手状态机在这一块会写出“VALID等READY才拉”的逻辑结果造成死锁。第二个是LAST信号它表示一个burst的最后一个beat在R和W通道里都要正确拉否则控制器不知道一个事务何时结束。第三个是地址对齐AXI要求突发首地址要与突发长度对齐比如32bit位宽、Burst16那就要求地址低6位为0否则IP核会报地址对齐错误。我在调试中确实被地址对齐坑过后面排查表里会提到。突发长度选择也需要权衡。AXI侧Burst设得越大单次事务传输的数据越多效率越高因为地址建立开销被摊薄了但代价是缓存资源和多设备并发度降低。我这里DDR3侧数据位宽32bit、速率1600MbpsDDR侧理论带宽是6.4GB/s32bit乘1600Mbps除以8AXI用户接口时钟大概跑200MHz按32bit位宽端口速率只有800MB/s所以瓶颈在用户侧接口时钟burst长度设成16就够了再加大只会白白增加内部FIFO使用量。2.2 带宽计算公式与实测性能DDR3子系统的实际可用带宽从来不是颗粒物理带宽本身而是要乘上效率系数。计算公式很简单实际带宽等于用户数据位宽乘用户接口时钟频率再乘效率系数。以我这边为例用户接口时钟200MHz、位宽32bit理论端口带宽等于200M乘32除以8等于800MB/sDDR侧理论值6.4GB/s但加上刷新、读写切换、行激活和预充电这些损耗IP核能跑到的效率通常也就45%到65%。也就是说整个系统最终有效带宽在360MB/s到520MB/s之间。如果还要进行读改写、非连续写效率会进一步下降。实测下来我在纯连续写模式下测到约470MB/s连续读约430MB/s混合读写按1比1交替约300MB/s。这个数字和理论预测基本吻合也说明IP核工作状态是健康的。如果你的实测值远低于这个范围就要往回查是不是仲裁策略不对是不是burst长度太小是不是刷新太频繁甚至是不是PHY训练不理想导致控制器在等待信号稳定这些都要一层层排查。调试AXI接口时我建议一定要在早期就把逻辑分析仪或调试桥挂上去。紫光PDS环境里有类似在线逻辑分析仪的功能可以直接观察AXI通道的握手波形。用起来不复杂但要注意探针数量别太多不然编译时间和布局布线都会变差。我的做法是先在关键通道上加少量探针比如AW和W的VALID、READY、LAST跑一轮简单读写测试确认握手正常再逐步增加探针定位时序问题。3. 状态机设计从初始化到刷新管理3.1 DDR3命令与时序参数速查DDR3工作时需要执行的行命令包括激活ACT、写WR、读RD、预充电PRE、自动刷新REF、模式寄存器设置MRS、ZQ校准ZQCL等。每个命令之间都有最小时间间隔约束这就是DDR3标准里那些t参数。常见的几个必查项tRCDACT到列读写命令之间的延迟通俗理解是“选通行地址后要等列地址稳定再操作”。DDR3-1600大约13.125ns。tRPPRE到下一个ACT的最小间隔即预充电的恢复时间约13.125ns。tRC连续两次ACT同一个bank的最小间隔约48.125ns。tRFC两次刷新命令之间的最小间隔约160ns到350ns与容量有关。tFAW连续四个ACT命令窗口的时间约束约30ns到40ns。tWTR写命令结束到下一次读命令开始的间隔写后读必须额外等待约7.5ns。看到这些参数别慌实际项目中写IP核和控制器会按JEDEC标准把这些参数固化在寄存器里你只需要告诉它颗粒型号和速度等级。关键是你自己写状态机时在读写切换、刷新请求之间必须给控制器留出执行时间不要以为命令一发出就结束了。尤其在连续读写切换时如果控制器内部没有足够缓冲你需要在状态机里插入等待周期等待tWTR或tRTW过去否则命令被控制器拒绝或者被缓冲丢弃的话调试起来会非常难查。3.2 读写状态机架构我把用户侧的状态机分成三个并行子状态机初始化状态机、刷新状态机、应用读写状态机。三者之间的关系是初始化优先刷新次之应用读写优先级最低。这样划分的好处是职责单一每个状态机逻辑都不复杂并且可以独立测试。有人喜欢用一个超级状态机把所有逻辑串起来写到后面状态暴多、条件交叉一旦有问题很难定位。三状态机并行的做法让每个状态机只关心“自己该干什么”互相之间只通过握手信号协调。初始化状态机的流程是上电复位、拉高CKE、发送NOP等待至少500us、片选使能、发送MRS设置模式寄存器、发送ZQ校准命令、等待校准完成、进入正常操作状态。在PG2L100H的IP核里初始化序列实际上由PHY硬核自动完成你只需要在用户层等待初始化完成信号。但我仍然会写一条初始化状态机目的就是为了做一个“门卫”初始化没完成之前锁住所有读写请求不向控制器发送任何命令。这样即使应用侧逻辑误触发也不会在DDR3还没ready时乱发命令把整个存储系统搞挂。读写状态机我用四态实现IDLE、ACTIVE、BURST、WAIT。IDLE状态下等待仲裁器授权授权后根据读写方向进入ACTIVE此时拉高片选、使能ACT命令BURST状态下发送列读写命令并跟随数据WAIT状态下处理行切换和命令间隔等tRCD、tWTR这些时间过去后再回IDLE。这套结构简单可靠所有时序等待都落到WAIT态代码可读性高后续加超时保护、错误恢复也方便。3.3 刷新策略的关键性刷新是DDR3可靠运行的命脉。DDR3要求每行在64ms内至少刷新一次常见颗粒需要8K次刷新也就是大约每7.8us要发一次刷新命令。如果控制器在刷新期间收到读写请求最简单的策略是让读写请求等一拍等到刷新完成后再执行。单看一次刷新开销可以忽略但7.8us一个周期每次刷新大约占用tRFC时间比如160ns理论上刷新平均开销只有2%左右完全能接受。但有一个工程细节需要特别注意刷新请求的优先级不能太低也不能一次性积压太多。我调试时遇到过一个问题写入数据压力大时状态机一直在响应写请求刷新请求被长期搁置结果刷新间隔超过JEDEC上限存储单元电荷泄漏数据开始随机出错。这种错毫无规律特别难排查。后来我在刷新状态机里加了一个“刷新紧急窗口”逻辑当距上次刷新超过7us还没执行时强制挂起所有读写请求优先执行刷新。加了这层逻辑之后数据就稳定了。刷新这种事情看似不起眼实际是关键中的关键。3.4 状态机实现要点与伪代码这里给一个简化版读写状态机的Verilog骨架供参考。实际项目中至少还要加读写FIFO、地址映射和握手超时保护。localparam IDLE 2d0; localparam ACTIVE 2d1; localparam BURST 2d2; localparam WAIT 2d3; always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else begin case (state) IDLE: if (refresh_urgent) state WAIT; // 刷新优先抢占 else if (wr_req || rd_req) state ACTIVE; ACTIVE: state BURST; BURST: if (burst_done) state WAIT; WAIT: if (time_elapsed) state IDLE; endcase end end这段代码的核心思想是每个状态都有明确的退出条件读写操作强制经过WAIT态来做时序隔离。你可能会问为什么不在IDLE直接等时序因为读写频繁时如果总是在IDLE等刷新请求会被读写请求持续抢占所以我在进入ACTIVE后也会随时监测刷新请求如果刷新紧急就立刻切到WAIT让出总线而不是非要等当前burst完成。这个设计细节极大地提高了系统的可靠性。4. PCB布局布线规则与硬件实测4.1 DDR3布线规则与实例DDR3的高速部分不能只看IP核硬件上布线不过关你再怎么调状态机都是白搭。我的第一版PCB就栽在布线不规范上DDR3只能跑到约900Mbps再高就报训练失败重新改了板子才解决问题。这里把我的经验整理成硬规则大家做4层或6层板时可以直接参考。先说阻抗要求。DDR3单端信号DQ、DQS、地址、控制通常要求50欧姆DQS差分对要求80到100欧姆差分阻抗。叠层不同线宽线距不一样需要根据板厂的叠层参数计算。两层板基本不用考虑跑DDR3至少要4层并且DDR3信号优先走靠近参考平面的两层保证有完整的参考地平面。然后是等长要求。数据组内等长是基本功DQ与对应的DQS要尽量做到等长比如DQ0到DQ7这组要和DQS0控制在25mil以内。地址、控制线是一组要求与系统时钟等长容差通常在50mil以内。很多工程师只注意同组内等长忽略了地址组与时钟的关系结果CKE、CS这些信号和时钟的偏斜过大初始化时序就不对。我整理一个实际的等长目标供参考注意这是经验值不要当成绝对标准信号组等长要求说明DQ与DQS25mil内数据采样窗口最敏感地址/控制/命令与CLK等长50mil内初始化时序依赖DQS与CLK300mil内相对关系影响数据对齐所有DDR3信号远离其他高速信号建议间距至少3W还有一个容易踩的坑DDR3的DQS串接电阻和ODT配置。ODT片上端接配置得不好信号反射会非常严重。我的建议是在IP核里开启ODT阻值选RZQ/4约60欧和RZQ/6约40欧写入和读取时使用不同的ODT值设置。如果PCB上同时有端接电阻要注意和ODT配合否则阻抗不连续信号质量反而更差。我第一次画板时在数据线上加了串阻后来又开了ODT结果阻抗不匹配训练一直过不了去掉串阻后问题才解决。4.2 硬件实测方法调DDR3时我习惯按这样的顺序做硬件验证先量电源VDD和VDDQ电压必须稳定再看时钟波形用示波器看差分时钟的幅度和抖动确认没问题后再开始训练。第一版板子问题就出在VDDQ纹波太大150mV左右而DDR3颗粒的电源纹波一般要求小于50mV结果训练时PHY老是校准失败。后来在颗粒电源附近加了去耦电容阵列纹波压到30mV以内训练一次通过。所以遇到IP核训练失败除了查逻辑先回头看看硬件信号质量。PHY训练这块紫光的工具也提供了类似MIG的Training流程可以通过调试软件查看训练结果包括写均衡、Read DQS gating等步骤。训练全部通过后再跑读写测试。如果训练卡在中间某一步问题多半在PHY层要么是时钟问题要么是DQ和DQS等长偏差太大要么是端接不合理。千万不要怀疑是状态机的锅因为在训练阶段用户逻辑的命令还没发出去。这个定位思路能帮你省很多时间。5. 调试踩坑实录与排查路径5.1 初始化失败先查硬件再查逻辑我遇到的第一类问题就是初始化失败表现是IP核的状态寄存器里初始化完成标志一直不拉高或者一拉高就掉。排查过程从简到难查复位逻辑。DDR3 IP核复位要求稳定时钟如果系统复位信号在时钟稳定前就释放会导致初始化状态机乱跳。我这边加了一个上电延时复位模块确保时钟锁定后再释放IP核复位。查电源和地重点看VDD和VDDQ纹波。前面说过纹波过大是最常见的原因。查时钟。如果PLL输出给DDR3的时钟有问题比如占空比失真严重也会导致初始化失败。用示波器量一下差分时钟对幅度和交叉点都要在JEDEC范围内。查命令通道。如果以上都没问题再怀疑PHY层命令时序这时候就是用逻辑分析仪抓控制器侧命令是否按MRS、ZQ顺序执行。我一次调试中就是发现ZQ校准命令少发了一个后来确认是我在IP核配置里勾选了一个跳过ZQ校准的错误选项去勾后解决。提醒一句初始化失败可能是多个原因叠加不要一上来就改状态机先排除硬件因素再挑逻辑的毛病。5.2 数据错误大概率是时序或刷新问题数据读写错误分为两类固定错误和随机错误。固定错误好查比如某个bit总是读出来不对那多半是DQ线序、字节通道映射或者颗粒引脚连接问题。拿着颗粒引脚图逐一核对PCB网表基本能解决。随机错误就头疼了最常见的两大类原因第一是建立保持时间不满足PHY训练虽然通过了但余量不足。这种情况可以通过调整DQS的相位偏移来微调在训练接口里手动微调每个字节通道的DQS延迟或者降低一档DDR3工作频率再跑如果降低后错误消失说明就是时序余量问题。第二是刷新处理不当。如果错误每隔一段固定时间就出现一次怀疑方向就是刷新。我前面提的刷新被读写请求长期搁置的例子就是这样定位出来的。用计数器统计两次刷新之间的间隔如果偶尔超过8us那就说明刷新逻辑有bug。5.3 AXI握手常见问题速查我在调试AXI接口时总结了一张速查表列几个高频问题故障现象可能原因排查方向写事务完成后无B响应AW或W的LAST信号未拉高检查突发描述是否一致读数据丢失ARADDR未按地址对齐检查地址位与位宽、Burst组合系统死锁VALID一直等待READY检查是否提前拉低VALID带宽异常低读写切换太频繁/仲裁策略调整仲裁策略加大burst长度数据偶发错刷新被搁置或时序余量不足加刷新紧急窗口检查训练余量再补一个特别建议初期调试时建议做一个AXI写读回环验证模块。简单说就是按固定地址顺序写一串递增数据然后再按相同顺序读回用校验逻辑逐笔比对。这比用自研逻辑去复杂读写要直观得多一旦比对失败能很快知道是哪个地址段、哪个数据。我整个调试阶段这个回环模块一直没关等到最后整个系统联调通过才移除。它就像是DDR3端到端验证的“体检仪”非常实用。5.4 PDS工具链使用心得最后说一下紫光PDS环境的使用心得。在PDS里配置DDR3 IP核和Xilinx MIG的流程相似但也有不少差别我刚开始切换时也花了些时间适应。几个实用建议第一在IP配置界面里仔细看好每个下拉选项尤其是DDR3频率和颗粒型号选择选错了会导致训练参数完全不对。第二约束文件的时序约束一定要写全特别是生成时钟和输入输出延迟PDS的检查和Vivado一样严格不写全很容易出现时序收敛不了。第三PDS的工程构建支持增量编译调试阶段建议开启能明显加快反复修改测试的迭代速度。PDS工具里还有一个让我印象深刻的点它对不同IP核的配置界面风格不完全统一。比如你用SGMII IP核和外部PHY芯片配合时需要在IP里明确配置成MAC模式否则PHY侧的接口协议对不上你用FFT IP核时数据时钟的设置又完全不是同一个套路。这种“每个IP脾气都不一样”的现象在紫光生态里比在Xilinx生态里更明显。所以我的经验是不要想当然每个IP拿到手先翻开配置手册核对一遍关键参数。另外在线逻辑分析仪可以设置触发条件去抓指定事件比如抓刷新请求、抓AXI握手异常用好了能大大加速定位问题的进程。6. 写在最后的几点工程体会这篇实战记录写到这里核心内容基本都覆盖了。最后聊点我个人最想传达的体会。调试DDR3这类高速存储子系统最忌讳的就是上来就改代码。先清楚三个层级的职责边界硬件问题看电源和信号质量PHY问题看训练结果逻辑问题看状态机和握手协议。用逐层排查的思路能把问题定位时间缩短一半以上。再一个体会是文档和注释很重要。DDR3调试涉及的知识点非常密集时序参数、寄存器配置、布线规则揉在一起很容易忘。我习惯在工程里维护一份“调试记录”文档每个问题、每个尝试方案、每个波形截图都记录下来。后来回看这些记录很多当时花了一天才解决的诡异问题原因其实写在原始记录里。这种工作方式看起来笨但在长期工程中是最省力的。如果你正准备在PG2L100H上开始DDR3方案希望这篇实战记录能帮你少踩几个坑。调试的乐趣就在于一点点啃下硬骨头的过程祝大家顺利点亮自己的DDR3。