尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA实战:多路电源时序控制模块设计与调试指南

FPGA实战:多路电源时序控制模块设计与调试指南 做嵌入式或者FPGA开发的工程师对上电时序这四个字应该都不陌生。但真正被它折腾过的人才知道这四个字背后的分量。前阵子我调一块板卡上电后大概有三分之一的概率系统起不来软件查了几轮没结果最后用示波器同时抓四路电源的波形才发现VCCINT1.0V内核电源还没建立3.3V的IO电源已经先跳到高电平FPGA的IO引脚在反向馈电整个初始化过程被一路电源彻底带偏。这种问题不是改一行代码能解决的必须在硬件和逻辑设计阶段就把多路电源的上电顺序控制住。这篇文章我就把自己在FPGA上实现的一个多路电源时序控制模块完完整整梳理一遍包括方案选型、状态机设计、Verilog实现、仿真验证还有上板调试时踩过的那几个大坑。内容面向正在做多电源域板卡的工程师也适合刚入门FPGA、想找一个真正有硬件价值的小项目练手的同学。1. 上电顺序失控的代价从一块板卡的偶发死机讲起1.1 多电压域板卡里的寄生通路问题为什么上电顺序这么重要核心在于CMOS芯片内部并不是一个理想的数字世界。无论是FPGA、DDR、还是各类SoC内部都存在大量的寄生二极管和寄生晶体管结构。以FPGA为例IO引脚到内部电源轨之间通常会通过ESD保护二极管连接到VCC和GND。当某一个电源域没有上电、处于0V状态时如果相邻的另一个电源域已经上电到3.3V电流就会顺着IO引脚、穿过ESD二极管灌入还没上电的电源域。这个反向馈电电流会造成两个后果。第一是逻辑初始化失败因为内部寄存器在上电瞬间被外部灌入的电压强行置成了不确定状态复位信号来了也拉不回来。第二是更严重的闩锁效应latch-upCMOS工艺里的寄生PNP和NPN晶体管在某些条件下会形成一条低阻通路电流急剧增大芯片表面温度飙升直接物理损坏。我见过不止一块FPGA在错误的电源时序下烧出一个小洞就是这个原因。这种问题在纯数字逻辑层面很难复现因为它是上电瞬间的物理现象跟温度、电压斜率、负载电容都有关系。通常表现为十次上电有两三次起不来或者跑了一晚上温循偶发死机。排查起来非常耗时间最好的办法就是提前在设计和验证阶段把时序控制做对别给这类故障留机会。1.2 哪些场合必须做、哪些场合可以不做不是说所有板卡都需要复杂的多路上电时序管理。如果板子上只有一颗LDO输入输出都是单电压域那直接用RC延时或者干脆同时上电都没什么问题。需要认真评估时序的场景具备几个特征多个电压域、电源轨之间有信号交互、涉及FPGA/DDR/SERDES这类对电源顺序敏感的芯片。FPGA厂商一般会在数据手册或者应用笔记里给出明确的上电顺序要求。比如Xilinx的主流器件通常要求VCCINT先于或同时于VCCBRAM、VCCAUX上电VCCIO可以最后上电Altera/Intel的器件也有类似的内核优先、IO靠后的建议。国产FPGA像高云、易灵思同样在上电顺序上有自己的推荐。最稳妥的做法就是把内核电源放在最前面辅助电源和IO电源放后面中间加一个可控延时。另外还要考虑板上的DDR、PHY、ADC等器件它们各自的数据手册里也会有上电顺序的要求通常会规定AVDD必须早于DVDD或者VDD必须先于VDDQ。这些要求散落在不同的文档里整理成一张电源时序表是很有必要的。2. 方案之争RC延时、专用时序芯片与FPGA的边界2.1 三种主流方案的横向对比电源时序控制的实现方案我实际接触过的主要有三类RC延时电路、专用电源时序控制器、FPGA/CPLD逻辑控制。它们在成本、精度、灵活性上的差异非常明显。方案成本精度可配置性状态监控适用场景RC延时很低低受电阻电容容差影响大差改参数要换器件无单路或对时序要求极低专用时序控制器高高支持数字时序配置和电压监控中需通过PMBus/I2C配置完善服务器、通信设备等多轨高可靠系统FPGA/CPLD逻辑中低由晶振决定精度高强Verilog参数或寄存器配置灵活可按需定制已经使用FPGA的板卡、中小批量产品我在综合评估时最终选择了FPGA方案有几层考虑。项目里本来就已经有一颗FPGA负责业务逻辑那我再写一个电源时序控制模块的边际成本就只是几百行Verilog。相比额外加一颗专用时序控制芯片FPGA方案省下了那颗芯片的位置和成本也不需要学习PMBus那一套配置工具。相比RC延时FPGA能做到毫秒级精确延时、支持PGOOD状态反馈、还能做超时和掉电监控这是纯硬件RC完全做不到的。2.2 FPGA方案的边界条件FPGA方案并不是万能的它有几个绕不开的问题要在方案早期就意识到。第一FPGA本身也是需要供电的芯片它自己在上电配置完成之前IO处于高阻或未定义状态。如果直接用FPGA的引脚去驱动DC-DC的EN引脚必须在外部加下拉电阻确保FPGA还没配置时EN处于确定的关断电平。这个问题我在后文会单独展开是所有FPGA做时序控制时必须处理的细节。第二FPGA只能控制DC-DC的使能引脚不能改变电源模块本身的软启动时间。现在很多DC-DC芯片都有软启动功能EN拉高之后输出电压不是瞬间建立而是按一个斜率爬升。FPGA输出的使能信号顺序虽然正确但实际电压到达稳定的时刻还取决于DC-DC的软启动参数和输出电容大小。这些信息要从电源模块的数据手册里查最好在实测中用示波器确认。第三如果板卡有几十路电源需要监控FPGA的引脚资源和逻辑资源可能不够还得外扩比较器、ADC或I/O扩展芯片复杂度会上升。这时候专用电源时序控制器可能反而更合适。3. 四路电源时序控制模块的设计草图与接口电路3.1 一个典型四路电源树的时序需求我给这个模块设定了一个典型的多电压域板卡场景FPGA主控 DDR 高速收发器 各类外设一共四路电源分别是VCC_INT1.0V内核、VCC_AUX1.8V辅助、VCC_IO3.3V IO、VCC_SER1.2V高速收发器。这四路电源的典型上电顺序是内核优先、辅助其次、SERDES第三、IO最后相邻两路之间的延时设5ms掉电顺序与上电相反间隔3ms。这个顺序的设计逻辑是VCC_INT是FPGA内部所有逻辑的地基必须先建立VCC_AUX服务于内部辅助电路和时钟管理紧随其后VCC_SER给高速收发器供电必须在IO之前避免高速引脚在收发器无供电时被外部信号驱动VCC_IO放最后是因为IO通常在外部接口电路中承担开关角色IO电源没起来之前外部信号无法进入FPGA内部的敏感域。掉电顺序反过来也容易理解先断IO防止外部信号继续灌入再断SERDES和AUX最后断内核。普通的掉电不需要非常复杂只要保证内核最后断电这条底线绝大多数器件的反向馈电风险就能避免。3.2 EN/PGOOD接口电路与电平匹配DC-DC芯片的EN引脚通常是高电平使能或低电平使能具体要看数据手册。FPGA的IO输出标准如果是LVCMOS33或LVCMOS18直接连接EN引脚一般问题不大但要注意电平域是否匹配。如果FPGA所在的Bank是1.8V而DC-DC的EN引脚要求3.3V高电平就需要加电平转换或者用三极管搭一级驱动。PGOOD引脚的情况要复杂一些。很多电源芯片的PGOOD是开漏输出需要外部上拉电阻到对应的监控电压上电完成后输出高电平。判断电平方向时要特别小心有些芯片的PGOOD在电源正常时输出高有些则是输出低还有的芯片没有PGOOD引脚只能通过电阻分压加比较器搭一个简单的电源状态监测电路。我这边用的四路DC-DC都是开漏PGOOD所以在每个PGOOD引脚上都加了10kΩ上拉电阻到各自的输出电压。EN引脚的下拉电阻我也都加了阻值选47kΩ兼顾抗干扰和低静态功耗。这个电阻的作用很关键在FPGA未配置、IO高阻的瞬间它确保EN处于确定的低电平防止电源模块误启动。3.3 模块内部架构与对外接口整个模块的内部架构分几层输入信号同步层、状态机层、定时器层、输出控制层、故障处理层。输入信号同步层对所有外部PGOOD信号做打拍同步消除跨时钟域和上电抖动带来的亚稳态状态机层是核心管理上电、运行、掉电、故障四种工作阶段定时器层提供统一的延时计数基准输出控制层把状态机产生的使能控制信号同步输出到EN引脚故障处理层负责超时判定、异常掉电检测和故障锁存。对外接口上我预留了一个power_on输入启动命令和power_down输入掉电命令可以通过按键触发也可以由系统里的其他逻辑控制。同时在模块顶部暴露了一组4位状态输出方便在调试时直接通过FPGA内部的逻辑分析仪ILA抓取状态机跳转。这个架构的优势是把控制逻辑和时序参数解耦。控制逻辑关注状态怎么跳转参数只影响延时和超时的计数值后续要改时序不需要动状态机本身只需要改参数或寄存器。4. Verilog核心实现状态机怎么走、延时怎么算、PGOOD怎么判4.1 状态机的状态定义与转移条件状态机是整个模块的灵魂。我把它设计成13个状态覆盖上电、运行、掉电、故障四类场景。上电阶段依次经过S_EN_INT、S_WAIT_INT、S_EN_AUX、S_WAIT_AUX、S_EN_IO、S_WAIT_IO、S_EN_SERDES、S_WAIT_SERDES最终进入S_RUN。每个S_EN_xxx状态负责拉高对应EN信号并等待延时每个S_WAIT_xxx状态负责等待该路PGOOD稳定超时则跳转S_FAULT。这里有一个设计细节值得展开。EN信号拉高之后我不会立即检查PGOOD而是先等一个延时EN延时。原因是很多DC-DC从EN有效到输出电压稳定需要一定时间如果立刻检查PGOOD大概率读到低电平然后触发超时误报。所以每个S_EN状态其实是延时等待电压建立而S_WAIT状态才是检查PGOOD并去抖。这两个阶段分开时序逻辑才清晰。掉电阶段按S_PD_IO、S_PD_AUX、S_PD_INT顺序执行每步延时3ms后拉低对应的EN。这里有一个容易忽视的点掉电指令到来时系统可能正在运行中也可能刚上电到一半所以掉电状态机的进入条件要覆盖从任意状态响应power_down的情况。更简单的做法是一旦power_down有效无论当前在哪个工作状态都先关掉所有未完成的使能步骤再按顺序从IO开始往下关。4.2 延时参数的计算方法与代码实现延时计算其实就是一个简单的公式换算。假设系统时钟是50MHz周期是20ns要实现5ms的延时需要的时钟周期数是5ms / 20ns 250000个周期。写成通用形式就是delay_cycles delay_ms × clk_freq_mhz × 1000我用Verilog的parameter直接定义延时和超时时间让工具在综合时就算出计数值而不是在代码里写死一堆魔术数字。这样后续调整时序只需要改一个地方。module power_seq_ctl #( parameter CLK_FREQ_MHZ 50, parameter EN_DELAY_MS 5, // 相邻两路EN之间的间隔 parameter PG_SETTLE_MS 2, // PGOOD去抖确认时间 parameter PG_TIMEOUT_MS 50 // 等待PGOOD的超时时间 )( input wire clk, input wire rst_n, input wire power_on, input wire power_down, output reg en_int, output reg en_aux, output reg en_io, output reg en_serdes, input wire pg_int, input wire pg_aux, input wire pg_io, input wire pg_serdes, output reg fault_n, output reg [3:0] dbg_state ); // 延时计数值delay_cycles delay_ms * clk_freq_mhz * 1000 localparam DELAY_EN_CNT EN_DELAY_MS * CLK_FREQ_MHZ * 1000; localparam DELAY_SETTLE_CNT PG_SETTLE_MS * CLK_FREQ_MHZ * 1000; localparam DELAY_TIMEOUT_CNT PG_TIMEOUT_MS * CLK_FREQ_MHZ * 1000; localparam S_IDLE 4d0; localparam S_EN_INT 4d1; localparam S_WAIT_INT 4d2; localparam S_EN_AUX 4d3; localparam S_WAIT_AUX 4d4; localparam S_EN_IO 4d5; localparam S_WAIT_IO 4d6; localparam S_EN_SERDES 4d7; localparam S_WAIT_SERDES 4d8; localparam S_RUN 4d9; localparam S_FAULT 4d10; localparam S_PD_IO 4d11; localparam S_PD_AUX 4d12; localparam S_PD_INT 4d13;在状态机里每个EN状态会启动一个定时器定时器溢出后才允许进入下一个状态。例如S_EN_INT状态下en_int置1同时计时DELAY_EN_CNT个时钟周期计时完成后如果此时没有超时故障就跳转到S_WAIT_INT。这种方法让每个阶段的间隔完全由晶振时钟决定精度是RC方案完全不能比的。reg [23:0] timer_cnt; // timer_enable为高时计时timer_done在计数到目标值时拉高一个周期 always (posedge clk or negedge rst_n) begin if (!rst_n) timer_cnt 24d0; else if (!timer_enable) timer_cnt 24d0; else if (timer_done) timer_cnt 24d0; else timer_cnt timer_cnt 1b1; end assign timer_done (timer_cnt timer_target - 1b1);4.3 PGOOD去抖与运行监控PGOOD信号在真实板子上比仿真里脏得多。DC-DC的PGOOD是模拟比较器输出在上电过程中可能因为输出纹波、负载突变产生毛刺如果直接拿它驱动状态机很可能把电压还没稳误判成电压已OK然后提前进入下一步。所以我在模块里对每路PGOOD做了两级同步打拍加连续计数去抖。reg pg_int_sync0, pg_int_sync1; reg [9:0] pg_int_cnt; wire pg_int_stable; always (posedge clk or negedge rst_n) begin if (!rst_n) begin pg_int_sync0 1b0; pg_int_sync1 1b0; pg_int_cnt 10d0; end else begin pg_int_sync0 pg_int; pg_int_sync1 pg_int_sync0; if (pg_int_sync1) pg_int_cnt (pg_int_cnt 10d1023) ? pg_int_cnt : pg_int_cnt 1b1; else pg_int_cnt 10d0; end end assign pg_int_stable (pg_int_cnt 10d1023);连续1023个周期约20us都保持高电平才认为这路电源真正稳定。这个去抖时间虽然比主延时小两个数量级但能挡住绝大多数毛刺。要注意的是去抖时间不能设得太长否则会让整个上电流程变得迟缓尤其在上电瞬间电源建立斜率较慢的场合PGOOD本身就需要一定时间才拉高去抖时间过长会叠加在超时判定的基数上产生时间偏差。运行监控方面进入S_RUN之后我不会就此不管。每一路PGOOD信号在运行阶段都会被持续监控一旦某一路在运行状态下跌落50ms以上状态机立刻跳到S_FAULT把所有EN信号拉低。这样可以防止运行中电源异常导致芯片长期工作在欠压状态。故障发生后fault_n输出拉低外部电路可以点亮故障灯或者触发系统看门狗。5. 仿真闭环用Testbench验证上电、掉电与超时故障5.1 用Testbench模拟各路PGOOD行为仿真在这个项目里非常重要因为上电时序涉及的逻辑链路很长一旦状态机某一步跳错上板调试会非常费劲。我在Testbench里不需要模拟完整的DC-DC行为只需要模拟PGOOD对EN信号的响应关系EN拉高之后延时约2msPGOOD拉高。再留一个开关变量可以强制某一路PGOOD永远不拉高用来验证超时故障路径。module tb_power_seq_ctl; reg clk 0; reg rst_n 0; reg power_on 0; reg power_down 0; reg pg_int 0; reg pg_aux 0; reg pg_io 0; reg pg_serdes 0; wire en_int, en_aux, en_io, en_serdes; wire fault_n; wire [3:0] dbg_state; always #10 clk ~clk; // 50MHz initial begin #100 rst_n 1; #200 power_on 1; ... end // EN拉高后2msPGOOD跟随拉高 always (posedge en_int) #200000 pg_int 1; always (posedge en_aux) #200000 pg_aux 1; ... endmodule这个Testbench的写法非常直接每个always块监听一个EN信号的上升沿延时后拉高对应的PGOOD。在50MHz仿真时钟下2ms就是200000个时钟周期仿真器跑起来大概也就是几秒钟的事情。我把上电间隔设成5ms、PGOOD响应设成2ms因此在仿真波形里可以很清楚地看到状态机每5ms前进一步、每等2ms确认一拍。5.2 验证三个关键时序点仿真阶段我最关注三个点。第一是上电顺序本身en_int、en_aux、en_io、en_serdes四个信号必须严格按设定顺序出现间隔不能小于5ms。第二是PGOOD确认逻辑如果PGOOD拉高之后立刻出现一个短毛刺掉到0状态机不应该被打断。第三是超时路径任何一路PGOOD在50ms内没有拉高状态机必须进入S_FAULTfault_n拉低所有EN信号清零。实际仿真中发现的一个小问题也值得说说。最初版本的逻辑在进入S_RUN之后如果power_on信号还保持高电平没有任何问题但如果power_on信号在运行中被外部逻辑拉低又拉高状态机会错误地从S_RUN重新回到S_EN_INT导致系统在上电完成后被反复重启。解决方法是把power_on处理成脉冲触发只在上升沿有效运行阶段直接忽略它的低电平状态除非power_down信号有效。这算是一个典型的边界条件如果不做仿真很难提前发现。6. 上板调试实录三个让我怀疑人生的电源时序坑6.1 坑一FPGA还没配置IO高阻导致EN乱跳仿真全部通过之后我把模块综合下载到板子上第一次上电就遇到了诡异现象。用示波器探头抓EN_INT引脚发现在FPGA配置完成之前这个引脚上出现了几个宽度不等的窄脉冲有时候甚至能看到接近电源电压的尖峰。这几个窄脉冲直接导致DC-DC间歇性启动输出电压没有稳定建立PGOOD信号也断断续续。原因是FPGA在上电配置阶段IO引脚处于高阻状态而DC-DC的EN引脚内部有微弱的上拉或者对噪声比较敏感高阻状态的引脚很容易被周围走线耦合过来的噪声拉高。我在设计时虽然加了47kΩ下拉电阻但那个电阻的封装位置离DC-DC的EN引脚较远中间夹了好几根开关节点走线噪声耦合进来之后把下拉电阻的强制能力抵消了一部分。解决的办法有两步。第一步是把下拉电阻从47kΩ改成10kΩ增强EN引脚的低电平保持能力。第二步是在PCB布局上把下拉电阻尽量靠近DC-DC的EN引脚放置减少耦合路径。改完这两处之后再用示波器抓EN引脚上电全程干净再也没有毛刺。6.2 坑二复位信号亚稳态导致状态机偶发错乱第二个坑比第一个更隐蔽。系统上电后状态机偶尔会停在一个奇怪的中间状态既没有进入S_RUN也没有报故障看起来像卡住了。我最初怀疑是代码问题反复检查状态转移条件都没发现问题。后来用逻辑分析仪抓了多次上电波形发现故障每次都出现在外部复位信号释放的时刻附近。问题出在复位信号上。外部复位芯片输出的复位信号和FPGA内部时钟是异步的复位释放的瞬间如果正好接近时钟上升沿状态机寄存器的复位端会进入亚稳态导致部分寄存器被复位、另一部分没有整个状态就乱了。这就是热词里经常提到的FPGA复位亚稳态问题处理不当轻则偶发错乱重则系统死机。解决方法是把外部异步复位信号在FPGA内部做同步释放处理代码逻辑不复杂但非常实用。简单说就是先在时钟域内打两拍再把同步后的复位信号作为内部复位的低有效信号。这样复位释放时所有寄存器在一个确定的时钟沿同步解除复位避免亚稳态。reg rst_n_meta, rst_n_sync; always (posedge clk or negedge rst_n) begin if (!rst_n) begin rst_n_meta 1b0; rst_n_sync 1b0; end else begin rst_n_meta 1b1; rst_n_sync rst_n_meta; end end改完之后连续做了50次上电循环测试状态机每次都严格按照预期跳转没有再出现卡状态的问题。6.3 坑三PGOOD毛刺让时序判定提前第三个坑和PGOOD的去抖有关。我在仿真里给去抖逻辑做了验证但上板实测时还是发现某一路PGOOD在EN拉高之后约500us处会出现一个大约几十微秒宽的高电平脉冲然后又掉回低电平直到2ms后才真正稳定。虽然最终状态机还是正常运行但这个毛刺有概率让我的去抖计数器提前开始计数导致后面的判定时序偏差。这个毛刺的来源很可能是DC-DC内部的软启动环路在上电初期产生了一小段输出电压超过阈值又回落的过程。PGOOD比较器本身没有迟滞或者迟滞很小就会输出这种假高电平。解决方式是引入EN拉高后的屏蔽时间。我在S_EN_xxx状态里加了一个blanking_count在EN拉高后的1ms内完全不采样PGOOD信号。这个屏蔽时间能挡住绝大多数电源建立初期的假脉冲等屏蔽结束后再去采样采到的高电平才是真正稳定的高电平。这个思路也可以理解为先让子弹飞一会再看靶子上的环数。6.4 与PCB布局布线的联动细节做这个项目最大的体会之一是FPGA代码写得再漂亮如果PCB布局不配合上板调试照样让你崩溃。EN引脚和PGOOD引脚虽然不是什么高速信号但其所在的电源电路往往充满了开关噪声和电流突变走线布局不好就非常容易引入干扰。EN信号线尽量避免与DC-DC电感、开关节点走线平行哪怕多绕一点也要拉开距离。PGOOD信号线同样要保持干净如果不得不穿过电源区域可以在PGOOD线上加一个小电容比如1nF做硬件滤波。下拉电阻和上拉电阻的位置都必须靠近目标引脚不要放在远端再拉长线过来否则电阻的作用大打折扣。还有一个经常被忽略的点电源模块的sense线远程检测线要接到负载端而不是直接接在电源模块的输出引脚上。如果sense线接错电源模块会认为输出电压不足一直往上调输出电压漂高时序控制的稳定判断标准也会跟着出错。这些细节不是FPGA工程师一个人能解决的需要和PCB工程师密切配合所以热词里fpga与pcb开发如何互动不是空话做电源时序控制这个模块就是一次非常典型的软硬件协同。模块最终稳定运行之后我又把它封装成了一个可例化的小IP参数化延时完全保留下来。后续如果换器件、换电源数只需要改parameter或者重新例化一个实例就行不用重新设计逻辑。这种把一次性的调试经验沉淀成可复用模块的过程是我觉得FPGA开发最有价值的部分之一。
返回列表