介绍coremark。
coremark简介
该标准于2009年由EEMBC组织的Shay Gla-On提出,并且试图将其发展成为工业标准,从而代替陈旧的Dhrystone标准。代码使用C语言写成,包含如下的运算法则:列举(寻找并排序),数学矩阵操作(普通矩阵运算)和状态机(用来确定输入流中是否包含有效数字),最后还包括CRC(循环冗余校验)。
coremark移植
coremark源码:
1 | https://github.com/eembc/coremark |
riscv移植源码:
1 | https://github.com/riscv-boom/riscv-coremark |
Coremark的代码主要分为两部分,一部分是不能修改的程序主体,另一部分是为了不同平台移植的代码。所以移植的工作主要体现在子目录下的core_portme文件中。
1 | ├── barebones --移植到裸机环境下需要修改的目录 |
baremetal
修改core_portme.c
1
2
3
4
5
6
7
8
9
10
11// Defined for RISCV
#define NSECS_PER_SEC 1000000000 // TODO: What freq are we assuming?
#define EE_TIMER_TICKER_RATE 1000 // TODO: What is this?
#define CORETIMETYPE clock_t
#define read_csr(reg) ({ unsigned long __tmp; \
asm volatile ("csrr %0, " #reg : "=r"(__tmp)); \
__tmp; })
#define GETMYTIME(_t) (*_t=read_csr(cycle))
#define MYTIMEDIFF(fin,ini) ((fin)-(ini))
#define TIMER_RES_DIVIDER 1
#define SAMPLE_TIME_IMPLEMENTATION 1===>
1
2
3
4
5
6
7
8
9
10
11// Defined for RISCV
#define NSECS_PER_SEC 1000000 // 假设频率为1MHz
#define EE_TIMER_TICKER_RATE 1000 // 这个没有用到,不用关心
#define CORETIMETYPE clock_t
#define read_csr(reg) ({ unsigned long __tmp; \
asm volatile ("csrr %0, " #reg : "=r"(__tmp)); \
__tmp; })
#define GETMYTIME(_t) (*_t=read_csr(cycle))
#define MYTIMEDIFF(fin,ini) ((fin)-(ini))
#define TIMER_RES_DIVIDER 1
#define SAMPLE_TIME_IMPLEMENTATION 1修改core_portme.h
1
typedef unsigned int ee_u32;
===>
1
typedef signed int ee_u32; // 由于riscv编译器的原因,改为signed会使指令数更少,从而性能更高
修改core_portme.mak
1
2
3RISCVTOOLS=$(RISCV) // 修改编译器
PORT_CFLAGS = -O2 -mcmodel=medany -static -std=gnu99 -fno-common -nostdlib -nostartfiles -lm -lgcc -T $(PORT_DIR)/link.ld // 可以修改编译选项
ITERATIONS=10 // 一般baremetal跑,跑10次就够了修改syscalls.c
增加
%f
的支持1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#define CIFRAS 10E3
static void vprintfmt(void (*putch)(int, void**), void **putdat, const char *fmt, va_list ap)
{
register const char* p;
const char* last_fmt;
register int ch, err;
unsigned long long num;
int base, lflag, width, precision, altflag;
char padc;
double numfloat; // support %f
double f_tmp; // support %f
.....
case 'f':
numfloat = va_arg(ap, double);
f_tmp = numfloat - ((int)numfloat);
if(f_tmp>=0.1)
printf("%d.%d",(int)numfloat, (int)((numfloat-((int)numfloat))*CIFRAS));
else if(f_tmp>=0.01)
printf("%d.0%d",(int)numfloat, (int)((numfloat-((int)numfloat))*CIFRAS));
else if(f_tmp>=0.001)
printf("%d.00%d",(int)numfloat, (int)((numfloat-((int)numfloat))*CIFRAS));
else if(f_tmp>=0.0001)
printf("%d.000%d",(int)numfloat, (int)((numfloat-((int)numfloat))*CIFRAS));
else
printf("%d.0000",(int)numfloat);
break;
.....编译命令
1
make PORT_DIR=../riscv64-baremetal compile
linux
在linux环境下跑的只需要修改下真实频率,和编译参数,就可以直接跑了。
修改core_portme.c
1
2
3
4
5
6
7
8
9
10
11// Defined for RISCV
#define NSECS_PER_SEC 1000000000 // 修改为真实频率
#define EE_TIMER_TICKER_RATE 1000 // TODO: What is this?
#define CORETIMETYPE clock_t
#define read_csr(reg) ({ unsigned long __tmp; \
asm volatile ("csrr %0, " #reg : "=r"(__tmp)); \
__tmp; })
#define GETMYTIME(_t) (*_t=read_csr(cycle))
#define MYTIMEDIFF(fin,ini) ((fin)-(ini))
#define TIMER_RES_DIVIDER 1
#define SAMPLE_TIME_IMPLEMENTATION 1编译命令
1
make PORT_DIR=../riscv64 compile
coremark分数
coremark的分数含义是:一定时间内跑了多少次coremark。单位是coremark/MHz
在移植完之后,运行coremark可以得到如下信息。
1 | 2K performance run parameters for coremark. |
从上面Log信息可以看出:
- CPU频率是1000MHz
- 跑了16秒,每秒跑了6700次coremark
- 所以最后的得分是:6700/1000 = 6.7 Coremark/MHz
可以看出,最后的得分和频率没有什么关系,所以在仿真阶段,一般可以把NSECS_PER_SEC
设置为 1000000
,也就是1MHz
,然后得到的Iteration/sec
就是最后的分数了。
也就是说coremark的分数,基本和IPC
是正比关系了。
根据经验,在riscv架构下,coremark iteration一次,指令数大概25W左右,当IPC
为 1.7
左右,coremark分数大概 6.7
左右。