1.2 Armv8.1-M架构

2019年2月,Arm公布了Armv8.1-M架构。这是Armv8-M架构的扩展版本,包括一系列新特性。新的矢量指令集架构Helium是本书的主要关注点,除此之外,还有其他几个新特性:

• 循环和分支增强的附加指令集(低开销分支扩展)。本书的3.3节和6.1节将介绍该内容。

• 支持半精度浮点指令。本书的第4章将介绍该内容。

• 调试功能增强,包括性能监测单元(Performance Monitoring Unit,PMU)和针对信号处理应用开发的调试附加功能支持。信号处理调试的功能包括能够设置断点——该断点在达到一定的计数值时被触发(即停止执行代码并将控制权交给调试器),以及能够设置带有位掩码的数据观察点——可用于数据值的比较(例如,用于查找特定范围内的信号值)。这些内容将在第8章介绍。

还有一些与Helium没有直接关系的新特性或者没有被涵盖在本书中的新特性。有关这些内容的更多详细信息,请参阅Arm文档。

• 用于FPU的TrustZone管理的增强指令集。这个特性使得在安全和非安全代码之间进行上下文切换时,如果两者都使用FPU,切换速度更快。当非安全代码调用安全的应用程序接口(Application Programming Interface,API)函数时,安全代码能够对非安全状态下的浮点状态和控制寄存器(Floating-Point Status and Control Register,FPSCR)进行保存与恢复。如果需要,这允许安全API使用与非安全代码不同的FPU配置。

• 非特权调试扩展,允许将细粒度调试访问权限仅授予选定的支持非特权访问的模块。

• 内存保护单元(Memory Protection Unit,MPU)提供了一个新的内存属性,即“特权模式下永不执行”(Privileged eXecute Never,PXN)的属性。这允许当CPU处于特权模式时阻止执行任意代码,而这些代码可能已经写入了用户空间。这是一个重要的安全特性。

• 可靠性、可用性和可维护性(Reliability, Availability and Serviceability,RAS)扩展。这提供了一个编程模型和机制,可用于处理CPU硬件故障,也可避免损坏的数据在软件上下文中传播[例如,一个新的错误同步屏障指令(ESB)]。

需要注意的是,所有现有的Armv8-M软件都可以在Armv8.1-M上运行,以轻松实现软件移植。

基于Armv8.1-M架构的第一款处理器是由Arm在2020年2月宣布的Cortex-M55处理器。

本书的核心主题是Arm Helium技术——Arm Cortex-M系列处理器的MVE。它是Armv8.1-M架构的扩展,可以显著提升机器学习和数字信号处理应用的性能。

借助Helium,Arm Cortex-M处理器可以提供许多应用所需要的计算性能。这些应用包括音频、传感器集线器、关键字识别和语言命令控制、电力电子、通信(例如在物联网领域)和静态图像处理(在摄影领域)等。

Helium为Cortex-M系列处理器提供了单指令多数据功能。这也表示它提供一组128位寄存器,例如可用于保存16个单独的8位数值。单条指令可以独立地对这些128位数值中的每个值进行操作(例如,对每个值单独执行乘法)。这类指令被列为Arm处理器现有Thumb指令集的扩展指令。

Helium指令集对具有相同数据类型元素的矢量进行操作。这些数据类型可以是浮点型或整型。整型元素可以是有符号或无符号8位、16位、32位或64位值,而浮点型元素可以是单精度(32位)或半精度(16位)浮点数。矢量寄存器元素的位置称为通道。Helium指令集是规则且正交的,而且几乎所有这类指令在所有通道中执行相同的操作。这也表示大多数指令具有n个并行操作,其中n是由输入矢量的通道数。每个操作都包含在通道内。对于大多数指令而言,都不存在从一个通道进位或者溢出到另一个通道的情况,尽管第4章会有一些例外情况。

正如稍后会介绍的,Helium通常不执行64位矢量运算,尽管某些指令可以产生64位结果,或者采用64位输入。正因为Helium所使用的寄存器组与浮点寄存器组是共用的,一些Armv8-M浮点扩展指令可以作用于64位宽的数据(例如VLDR.64),并且硬件中对于64位双精度浮点数的支持是可选的。

要被执行的操作的通道宽度由正在执行的指令指定。例如,后缀为.S16表示指令将对存在于寄存器组中的有符号16位整型数据进行操作。

图1-1显示了Helium指令运行在128位宽的矢量上,这些矢量均由大小相同的元素组成。

Helium允许指令交织执行。这意味着如果CPU的微架构支持这一特性,多条指令可能在指令流水线的执行阶段重叠。例如,一条矢量加载(VLDR)指令将多个字从内存中读取到一个矢量寄存器中,可以与使用该数据的矢量乘法(VMUL)指令同时执行。由CPU硬件设计人员决定每个时钟周期内执行多少个矢令块[2](beat)(用Arm架构中更准确的术语来说是Helium实现中的一个架构节拍)。本书后面将更加详细地介绍这一点。

图1-1 Helium矢量元素

图1-2显示了多周期指令如何重叠执行。在这里可以观察到一条矢量加载指令和一条矢量乘加指令在第3个周期和第4个周期同时被执行的情况。

图1-2 Helium指令重叠

高效的基于矢量的SIMD机器的一个关键特性是编译器可以自动生成矢量化循环代码,以便每次循环迭代可以执行多个操作。例如,可以编写一个简单的内存复制例程来一次复制一个字,但编译器可以将其转换为一段更高效的代码,使其每次迭代可以复制一个或多个矢量寄存器的内容。但是,要复制的字数可能不是矢量长度的精确倍数,并且通常需要一些尾部清理代码来处理这个问题。Helium清理方案需要使用一种称为循环尾部预测的技术来执行此操作。这将在3.4节中详细介绍。

Helium还利用了一种称为通道预测的技术,它可以应用在那些单条指令可以有条件地在某些通道上操作,但不能在其他通道上运行的场景中。这使编译器得以避免产生分支,并成功地对使用复杂的if-then-else类型操作的代码进行矢量化。这将在4.4节详细介绍。

一些读者对“预测”一词可能并不熟悉。在许多计算机架构中,条件控制语句使程序分支跳转到程序的其他位置,而具体跳转位置取决于一些比较或标志的结果。处理器将实现一些指令,例如条件分支、条件调用、返回和可能的跳转表等指令。预测是一个处理此类控制操作的替代方法,其中该类指令具有一些“预测”相关的表示(一个布尔值)。当执行此类指令时,只有在相关预测条件允许的情况下,才会修改处理器的状态或者内存值。这避免了跳转到一小段代码,从而避免了由CPU流水线带来的分支惩罚。正如你将看到的,Helium利用预测机制生成更小、更快、更容易被编译器矢量化的代码。

Helium提供了交织与解交织的加载和存储指令,该类指令可以用步幅为2或者4的跨度把矢量寄存器中的值读写到内存中。它同样包含了矢量聚合加载和矢量离散存储的指令。离散-聚合机制是指从非连续的内存位置将数据聚合加载到矢量寄存器,或者将数据从矢量寄存器中离散存储到非连续的内存位置内,而不是从单个缓冲区按顺序读取数据。这些指令提供对矢量寄存器中元素的内存访问,对矢量中的每个元素使用单独的地址偏移量,该偏移量具体的值则使用另一个矢量寄存器来存储。这允许软件有效地处理任意内存访问模式,例如访问数据数组中的非顺序元素。它可用于模拟特殊寻址模式,如通常用于数字信号处理中的循环寻址。5.2节将给出更详细的说明。

最后,Helium添加了对复数(具有实部和虚部)执行算术运算的矢量指令,这些复数可以是整数,也可以是浮点数。它还包括可以使用128位矢量寄存器来支持处理非常大的整数的指令。还有一些指令可以执行128位算术运算,并将这些运算链接在一起以支持更大数值的整数的运算。

CPU设计人员有几种方法来实现符合Armv8.1-M架构的硬件:

• Helium选项可以省略。这意味着只有Armv8.1-M整型内核,它也可以选择包括标量FPU。该FPU可以支持双精度浮点运算。

• CPU的实现可以包括Helium,但仅支持整型矢量指令。同样,也可以包含可选的标量FPU(是否支持双精度浮点运算也是可选的)。

• CPU的实现包含支持整型矢量指令以及浮点型矢量指令的Helium。