资讯详情

资讯详情

芯片内部通信揭秘:总线、握手信号与时序的工程实践

做过嵌入式、接触过IC验证或者说自己玩过底层驱动的朋友多半都有过这种困惑芯片在电路板上能跑程序能执行外设能响应但芯片内部那些模块之间到底是怎么沟通的始终像隔着一层黑盒。你给芯片时钟、复位、指令它把结果算出来可几十个模块怎么对齐、怎么传数据、怎么互相催促“快给我结果”很多人其实说不清。这篇“从沙子到车辙”系列的第4章第1节我想聊的就是这个黑盒里最日常、也最关键的一件事——芯片内部的“悄悄话”。往大了说这是总线与片上互联往小了说就是两个模块之间的“打个招呼、递个东西、确认收到”。这篇内容适合刚接触芯片底层开发的工程师、做嵌入式裸机或RTOS的朋友也适合正在做车规控制器调板子的人。“悄悄话”这个说法听着轻松实际一点也不简单。芯片内部通信一旦出问题轻则数据错位、总线挂死重则整个系统跑飞在车规场景下还可能触发功能安全机制导致系统降级。我从读芯片手册、调总线时序、踩I2C卡死的坑到后来看总线矩阵、理解AXI握手前后折腾了不少项目今天想把这条主线上的经验串起来讲清楚。1. 芯片内部的通信本质上是造一座“微型城市”1.1 模块之间为什么非要“说话”一颗SoC里面住的可不是一个单元而是一大帮“住户”。CPU核、GPU、NPU、SRAM、Flash控制器、DDR控制器、UART、SPI、I2C、定时器、中断控制器、DMA……每个模块都有自己的活要干但它们之间必须交换数据和控制信息。CPU要读Flash里的指令要往DDR里写中间计算结果DMA要把外设收到的数据搬到内存GPU要把渲染好的画面送给显示控制器。如果没有一套内部通信机制这些模块只能各干各的那芯片就成了一堆互不相干的晶体管。我把这个场景类比成一座城市模块是城市里的机构和住户总线是路网仲裁器是路口的红绿灯。地址信号相当于快递单数据信号是包裹本身控制信号则是一路上的签收确认。没有路网包裹送不出去没有红绿灯车辆全挤在路口没有签收确认谁也不知道那批货到底到没到。芯片内部的“悄悄话”本质上就是这套路网加物流规则怎么设计、怎么跑、怎么不出错。这里有一个很反直觉的点很多时候芯片内部通信的瓶颈不在带宽而在“规则”。数据线再宽如果握手信号时序不对传输一样卡死。我调试过程中遇到的多数诡异问题最后追到根上都是时序或状态机的问题而不是带宽不够。1.2 所有通信都得拆成地址、数据、控制三路看芯片手册的时候你会看到内部总线经常分成三组信号地址总线、数据总线、控制总线。新手常问为什么不能把地址和数据复用一根线像串口那样一个一个发不也能通吗能通但性能完全不同。芯片内部是并行世界模块之间要追求高吞吐把地址和数据分开就可以在同一个时钟周期里同时完成“报地址”和“传数据”甚至用流水线的方式让下一笔请求的地址与上一笔请求的数据在时间上交叠。打个比方这就像高铁站的进站口和站台分开一个通道专门查验车票一个通道专门放人上车人还没走到站台下一批人的票已经查完了。如果把检票和上车合并成一个口子每一批人都得排队等前面的人完全离开再动整个车站的吞吐量就塌下来了。内部总线把地址、数据、控制分开就是为了让信息流像流水线一样跑起来。但分开是有代价的。每组总线都是实实在在的金属走线占用芯片面积也给后端物理设计增加麻烦。这也是热词里“芯片后端”经常提到的痛点总线宽度越大布线越拥挤绕线资源和时序收敛压力都会上来。所以芯片设计师不会无脑把总线做到512位而是根据实际带宽需求选一个折中的宽度。你在STM32这类MCU里看到的内部总线宽度和你在RK3588、Jetson Orin这类高性能SoC里看到的总线矩阵完全不是一个量级的工程。1.3 车规芯片为什么格外看重内部通信这个系列叫“从沙子到车辙”落脚点最后还是车。车规芯片对内部通信的要求和消费电子芯片有本质区别。消费SoC追求峰值性能偶尔遇到一次卡顿或延迟用户顶多觉得App启动慢但车上的控制器不一样从ADAS传感器数据进来到决策、控制指令发出去每个环节的延迟必须是确定的、可预测的否则该刹停的时候没刹停后果就是事故。功能安全标准里反复提到的“确定性”和“隔离性”落到芯片内部通信上就是总线要为不同安全等级的任务划分资源。高安全等级的制动控制任务访问内存时不能被娱乐系统下载地图的DMA流量堵住这就要求总线矩阵具备优先级仲裁甚至带宽隔离能力。芯片内部的“悄悄话”如果乱成一锅粥外围的功能安全软件写得再好也补不回来。所以当你看到一款车规SoC的宣传页上写着“支持多级QoS”“总线带宽可配置”本质上都是在说内部通信不只是物理通道更是一套可调度的资源分配策略。这个视角是普通嵌入式开发很少接触但确实存在的。2. 总线协议里的“对话礼仪”时序、握手与通道2.1 APB、AHB、AXI怎么选芯片内部总线的家族非常庞大但绝大多数ARM系芯片里你会反复见到三个名字APB、AHB、AXI。它们不是三套互相替代的方案而是按性能需求和复杂度分层设计的产物。APB最简单适合挂低速外设比如UART的寄存器配置、GPIO控制器、看门狗这类模块。它的特点是没有突发传输每次访问都是一次独立的读写事务时序简单、功耗低。AHB则是带流水线的总线支持突发传输适合内存控制器、DMA这类需要连续搬数据的模块。AXI是目前高性能SoC的主干支持多通道、乱序完成、多笔事务并行CPU核和DDR控制器之间基本都是AXI互连。总线类型典型用途特点常见场景APB低速外设寄存器配置时序简单、功耗低UART配置、GPIO、看门狗寄存器AHB中高速数据搬运流水线、突发传输DMA、SRAM控制器AXI高性能主从互连多通道、乱序、并行事务CPU到DDR、多核互联、加速器访问内存选型逻辑其实很朴素给一个只配置寄存器的外设挂上复杂的AXI接口纯属浪费面积和功耗给CPU到内存的通道用APB带宽又完全不够。就像城市交通社区内部的小路和城市快速干道各有分工不可能让所有车辆都走高速也不可能让快递员骑车走快速路的匝道。2.2 VALID和READY一组握手信号的门道AXI这套协议里最基础也最容易让人琢磨不透的就是握手信号。每个通道都有一对VALID和READY发起方拉高VALID表示“我要发送的信息已经在总线上了此刻有效”接收方拉高READY表示“我现在准备好了可以接收”。只有当两个信号同时为高在时钟上升沿采样这一拍才算真正完成一次数据传递。我见过很多新手搞不明白为什么要有两个信号而不是像串口那样发完就算。原因在于接收方不一定随时都有空。DDR控制器正在刷新总线矩阵正在处理另一笔高优先级事务这时CPU发来的数据就不能硬塞。如果发起方不等接收方准备好就强行传数据可能丢失或者覆盖别人的数据。VALID和READY这种“双方确认”机制本质是让通信双方可以各自掌握节奏快的一方等慢的一方。这里面最容易踩的坑是死锁接收方在等某个条件才拉高READY而那个条件恰恰需要这笔事务完成之后才成立。比如一个外设被配置成“接收数据后产生中断中断服务程序再读取状态寄存器”而总线正在等外设的READY中断却因为没有收到数据永远不来这就卡死了。所以做内部总线验证时协议检查器里都会重点查“VALID和READY不能无限等待”这类属性。从模拟的角度看握手信号还有一个作用天然构成了流控。你可以把VALID/READY想象成两个人打电话一个说“我有事找你”另一个说“你说吧”双方对齐了才开始说正事。如果对方正在开会就只能先不接电话发起方得等一会儿再拨。没有这套机制消息就会在“没人听”的情况下被硬塞进去然后丢在空地上。2.3 除数据之外芯片还有几种“悄悄话”总线传输的是信息但芯片内部还有几种“悄悄话”不通过数据总线却同样决定系统能不能跑起来。时钟信号是一项。芯片里所有模块都得跟着同一个节拍工作。时钟不是数据但它决定了数据什么时候被采样、什么时候被锁存。时钟如果抖动过大或频率配置错误哪怕总线协议完全正确数据也会在采样点前后“随风摇摆”。复位和中断是另外两项。中断信号是“有人按门铃”比如定时器到点、UART收到一个字节、DMA搬运完毕它的传递路径通常是专用的中断控制器不走AXI数据通路的带宽。复位则是“清场重来”上电时序里各个模块按剧本依次退出复位谁先谁后都有讲究。热词里的“soc芯片启动”“qspi芯片”之所以被单独拎出来就是因为芯片上电启动的每一个阶段本质上都是BOOTROM、时钟控制器、复位控制器、启动介质之间在传“悄悄话”。你只看到芯片跑起来了实际上背后已经完成了一整套按序握手。还有一类比较新的“悄悄话”跟安全相关比如不同模块之间的错误状态传递、安全等级信号。车规芯片里一个模块做了安全校验发现数据异常不可能有精力去广播一条完整报文它只需要拉高一条专用的错误信号线让安全岛模块感知到并做出反应。这种信号虽然只有一位但重要性远高于32位数据线。3. 从读指令到拿到数据一次完整通信的实操视角3.1 CPU的一条读请求到底在芯片里经历了什么我建议每个做底层的工程师都试着在纸上画一遍CPU执行一条加载指令要读内存地址0x20001000处的数据这中间内部通信经历了什么。第一步CPU核把虚拟地址翻译成物理地址。如果开了缓存先查L1 Cache是否命中命中就直接返回数据根本不走内部总线这也是缓存最得意的地方。如果没有命中请求会继续往下走可能需要查L2再到总线矩阵。第二步总线矩阵做仲裁。这颗芯片里可能同时有CPU核、GPU、DMA在发访问请求总线矩阵要按照优先级和QoS配置决定谁先通过。你的读请求这时候可能排在别的请求后面等待时间取决于当前总线负载和仲裁策略。第三步事务到达DDR控制器。DDR控制器本身又是一个“小城市”它要把收到的读写请求排入队列按bank、行、列地址做优化排序再生成符合DDR协议时序的信号发给外部存储颗粒。等到数据从DDR颗粒读回来再由控制器沿着原路返回给请求方。这条链路听起来简单每一站都有超时、错位、优先级翻转的可能。我在调一个AI加速板卡时遇到过一种现象CPU访问某个地址偶尔会读到旧数据查到最后是总线矩阵中读返回路径的乱序完成导致的软件侧的缓存维护指令没做对读请求居然被前面的写请求“超车”了。那之后我养成了一个习惯——凡是涉及内部高速总线的多核场景一定要把内存屏障和缓存维护命令写到位不能靠运气。3.2 在调板现场抓一次I2C总线通信内部总线对普通工程师来说看不到摸不着但芯片外部引脚上的通信是可以直接观测的而且两者的很多原理相通。I2C是我最常用的教学案例因为它足够简单又足够暴露问题。I2C只有两根线SCL和SDA。主机发一个起始条件然后发7位地址加读写位从机如果地址匹配就拉低SDA回一个ACK之后主机和从机一个字节一个字节地传。这本质上就是一次“握手对话”主机问“你在吗我要读你的数据”从机答“我在我准备好了”然后双方按SCL的节拍一比特一比特交换信息。实操中我踩过最典型的坑在上拉电阻。I2C是开漏结构SCL和SDA必须外接上拉电阻才能产生高电平。某次我做一块温湿度采集板I2C速率选400k传感器就在隔壁上拉电阻用了2k结果总线上波形上升沿特别缓从机偶尔应答偶尔不应答。后来用示波器一看SDA在SCL的采样窗口内根本没抬到高电平阈值数据已经是“薛定谔的1”了。把上拉电阻换成1k再测就稳定了。这里有一个“为什么”I2C速率越高允许的上升沿时间越短。上拉电阻越大RC充电时间越长上升沿越缓。在400k速率下建议总线上拉电流控制在3mA左右对应1k到2k电阻具体还要看总线上挂了多少设备和分布电容。很多工程师觉得I2C简单直接套一个10k电阻模板结果就栽在这上面。3.3 想看清内部通信三种调试手段可以组合用芯片内部的“悄悄话”不是完全看不见只是需要合适的工具和入口。第一种是芯片自带的Trace或总线监控单元。ARM CoreSight里有一整套调试基础设施可以追踪CPU执行的指令流和访问记录。当你怀疑程序“卡在某处”用Trace抓一下PC指针和历史访问比盲猜高效得多。第二种是逻辑分析仪和示波器适合抓芯片外部的通信引脚比如I2C、SPI、UART、并行接口。这类手段不能直接看内部总线但可以通过外部行为反推内部状态。第三种是软件手段在代码里做寄存器读回、状态标志位轮询、硬件断点、内存监视点。比如配DMA前先读一遍DMA的状态寄存器确认空闲写完外设寄存器再读回来对比是否一致。这些习惯在条件简陋的现场尤其有用。我自己常用的组合是先用软件断点加寄存器读回缩小范围再用逻辑分析仪抓外部时序最后实在不行才上Trace。原因很简单Trace工具贵、环境配置麻烦很多开放板卡还不一定接出调试接口。先把能低成本拿到的证据收集齐往往问题就已经浮出水面了。4. 内部通信的拥堵与干扰几个容易忽视的环节4.1 多核多加速器挤着访问DDR路口谁来调度高性能SoC里DDR接口是兵家必争之地。CPU、GPU、NPU、视频编解码器、DMA全都往DDR灌数据而DDR控制器的带宽是有限的。这里的“悄悄话”一多就变成早晚高峰的路口所有人都想过去但路口一次只能放行有限的车辆。硬件上解决这个问题靠的是总线矩阵和内存控制器里的仲裁调度。常见的思路是给每个主设备配置优先级和带宽配额再按QoS策略动态调整。汽车场景里ADAS前视摄像头数据必须优先进入内存而娱乐系统的后台下载任务可以靠后。如果仲裁策略配错高优先级任务可能被低优先级的大块突发请求阻塞导致实时性崩溃。软件层面也有配合手段分配内存时可以按主设备划分区域让不同引擎访问不同的DDR通道或不同的内存池降低争抢概率。热词里那块RK3588以及Jetson Orin Nano背后的工程相当一部分时间都在做这种“流量规划”。别小看这个事我见过一个项目摄像头采集偶发掉帧怎么调驱动都没用最后发现是另一个核心的游戏Demo一直在跑大带宽的内存训练任务把内存控制器带宽吃满了。给游戏任务限了帧率问题立刻消失。4.2 低速总线上挂设备带宽要自己先算明白很多工程师喜欢“够用就行”SPI能跑到10Mbps就认为啥都够了。但一旦总线上挂了多个外设带宽是要按请求分配的。SPI是半双工为主一个字节发出去同时收回来一个字节这个特性往往被忽略。拿一个具体场景算一笔账某传感器通过SPI接口每次读取需要先发2字节命令再读8字节数据。SPI时钟10MHz即1.25MB/s。每轮事务10字节耗时约8微秒。如果每10毫秒读一次总线上实际只用了0.08%的带宽确实很宽裕。但如果有另一个外设每1毫秒要传2KB数据那就需要约1.6毫秒的总线占用时间占满160%带宽——显然是做不到的。这时就得评估换更快的SPI时钟、改用DMA减少CPU参与时间、或者给两个外设分时复用不同的片选并错开事务窗口。这个计算“为什么”重要因为总线是共享的不是每个外设独享一整条带宽。你以为“我的SPI有10Mbps”实际上多个主设备或外设共享同一物理总线时每个设备的可用带宽要按占用时间折算。4.3 电源和地线上的噪声才是“悄悄话”最大的噪音源芯片内部通信再考究归根到底物理层面还是电压的高低变化。如果电源域波动剧烈芯片内部的逻辑门就可能判断错电平。尤其现在很多芯片是多电源域设计CPU核用0.8VIO用3.3V内部总线可能横跨多个电压域需要电平转换电路在中间翻译。电源纹波一旦超标电平转换就可能出现亚稳态数据白屏、程序跑飞都会来。热词名单里频繁出现的“看门狗芯片”“电源芯片8205”“5V Buck”本质上都在解决芯片供电的可靠性问题。硬件上给每个电源域配足够的去耦电容、把数字地模拟地分开、注意PCB走线阻抗这些都能减少电源噪声对内部通信的干扰。很多人只关注“总线怎么通”忽略了“供电是否稳”结果软件查了一个星期毫无头绪最后示波器量电源发现纹波200mV问题当场破案。我之前有一块板子I2C总线每次温度升高以后就乱码降温又正常。排查到最后不是I2C本身的问题而是LDO在高温下压差不足输出纹波变大SDA采样点一片模糊。换了一颗更低压降的LDO问题再没出现过。芯片内部通信的安全往往是从外面先塌掉的。5. 踩坑实录总线挂死、数据错位、传输不触发5.1 三个近似的“事故现场”第一个案例是I2C总线卡死。某次和一颗电源管理芯片通信冷不丁SCL就一直停在低电平了。用示波器一量SCL和SDA都被拉死像两个人互相都掐着对方喉咙。最后发现是某个异常时序让从机锁进了内部状态机它一直在等一个根本不存在的停止条件。恢复的办法是用GPIO模拟I2C主机连续给SCL发送9个时钟脉冲把从机的状态机“摇醒”。第二个案例是DMA配置好了就是不触发。我反复查寄存器使能位、源地址、目标地址、长度全是对的传输计数就是不动。后来对比手册发现这个DMA控制器要求源地址必须按事务宽度对齐而我填了一个偶地址但事务宽度配置成32位地址的最低两位和宽度约束冲突。硬件仲裁直接拒绝了这次传输但错误信息只是状态寄存器的某一位置了位我没第一时间看。拖动数据前先检查对齐和宽度现在成了我配置DMA的固定动作。第三个案例是GPIO模拟的外部总线访问总读到0xFF或0x00。这个坑最经典外设的时钟域根本没开也就是那个模块实际上还“睡”着内部访问被总线返回了错误响应。我最初还怀疑是IO方向配置错了拿着万用表量了半天引脚最后翻开手册看电源控制寄存器发现应该先给对应外设上电、使能时钟再配置引脚功能。这类问题在MCU和SoC上很常见排查顺序错了非常浪费时间。5.2 排查顺序定了问题就少一半这些年的调试经验让我总结出一条顺序先供电再时钟复位再握手信号再地址映射再数据宽度和端序最后才怀疑协议和代码。为什么这样排序因为链路是分层的底层不对上层必然出错。供电不稳时钟可能漂移时钟没有寄存器根本写不进去寄存器都写不进去谈地址映射没有意义。握手信号卡住说明通信双方没有对齐节奏地址映射错了访问可能打到一个不存在的模块宽度和端序错了数据即使拿到了也是错的。按这个顺序排查能避免大量“头痛医脚”的冤枉路。排查步骤检查内容常见异常1供电、时钟、复位电源纹波大、时钟未使能、复位一直拉低2握手与超时VALID/READY卡死、事务超时3地址映射外设地址偏移写错、总线地址译码错误4数据宽度与端序32位/16位配置错位、大小端处理不一致5协议与代码逻辑状态机出错、软件流程漏步骤5.3 一些让我长期受益的底层调试习惯有几个习惯是踩了太多坑之后才真正立起来的现在觉得非常值得分享。第一初始化任何外设之前先读一次状态寄存器确认模块处于已知状态。很多外设上电时状态是随机的不清标志位直接配置第一帧数据就可能错。第二写寄存器之后习惯性读回验证。嵌入式编译器优化级别开高以后连续写同一个地址有时会被优化掉虽然大多数情况不会有问题但在芯片内部总线通信这种场景还是自己确认一遍更稳妥。用volatile修饰寄存器映射指针也是我一直坚持的做法。第三换板卡、换芯片后先跑基础的内存读写自测和总线回环测试再跑业务代码。我在项目里习惯准备一段很小的自检程序写SRAM、读回来比对配置UART自发自收对指定GPIO做回环。这几项过了才能说明供电、时钟、总线“悄悄话”的基本盘是好的。第四特别注意中断服务程序里的总线访问。中断处理函数不要做长时间的总线事务尤其是不能在某条握手信号卡住时死等。中断上下文里死等总线等于把整个系统按了暂停键还要冒着中断嵌套后面恢复不了的风险。写在最后的一个体会是芯片内部的“悄悄话”虽然看不见摸不着但它的规律和现实世界里的沟通是一样的。只要把收发双方、确认机制、时钟节拍这三件事搞清楚再复杂的问题也能拆成一个个小步骤去验证。我现在拿到一块新板子第一件事永远是看电源、看时钟、看复位这三样基础信号确认无误之后才会去调总线和外设。省下的时间都是这些习惯挣回来的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →