什么是CoreMark?
来自CoreMark首页的解释是:
CoreMark is a simple, yet sophisticated benchmark that is designed specifically to test the functionality of a processor core. Running CoreMark produces a single-number score allowing users to make quick comparisons between processors.
翻译一下就是:
CoreMark是一个简单而又精密的基准测试程序,是专门为测试处理器核功能而设计的。运行CoreMark会产生一个“单个数字”的分数,(从而)允许用户在(不同)CPU之间进行快速比较。
简单来说,就是一个测试CPU性能的程序,类似PC上的Cinebench、CPU-Z之类的CPU性能测试工具。
了解了CoreMark是什么之后,接下来我们尝试在瑞萨FPB-RA6E1快速原型板上跑一下CoreMark,看看分数是多少。
接下来就可以开始进行CoreMark移植了,为了让移植步骤清晰明确,这里我把移植分为两大部分:
CoreMark是一个基准测试功能程序,在MCU上运行它通常依赖两个基础功能:
首先是RASC创建项目,名为RA6E1_CoreMark,如下图所示:
接着,选择MCU、TrustZone、RTOS,和上一篇一样即可生成Keil项目。
查阅开发板原理图,发现JLink OB和主控芯片直接只有JTAG/SWD接口的连接,没有UART接口连接。因此,只能用Arduino接口的串口了:
可以看到,Arduino接口上:
P109
上,对应功能为TXD9
P110
上,对应功能为RXD9
查阅用户手册,和原理图中的TXD9
、RXD9
也可以对应上:
如果RASC没有关闭,则可以直接进行配置,无需额外操作。
如果RASC已经关闭,可以通过Manage Run-Time Environment菜单,打开已有配置,具体步骤如下图所示:
以上截图自RASC帮主文档,说的比较清楚,图文并茂,这里就不翻译了。
默认创建的RASC项目中,P109、P110为JTAG功能,如下图所示:
因此,将P109、P110设置为TXD、RXD功能之前,需要将调试配置从JTAG修改为SWD;否则,将其设置为UART功能时会报错。
首先,将调试模式从JTAG改为SWD,具体操作如下。
在RASC中,切换到Pins标签页;
找到System:DEBUG -> DEBUG0
,将其设置修改为SWD,引脚修改为P108、P300,如下图所示:
P108、P300引脚也正是和开发板原理图上的SWDIO、SWDCLK相对应。
接着,将P109、P110设置为TXD9、RXD9功能,具体操作如下:
修改完成后,记得Ctrl+S保存修改。
在RASC中,切换到Stacks标签页,依次点击“New Stack”->“Connectivity”->“UART”,添加一个uart组件,如下图所示:
添加UART组件后,鼠标选中UART组件图标,如下图所示:
在Properties视图中,修改如下几个属性:
修改后Ctrl+S保存。
点击“Generate Project Content”按钮,生成Keil项目。
在Keil中,实现UART打印到printf需要重新实现C标准库的putc
函数,具体参考下面两个资料:
打开hal_entry.c
,将头部代码修改为:
#include
#include "hal_data.h"
#include "r_sci_uart.h"
void hal_uart9_init()
{
R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
}
volatile bool hal_uart_tx_done = false;
void hal_uart9_callback(uart_callback_args_t* p_args)
{
switch (p_args->event)
{
case UART_EVENT_RX_CHAR:
break;
case UART_EVENT_TX_COMPLETE:
hal_uart_tx_done = true;
break;
default:
break;
}
}
int fputc(int ch, FILE* f)
{
(void) f;
hal_uart_tx_done = false;
R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&ch, 1);
while (hal_uart_tx_done == false);
return ch;
}
这里使用到的R_SCI_UART_Open
和R_SCI_UART_Write
是FSP的API,可以查阅FSP API参考,找到说明(或者查看头文件的注释):
这样,在hal_entry
中调用了hal_uart9_init
之后,就可以使用printf
输出到串口了。
接下来,在hal_entry
函数的开头添加如下代码,对以上修改进行测试:
hal_uart9_init();
while (1) {
printf("Hello, RA6E1!\r\n");
R_BSP_SoftwareDelay(500, BSP_DELAY_UNITS_MILLISECONDS);
}
调试器设置可以参考上一篇帖子: 【瑞萨FPB-RA6E1快速原型板】简单开箱和RASC+Keil开发环境搭建。
重新编译,烧录,将开发板的Arduino扩展口的D0、D1通过USB转UART连接到PC,则可以在串口上看到输出了:
CoreMark依赖计时功能,在基于ARM Cortex-M系列内核的MCU上,一般使用SysTick进行计时;例如RA4M2是基于Cortex-M33的。
而SysTick属于Cortex-M33的外设,关于SysTick的具体说明可以参考野火的一篇教程。
而在CMSIS(Cortex-M Software Interface Standard)软件包中,已经对Cortex-M系列内核自带的外设的操作接口进行了封装,我们在使用SysTick时一般只需要在代码中:
SysTick_Config
函数设置SysTick中断频率;SysTick_Handler
函数实现SysTick中断处理;主要实现代码如下:
#include
#define TICKS_PER_SECOND 1000
volatile uint32_t g_tick_count = 0;
void hal_systick_init()
{
SysTick_Config(SystemCoreClock / TICKS_PER_SECOND);
}
void SysTick_Handler(void)
{
g_tick_count += 1;
}
uint32_t hal_systick_get()
{
return g_tick_count;
}
这里,hal_systick.h中定义了宏TICKS_PER_SECOND
,值为1000,也就是每秒1000次SysTick中断。
在hal_entry.c
中,将hal_entry
函数的开头的代码修改为:
hal_uart9_init();
hal_systick_init();
while (1) {
printf("ticks: %d\n", hal_systick_get());
R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);
}
对上SysTick计时功能进行测试。
重新编译、下载后,可以看到串口输出如下:
CoreMark代码仓:https://github.com/eembc/coremark.git
将代码下载下来之后,将其中的如下文件和目录拷贝到keil项目的src子目录下:
拷贝完成后,重新使用RASC生成项目,RASC会自动将src目录下的源码文件添加到Keil项目。
CoreMark源码本身已经为了移植做了充分考虑了,移植到新的MCU裸机或OS上一般只需要修改很少代码即可,下面分别介绍。
core_portme.c
文件core_portme.c文件中,需要修改的是计时的几个宏定义,具体如下:
// 注释(或者删除)原来的这三个宏定义
//#define CORETIMETYPE clock_t
//#define GETMYTIME(_t) (*_t = clock())
//#define EE_TICKS_PER_SEC (NSECS_PER_SEC / TIMER_RES_DIVIDER)
// 添加以下代码:
extern uint32_t hal_systick_get();
#define CORETIMETYPE uint32_t
#define GETMYTIME(_t) (*_t = hal_systick_get())
#define EE_TICKS_PER_SEC 1000
core_portme.h
文件core_portme.h文件中,需要新增几个宏定义:
#define ITERATIONS 4000 // 这个值需要保证能够运行至少10秒,可以先写一个值,运行不足10秒会报错,再回来修改
#define FLAGS_STR "-Ofast" // 这个值根据实际的编译优化选项进行填写,在最终输出种原样输出,实际上只要是字符串就行,无关紧要
#define MAIN_HAS_NOARGC 1 // coremark main不使用返回值
#define MAIN_HAS_NORETURN 1
这里我们将FLAGS_STR
定义为-Ofast
,编译选项也相应的修改为-Ofast
。
完成上述修改之后,开始编译可能会遇到如下问题:
下面分别介绍如何解决。
首先是——头文件找不到的问题,Keil中需要通过菜单将对应文件所在目录添加到搜索列表中,具体步骤为:
src
和src/simple
子目录添加上;然后是——main重复定义的问题,原因是FSP框架中已经定义了一个main函数。解决办法是,修改代码,具体修改为:
core_portme.h中,将MAIN_HAS_NOARGC
宏定义代码行修改为:
#define MAIN_HAS_NOARGC 1
core_main.c中,将main函数重命名为coremark_main
:
coreamrk.h中,添加coremark_main
声明的代码:
#if MAIN_HAS_NOARGC
MAIN_RETURN_TYPE
coremark_main(void);
#else
MAIN_RETURN_TYPE
coremark_main(int argc, char *argv[]);
#endif
hal_entry.c中,hal_entry
函数中添加如下代码:
添加#include "coremark.h"
调用hal_systick_init()
和hal_uart9_init()
;
调用coremark_main()
,具体代码如下:
void hal_entry(void)
{
/* TODO: add your own code here */
hal_uart9_init();
hal_systick_init();
printf("CoreMark running on Renesas FPB-RA6E1 ...\r\n");
printf("porting by xusiwei1236: https://bbs.elecfans.com/user/731071/\r\n");
coremark_main();
}
完成上面的操作步骤后,CoreMark项目就可以正常编译了。如果此时直接运行,将会发现系统复位,无法正常运行;通过断点调试,可以发现是因为栈空间不足导致的。
默认情况下,CoreMark使用的是栈内存进行的计算,而RASC默认的栈空间大小为1024
字节(0x400)。需要增大栈内存大小,才可以正常运行CoreMark。
具体设置位于RASC菜单的BSP->Properties界面,将栈空间修改为4096(或者0x1000),如下图所示:
完成以上所有操作后,就可以在RA4M2上正常运行CoreMark程序了。CoreMark程序运行完成后,会输出具体跑分,如下图所示:
跑分为:383.619450
官方跑分,可以在CoreMark项目官网的Scores页面查到,具体如下图:
可以看到官方的跑分为790.27分,和实际测得的383差距不小。
网页上的编译选项为-Omax,Keil中也将编译选项修改为-Omax,跑分如下:
这次测得的跑分为 466.47 ,也比网页上的要少。具体原因不太清楚,也不是本文关注的重点,本文重点介绍如何实现printf输出到UART以及基于SysTick的计时功能。
本文所有代码,以及具体修改,见代码仓:https://gitee.com/swxu/RA6E1_CoreMark.git
代码仓中的提交记录也体现了移植步骤,感兴趣的小伙伴课可以去看每个提交记录的修改。
本篇记录了完整的将CoreMark移植到RA6E1系列MCU的操作步骤,以及遇到问题的解决方法。CoreMark依赖的两个基础功能为——输出和计时,因此本篇介绍首先介绍了如何在RA6E1上实现printf输出到UART;然后介绍了如何实现基于SysTick的计时,最后才介绍CoreMark移植相关的源码修改和编译、运行问题解决。
printf
的说明:https://developer.arm.com/documentation/dui0475/c/the-arm-c-and-c—libraries/redefining-low-level-library-functions-to-enable-direct-use-of-high-level-library-functions