移植Cortex-M程序到RV32中的问题
扫描二维码
随时随地手机看文章
1. 引言
开源架构处理器RISC-V [1],在嵌入式系统中得到了越来越多的应用,近年多家处理器厂商发布了RV32架构MCU。2019年4月,SiFive [2]发布了Freedom E310;2020年2月,兆易创新[3]发布RV32 MCU GD32VF103;沁恒微电子[4]发布CH32V、CH32X、CH32L系列RV32 MCU;先辑半导体[5]发布HPM5XXX和HPM6XXX系列RV32单核和多核MCU;瑞萨电子[6]发布RV32汽车MCU RH850/U2B和通用MCU R9A02G021等。
随着RISC-V MCU的快速发展,其软件生态正逐步完善和丰富。Embedded Studio [7]、IAR [8]等主流商业IDE以及开源IDE eclipse [9]等嵌入式软件开发环境支持RISC-V MCU。一些主流操作系统已经支持RISC-V架构[10]。QEMU RISC-V虚拟化平台[11]支持多种RISC-V处理器仿真;FreeRTOS发布支持RISC-V MCU版本[12];邰阳等[13]基于QEMU RISC-V实现OpenHarmony移植和优化;Nicholas Gordon等[14]将Kitten Lightweight Kernel操作系统移植到RISC-V;Luming Zhang [15]移植并优化RISC-V UFEI Boot;Robert Balas等[16]分析RV32IMC结构特点并提出程序方法。
第二届滴水湖中国RISC-V产业论坛现场调查[17]结果表明,RISC-V架构处理器已经成功应用于无线连接芯片、工业控制芯片,网络通信芯片和边缘计算芯片等场景。目前,在移动通信、物联网和工业控制等嵌入式应用领域,ARM架构处理器占市场主导地位[18]。在一些领域和应用场景,RISC-V将对ARM的市场地位构成挑战。RV32 MCU正争夺ARM Cortex-M MCU的市场份额。将Cortex-M MCU应用程序移植到RV32 MCU,充分利用Cortex-M MCU的成熟生态,将有利于RISC-V的发展和推广。由于RV32和Cortex-M的结构和编程模式存在差异,尽管使用程序开发工具能够将源程序的编译、汇编和链接生成RV32执行程序,但在程序移植过程中仍然会遇到一些问题。
本文根据RV32与Cortex-M在结构、编程模型和过程调用规范等方面的不同点,分析应用程序从RV32移植到Cortex-M过程中遇到的问题,提出解决方法和建议,并进行相关性能分析和比较。
本文第一节简介RISC-V发展状况,讨论RV32的应用前景;第二节比较RV32与Cortex-M异常处理器机制,说明移植中断处理程序面临的问题;第三节对比RV32与Cortex-M指令架构,分析RV32指令模块组合对程序性能的影响;第四节讨论RISC-V与ARM处理器程序过程调用规范的差别,分析其对程序移植影响;第五节对论文工作进行总结。
2. 中断处理
在ARM架构处理器手册[19]和RISC-V架构处理器使用手册[20]中,用编程模式(Programmer’s model)表示处理器架构中涉及程序开发的内容。编程模式通常包括处理器支持的数据类型,通用和特殊功能寄存器、异常和中断响应机制和指令集等,异常和中断处理处理器基本功能。
通常将由外部信号引起的异常称为中断。中断处理是MCU应用系统的关键功能之一。中断处理功能包括两个部分,中断响应机制和中断服务程序(Interrupt Service Routine)。中断响应机制由处理器硬件结构决定,中断服务程序则与中断响应机制相关。
2.1. 中断响应机制
表1列出了Cortex-M和RV32中断响应机制的差异。将应用程序从Cortex-M移植到RV32时,需要修改中断处理功能相关程序。
Table 1. The exception handling of RV32 and Cortex-M
表1. RV32和Cortex-M异常管理
|
||
Cortex-M仅支持向量中断响应,中断向量指向对应的中断服务程序入口,处理器启动后通过寄存器VTOR冲重定位中断向量表。RV32支持向量和非向量两种中断响应方式,处理器器复位时缺省为非向量响应形式,中断响应时指向程序空间的起始地址,通常为0x00。处理器启动后,通过设置机器模式异常向量基址寄存器mtvec设置中断响应模式和异常向量入口地址。
为了保证移植前后功能的一致性,将应用程序移植到RV32后仍保证向量中断响应方式。RV32 MCU复位后首先执行初始化序,将中断向量表地址值的高30位写入RV32寄存器(CSR) mtvec的mtvec [31:2],将01写入mtvec [1:0],选择向量中断响应模式。
目前市场上RV32 MCU外设类型、接口和控制方法与Cortex-MMCU [3] [19] [20]相似,中断向量表结构的结构相近。表2对比了Cortex-M MCU STM32F429与RV32 MCU GD32VF103中断向量表结构。
Table 2. The interrupt vector table of STM32F429 and GD32VF103
表2. STM32F429与GD32VF103中断向量表结构
|
||
如表2所示,RV32 MCU中断向量中,除向量0外,每个32位向量值是对应中断服务程序的入口地址。由于复位后RV32缺省为非向量中断响应模式,需要首先设置中断响应模式和向量表基地址,向量0是跳转指令,跳转到复位启动程序入口。
2.2. 上下文处理
Cortex-MMCU响应中断请求时,硬件自动依次将xPSR,PC,LR,R12以及R3‐R0压入栈中,保存上下文;中断服务程序返回时,硬件自动从栈中将数据弹回对应寄存器,恢复上下文。
RV32 MCU响应中断请求时硬件自动保存PC到控制和状态寄存器mepc,并保存特权模式至mstatus.MPP,但不保存上下文相关的通用寄存器和其他特殊功能寄存器。从中断服务程序返回时,硬件仅自动恢复寄存器mstatus和PC。将Cortex-MMCU中断服务程序移植到RV32 MCU时,需要在中断服务程序中添加保存和恢复上下文语句或函数。
Cortex-MMCU应用程序和中断服务程序遵守ARM过程调用规范AAPCS [17],硬件在中断响应过程中自动保存4个特殊功能寄存器和4个参数寄存器r0-r3或称为a0-a3。RISC-V应用程序过程调用规范约定8个参数寄存器x10-x17或称为a0-a7。与Cortex-MMCU自动保存的上下文内容对照,RV32 MCU中断服务程序中需要保存和恢复上下文内容最小包括参数寄存器a0-a7和特殊功能寄存器。表3列出了RV32 MCU中断服务程序中保存和恢复上下文最小集的程序语句。
Table 3. The programs of save and restore context in RV32 MCU interrupt service routine
表3. RV32 MCU中断服务程序保存和恢复上下文程序语句
在中断服务程序的入口添加表3中保存现场语句或函数,在返回语句前添加恢复现场语句或函数。
对于定制中断响应机制Gd32VF103 [21]等MCU,则需要依据其使用手册编写中断处理相关的程序。
3. 指令集模块
Cortex-M MCU采用Thumb指令集。RV32 MCU采用模块化指令,生成应用程序时可以选择指令集模块及其组合。将Cortex-M MCU应用程序移植到RV32 MCU时,选择与Thumb指令集相应功能指令集模块组合,以保持移植前后程序功能和性能的一致性。
为了便于分析和评估性能,本文选择STM32F429和FE310分别作为Cortex-M MCU和RV32 MCU样本,使用CoresMark [22]做性能分析;程序生成工具选择Segger公司Embeddedstudio [7];汇编和编译器选择gcc,版本为gnu4.2.1。
STM32F429内核为Cortex-M4 [23],指令集为Thumb2。FE310支持RV32imac指令集模块,处理器指令集功能是所有子模块功能的并集。表4对STM32F29与FE310指令集部分功能进行了比较。
Table 4. The partial functions of STM32F29 and FE310 instruction sets
表4. STM32F29与 FE310指令集部分功能
生成RV32 MCU应用程序时,选择不同的指令集或指令集组合,将会影响程序的性能。
3.1. RV32i vs RV32im
Embedded studio创建FE310应用程序工程时缺省使用RV32i指令集模块,该模块没有乘法和除法指令。程序中的乘除法运算能够正常编译,编译器通过调用由RV32i指令实现的运算函数库实现。如果在编译时选择RV32im指令集模块组合,则编译器将直接使用乘法和除法指令实现运算,提高程序执行速度。表5列出了c程序采用RV32i和RV32im指令集编译后生成汇编指令对照。
Table 5. The assembly instructions for RV32i and RV32im
表5. RV32i与RV32im汇编指令
表6列出了选择RV32i与RV32im指令集在FE310模拟器上运行CoreMark得分。结果表明,如果应用程序中含有乘法和除法运算,将RV32i改为RV32im指令集将提高程序运行速度。
Table 6. The CoreMark scores of RV32i and RV32im
表6. RV32i与RV32im CoreMark得分
|
||
3.2. RV32im vs RV32imc
由于MCU处理器中存储资源受限,对ROM和RAM的需求是开发MCU应用程序关注的重点之一。Cortex-M采用支持16位指令Thumb指令模式,以减少应用程序的体积。RV32i和RV32im指令长度是32位。对于同一源程序,使用RV32im指令集生成的二进制目标程序的长度将会大于Cortex-M目标程序的长度,从而增加对存储资源的需求。选择RV32imc指令集组合,将指令长度从32位变为16位,减少所生成二进制目标程序的长度。表7列出了Coremark 4个主要文件不同指令集生成的二进制代码长度。
Table 7. The length of binary code generated from CoreMark’s main files (in bytes)
表7. CoreMark主要文件生成的二进制代码长度(字节)
|
||||
从表7可见,RV32i程序的平均长度是Cortex-M4的2.05倍,RV32imc程序的平均长度是Cortex-M4的1.2倍。可见,在将程序从Cortex-M移植到rv32时,选择RV32imc指令集组合,将基本满足原系统对存储资源限制要求。
添加指令集模块“A”,选择RV32imac指令集组合,编译后主要文件二进制代码长度与RV32imac完全相同,CoreMark得分2.34/MHz,与选择RV32imc指令集组合时相近。
4. 过程调用规范
过程调用规范(Procedure Call Standard)定义了应用程序二进制接口(Application Binary Interface),通常包括函数或过程调用中参数传递和结果返回方式,处理器寄存器使用,以及数据类型处理等内容。将Cortex-MMCU应用程序移植到RV32 MCU时,需要考虑ARM处理器和RISC-V处理器过程调用规范之间的差异。本节将讨论函数调用过程参数传递方式的差别对移植程序带来的影响。
ARM架构过程调用规范(AAPCS) [24]约定:在函数和过程调用过程中,调用者(主程序)通过4个寄存器r0-r3或称为a0-a3,向函数(被调用者)传递参数。如果参数超过4个寄存器数值范围,超出部分利用栈传递;函数通过a0-a1返回结果。RISV-V过程调用规范(RISC-V Procedure Calling Convetion) [25]约定:调用者通过8个寄存器x10-x17或称为a0-a7,向被调用者传递参数。如果参数超过8个寄存器数值范围,超出部分利用栈传递;被调用者利用a0-a1返回结果。表8列出了6个整数型参数C语言函数编译后所生成的Cortex-M和RV32imac汇编函数。
Table 8. The generated assembly functions from C function
表8. 由C函数生成汇编函数
如表8汇编函数所示,Cortex-M4函数,参数1到参数4通过a0-a3传递,参数5和6通过栈传递。在RV32imac函数中,参数1-6通过a0-a5传递。Cortex-M4和RV32imac利用a0返回结果。
由于访问栈的延时高于访问寄存器,在设计Cortex-M MCU应用函数时通常使参数不超过4*32位。将Cortex-M MCU应用程序移植到RV32 MCU时,利用多达8*32位寄存器参数传递特性,将减少函数和过程调用带来的延时。
5. 总结与展望
本文从MCU中断处理机制,指令集模块组合,以及程序过程调用规范三方面比较Cortex-M和RV32 MCU的差别,分析了这些差别对将应用程序从Cortex-M MCU移植到RV32 MCU的影响。为了兼容中断处理程序,将RV32 MCU设置为向量中断响应方式,并在中断服务程序中添加保存和恢复上文语句。在生成应用程序时选择RV32imc指令集组合,实现高性能和小体积的应用程序。利用MCU寄存器在函数和过程调用过程传递更多参数,降低调用过程中的延时。
RV32 MCU与Cortex-M MCU指令差别很大,指令之间的差异对程序移植性能优化带来挑战。未来将进一步探讨RV32程序移植中的性能优化问题。