ARM微处理器基础

ARM核硬件结构

各大半导体生产商从ARM公司购买添加了特定模块的ARM微处理器核,再根据不同的应用需求,加入适当的外围电路,最终形成了半导体厂商自己的ARM微处理器芯片。一个典型的使用ARM内核的微处理器硬件结构如下示:

ARM内核的微处理器硬件结构

每个方框表示一个功能或特性,方框之间连线为传输数据的总线。

可以看出,图示已经将硬件部分分成四类,以不同颜色/符号表示出

  • ARM处理器
  • 控制器:AHB裁决、中断控制器、存储器控制器、AHB-外桥、AHB-APB桥
  • 外围器件:UART、计数/定时器、以太网、RTC
  • 总线

四种不同器件负责不同的功能

ARM处理器:作为核心控制整个器件,一个处理器中包含了一个或多个ARM内核以及一些外围期间,如内存管理单元MMU(memory management unit)、处理器缓存CaChe等。

控制器:协调系统的重要功能模块。两个最常见典型的器件为中断控制器存储器控制器

外设接口部件:提供芯片与外部的所有输入/输出(I/O)功能。

总线:总线用于在器件不同部件之间通信。在传输总线中,ARM处理器作为总线主设备——拥有对总线的裁决权,通过同一总线,主设备可主动发起数据传输请求;外围器件作为总线从设备——在总线上处于被动,只能对主设备的传输请求做出反应。

ARM处理器中广泛使用的总线结构称为高级微控制总线结构(AMBA)。最初的AMBA总线只包含ARM系统总线(ASB)和ARM外设总线(APB),后续又提出一种称为ARM高性能总线(AHB)ARM高级可扩展接口(AXI)添加到AMBA中。

  • AHB (Advanced High-performance Bus): 提供高性能的总线接口,适用于高带宽的需求。
  • APB (Advanced Peripheral Bus): 适用于较低带宽和低功耗的外设。
  • ASB (Advanced System Bus): 是AHB的前身,已经逐步被AHB替代。

下面是AMBA架构及其主要总线的关系结构树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AMBA
├── AHB (Advanced High-performance Bus)
│ ├── AHB-Lite (简化版本)
│ ├── AHB5 (支持TrustZone安全扩展)
│ └── AHB5-Lite (AHB5的简化版本)
├── APB (Advanced Peripheral Bus)
│ ├── APB1
│ └── APB2
├── AXI (Advanced eXtensible Interface)
│ ├── AXI3
│ ├── AXI4
│ ├── AXI4-Lite (简化版本)
│ └── AXI4-Stream (数据流接口)
└── ASB (Advanced System Bus) - 已逐步被AHB替代

AMBA各总线的主要特点

AHB (Advanced High-performance Bus)

  • 高性能、支持突发传输、分割传输和总线仲裁
  • 多层AHB允许多个主设备和从设备并行操作

APB (Advanced Peripheral Bus)

  • 简单的总线协议,主要用于连接外围设备
  • 不支持突发传输和分割传输,适用于低功耗设计

AXI (Advanced eXtensible Interface)

  • 高性能和高带宽,支持多主设备、多从设备
  • 支持乱序传输和数据流传输 (AXI4-Stream)

ASB (Advanced System Bus)

  • AHB的前身,逐步被AHB替代
  • 类似AHB的功能,但性能较低

上图所示的器件中有三条总线:一条AHB总线连接高性能片内外设;一条APB总线连接较慢的片内外设接口;第三条总线用于连接片外外设,这条外部总线需要一个特殊的桥用于与AHB连接。

总线分类

ARM核的数据流模型

不同版本的ARM内核及其硬件特性是有所区别的,但是其数据流模型基本上是一致的。从软件开发者的角度来看,可以抛开具体硬件结构的细节,而抽象的将ARM内核看作是通过数据总线连接的各个功能单元模块所组成的集合,ARM内核功能的实现最终体现为各种数据在不同部件之间的流动

现在以一个典型的冯诺依曼结构的ARM内核为例,下图为ARM核的数据流模型。

典型的冯诺依曼结构的ARM内核

此图的绘制过于综合,将两种指令的流程混合绘制在内了,不好理解,此处将其拆分成为数据处理指令内存访问指令(Load-store指令)的两个不同流程进行说明,但让我们先看看原文章的描述

数据通过数据总线进入处理器核,这里所指的数据可能是一条要执行的指令或一个数据项。处理器如何区分是指令还是数据项,主要依赖于程序计数器(Program Counter,PC)的管理。PC指向当前要执行的指令地址,当需要从存储器读取指令时,PC的值会被送到存储器,读取出的内容会被当作指令进行处理;当需要从存储器读取数据时,指令中的地址会被用来访问存储器,读取出的内容会被当作数据进行处理。

指令译码器在指令执行前先将它们翻译。每一条可执行指令都属于一个特定的指令集。

与所有的RISC处理器相同,ARM处理器采用Load-store体系结构,这就意味着它只有两种类型的指令用于把数据移入/移出寄存器:load指令从存储器复制数据到内核的寄存器;反过来,store指令从寄存器里复制数据到存储器。没有直接操作存储器的数据处理指令,因此数据的处理只能在寄存器里进行由此可知,数据从存储器加载至寄存器,再通过处理器的运算单元运算得出结果,再存入指定寄存器,再进一步写入存储器,这既是数据处理指令的大概流程

对于32位处理器的ARM内核,大部分指令认为寄存器中保存的是32位有符号或无符号数。当从存储器读取数据至一个寄存器时,符号扩展硬件会把8位和16位的有符号数转换成32位。有符号数的二进制存储中,若为正数,其最高位为1,而不同位数的数据符号位不相同,符号扩展硬件的意义正如其名:在于保持数据符号和数值的同时,增加数据的二进制位数,相信这应该很好理解

典型的ARM指令通常由2个源寄存器Rn和Rm、一个结果或目的寄存器Rd。源操作数分别通过内部总线A和B从寄存器文件中读出。

算术逻辑单元(ALU)或乘加器(MAC)通过总线A和B得到寄存器值Rn和Rm,并计算返回一个结果。数据处理指令直接把Rd终端计算结果写到寄存器中。Load-store指令使用ALU来产生一个地址,最高地址将被保存到地址寄存器并发送到地址总线上。Load-store指令就是内存访问指令。它们是ARM处理器中的一种指令类型,专门用于从内存加载数据到寄存器或将寄存器中的数据存储到内存中

ARM的一个重要特征是,寄存器Rm可以选择在进入ALU运算前是否先经过桶形移位器预处理。桶形移位器和ALU协作可以计算较大范围的表达式和地址。

数据处理指令

ARM数据处理指令处理数据流的详细流程:

  1. 指令获取(Instruction Fetch)
    • ARM处理器从程序计数器(Program Counter, PC)指定的内存地址读取指令。
    • 读取的指令通过数据总线进入处理器核。
  2. 指令译码(Instruction Decode)
    • 指令进入指令译码器。
    • 指令译码器将指令翻译成处理器能够理解和执行的微操作(micro-operations)。
    • 译码过程识别指令的类型(如数据处理指令、内存访问指令等)及其操作数和操作码。
  3. 操作数获取(Operand Fetch)
    • 处理器根据译码结果,从寄存器文件中读取操作数。
    • 数据处理指令通常涉及从两个源寄存器(Rn和Rm)读取操作数,并可能涉及一个立即数(Immediate Value)。
    • 如果操作数包含立即数,立即数可能会经过旋转扩展(Rotate)来形成一个32位的值。
  4. 符号扩展(Sign Extension)
    • 如果操作数是一个8位或16位的有符号数,处理器将进行符号扩展(Sign Extension),将其扩展为32位。
    • 符号扩展确保有符号数在操作过程中保持正确的符号和数值。
  5. 执行(Execute)
    • 执行阶段,算术逻辑单元(ALU)或乘加器(MAC)执行具体的运算操作。
    • ALU可以执行加法、减法、逻辑运算、移位操作等。
    • MAC可以执行乘法和乘加运算(Multiply-Accumulate)。
  6. 结果写回(Write Back)
    • 运算结果被存入目的寄存器(Rd)。
    • 结果也可能会更新条件标志寄存器(CPSR)的条件码位(如S位为1时)。

总结起来,ARM数据处理指令处理数据流的详细流程如下:

  1. 处理器获得指令并从内存读取。
  2. 指令译码器将指令翻译为微操作。
  3. 处理器从寄存器文件中读取操作数,并可能进行符号扩展。
  4. ALU或MAC执行具体运算操作。
  5. 运算结果存入目的寄存器,并可能更新条件标志。

示例流程:加法指令的执行

假设有一条加法指令ADD R1, R2, R3,其处理流程如下:

  1. 指令获取
    • 从内存读取指令ADD R1, R2, R3
  2. 指令译码
    • 译码器识别指令类型为加法,并识别出操作数寄存器R2R3,以及目标寄存器R1
  3. 操作数获取
    • 从寄存器文件中读取R2R3的值。
  4. 执行
    • ALU执行加法运算:R1 = R2 + R3
  5. 结果写回
    • 将运算结果写入目的寄存器R1
  6. 条件标志更新(如果S位设置)
    • 如果数据处理指令设置了S位(即设置条件标志位),则结果还会更新条件标志寄存器(CPSR)的条件码位。
    • 这些标志包括零标志(Z)、进位标志(C)、溢出标志(V)和负号标志(N),它们用于影响后续指令的条件执行。
    • 更新条件标志寄存器(CPSR)。
  7. 继续执行下一条指令
    • ARM处理器会更新程序计数器(PC)以指向下一条指令的地址。
    • 指令流水线(Pipeline)机制确保下一条指令可以被预取、译码和执行,以提高处理器的效率。
    • 更新PC,预取和执行下一条指令。
  8. 返回处理结果
    • 调用程序通过读取寄存器R1获取结果。
    • 对于需要返回结果的情形,例如子程序调用(subroutine call),结果通常会存放在特定的寄存器中(如R0用于返回值)。
    • 主程序或调用程序可以通过读取这些特定寄存器来获取结果。
  9. 内存写回(如果需要)
    • 在某些情况下,处理器可能需要将结果从寄存器写回到内存。例如,当处理结果需要保存到特定内存地址时,会使用存储指令(如STR)将结果写入内存。
    • 例如,STR R1, [R0]指令将R1中的结果存储到R0指向的内存地址。
    • 如果结果需要保存到内存,使用存储指令将结果写入特定内存地址。

内存访问指令

内存访问指令(例如加载和存储指令)在ARM处理器中的执行流程包括从内存读取数据到寄存器或将寄存器中的数据写入内存。以下是内存访问指令的详细执行流程:

  1. 指令获取(Instruction Fetch)
  • ARM处理器从程序计数器(PC)指定的内存地址读取指令。
  • 读取的指令通过数据总线进入处理器核。
  1. 指令译码(Instruction Decode)
  • 指令进入指令译码器。
  • 指令译码器将指令翻译成处理器能够理解和执行的微操作(micro-operations)。
  • 译码过程识别指令类型为内存访问指令(如LDRSTR),以及其操作数和操作码。
  1. 地址计算(Address Calculation)
  • 内存访问指令通常包含一个基地址寄存器(Rn)和一个偏移量(offset)。
  • 处理器计算内存地址,基地址寄存器(Rn)中的值与偏移量(offset)相加或相减,得出最终的内存地址。
  • 如果是前索引(P=1),则地址计算在访问内存前完成。如果是后索引(P=0),则地址计算在访问内存后完成。
  1. 内存访问(Memory Access)
  • 加载指令(Load Instruction, 如LDR)
    • 处理器将计算出的内存地址发送到内存控制器。
    • 内存控制器从指定地址读取数据,并通过数据总线将数据传送回处理器。
    • 数据被写入目标寄存器(Rd)。
  • 存储指令(Store Instruction, 如STR)
    • 处理器从源寄存器(Rd)中读取数据。
    • 处理器将计算出的内存地址和要存储的数据发送到内存控制器。
    • 内存控制器将数据写入指定内存地址。
  1. 结果写回和条件标志更新(Write Back and Condition Flags Update)
  • 对于加载指令,读取的数据被写回到目标寄存器(Rd)。
  • 对于存储指令,数据被写入指定内存地址后,寄存器内容不变。
  • 条件标志寄存器(CPSR)的更新通常不涉及内存访问指令,但某些特定指令或条件下可能会更新。
  1. 继续执行下一条指令(Next Instruction Execution)
  • ARM处理器会更新程序计数器(PC)以指向下一条指令的地址。
  • 指令流水线(Pipeline)机制确保下一条指令可以被预取、译码和执行,以提高处理器的效率。

示例流程:加载指令(LDR R1, [R0, #4])

假设有一条加载指令LDR R1, [R0, #4],其处理流程如下:

  1. 指令获取
    • 从内存读取指令LDR R1, [R0, #4]
  2. 指令译码
    • 译码器识别指令类型为加载指令,并识别出基地址寄存器R0、偏移量#4,以及目标寄存器R1
  3. 地址计算
    • 处理器计算内存地址:地址 = R0 + 4
  4. 内存访问
    • 内存控制器从计算出的地址读取数据。
    • 读取的数据通过数据总线传送回处理器,并写入寄存器R1
  5. 结果写回
    • 数据被写入目标寄存器R1
  6. 继续执行下一条指令
    • 更新PC,预取和执行下一条指令。

示例流程:存储指令(STR R1, [R0, #4])

假设有一条存储指令STR R1, [R0, #4],其处理流程如下:

  1. 指令获取
    • 从内存读取指令STR R1, [R0, #4]
  2. 指令译码
    • 译码器识别指令类型为存储指令,并识别出基地址寄存器R0、偏移量#4,以及源寄存器R1
  3. 地址计算
    • 处理器计算内存地址:地址 = R0 + 4
  4. 内存访问
    • 处理器从寄存器R1读取数据。
    • 内存控制器将数据写入计算出的内存地址。
  5. 结果写回
    • 数据写入内存后,寄存器R1内容保持不变。
  6. 继续执行下一条指令
    • 更新PC,预取和执行下一条指令。