LLVM 项目博客

LLVM 项目新闻和来自前线的详细信息

TCE 项目:使用 LLVM 编译支持的应用专用处理器的协同设计

基于 TTA 的协同设计环境 (TCE) 是一种应用专用指令集处理器 (ASIP) 设计工具集,自 2003 年以来,在坦佩雷理工大学 的多个研究项目中开发。这篇博客文章介绍了该项目以及如何使用 LLVM 为设计的 ASIP 提供高级语言编译器支持。




应用专用处理器的用例

特别是在嵌入式设备中,所谓的通用“现成处理器”通常不是手头应用程序的最佳选择。现成的处理器可能在芯片面积上太大,可能消耗太多功率,可能太贵(对于大规模产品而言),或者运行程序的速度不够快。为了解决性能问题,一种常见的設計选择是将应用程序的一部分在软件中运行,同时使用定制的硬件加速器或作为应用专用集成电路 (ASIC) 实现的协处理器来加速性能关键功能。

硬件加速器的设计、实现和验证时间会花费资金,并延长设计设备的上市时间。此外,使用硬件描述语言 (HDL)(如 VHDL 或 Verilog)设计的固定功能硬件加速器存在“一成不变”的问题,因此无法提供可编程性,从而无法进行后期错误修复和对支持功能集进行现场更新。

现场可编程门阵列 (FPGA) 允许在现场重新配置已实现的硬件逻辑。但是,由于 FPGA 只是一个硬件设计实现技术,因此使用 HDL 设计硬件逻辑的“非经常性工程成本”仍然存在。

应用专用处理器可以在“设计空间”中找到,介于现成处理器(如 ARM 产品或德州仪器 DSP,其中功能完全由设计者在软件中描述)和定制的固定功能硬件加速器(其中功能完全由设计者在硬件描述语言中描述)之间。在 ASIP 的情况下,工程师能够同时设计软件和硬件(协同设计),并可以自由选择应用于处理器的应用程序专用程度。

ASIP 中可以定制的内容取决于使用的处理器模板。处理器的一个常见定制部分是指令集。可定制的指令集允许设计者为处理器定义新的应用程序专用指令,以比一组基本软件操作(如加法或移位)更快地实现所需功能。此类特殊指令的示例包括复杂的算术运算、非标准浮点算术运算、应用程序专用精度定点算术运算、多于两个输入的加法器等等。

TCE 对可以添加到处理器设计中的自定义指令类型几乎没有限制。例如,对输入操作数或生成的结果的数量、操作可以执行的时钟周期数量没有限制。除了自定义操作之外,寄存器文件 (RF) 的数量和大小、功能单元 (FU) 的数量和类型以及 RF 与 FU 之间的连接都可以自由定制。

关于传输触发架构

TCE 基于一种简单但可扩展的架构模板,称为传输触发架构 (TTA,不要与“时间触发架构”混淆)。TTA 可以被描述为一个暴露的 datapath VLIW 架构。“暴露的 datapath”部分意味着在功能单元(例如算术逻辑单元或乘法器)和寄存器文件之间发生的數據传输对于程序员来说是显而易见的。换句话说,虽然处理器通常通过定义要执行的操作(包括操作数来源和结果目标的信息)来进行编程,但 TTA 通过定义操作数和结果的传输来进行编程。TTA 这个名字来源于操作执行的方式:当操作数数据移动到功能单元的触发端口时,操作开始执行。经过固定的延迟(从架构角度来看),结果可以从功能单元的输出端口读取到下一个目标。

编程模型可以用一个汇编代码片段示例更容易地说明。

1) 传统的“操作触发”(第一个参数是目标)

  1. ADD R1, R2, R3
  2. MUL R4, R1, R5

2) 传输触发

  1. R2 -> ADD.OPERAND, R3 -> ADD.TRIGGER
  2. ADD.RESULT -> R1
  3. R1 -> MUL.OPERAND, R5 -> MUL.TRIGGER
  4. MUL.RESULT -> R4
传输编程使一些特定的软件优化成为可能,例如软件(寄存器)旁路,这反过来可以实现“死结果读取消除”,可以在所有结果读取都可以直接传输到目标功能单元的情况下应用。这可以减少寄存器(文件)压力。以下是一个示例

3. 带有软件旁路和死结果读取消除的传输触发

  1. R2 -> ADD.OPERAND, R3 -> ADD.TRIGGER
  2. ADD.RESULT -> MUL.OPERAND, R5 -> MUL.TRIGGER
  3. MUL.RESULT -> R4
这里,加法器的结果直接复制到乘法器的操作数输入端口,从而无需使用作为临时存储的通用寄存器 R1。除了减少寄存器压力之外,在多个周期中调度操作数/结果数据移动的自由度还减少了寄存器文件端口压力,这是 TTA 的主要动机之一。在传统的 VLIW 中,RF 端口的数量需要根据连接的功能单元的数量及其最坏情况的 RF 端口要求(最大同时操作数读取和结果写入)进行扩展,从而导致更复杂的 RF,延迟和面积更大。

使用 TCE 进行工具集辅助的处理器设计

从头开始设计新的处理器并非一项简单的任务。需要处理设计、验证和为每个处理器移植高级语言编程工具链,以便程序员满意(为不断变化的目标编写奇特的汇编程序语法会很快变得令人沮丧!)。因此,设计过程应该尽可能自动化,以使对不同处理器架构方案进行实验成为可能。

ASIP 设计工具集的最终目标是尽可能易于使用,例如将高级语言程序作为输入,并生成 VHDL 或 Verilog 中的最佳处理器实现作为结果。它应该在不进行任何用户干预的情况下,有效地将程序并行化到处理器的资源,同时智能地利用自定义指令。根据我们的经验,这种完全自动化的“设计空间探索”往往无法产生足够好的结果,因为协同设计过程通常是人类可以更高效地完成的任务。例如,有时需要将软件重构为可以更好地利用指令级并行性的形式。有时,对于软件算法来说,很难甚至不可能意识到一个看起来很复杂的循环可以被一个简单的单周期自定义指令替换,如果该指令在硬件中实现,等等。因此,我们认为,ASIP 设计工具集的现实用例是尽可能地辅助设计任务,同时仍然留出余地供工程师利用他们在算法或硬件设计领域的知识。这样,如果工程师足够熟练,并且辅助 ASIP 设计任务的工具集足够灵活,处理器设计最终可以达到固定功能硬件加速器的性能,而设计过程也可以在结果足够好时随时停止。

TCE 处于相对成熟的状态,提供了用于设计 TTA 架构的图形工具、架构描述驱动的指令集模拟器、可重定向编译器和支持 VHDL 输出的处理器实现生成器。由于 TCE 使用 TTA(一种静态 ILP 架构)作为其处理器模板,因此最终结果的效率高度依赖于高效的编译器。编译器一直是我们近年来关注的重点,在未来也很可能继续如此。

TCE 中的 LLVM

我们在大约 2006 年的时候接触到了 LLVM 项目。在那之前,我们使用了一个从 MOVE 移植来的旧版 gcc v2.7.0 编译器,MOVE 是 TCE 的前身。不用说,维护这样一段古老的 gcc 代码是一项相当大的挑战,我们积极地尝试寻找更易于使用的工具。

除了基于 C++ 的干净代码库之外,吸引我们从纯粹基于 gcc 的编译器转向 LLVM 的主要原因之一是过程间优化。过程间优化对我们非常有用,因为我们使用的是独立的(不假设具有运行时链接器的操作系统)完全链接的程序。事实上,我们的编译器工具链目前根本不包含任何链接器,而是将其完全链接的 LLVM 位码输入到代码生成阶段。这意味着大多数程序都受益于 LLVM 的全局优化,例如积极的内联和死代码消除,因为编译程序中外部可见的接口可以仅限于启动函数。

在进行了一些探索和使用 LLVM 进行一些实验性的 TTA 代码生成原型设计之后(我认为 LLVM 在那时大约是 1.7 版),我们注意到 LLVM 正在获得越来越多的关注,并开始寻找一种适当的方式来将 LLVM 用于 TCE 代码生成过程的某些部分。

如前所述,我们的目标,除了可定制之外,是传输触发,因此与 LLVM 支持的任何其他架构都不一样。这导致了一些反复试验的编码工作,同时寻找利用 LLVM 现有代码库的最佳方法,同时仍然支持利用 TTA 启用的特殊技巧和额外调度自由度的指令调度。另一个要求是自动重新定位 LLVM 后端以适应不同设计处理器的资源,这导致了一些漫长的工作日才能使其正常运行。

最终,我们采用了一种基于插件的后端方法。TCE 代码生成链现在包含一个工具,它可以从我们的 XML 格式架构描述文件生成 LLVM 后端,使用 C++ 编译器将它们编译成动态库,并在代码生成期间动态加载生成的 LLVM 后端。目前,每个 TTA 被建模为一个简单的操作触发架构,因此支持的指令集和寄存器可以在 TableGen 格式中描述。这样,我们可以使用 LLVM 指令选择和寄存器分配代码,但生成的顺序代码还不是我们可以直接在 TTA 中执行的代码。为了完成代码生成,在 LLVM 寄存器分配之后,我们将机器指令转换为另一种内部表示形式(带有移动作为图节点的 CFG+DDG),并在 TCE 端执行其余的代码生成和 TTA 特定的优化。

我们在 LLVM 代码生成框架中缺少的主要部分是 VLIW 类型的指令调度器。对于 TTA 这种具有程序员可见操作延迟的静态调度架构,编译器指令调度不仅仅是可选执行以获得更多性能的优化。它完全由编译器来调度操作,以确保操作不会过早开始(如果之前操作正在执行)或过早读取结果(如果操作尚未完成)。因此,在我们的案例中,指令调度实际上是获得正确结果的必要条件。此外,为了在像 TTA 或 VLIW 这样的架构中利用指令级并行性,需要一种方法将多个指令(在我们的案例中是数据传输移动)捆绑到单个宽指令(或“一个周期”)中,以便并行执行。最后,在调度期间对处理器资源进行建模(使用资源表或类似方法)的方法是必需的,以便在没有结构性危险检测的情况下生成正确的代码。

在 LLVM 中拥有这样一个 VLIW 风格的调度框架将非常棒,这样我们就可以将更多代码生成迁移到 LLVM 侧。不幸的是,由于时间限制,我们尚未开始着手这项工作,因为我们现有的指令调度器在从 LLVM 代码生成的“顺序 RISC 类操作触发”输出开始时已经足够好了。

总的来说,与 LLVM 及其核心开发人员合作非常愉快,我们希望将来能够更多地为 LLVM 项目做出贡献。继续努力!

未来工作和总结

目前,我们正在研究 GPGPU 风格的工作负载编译问题。我们正在尝试使用 OpenCL 来描述应用程序,以便更轻松地提取并行性,同时仍然为从内核代码调用自定义操作提供干净的支持。还有一些工作正在进行,以扩展 TCE,更好地支持使用多核 ASIP 生成和编译器辅助多线程的任务级并行性。

如果您对该项目感兴趣或有任何问题,请加入邮件列表 [email protected].

希望在那里见到你!

-- Pekka Jääskeläinen,
从项目开始就一直参与 TCE 项目的研究人员。