Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

4.1 芯片内部的“悄悄话“

几百人的办公大厅:给你一道设计题

想象一个足球场大小的开放式办公大厅。几百人在各自的工位上工作,偶尔需要和坐得老远的人交换信息。

如果是你——你会怎么设计这个空间里的通讯?

方案一:每个人站起来大声喊。简单粗暴。但当几百人同时喊,你什么也听不见——这叫“广播风暴“。

方案二:每个人都拉一根线到信息中心。你要跟谁说话,先把消息发给话务员,话务员帮你转发。有效,但话务员变成瓶颈——这叫“星型拓扑“。

方案三:按区域划分——相邻的十几个人共享一条区域线路。每个区域有一个区域总机,各区域总机之间再由主干线路互联。这叫“层次化总线“。

在你的MCU内部,有CPU核、SRAM、Flash控制器、DMA引擎、几十个外设。它们需要互相通信。如果每个模块都点对点拉线——布线量是O(N²),芯片面积直接炸。所以芯片设计师只用一种方案:方案三——层次化总线。

但这只是架构级的答案。真正的魔鬼藏在细节里:那条“A区到B区的主干线路“在硅片上到底是什么?它有多宽?信号走完需要多长时间?当两个人同时要说话,谁先说?

两条路:高速公路和普通道路

ARM的AMBA(Advanced Microcontroller Bus Architecture)规范是嵌入式MCU最主流的总线规范。你手上的S32K、STM32、RH850的内部总线都遵循AMBA,或与之类似的私有总线。

AMBA的核心是两层总线。

AHB(Advanced High-performance Bus)——“高速公路”

AHB服务于高速模块:CPU核、SRAM、Flash控制器、DMA。

  • 支持burst传输——连续地址的多个数据在单次传输中完成。CPU从Flash取指令就是典型的AHB burst:一次读出8个32位字(8-beat wrapping burst),填满Cortex-M的预取缓冲区。
  • 支持多主设备。多个master可以发起传输,由仲裁器决定谁占用总线。CPU Core、DMA、Ethernet MAC都可以成为AHB master。
  • 地址和数据流水线化——地址阶段和数据阶段在不同周期,当前传输的地址阶段可以和上一次传输的数据阶段重叠。这被称为“地址流水线“(pipelining),是AHB在单一时钟周期内完成一次传输的关键。

APB(Advanced Peripheral Bus)——“普通道路”

APB是一条低速、低功耗的总线,服务于外设:GPIO、UART、SPI、I2C、定时器、看门狗。

  • 不支持burst。每次传输独立完成。
  • 单向单主设备——APB bridge是唯一的master。所有APB外设都是slave,只能被bridge访问。
  • 简化的握手协议,状态机只有三个状态:IDLE → SETUP → ENABLE → IDLE。

APB就是“普通道路“——限速、没有超车道、只有一个出入口。但它的优势是极低的功耗——不需要维持高速信号的眼图。

为什么APB比AHB省电?因为它的时钟树简单。APB外设挂在APB时钟域上,这个时钟在APB没有传输时可以门控(clock gating)关掉。AHB则不同——AHB master随时可能发起传输,AHB时钟几乎永远在跑。在低功耗MCU里,AHB功耗可以占芯片总功耗的15%-20%。APB外设的功耗通常不到1%。

AHB-APB Bridge

AHB和APB之间的桥梁。它把CPU在AHB上发起的访问转换到APB协议的时序。同时它作为AHB的slave和APB的master。

AHB到APB的桥,就是一个高速公路出口匝道——把120km/h的车流(AHB 112MHz)减速到30km/h(APB 28MHz),然后汇入普通道路。

        CPU (AHB Master)
             |
      +------+------+
      | AHB Matrix  |
      +--+---+---+--+
         |   |   |
    +----+ +-+-+ +----+
    |SRAM| |Bridge| |Flash|
    +----+ +--+---+ +----+
              |
         +----+----+
         | APB Bus |
         +-+--+--+-+
           |  |  |
       +---+ +-+-+ +---+
       |GPIO| |UART| |SPI|
       +---+ +---+ +---+

每个外设被映射到统一的存储器地址空间内。GPIOA的寄存器从0x40020000开始;SPI1的寄存器从0x4002C000开始。这些地址不是“碰巧“的——它们是AMBA spec的分区规则推导出来的:base address选定、偏移量叠加。

AHB Burst:一次取指的完整旅程

理解总线的最好方式是看一个具体的事务。当Cortex-M4从Flash取一条指令时——比如0x08000100处的STR R0, [R1]——CPU的指令预取单元在AHB上发起一个读事务。但这不只是单次传输——Cortex-M4的预取缓冲区(Prefetch Buffer)是4×32-bit,所以它会发起一个4-beat的增量burst(INCR4),一次读出4个32位字。

完整的AHB burst时序如下:

AHB的流水线突发传输就像接力赛——地址阶段是预跑(还没接到棒但已经开始加速),数据阶段是接棒(接力棒传递的瞬间)。当前传输的地址阶段和上一笔传输的数据阶段在同一个时钟周期内重叠——这就是“地址流水线“。想象四个跑者在跑道上:第一个已经递出了棒(数据阶段),第二个正在伸手(地址阶段),第三个刚站上起跑线,第四个还在等待区。每个周期,都有一个人在递棒,一个人在接棒。

周期1(地址阶段 #1):地址总线上送出0x08000100。HTRANS=NONSEQ(非连续,burst的第一个传输)。HBURST=INCR4(4-beat增量burst)。HSIZE=WORD(32位)。HWRITE=0(读)。HSEL_Flash=1。

周期2(数据阶段 #1 + 地址阶段 #2):Flash控制器在HREADY=1时驱动HRDATA——返回0x08000100-0x08000103的内容(第一条指令的编码)。同时地址总线上送出0x08000104,HTRANS=SEQ(连续,burst的后续传输)。

周期3(数据阶段 #2 + 地址阶段 #3):HRDATA返回0x08000104-0x08000107的内容。HADDR送出0x08000108。HTRANS=SEQ。

周期4(数据阶段 #3 + 地址阶段 #4):HRDATA返回0x08000108-0x0800010B的内容。HADDR送出0x0800010C。HTRANS=SEQ。

周期5(数据阶段 #4):HRDATA返回0x0800010C-0x0800010F的内容。HTRANS=IDLE。传输完成。

AHB在4个数据周期(5个总线周期总计,因为有1个地址流水线重叠周期)内读回了16字节。这就是burst的威力——不需要为每个32位字重新启动一次总线事务。NONSEQ只出现一次。后续三个全是SEQ。

如果Flash控制器需要等待周期(比如Flash访问延迟是3个AHB周期),它会在数据阶段拉低HREADY——AHB master在每个HCLK上升沿采样HREADY,如果为低,当前数据阶段延长一个周期。这就是为什么Flash访问延迟会直接影响CPU执行速度——AHB在插入等待周期时,CPU的流水线在干等。

穿透:追踪一次寄存器写操作

当你执行:

*(volatile uint32_t *)0x40020014 = 0x00000001;  // 设置 GPIOA pin 0 为高

这个操作在芯片内部经历的是一个漫长的“供应链“。我们一层一层追踪。

第一层:Cortex-M4 的 store 指令。 编译器把这条C语句翻译为 STR R0, [R1]——寄存器间接寻址的存储指令。R1里装着0x40020014。这条指令进入Cortex-M4的三级流水线:取指、译码、执行。在执行阶段,store指令被发往AHB总线接口单元(BIU)。

第二层:AHB-Lite 总线周期。 BIU在AHB总线上发起一个写事务(Non-Sequential,无等待)。地址总线(HADDR)上送出0x40020014,写数据总线(HWDATA)上送出0x00000001,控制信号HTRANS=NONSEQ、HWRITE=1、HSIZE=WORD(32-bit)。BIU等待从设备拉低HREADY——否则插入等待周期。

第三层:AHB总线矩阵查找。 地址0x40020014的高位进入地址译码器。译码器是一组组合逻辑——本质上是几百个与非门构成的地址范围比较器。它判断出这个地址落在“APB Bridge 1“的地址窗口内(通常是0x40000000-0x400FFFFF),然后把HSEL信号路由到APB Bridge 1。

第四层:AHB-APB Bridge的协议转换。 Bridge收到AHB写事务。APB协议不支持流水线——每一个传输都是原子操作。Bridge启动APB状态机:

  • IDLE:默认状态。
  • SETUP:Bridge拉高PSEL(外设选择),地址PADDR上送出地址的低位0x20014,写数据PWDATA上送出0x01,写控制PWRITE=1。
  • ENABLE:下一个APB时钟周期,Bridge拉高PENABLE。在这个周期的上升沿,被选中的APB外设(GPIOA)采样PWDATA、PADDR、PWRITE。
  • 回到IDLE。传输完成。

第五层:APB地址译码。 GPIOA内部又有一层地址译码。0x20014的低位被分解:bit[14:10]选中GPIOA模块,bit[9:2]选中GPIOA内部寄存器组中的位设置/复位寄存器ODR(输出数据寄存器)。0x01被写入BSRR的低16位——BS0=1,意味着GPIOA的Pin 0被设置为高电平。

第六层:IO Pad输出。 GPIOA的输出数据寄存器(ODR)的bit 0从0翻转为1。这个逻辑1通过输出缓冲器(一组串联的buffer链,逐渐增大驱动能力)推送到IO Pad。Pad上的PMOS管导通,把引脚电压从0V拉到VDD(3.3V)。引脚对PCB走线的负载电容(通常5-20pF)充电。电压以RC曲线上升——时间常数约几个纳秒。

这一切——从Cortex-M4执行STR指令,到物理引脚电压从0V变成3.3V——在几个AHB/APB时钟周期内完成。如果AHB跑112MHz(约9ns周期),APB跑56MHz(约18ns周期),整个过程不到100ns。

从C代码到物理电压——不到100纳秒。但你穿过了六层抽象。

总线矩阵:仲裁

总线矩阵(Bus Matrix)是AMBA里最容易被忽略、但也最容易出问题的部分。当多个AHB master同时想访问同一个slave——比如CPU要读Flash、DMA要写SRAM、Ethernet MAC要读SRAM——谁来给那一个slave服务?

仲裁器(Arbiter)决定。

常见的两种仲裁策略:

固定优先级(Fixed Priority)。 CPU优先级最高,DMA次之,Ethernet MAC最低。简单,但致命的缺点:高优先级的master可以饿死低优先级的master。如果CPU连续发起AHB burst,DMA永远拿不到总线——SPI接收FIFO溢出,数据丢失。

轮转优先级(Round-Robin)。 每次传输后,刚得到服务的master降到最低优先级,下一个master轮上来。公平,但增加了一层组合逻辑——面积和延迟略高于固定优先级。

用一个具体例子理解轮转和固定优先级的差别。假设三个master——CPU、DMA、Ethernet MAC——同时请求访问同一个Flash slave。CPU向Flash发一个8-beat burst读指令(约72ns在112MHz下),DMA需要从Flash搬SPI接收数据到SRAM,MAC需要从Flash读以太网描述符。

固定优先级体系(CPU > DMA > MAC): CPU完成整个8-beat burst——72ns。然后DMA得到总线,完成一次单笔传输(约9ns)。然后MAC得到总线。问题:如果CPU连续发出burst(指令预取单元一直在填预取缓冲区),DMA和MAC可能等上几百个纳秒——对DMA来说,这意味着SPI接收FIFO在溢出,对MAC来说意味着接收描述符来不及更新,帧丢失。

轮转优先级体系: CPU发出burst的第一beat后被降为最低优先级。DMA完成传输后被降为最低。MAC完成传输后被降为最低。三个master交替获得服务——没有一个会被饿死。burst被“打断“了——CPU的8-beat burst被分成4+2+2三个片段,中间插入了DMA和MAC的传输。总延迟增加了——但公平性得到了保证。系统的整体数据吞吐不变,但没有master积压。

S32K的Crossbar Switch(AXBS)用的是混合策略:两个round-robin组,组内有固定优先级。CPU和DMA被分在同一组。这种设计是典型的工程权衡——在公平性、面积、延迟之间找到“够用“的方案。

但仲裁器也可能失效。

物理连接:硅片上的走线不是理想的

在芯片上,一条AHB总线可能跨越2毫米的硅片。

2毫米听起来很短。但在28nm工艺下,最细的金属走线宽度只有几十纳米——长宽比高达十万比一。这不是“导线“——这是一个超长的RC分布式网络。

走线的单位长度电阻取决于金属层。低层金属(Metal 1-3)通常薄而窄,电阻率高——单位长度电阻可达1Ω/μm。高层金属(Metal 6-8)厚而宽,电阻率低——0.1Ω/μm。芯片设计师会把AHB这种全局总线放在高层金属上。

走线的单位长度电容由金属间距和层间介质(通常SiO₂,k≈3.9)决定——通常在0.15-0.25 fF/μm。2毫米的走线,总电容约300-500fF。加上每个slave的输入电容(约50fF/slave),一个典型的AHB总线总电容约1pF。

RC延迟:1pF × 200Ω(走线总电阻+驱动晶体管输出电阻)≈ 200ps。如果AHB跑在150MHz(周期6.67ns),这个RC延迟只占周期的3%——没问题。但如果把总线频率翻倍到400MHz,RC延迟就占了8%——时序收敛变得困难。再考虑到PVT(工艺、电压、温度)variation——慢工艺角下走线电阻高30%,高温下电阻再高20%,总RC延迟可能到400ps——占400MHz周期的16%,setup时间不够了。

这就是为什么你在S32K的reference manual里看到:AHB最大频率112MHz。它不是“ARM的设计只能跑112MHz“——是这个工艺节点、这种走线长度、这个RC延迟的物理极限。

在芯片上,你的C代码的每一步,都是物理的。你的*reg = val对应的不仅是一个AHB写周期,还有几百飞法的电容充放电、几十微安的门级漏电流、热噪声影响下的亚稳态概率。这些物理效应,正是芯片设计者和良率工程师每天都在对抗的。

时钟树:总线的节拍器

你看到的总线周期,每一个HCLK上升沿,不是从天上掉下来的。它来自芯片的时钟树(Clock Tree)。

芯片的时钟源是一颗外部晶振(或内部RC振荡器)。这个原始时钟通过PLL(锁相环)倍频到目标频率——S32K的SYSCLK最高112MHz。但112MHz不能直接送到芯片上几千个寄存器——因为时钟信号需要同时到达所有寄存器,而芯片上的走线延迟是不均匀的。

时钟树是一棵H形或鱼骨形分布的缓冲器网络。从PLL出来的时钟信号,经过一级一级的buffer(时钟缓冲器),均匀分布到芯片的每个角落。每一级buffer的延迟被精确设计和仿真,确保从PLL到任何一个寄存器的时钟到达时间差异(时钟偏移,clock skew)控制在几十皮秒内。

想象一棵倒置的树。树根是一个PLL(锁相环),从外部8MHz晶振倍频到112MHz。树干分出几十根“树枝“(clock buffer链),每根树枝再分出几百根“树梢“(leaf clock pin),每个树梢驱动一个寄存器或一个逻辑门的时钟输入端。这棵树的每一片“叶子“——每一个被时钟驱动的晶体管栅极——必须在同一个时钟周期内收到同一个上升沿。时钟树综合(Clock Tree Synthesis)是芯片物理设计中最考验耐心的环节——EDA工具花了比你想象的多得多的时间,只是为了确保时钟沿到达芯片上每一个点的时差(skew)不超过几十皮秒。

APB的时钟门控就发生在这里。当APB bridge不在传输时,APB时钟域上的一级时钟buffer被一个AND门关掉——时钟信号不向后传播。整个APB域几十个外设的寄存器、状态机、解码器的动态功耗降到接近零。门控的物理实现是一个“latch + AND“结构——latch防止门控信号在时钟高电平期间变化产生毛刺。

这就是为什么APB的功耗不到AHB的1/20——不是APB跑得慢,而是APB在不传输时根本没有在“跑“。时钟树上的buffer不翻转,门电容不充放电,漏电流只在几微安量级。

你的每一个*reg = val,都在硅片上驱动几百个时钟buffer翻转、几千个门的电容充放电。 0和1不只是逻辑概念——它们是几平方微米的硅片上电子密度的分布变化。

为什么你的SPI速度卡在5MHz?

你在调试平台上把MCU配置为SPI主模式,S32K跑在112MHz。你想跑20MHz SCLK,但示波器上SCLK只有5MHz。

查reference manual,找到SPI模块挂在哪个总线上——是APB。APB的最大时钟频率在S32K上可能是SYSCLK / 2 = 56MHz。但SPI波特率取决于给SPI模块的实际时钟(SPI模块内部的时钟源),以及波特率分频器的分频系数。如果你在时钟配置代码里漏了给SPI模块的时钟门控使能——SPI模块根本没拿到时钟,波特率分频器在默认的最低时钟下工作——5MHz就是天花板。

更糟的情况:SPI的FIFO深度只有4个字节。如果CPU没及时填充FIFO,SCLK会在字节间拉伸或直接产生间隔。你看到的SCLK不是连续的方波,而是一簇一簇的脉冲。

而这一切的根因——都不在SPI寄存器本身。在时钟树。在AHB到APB的信号路径。在总线矩阵的仲裁延迟。

这就是总线架构对你代码性能的直接反馈。

你和30年前的spec作者在对话

芯片内部的“悄悄话“,不是Magic——是工程师在几十年前约定的规则,被亿万个MOSFET忠实地执行。

AHB和APB的设计者——在1990年代ARM剑桥办公室的工位上定义“多主仲裁“、“APB低功耗握手”、“地址译码区“的那群工程师——他们不会知道你今天在调试S32K上SPI的速度问题。但他们定义的规则,正在你手指下的硅片上被精确执行。

你看到的0x40020014这个地址,不是“碰巧“的。它是AMBA spec的分区规则推导出来的——base address选定、偏移器叠加。你写的每一行volatile指针操作,都在激活他们二十年前画在SPEC文档里的状态转移图。

每一次你对着数据手册查寄存器地址的时候,每一次你用示波器抓SPI波形的时候,每一次你发现“为什么SCLK只有5MHz“的时候——你都在和30年前的spec作者对话。

致敬所有在历史深处定义了基础协议和架构的人。你们今天可能已经退休,但你们的劳动,每天都在几十亿颗芯片上被忠实地运行着。

“后之视今,亦犹今之视昔”——30年前的工程师看不见今天的S32K。但30年后的工程师回头看今天的你——你写下的每一行驱动代码、你画的每一张PCB、你调通的每一条I2C——也将在他们那个时代的硅片上继续运行。

工程,就是这样一代代人接力的。你手上拿着的,不只是数据手册——是上一代工程师递过来的接力棒。


本篇小结

今天我们做了一件事:理解了片内总线——AHB/APB如何让CPU和外设在硅片上“悄悄说话“。

关键结论:

  1. AHB是高速主干道:多主仲裁、流水线传输、支持DMA——CPU和DMA可以轮流当主机访问同一组外设。
  2. APB是低速支路:低功耗握手、无流水线、单主机——挂UART、I2C、SPI、定时器等低速外设。
  3. 总线矩阵是交通调度中心:仲裁逻辑决定谁先走——ARM的默认优先级是固定的,你的SPI被DMA抢了带宽,不是bug,是仲裁结果。

下一节,SPI与I2C——让两颗独立的硅片对话。

【下集预告】 芯片内部是AHB/APB。CPU和外设在同一片硅上“悄悄说话“。但单片硅解决不了所有问题——你需要EEPROM存配置、需要安全芯片做身份认证、需要传感器采集数据。

I2C的两根线能不能跑SPI那么快?如果能,代价是什么?如果不能——那个“不能“的物理原因,正好是理解“为什么不同的通信协议存在“的钥匙。