复位序列的底层逻辑:为什么顺序错了,你的SoC连第一条指令都跑不起来?

张开发
2026/4/17 17:53:46 15 分钟阅读

分享文章

复位序列的底层逻辑:为什么顺序错了,你的SoC连第一条指令都跑不起来?
该文章同步至OneChan做过SoC Bringup和原型验证的人都懂世界上最绝望的bug不是代码编译不过不是功能跑不通而是流片回来的第一颗芯片上电就死JTAG连不上所有电源时钟波形完美你连调试的入口都没有。这个故事发生在我同事前公司他们团队花了一年时间设计的一颗车规RISC-V芯片FPGA原型验证跑了整整六个月所有测试用例全过覆盖率100%。流片回来第一次上电50%的芯片直接挂死没有任何征兆。他们换了三版封装测了所有工艺角扫了每一根电源和时钟线甚至把芯片磨开用电子显微镜看都没发现任何问题最后是他们团队里一个做了十五年数字设计的老工程师在复位控制器的RTL代码里找到了问题只是两个寄存器的复位释放顺序写反了时间差刚好是一个时钟周期——1ns。很多做上层嵌入式开发的人永远不会理解复位不就是拉个高低电平吗为什么差1ns就能让一颗价值几百万的芯片直接报废因为你看到的复位是软件层面的一个函数调用而我们看到的复位是几十亿个晶体管从混沌到有序的过程。这个过程差一个皮秒结果就是天壤之别。一、先推翻你所有的认知复位到底是什么绝大多数人对复位的理解从根上就是错的。复位不是把寄存器清零。复位不是把状态机拉回初始状态。复位甚至不是一个硬件功能。复位的本质是给整个数字系统注入一个确定的初始条件。整个现代数字电路的理论基础就是有限状态机。而有限状态机能够正常工作的唯一前提就是它必须从一个确定的初始状态开始运行。如果这个初始条件不确定那么整个系统的所有后续行为都是不确定的。它可能正常工作可能跑飞可能死锁也可能产生任何你想象不到的奇怪行为。这就是为什么复位的bug永远是随机的、不可复现的、最难调试的。因为它不是一个错误而是一个概率事件。从固件和原型的角度看一个完整的复位过程要经过这四个完全不同的阶段每个阶段都有严格的顺序要求电源建立阶段所有电源域按顺序上电达到额定电压并稳定模拟初始化阶段所有模拟电路晶振、PLL、ADC完成初始化并稳定数字复位阶段所有数字电路按顺序进入复位状态保持足够时间复位释放阶段所有数字电路按严格的逆顺序释放复位进入正常工作状态注意最后一个阶段——复位释放的顺序比复位本身重要一万倍。99%的复位bug都不是出在复位拉低的时候而是出在复位拉高的那一瞬间。二、四个底层原理讲透为什么顺序绝对不能乱我不会再跟你讲什么电源域依赖、时钟域依赖这种谁都能说两句的废话。我要从数字电路的物理底层从晶体管的层面告诉你为什么顺序错了系统就一定会出问题。1. 亚稳态的雪崩效应差1ps整个系统就会崩溃所有数字电路的核心都是D触发器。D触发器有一个铁律在时钟上升沿前后的建立时间和保持时间窗口内数据输入必须保持稳定。如果在这个窗口内数据发生了变化触发器就会进入亚稳态。亚稳态不是0也不是1而是一个介于两者之间的不确定状态。更可怕的是亚稳态会沿着整个数字电路传播。如果复位释放的顺序错了会发生什么触发器A先释放复位输出变成0触发器B后释放复位在它的时钟上升沿刚好采样到触发器A的输出变化触发器B进入亚稳态下一个时钟周期所有采样触发器B输出的触发器都会进入亚稳态亚稳态像雪崩一样传遍整个芯片整个系统进入完全不确定的状态这就是为什么他们的芯片50%的概率上电失败。因为亚稳态的结果是完全随机的有一半的概率它会最终稳定在正确的状态另一半的概率会稳定在错误的状态。而这个过程发生在1ns的时间内。你用任何示波器、任何逻辑分析仪都抓不到。2. 状态机的不可达状态一旦进去永远出不来所有的数字模块本质上都是一个有限状态机。一个设计良好的状态机应该只有一个初始状态并且所有的状态都是可达的。但是如果复位顺序错了状态机就可能进入一个根本不存在的不可达状态。举个最简单的例子一个SPI控制器的状态机有IDLE、TX、RX、WAIT四个状态。正常情况下复位后它会进入IDLE状态。但是如果复位释放的时候状态寄存器的两个bit不是同时变的而是一个先变一个后变那么它就可能进入一个二进制为11的状态——这个状态在RTL代码里根本没有定义。一旦状态机进入了不可达状态它就永远不会再响应任何输入了。它会一直停在那里直到下一次复位。这就是为什么很多人会遇到外设初始化失败重启一下就好的问题。不是你的驱动写错了是外设的状态机进入了不可达状态。3. 总线协议的原子性破坏死锁是必然结果AMBA总线协议看起来很复杂但它的核心只有一条所有的总线传输都是原子的。主设备发起一个传输从设备必须在规定的时间内给出响应。如果从设备没有响应总线就会一直等待进入死锁状态。如果复位释放的顺序是主设备先释放从设备后释放会发生什么主设备释放复位后立刻发起一个总线传输从设备还在复位状态根本不会响应这个传输总线进入死锁状态整个系统挂死连JTAG都连不上因为JTAG也是挂在总线上的。一旦总线死锁JTAG控制器也无法访问总线你就彻底失去了对芯片的控制权。这就是为什么流片回来的芯片如果复位顺序错了你连调试的机会都没有。4. CPU取指的流水线效应第一条指令就已经错了CPU不是一条指令执行完再取下一条指令而是流水线执行的。一个典型的RISC-V CPU有五级流水线取指、译码、执行、访存、写回。这五个阶段是同时进行的。如果复位释放的时候PC寄存器和指令寄存器的顺序错了会发生什么PC寄存器先释放复位变成0x00000000指令存储器后释放复位输出的是随机值CPU取指阶段取到了这个随机值复位释放完成CPU开始执行指令CPU执行的第一条指令就是一个随机的非法指令于是CPU就会直接进入异常状态然后跳转到异常处理向量表。如果异常处理向量表也没有初始化CPU就会再次复位进入死循环。这就是为什么很多人会遇到板子上电不断复位JTAG根本抓不住的问题。不是你的bootloader写错了是CPU执行的第一条指令就已经错了。三、一个流片踩坑案例价值800万就是我开头说的那个车规RISC-V芯片的案例。他们的复位控制器RTL代码里有这么两行assign cpu_rst_n sys_rst_n pll_locked; assign pc_rst_n sys_rst_n pll_locked;看起来没有任何问题对吧CPU和PC寄存器的复位都是系统复位和PLL锁定信号的与。但是在实际的电路中这两个信号的路径长度是不一样的。CPU复位信号要经过一个缓冲器才能到达CPU内核而PC复位信号直接连到了PC寄存器。所以PC复位信号会比CPU复位信号早1ns释放。于是就出现了这样一个时间窗口PC寄存器先释放复位变成0x00000000指令存储器开始输出0x00000000地址的指令1ns后CPU复位释放开始取指CPU取到的指令是指令存储器还没稳定时输出的随机值就是这1ns的时间差让他们的芯片50%的概率上电失败。解决方案简单到可笑把PC复位信号也经过一个同样的缓冲器保证两个复位信号同时释放。改了一行RTL代码重新流片花了800万延期了三个月。四、固件/原型工程师专属复位序列经过流片验证说了这么多给大家一个经过十多颗芯片流片验证的复位序列。这个序列从RTL设计到固件实现全程严格遵循没有出过一次复位问题。1. RTL设计阶段复位顺序规范复位拉低顺序上电时 1. 所有主设备CPU、DMA、GPU、NPU 2. 总线矩阵和互联 3. 所有从设备DDR、Flash、UART、SPI等 4. 时钟系统PLL、晶振 5. 电源域 复位释放顺序上电后 1. 电源域 2. 时钟系统等待晶振起振、PLL锁定 3. 所有从设备 4. 总线矩阵和互联 5. 所有主设备铁律复位释放的顺序必须和复位拉低的顺序完全相反。2. 固件Bringup阶段复位序列模板纯汇编.section .reset, ax .global _start _start: # 1. 立即关闭所有中断防止任何干扰 csrw mie, zero csrw mstatus, zero # 2. 复位所有主设备停止所有总线访问 li t0, RESET_MASTERS_BASE sw zero, 0(t0) # 3. 复位总线矩阵 li t0, RESET_BUS_BASE sw zero, 0(t0) # 4. 复位所有从设备 li t0, RESET_SLAVES_BASE sw zero, 0(t0) # 5. 复位时钟系统 li t0, RESET_CLOCK_BASE sw zero, 0(t0) # 6. 等待所有复位完成至少100us li t0, 100000 1: addi t0, t0, -1 bne t0, zero, 1b # 7. 初始化时钟系统等待PLL锁定 call clock_init # 8. 释放从设备复位 li t0, RESET_SLAVES_BASE li t1, 0xffffffff sw t1, 0(t0) # 9. 释放总线矩阵复位 li t0, RESET_BUS_BASE sw t1, 0(t0) # 10. 初始化DDR和Flash call ddr_init call flash_init # 11. 释放主设备复位 li t0, RESET_MASTERS_BASE sw t1, 0(t0) # 12. 重定位中断向量表 la t0, vector_table csrw mtvec, t0 # 13. 跳转到C语言入口 call main # 永远不会执行到这里 j .3. 原型验证阶段复位调试Checklist所有复位信号的建立时间和保持时间都满足要求所有复位信号的释放顺序完全符合规范复位释放时所有时钟都已经稳定至少10个周期复位释放时没有任何总线传输正在进行所有状态机在复位后都能正确进入初始状态所有输出引脚在复位期间都处于安全电平五、最后说句心里话做了这么多年的SoC Bringup和原型验证我最大的感悟就是越基础的东西越致命。很多工程师热衷于研究复杂的架构、先进的工艺、高深的算法却看不起复位这种简单的东西。他们觉得复位就是拉个高低电平谁都会做。但恰恰是复位决定了一颗芯片的生死。一颗算法再先进、性能再强大的芯片如果复位做不好就是一块没用的硅片。一个再厉害的工程师如果连复位都做不好就永远成不了真正的高手。真正的底层能力不是能设计出多么复杂的电路写出多么精妙的代码。而是能把别人都看不起的最简单的事情做到极致的严谨和精确。在数字世界里没有差不多。要么是0要么是1。要么对要么错。差1ns就是生与死的区别。你在做原型验证或者Bringup的时候遇到过哪些离谱的复位bug评论区分享一下让大家避避坑。需要我把这个RTL复位控制器模板和汇编启动代码整理成可直接使用的完整工程吗

更多文章