mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-22 21:07:34 -04:00
[sw] Add Coremark makefile and support files
Signed-off-by: Greg Chadwick <gac@lowrisc.org>
This commit is contained in:
parent
217261f599
commit
6fc4110acf
7 changed files with 1188 additions and 0 deletions
91
examples/sw/benchmarks/README.md
Normal file
91
examples/sw/benchmarks/README.md
Normal file
|
@ -0,0 +1,91 @@
|
|||
# Benchmarks
|
||||
|
||||
This directory contains benchmarks that can be run on ibex simple system.
|
||||
Benchmarks may rely on code external to this directory (e.g. it may be found in
|
||||
`vendor/`) see the specific benchmark information below for details on how to
|
||||
build and run each benchmark and where benchmark code is located.
|
||||
|
||||
## Building Simulation
|
||||
|
||||
All of these benchmarks run on Simple System. A verilator simulation suitable
|
||||
for running them can be built with:
|
||||
|
||||
```
|
||||
fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_simple_system --RV32M=1 --RV32E=0
|
||||
```
|
||||
|
||||
See examples/simple_system/README.md for full details.
|
||||
|
||||
## Coremark
|
||||
|
||||
Coremark (https://www.eembc.org/coremark/ https://github.com/eembc/coremark) is
|
||||
an industry standard benchmark with results available for a wide variety of
|
||||
systems.
|
||||
|
||||
The Coremark source is vendored into the Ibex repository at
|
||||
`vendor/eembc_coremark`. Support structure and a makefile to build Coremark for
|
||||
running on simple system is found in `examples/sw/benchmarks/coremark`.
|
||||
|
||||
To build Coremark:
|
||||
|
||||
```
|
||||
make -C ./examples/sw/benchmarks/coremark/
|
||||
```
|
||||
|
||||
To run Coremark (after building a suitable simulator binary, see above):
|
||||
|
||||
```
|
||||
build/lowrisc_ibex_ibex_simple_system_0/sim-verilator/Vibex_simple_system --meminit=ram,examples/sw/benchmarks/coremark/coremark.elf
|
||||
```
|
||||
|
||||
The simulator outputs the performance counter values observed for the benchmark
|
||||
(the counts do not include anything from pre or post benchmark loops).
|
||||
|
||||
Coremark should output (to `ibex_simple_system.log`) something like the
|
||||
following:
|
||||
|
||||
```
|
||||
2K performance run parameters for coremark.
|
||||
CoreMark Size : 666
|
||||
Total ticks : 4244465
|
||||
Total time (secs): 8
|
||||
Iterations/Sec : 1
|
||||
Iterations : 10
|
||||
Compiler version : GCC
|
||||
Compiler flags :
|
||||
Memory location :
|
||||
seedcrc : 0xe9f5
|
||||
[0]crclist : 0xe714
|
||||
[0]crcmatrix : 0x1fd7
|
||||
[0]crcstate : 0x8e3a
|
||||
[0]crcfinal : 0xfcaf
|
||||
Correct operation validated. See README.md for run and reporting rules.
|
||||
```
|
||||
|
||||
A Coremark score is given as the number of iterations executed per second. The
|
||||
Coremark binary is hard-coded to execute 10 iterations (see
|
||||
`examples/sw/benchmarks/coremark/Makefile` if you wish to alter this). To obtain
|
||||
a useful Coremark score from the simulation you need to choose a clock speed the
|
||||
Ibex implementation you are interested in would run at, e.g. 100 MHz, taking
|
||||
the above example:
|
||||
|
||||
* 10 iterations take 4244465 clock cycles
|
||||
* So at 100 MHz Ibex would execute (100 * 10^6) / (4244465 / 10) = 235.6
|
||||
Iterations in 1 second.
|
||||
* Coremark (at 100 MHz) is 235.6
|
||||
|
||||
Coremark/MHz is often used instead of a raw Coremark score. The example above
|
||||
gives a Coremark/MHz of 2.36 (235.6 / 100 rounded to 2 decimal places).
|
||||
|
||||
To directly produce Coremark/MHz from the number of iterations (I) and total
|
||||
ticks (T) use the follow formula:
|
||||
|
||||
```
|
||||
Coremark/MHz = (10 ^ 6) * I / T
|
||||
```
|
||||
|
||||
Note that `core_main.c` from Coremark has had a minor modification to prevent it
|
||||
from reporting an error if it executes for less than 10 seconds. This violates
|
||||
the run reporting rules (though does not effect benchmark execution). It is
|
||||
trivial to restore `core_main.c` to the version supplied by EEMBC in the
|
||||
Coremark repository if an official result is desired.
|
19
examples/sw/benchmarks/coremark/Makefile
Normal file
19
examples/sw/benchmarks/coremark/Makefile
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Build coremark benchmark for Ibex Simple System
|
||||
|
||||
COREMARK_DIR = ../../../../vendor/eembc_coremark
|
||||
|
||||
export PORT_DIR = $(CURDIR)/ibex
|
||||
export ITERATIONS = 10
|
||||
export OPATH = $(CURDIR)/
|
||||
|
||||
# Export OPATH above doesn't seem to work so need to explicitly give it on the
|
||||
# make command line
|
||||
all:
|
||||
$(MAKE) -C $(COREMARK_DIR)
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(COREMARK_DIR) clean
|
183
examples/sw/benchmarks/coremark/ibex/core_portme.c
Normal file
183
examples/sw/benchmarks/coremark/ibex/core_portme.c
Normal file
|
@ -0,0 +1,183 @@
|
|||
// Copyright lowRISC contributors.
|
||||
// Copyright 2018 Embedded Microprocessor Benchmark Consortium (EEMBC)
|
||||
// Original Author: Shay Gal-on
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "core_portme.h"
|
||||
|
||||
#include "coremark.h"
|
||||
|
||||
#include "simple_system_common.h"
|
||||
|
||||
#if VALIDATION_RUN
|
||||
volatile ee_s32 seed1_volatile = 0x3415;
|
||||
volatile ee_s32 seed2_volatile = 0x3415;
|
||||
volatile ee_s32 seed3_volatile = 0x66;
|
||||
#endif
|
||||
#if PERFORMANCE_RUN
|
||||
volatile ee_s32 seed1_volatile = 0x0;
|
||||
volatile ee_s32 seed2_volatile = 0x0;
|
||||
volatile ee_s32 seed3_volatile = 0x66;
|
||||
#endif
|
||||
#if PROFILE_RUN
|
||||
volatile ee_s32 seed1_volatile = 0x8;
|
||||
volatile ee_s32 seed2_volatile = 0x8;
|
||||
volatile ee_s32 seed3_volatile = 0x8;
|
||||
#endif
|
||||
volatile ee_s32 seed4_volatile = ITERATIONS;
|
||||
volatile ee_s32 seed5_volatile = 0;
|
||||
/* Porting : Timing functions
|
||||
How to capture time and convert to seconds must be ported to whatever is
|
||||
supported by the platform. e.g. Read value from on board RTC, read value from
|
||||
cpu clock cycles performance counter etc. Sample implementation for standard
|
||||
time.h and windows.h definitions included.
|
||||
*/
|
||||
CORETIMETYPE barebones_clock() {
|
||||
ee_u32 result;
|
||||
|
||||
PCOUNT_READ(mcycle, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Define : TIMER_RES_DIVIDER
|
||||
Divider to trade off timer resolution and total time that can be
|
||||
measured.
|
||||
|
||||
Use lower values to increase resolution, but make sure that overflow
|
||||
does not occur. If there are issues with the return value overflowing,
|
||||
increase this value.
|
||||
*/
|
||||
#define GETMYTIME(_t) (*_t = barebones_clock())
|
||||
#define MYTIMEDIFF(fin, ini) ((fin) - (ini))
|
||||
#define TIMER_RES_DIVIDER 1
|
||||
#define SAMPLE_TIME_IMPLEMENTATION 1
|
||||
#define CLOCKS_PER_SEC 500000
|
||||
#define EE_TICKS_PER_SEC (CLOCKS_PER_SEC / TIMER_RES_DIVIDER)
|
||||
|
||||
void pcount_read(uint32_t pcount_out[]) {
|
||||
PCOUNT_READ(minstret, pcount_out[0]);
|
||||
PCOUNT_READ(mhpmcounter3, pcount_out[1]);
|
||||
PCOUNT_READ(mhpmcounter4, pcount_out[2]);
|
||||
PCOUNT_READ(mhpmcounter5, pcount_out[3]);
|
||||
PCOUNT_READ(mhpmcounter6, pcount_out[4]);
|
||||
PCOUNT_READ(mhpmcounter7, pcount_out[5]);
|
||||
PCOUNT_READ(mhpmcounter8, pcount_out[6]);
|
||||
PCOUNT_READ(mhpmcounter9, pcount_out[7]);
|
||||
PCOUNT_READ(mhpmcounter10, pcount_out[8]);
|
||||
}
|
||||
|
||||
const char *pcount_names[] = {"Instructions Retired",
|
||||
"LSU Busy",
|
||||
"IFetch wait",
|
||||
"Loads",
|
||||
"Stores",
|
||||
"Jumps",
|
||||
"Branches",
|
||||
"Taken Branches",
|
||||
"Compressed Instructions"};
|
||||
|
||||
const uint32_t pcount_num = sizeof(pcount_names) / sizeof(char *);
|
||||
|
||||
void dump_pcounts() {
|
||||
uint32_t pcounts[pcount_num];
|
||||
|
||||
pcount_read(pcounts);
|
||||
ee_printf(
|
||||
"Performance Counters\n"
|
||||
"--------------------\n");
|
||||
for (uint32_t i = 0; i < pcount_num; ++i) {
|
||||
ee_printf("%s: %u\n", pcount_names[i], pcounts[i]);
|
||||
}
|
||||
ee_printf("\n");
|
||||
}
|
||||
|
||||
/** Define Host specific (POSIX), or target specific global time variables. */
|
||||
static CORETIMETYPE start_time_val, stop_time_val;
|
||||
|
||||
/* Function : start_time
|
||||
This function will be called right before starting the timed portion of
|
||||
the benchmark.
|
||||
|
||||
Implementation may be capturing a system timer (as implemented in the
|
||||
example code) or zeroing some system parameters - e.g. setting the cpu clocks
|
||||
cycles to 0.
|
||||
*/
|
||||
void start_time(void) {
|
||||
pcount_enable(0);
|
||||
pcount_reset();
|
||||
pcount_enable(1);
|
||||
GETMYTIME(&start_time_val);
|
||||
}
|
||||
|
||||
/* Function : stop_time
|
||||
This function will be called right after ending the timed portion of the
|
||||
benchmark.
|
||||
|
||||
Implementation may be capturing a system timer (as implemented in the
|
||||
example code) or other system parameters - e.g. reading the current value of
|
||||
cpu cycles counter.
|
||||
*/
|
||||
void stop_time(void) {
|
||||
GETMYTIME(&stop_time_val);
|
||||
pcount_enable(0);
|
||||
}
|
||||
|
||||
/* Function : get_time
|
||||
Return an abstract "ticks" number that signifies time on the system.
|
||||
|
||||
Actual value returned may be cpu cycles, milliseconds or any other
|
||||
value, as long as it can be converted to seconds by <time_in_secs>. This
|
||||
methodology is taken to accomodate any hardware or simulated platform. The
|
||||
sample implementation returns millisecs by default, and the resolution is
|
||||
controlled by <TIMER_RES_DIVIDER>
|
||||
*/
|
||||
CORE_TICKS get_time(void) {
|
||||
CORE_TICKS elapsed = (CORE_TICKS)(MYTIMEDIFF(stop_time_val, start_time_val));
|
||||
return elapsed;
|
||||
}
|
||||
/* Function : time_in_secs
|
||||
Convert the value returned by get_time to seconds.
|
||||
|
||||
The <secs_ret> type is used to accomodate systems with no support for
|
||||
floating point. Default implementation implemented by the EE_TICKS_PER_SEC
|
||||
macro above.
|
||||
*/
|
||||
secs_ret time_in_secs(CORE_TICKS ticks) {
|
||||
secs_ret retval = ((secs_ret)ticks) / (secs_ret)EE_TICKS_PER_SEC;
|
||||
return retval;
|
||||
}
|
||||
|
||||
ee_u32 default_num_contexts = 1;
|
||||
|
||||
/* Function : portable_init
|
||||
Target specific initialization code
|
||||
Test for some common mistakes.
|
||||
*/
|
||||
void portable_init(core_portable *p, int *argc, char *argv[]) {
|
||||
ee_printf("Ibex coremark platform init...\n");
|
||||
if (sizeof(ee_ptr_int) != sizeof(ee_u8 *)) {
|
||||
ee_printf(
|
||||
"ERROR! Please define ee_ptr_int to a type that holds a pointer!\n");
|
||||
}
|
||||
if (sizeof(ee_u32) != 4) {
|
||||
ee_printf("ERROR! Please define ee_u32 to a 32b unsigned type!\n");
|
||||
}
|
||||
p->portable_id = 1;
|
||||
}
|
||||
/* Function : portable_fini
|
||||
Target specific final code
|
||||
*/
|
||||
void portable_fini(core_portable *p) {
|
||||
dump_pcounts();
|
||||
|
||||
CORE_TICKS elapsed = get_time();
|
||||
float coremark_mhz;
|
||||
|
||||
coremark_mhz = (1000000.0f * (float)ITERATIONS) / elapsed;
|
||||
|
||||
ee_printf("Coremark / MHz: %f\n", coremark_mhz);
|
||||
|
||||
p->portable_id = 0;
|
||||
}
|
201
examples/sw/benchmarks/coremark/ibex/core_portme.h
Normal file
201
examples/sw/benchmarks/coremark/ibex/core_portme.h
Normal file
|
@ -0,0 +1,201 @@
|
|||
// Copyright lowRISC contributors.
|
||||
// Copyright 2018 Embedded Microprocessor Benchmark Consortium (EEMBC)
|
||||
// Original Author: Shay Gal-on
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* Topic : Description
|
||||
This file contains configuration constants required to execute on
|
||||
different platforms
|
||||
*/
|
||||
|
||||
#ifndef CORE_PORTME_H
|
||||
#define CORE_PORTME_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
extern unsigned int _stack_start;
|
||||
|
||||
/************************/
|
||||
/* Data types and settings */
|
||||
/************************/
|
||||
/* Configuration : HAS_FLOAT
|
||||
Define to 1 if the platform supports floating point.
|
||||
*/
|
||||
#ifndef HAS_FLOAT
|
||||
#define HAS_FLOAT 1
|
||||
#endif
|
||||
/* Configuration : HAS_TIME_H
|
||||
Define to 1 if platform has the time.h header file,
|
||||
and implementation of functions thereof.
|
||||
*/
|
||||
#ifndef HAS_TIME_H
|
||||
#define HAS_TIME_H 0
|
||||
#endif
|
||||
/* Configuration : USE_CLOCK
|
||||
Define to 1 if platform has the time.h header file,
|
||||
and implementation of functions thereof.
|
||||
*/
|
||||
#ifndef USE_CLOCK
|
||||
#define USE_CLOCK 0
|
||||
#endif
|
||||
/* Configuration : HAS_STDIO
|
||||
Define to 1 if the platform has stdio.h.
|
||||
*/
|
||||
#ifndef HAS_STDIO
|
||||
#define HAS_STDIO 0
|
||||
#endif
|
||||
/* Configuration : HAS_PRINTF
|
||||
Define to 1 if the platform has stdio.h and implements the printf
|
||||
function.
|
||||
*/
|
||||
#ifndef HAS_PRINTF
|
||||
#define HAS_PRINTF 0
|
||||
#endif
|
||||
|
||||
/* Definitions : COMPILER_VERSION, COMPILER_FLAGS, MEM_LOCATION
|
||||
Initialize these strings per platform
|
||||
*/
|
||||
#ifndef COMPILER_VERSION
|
||||
#ifdef __GNUC__
|
||||
#define COMPILER_VERSION "GCC"
|
||||
#else
|
||||
#define COMPILER_VERSION "unknown"
|
||||
#endif
|
||||
#endif
|
||||
#ifndef COMPILER_FLAGS
|
||||
#define COMPILER_FLAGS "" /* "Please put compiler flags here (e.g. -o3)" */
|
||||
#endif
|
||||
#ifndef MEM_LOCATION
|
||||
#define MEM_LOCATION "STACK"
|
||||
#endif
|
||||
|
||||
/* Data Types :
|
||||
To avoid compiler issues, define the data types that need to be used for
|
||||
8b, 16b and 32b in <core_portme.h>.
|
||||
|
||||
*Imprtant* :
|
||||
ee_ptr_int needs to be the data type used to hold pointers, otherwise
|
||||
coremark may fail!!!
|
||||
*/
|
||||
typedef signed short ee_s16;
|
||||
typedef unsigned short ee_u16;
|
||||
typedef signed int ee_s32;
|
||||
typedef double ee_f32;
|
||||
typedef unsigned char ee_u8;
|
||||
typedef unsigned int ee_u32;
|
||||
typedef ee_u32 ee_ptr_int;
|
||||
typedef size_t ee_size_t;
|
||||
#define NULL ((void *)0)
|
||||
/* align_mem :
|
||||
This macro is used to align an offset to point to a 32b value. It is
|
||||
used in the Matrix algorithm to initialize the input memory blocks.
|
||||
*/
|
||||
#define align_mem(x) (void *)(4 + (((ee_ptr_int)(x)-1) & ~3))
|
||||
|
||||
/* Configuration : CORE_TICKS
|
||||
Define type of return from the timing functions.
|
||||
*/
|
||||
#define CORETIMETYPE ee_u32
|
||||
typedef ee_u32 CORE_TICKS;
|
||||
|
||||
/* Configuration : SEED_METHOD
|
||||
Defines method to get seed values that cannot be computed at compile
|
||||
time.
|
||||
|
||||
Valid values :
|
||||
SEED_ARG - from command line.
|
||||
SEED_FUNC - from a system function.
|
||||
SEED_VOLATILE - from volatile variables.
|
||||
*/
|
||||
#ifndef SEED_METHOD
|
||||
#define SEED_METHOD SEED_VOLATILE
|
||||
#endif
|
||||
|
||||
/* Configuration : MEM_METHOD
|
||||
Defines method to get a block of memry.
|
||||
|
||||
Valid values :
|
||||
MEM_MALLOC - for platforms that implement malloc and have malloc.h.
|
||||
MEM_STATIC - to use a static memory array.
|
||||
MEM_STACK - to allocate the data block on the stack (NYI).
|
||||
*/
|
||||
#ifndef MEM_METHOD
|
||||
#define MEM_METHOD MEM_STACK
|
||||
#endif
|
||||
|
||||
/* Configuration : MULTITHREAD
|
||||
Define for parallel execution
|
||||
|
||||
Valid values :
|
||||
1 - only one context (default).
|
||||
N>1 - will execute N copies in parallel.
|
||||
|
||||
Note :
|
||||
If this flag is defined to more then 1, an implementation for launching
|
||||
parallel contexts must be defined.
|
||||
|
||||
Two sample implementations are provided. Use <USE_PTHREAD> or <USE_FORK>
|
||||
to enable them.
|
||||
|
||||
It is valid to have a different implementation of <core_start_parallel>
|
||||
and <core_end_parallel> in <core_portme.c>, to fit a particular architecture.
|
||||
*/
|
||||
#ifndef MULTITHREAD
|
||||
#define MULTITHREAD 1
|
||||
#define USE_PTHREAD 0
|
||||
#define USE_FORK 0
|
||||
#define USE_SOCKET 0
|
||||
#endif
|
||||
|
||||
/* Configuration : MAIN_HAS_NOARGC
|
||||
Needed if platform does not support getting arguments to main.
|
||||
|
||||
Valid values :
|
||||
0 - argc/argv to main is supported
|
||||
1 - argc/argv to main is not supported
|
||||
|
||||
Note :
|
||||
This flag only matters if MULTITHREAD has been defined to a value
|
||||
greater then 1.
|
||||
*/
|
||||
#ifndef MAIN_HAS_NOARGC
|
||||
#define MAIN_HAS_NOARGC 0
|
||||
#endif
|
||||
|
||||
/* Configuration : MAIN_HAS_NORETURN
|
||||
Needed if platform does not support returning a value from main.
|
||||
|
||||
Valid values :
|
||||
0 - main returns an int, and return value will be 0.
|
||||
1 - platform does not support returning a value from main
|
||||
*/
|
||||
#ifndef MAIN_HAS_NORETURN
|
||||
#define MAIN_HAS_NORETURN 0
|
||||
#endif
|
||||
|
||||
/* Variable : default_num_contexts
|
||||
Not used for this simple port, must cintain the value 1.
|
||||
*/
|
||||
extern ee_u32 default_num_contexts;
|
||||
|
||||
typedef struct CORE_PORTABLE_S { ee_u8 portable_id; } core_portable;
|
||||
|
||||
/* target specific init/fini */
|
||||
void portable_init(core_portable *p, int *argc, char *argv[]);
|
||||
void portable_fini(core_portable *p);
|
||||
|
||||
#if !defined(PROFILE_RUN) && !defined(PERFORMANCE_RUN) && \
|
||||
!defined(VALIDATION_RUN)
|
||||
#if (TOTAL_DATA_SIZE == 1200)
|
||||
#define PROFILE_RUN 1
|
||||
#elif (TOTAL_DATA_SIZE == 2000)
|
||||
#define PERFORMANCE_RUN 1
|
||||
#else
|
||||
#define VALIDATION_RUN 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int ee_printf(const char *fmt, ...);
|
||||
|
||||
#endif /* CORE_PORTME_H */
|
90
examples/sw/benchmarks/coremark/ibex/core_portme.mak
Executable file
90
examples/sw/benchmarks/coremark/ibex/core_portme.mak
Executable file
|
@ -0,0 +1,90 @@
|
|||
# Copyright lowRISC contributors.
|
||||
# Copyright 2018 Embedded Microprocessor Benchmark Consortium (EEMBC)
|
||||
# Original Author: Shay Gal-on
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
OUTFILES = $(OPATH)coremark.dis $(OPATH)coremark.map
|
||||
|
||||
NAME = coremark
|
||||
PORT_CLEAN := $(OUTFILES)
|
||||
SIMPLE_SYSTEM_COMMON = ../../examples/sw/simple_system/common
|
||||
EXT_SRCS = $(wildcard $(SIMPLE_SYSTEM_COMMON)/*.c)
|
||||
CRT0 = $(SIMPLE_SYSTEM_COMMON)/crt0.S
|
||||
LINKER_SCRIPT = $(SIMPLE_SYSTEM_COMMON)/link.ld
|
||||
|
||||
# Flag : OUTFLAG
|
||||
# Use this flag to define how to to get an executable (e.g -o)
|
||||
OUTFLAG = -o
|
||||
# Flag : CC
|
||||
# Use this flag to define compiler to use
|
||||
CC = riscv32-unknown-elf-gcc
|
||||
# Flag : LD
|
||||
# Use this flag to define compiler to use
|
||||
LD = riscv32-unknown-elf-ld
|
||||
# Flag : AS
|
||||
# Use this flag to define compiler to use
|
||||
AS = riscv32-unknown-elf-as
|
||||
# Flag : CFLAGS
|
||||
# Use this flag to define compiler options. Note, you can add compiler options from the command line using XCFLAGS="other flags"
|
||||
PORT_CFLAGS = -g -march=rv32imc -mabi=ilp32 -static -mcmodel=medlow -mtune=sifive-3-series \
|
||||
-O3 -falign-functions=16 -funroll-all-loops \
|
||||
-finline-functions -falign-jumps=4 \
|
||||
-nostdlib -nostartfiles -ffreestanding -mstrict-align \
|
||||
-DTOTAL_DATA_SIZE=2000 -DMAIN_HAS_NOARGC=1 \
|
||||
-DPERFORMANCE_RUN=1
|
||||
|
||||
FLAGS_STR = "$(PORT_CFLAGS) $(XCFLAGS) $(XLFLAGS) $(LFLAGS_END)"
|
||||
CFLAGS += $(PORT_CFLAGS) $(XCFLAGS) -I$(SIMPLE_SYSTEM_COMMON) -I$(PORT_DIR) -I.
|
||||
#Flag : LFLAGS_END
|
||||
# Define any libraries needed for linking or other flags that should come at the end of the link line (e.g. linker scripts).
|
||||
# Note : On certain platforms, the default clock_gettime implementation is supported but requires linking of librt.
|
||||
#SEPARATE_COMPILE=1
|
||||
# Flag : SEPARATE_COMPILE
|
||||
# You must also define below how to create an object file, and how to link.
|
||||
OBJOUT = -o
|
||||
LFLAGS =
|
||||
ASFLAGS =
|
||||
OFLAG = -o
|
||||
COUT = -c
|
||||
|
||||
LFLAGS_END = -T $(LINKER_SCRIPT) -Xlinker -Map=$(OPATH)coremark.map -lm -lgcc
|
||||
# Flag : PORT_SRCS
|
||||
# Port specific source files can be added here
|
||||
# You may also need cvt.c if the fcvt functions are not provided as intrinsics by your compiler!
|
||||
PORT_SRCS = $(PORT_DIR)/core_portme.c $(PORT_DIR)/ee_printf.c ./barebones/cvt.c $(CRT0) $(EXT_SRCS)
|
||||
vpath %.c $(PORT_DIR)
|
||||
vpath %.s $(PORT_DIR)
|
||||
|
||||
# Flag : LOAD
|
||||
# For a simple port, we assume self hosted compile and run, no load needed.
|
||||
|
||||
# Flag : RUN
|
||||
# For a simple port, we assume self hosted compile and run, simple invocation of the executable
|
||||
|
||||
LOAD = echo "Please set LOAD to the process of loading the executable to the flash"
|
||||
RUN = echo "Please set LOAD to the process of running the executable (e.g. via jtag, or board reset)"
|
||||
|
||||
OEXT = .o
|
||||
EXE = .elf
|
||||
|
||||
$(OPATH)$(PORT_DIR)/%$(OEXT) : %.c
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(COUT) $< $(OBJOUT) $@
|
||||
|
||||
$(OPATH)%$(OEXT) : %.c
|
||||
$(CC) $(CFLAGS) $(XCFLAGS) $(COUT) $< $(OBJOUT) $@
|
||||
|
||||
$(OPATH)$(PORT_DIR)/%$(OEXT) : %.s
|
||||
$(AS) $(ASFLAGS) $< $(OBJOUT) $@
|
||||
|
||||
# Target : port_pre% and port_post%
|
||||
# For the purpose of this simple port, no pre or post steps needed.
|
||||
|
||||
.PHONY : port_clean port_prebuild port_postbuild port_prerun port_postrun port_preload port_postload
|
||||
|
||||
port_postbuild:
|
||||
riscv32-unknown-elf-objdump -SD $(OPATH)coremark.elf > $(OPATH)coremark.dis
|
||||
|
||||
# FLAG : OPATH
|
||||
# Path to the output folder. Default - current folder.
|
||||
MKDIR = mkdir -p
|
579
examples/sw/benchmarks/coremark/ibex/ee_printf.c
Normal file
579
examples/sw/benchmarks/coremark/ibex/ee_printf.c
Normal file
|
@ -0,0 +1,579 @@
|
|||
// Copyright lowRISC contributors.
|
||||
// Copyright 2018 Embedded Microprocessor Benchmark Consortium (EEMBC)
|
||||
// Original Author: Shay Gal-on
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <coremark.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "simple_system_common.h"
|
||||
|
||||
#define ZEROPAD (1 << 0) /* Pad with zero */
|
||||
#define SIGN (1 << 1) /* Unsigned/signed long */
|
||||
#define PLUS (1 << 2) /* Show plus */
|
||||
#define SPACE (1 << 3) /* Spacer */
|
||||
#define LEFT (1 << 4) /* Left justified */
|
||||
#define HEX_PREP (1 << 5) /* 0x */
|
||||
#define UPPERCASE (1 << 6) /* 'ABCDEF' */
|
||||
|
||||
#define is_digit(c) ((c) >= '0' && (c) <= '9')
|
||||
|
||||
static char *digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
static char *upper_digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
static ee_size_t strnlen(const char *s, ee_size_t count);
|
||||
|
||||
static ee_size_t strnlen(const char *s, ee_size_t count) {
|
||||
const char *sc;
|
||||
for (sc = s; *sc != '\0' && count--; ++sc)
|
||||
;
|
||||
return sc - s;
|
||||
}
|
||||
|
||||
static int skip_atoi(const char **s) {
|
||||
int i = 0;
|
||||
while (is_digit(**s))
|
||||
i = i * 10 + *((*s)++) - '0';
|
||||
return i;
|
||||
}
|
||||
|
||||
static char *number(char *str, long num, int base, int size, int precision,
|
||||
int type) {
|
||||
char c, sign, tmp[66];
|
||||
char *dig = digits;
|
||||
int i;
|
||||
|
||||
if (type & UPPERCASE)
|
||||
dig = upper_digits;
|
||||
if (type & LEFT)
|
||||
type &= ~ZEROPAD;
|
||||
if (base < 2 || base > 36)
|
||||
return 0;
|
||||
|
||||
c = (type & ZEROPAD) ? '0' : ' ';
|
||||
sign = 0;
|
||||
if (type & SIGN) {
|
||||
if (num < 0) {
|
||||
sign = '-';
|
||||
num = -num;
|
||||
size--;
|
||||
} else if (type & PLUS) {
|
||||
sign = '+';
|
||||
size--;
|
||||
} else if (type & SPACE) {
|
||||
sign = ' ';
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
if (type & HEX_PREP) {
|
||||
if (base == 16)
|
||||
size -= 2;
|
||||
else if (base == 8)
|
||||
size--;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
if (num == 0)
|
||||
tmp[i++] = '0';
|
||||
else {
|
||||
while (num != 0) {
|
||||
tmp[i++] = dig[((unsigned long)num) % (unsigned)base];
|
||||
num = ((unsigned long)num) / (unsigned)base;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > precision)
|
||||
precision = i;
|
||||
size -= precision;
|
||||
if (!(type & (ZEROPAD | LEFT)))
|
||||
while (size-- > 0)
|
||||
*str++ = ' ';
|
||||
if (sign)
|
||||
*str++ = sign;
|
||||
|
||||
if (type & HEX_PREP) {
|
||||
if (base == 8)
|
||||
*str++ = '0';
|
||||
else if (base == 16) {
|
||||
*str++ = '0';
|
||||
*str++ = digits[33];
|
||||
}
|
||||
}
|
||||
|
||||
if (!(type & LEFT))
|
||||
while (size-- > 0)
|
||||
*str++ = c;
|
||||
while (i < precision--)
|
||||
*str++ = '0';
|
||||
while (i-- > 0)
|
||||
*str++ = tmp[i];
|
||||
while (size-- > 0)
|
||||
*str++ = ' ';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *eaddr(char *str, unsigned char *addr, int size, int precision,
|
||||
int type) {
|
||||
char tmp[24];
|
||||
char *dig = digits;
|
||||
int i, len;
|
||||
|
||||
if (type & UPPERCASE)
|
||||
dig = upper_digits;
|
||||
len = 0;
|
||||
for (i = 0; i < 6; i++) {
|
||||
if (i != 0)
|
||||
tmp[len++] = ':';
|
||||
tmp[len++] = dig[addr[i] >> 4];
|
||||
tmp[len++] = dig[addr[i] & 0x0F];
|
||||
}
|
||||
|
||||
if (!(type & LEFT))
|
||||
while (len < size--)
|
||||
*str++ = ' ';
|
||||
for (i = 0; i < len; ++i)
|
||||
*str++ = tmp[i];
|
||||
while (len < size--)
|
||||
*str++ = ' ';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *iaddr(char *str, unsigned char *addr, int size, int precision,
|
||||
int type) {
|
||||
char tmp[24];
|
||||
int i, n, len;
|
||||
|
||||
len = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (i != 0)
|
||||
tmp[len++] = '.';
|
||||
n = addr[i];
|
||||
|
||||
if (n == 0)
|
||||
tmp[len++] = digits[0];
|
||||
else {
|
||||
if (n >= 100) {
|
||||
tmp[len++] = digits[n / 100];
|
||||
n = n % 100;
|
||||
tmp[len++] = digits[n / 10];
|
||||
n = n % 10;
|
||||
} else if (n >= 10) {
|
||||
tmp[len++] = digits[n / 10];
|
||||
n = n % 10;
|
||||
}
|
||||
|
||||
tmp[len++] = digits[n];
|
||||
}
|
||||
}
|
||||
|
||||
if (!(type & LEFT))
|
||||
while (len < size--)
|
||||
*str++ = ' ';
|
||||
for (i = 0; i < len; ++i)
|
||||
*str++ = tmp[i];
|
||||
while (len < size--)
|
||||
*str++ = ' ';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
#if HAS_FLOAT
|
||||
|
||||
char *ecvtbuf(double arg, int ndigits, int *decpt, int *sign, char *buf);
|
||||
char *fcvtbuf(double arg, int ndigits, int *decpt, int *sign, char *buf);
|
||||
static void ee_bufcpy(char *d, char *s, int count);
|
||||
|
||||
void ee_bufcpy(char *pd, char *ps, int count) {
|
||||
char *pe = ps + count;
|
||||
while (ps != pe)
|
||||
*pd++ = *ps++;
|
||||
}
|
||||
|
||||
static void parse_float(double value, char *buffer, char fmt, int precision) {
|
||||
int decpt, sign, exp, pos;
|
||||
char *digits = NULL;
|
||||
char cvtbuf[80];
|
||||
int capexp = 0;
|
||||
int magnitude;
|
||||
|
||||
if (fmt == 'G' || fmt == 'E') {
|
||||
capexp = 1;
|
||||
fmt += 'a' - 'A';
|
||||
}
|
||||
|
||||
if (fmt == 'g') {
|
||||
digits = ecvtbuf(value, precision, &decpt, &sign, cvtbuf);
|
||||
magnitude = decpt - 1;
|
||||
if (magnitude < -4 || magnitude > precision - 1) {
|
||||
fmt = 'e';
|
||||
precision -= 1;
|
||||
} else {
|
||||
fmt = 'f';
|
||||
precision -= decpt;
|
||||
}
|
||||
}
|
||||
|
||||
if (fmt == 'e') {
|
||||
digits = ecvtbuf(value, precision + 1, &decpt, &sign, cvtbuf);
|
||||
|
||||
if (sign)
|
||||
*buffer++ = '-';
|
||||
*buffer++ = *digits;
|
||||
if (precision > 0)
|
||||
*buffer++ = '.';
|
||||
ee_bufcpy(buffer, digits + 1, precision);
|
||||
buffer += precision;
|
||||
*buffer++ = capexp ? 'E' : 'e';
|
||||
|
||||
if (decpt == 0) {
|
||||
if (value == 0.0)
|
||||
exp = 0;
|
||||
else
|
||||
exp = -1;
|
||||
} else
|
||||
exp = decpt - 1;
|
||||
|
||||
if (exp < 0) {
|
||||
*buffer++ = '-';
|
||||
exp = -exp;
|
||||
} else
|
||||
*buffer++ = '+';
|
||||
|
||||
buffer[2] = (exp % 10) + '0';
|
||||
exp = exp / 10;
|
||||
buffer[1] = (exp % 10) + '0';
|
||||
exp = exp / 10;
|
||||
buffer[0] = (exp % 10) + '0';
|
||||
buffer += 3;
|
||||
} else if (fmt == 'f') {
|
||||
digits = fcvtbuf(value, precision, &decpt, &sign, cvtbuf);
|
||||
if (sign)
|
||||
*buffer++ = '-';
|
||||
if (*digits) {
|
||||
if (decpt <= 0) {
|
||||
*buffer++ = '0';
|
||||
*buffer++ = '.';
|
||||
for (pos = 0; pos < -decpt; pos++)
|
||||
*buffer++ = '0';
|
||||
while (*digits)
|
||||
*buffer++ = *digits++;
|
||||
} else {
|
||||
pos = 0;
|
||||
while (*digits) {
|
||||
if (pos++ == decpt)
|
||||
*buffer++ = '.';
|
||||
*buffer++ = *digits++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*buffer++ = '0';
|
||||
if (precision > 0) {
|
||||
*buffer++ = '.';
|
||||
for (pos = 0; pos < precision; pos++)
|
||||
*buffer++ = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*buffer = '\0';
|
||||
}
|
||||
|
||||
static void decimal_point(char *buffer) {
|
||||
while (*buffer) {
|
||||
if (*buffer == '.')
|
||||
return;
|
||||
if (*buffer == 'e' || *buffer == 'E')
|
||||
break;
|
||||
buffer++;
|
||||
}
|
||||
|
||||
if (*buffer) {
|
||||
int n = strnlen(buffer, 256);
|
||||
while (n > 0) {
|
||||
buffer[n + 1] = buffer[n];
|
||||
n--;
|
||||
}
|
||||
|
||||
*buffer = '.';
|
||||
} else {
|
||||
*buffer++ = '.';
|
||||
*buffer = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void cropzeros(char *buffer) {
|
||||
char *stop;
|
||||
|
||||
while (*buffer && *buffer != '.')
|
||||
buffer++;
|
||||
if (*buffer++) {
|
||||
while (*buffer && *buffer != 'e' && *buffer != 'E')
|
||||
buffer++;
|
||||
stop = buffer--;
|
||||
while (*buffer == '0')
|
||||
buffer--;
|
||||
if (*buffer == '.')
|
||||
buffer--;
|
||||
while (buffer != stop)
|
||||
*++buffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static char *flt(char *str, double num, int size, int precision, char fmt,
|
||||
int flags) {
|
||||
char tmp[80];
|
||||
char c, sign;
|
||||
int n, i;
|
||||
|
||||
// Left align means no zero padding
|
||||
if (flags & LEFT)
|
||||
flags &= ~ZEROPAD;
|
||||
|
||||
// Determine padding and sign char
|
||||
c = (flags & ZEROPAD) ? '0' : ' ';
|
||||
sign = 0;
|
||||
if (flags & SIGN) {
|
||||
if (num < 0.0) {
|
||||
sign = '-';
|
||||
num = -num;
|
||||
size--;
|
||||
} else if (flags & PLUS) {
|
||||
sign = '+';
|
||||
size--;
|
||||
} else if (flags & SPACE) {
|
||||
sign = ' ';
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the precision value
|
||||
if (precision < 0)
|
||||
precision = 6; // Default precision: 6
|
||||
|
||||
// Convert floating point number to text
|
||||
parse_float(num, tmp, fmt, precision);
|
||||
|
||||
if ((flags & HEX_PREP) && precision == 0)
|
||||
decimal_point(tmp);
|
||||
if (fmt == 'g' && !(flags & HEX_PREP))
|
||||
cropzeros(tmp);
|
||||
|
||||
n = strnlen(tmp, 256);
|
||||
|
||||
// Output number with alignment and padding
|
||||
size -= n;
|
||||
if (!(flags & (ZEROPAD | LEFT)))
|
||||
while (size-- > 0)
|
||||
*str++ = ' ';
|
||||
if (sign)
|
||||
*str++ = sign;
|
||||
if (!(flags & LEFT))
|
||||
while (size-- > 0)
|
||||
*str++ = c;
|
||||
for (i = 0; i < n; i++)
|
||||
*str++ = tmp[i];
|
||||
while (size-- > 0)
|
||||
*str++ = ' ';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int ee_vsprintf(char *buf, const char *fmt, va_list args) {
|
||||
int len;
|
||||
unsigned long num;
|
||||
int i, base;
|
||||
char *str;
|
||||
char *s;
|
||||
|
||||
int flags; // Flags to number()
|
||||
|
||||
int field_width; // Width of output field
|
||||
int precision; // Min. # of digits for integers; max number of chars for from
|
||||
// string
|
||||
int qualifier; // 'h', 'l', or 'L' for integer fields
|
||||
|
||||
for (str = buf; *fmt; fmt++) {
|
||||
if (*fmt != '%') {
|
||||
*str++ = *fmt;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process flags
|
||||
flags = 0;
|
||||
repeat:
|
||||
fmt++; // This also skips first '%'
|
||||
switch (*fmt) {
|
||||
case '-':
|
||||
flags |= LEFT;
|
||||
goto repeat;
|
||||
case '+':
|
||||
flags |= PLUS;
|
||||
goto repeat;
|
||||
case ' ':
|
||||
flags |= SPACE;
|
||||
goto repeat;
|
||||
case '#':
|
||||
flags |= HEX_PREP;
|
||||
goto repeat;
|
||||
case '0':
|
||||
flags |= ZEROPAD;
|
||||
goto repeat;
|
||||
}
|
||||
|
||||
// Get field width
|
||||
field_width = -1;
|
||||
if (is_digit(*fmt))
|
||||
field_width = skip_atoi(&fmt);
|
||||
else if (*fmt == '*') {
|
||||
fmt++;
|
||||
field_width = va_arg(args, int);
|
||||
if (field_width < 0) {
|
||||
field_width = -field_width;
|
||||
flags |= LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the precision
|
||||
precision = -1;
|
||||
if (*fmt == '.') {
|
||||
++fmt;
|
||||
if (is_digit(*fmt))
|
||||
precision = skip_atoi(&fmt);
|
||||
else if (*fmt == '*') {
|
||||
++fmt;
|
||||
precision = va_arg(args, int);
|
||||
}
|
||||
if (precision < 0)
|
||||
precision = 0;
|
||||
}
|
||||
|
||||
// Get the conversion qualifier
|
||||
qualifier = -1;
|
||||
if (*fmt == 'l' || *fmt == 'L') {
|
||||
qualifier = *fmt;
|
||||
fmt++;
|
||||
}
|
||||
|
||||
// Default base
|
||||
base = 10;
|
||||
|
||||
switch (*fmt) {
|
||||
case 'c':
|
||||
if (!(flags & LEFT))
|
||||
while (--field_width > 0)
|
||||
*str++ = ' ';
|
||||
*str++ = (unsigned char)va_arg(args, int);
|
||||
while (--field_width > 0)
|
||||
*str++ = ' ';
|
||||
continue;
|
||||
|
||||
case 's':
|
||||
s = va_arg(args, char *);
|
||||
if (!s)
|
||||
s = "<NULL>";
|
||||
len = strnlen(s, precision);
|
||||
if (!(flags & LEFT))
|
||||
while (len < field_width--)
|
||||
*str++ = ' ';
|
||||
for (i = 0; i < len; ++i)
|
||||
*str++ = *s++;
|
||||
while (len < field_width--)
|
||||
*str++ = ' ';
|
||||
continue;
|
||||
|
||||
case 'p':
|
||||
if (field_width == -1) {
|
||||
field_width = 2 * sizeof(void *);
|
||||
flags |= ZEROPAD;
|
||||
}
|
||||
str = number(str, (unsigned long)va_arg(args, void *), 16, field_width,
|
||||
precision, flags);
|
||||
continue;
|
||||
|
||||
case 'A':
|
||||
flags |= UPPERCASE;
|
||||
|
||||
case 'a':
|
||||
if (qualifier == 'l')
|
||||
str = eaddr(str, va_arg(args, unsigned char *), field_width,
|
||||
precision, flags);
|
||||
else
|
||||
str = iaddr(str, va_arg(args, unsigned char *), field_width,
|
||||
precision, flags);
|
||||
continue;
|
||||
|
||||
// Integer number formats - set up the flags and "break"
|
||||
case 'o':
|
||||
base = 8;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
flags |= UPPERCASE;
|
||||
|
||||
case 'x':
|
||||
base = 16;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
case 'i':
|
||||
flags |= SIGN;
|
||||
|
||||
case 'u':
|
||||
break;
|
||||
|
||||
#if HAS_FLOAT
|
||||
|
||||
case 'f':
|
||||
str = flt(str, va_arg(args, double), field_width, precision, *fmt,
|
||||
flags | SIGN);
|
||||
continue;
|
||||
|
||||
#endif
|
||||
|
||||
default:
|
||||
if (*fmt != '%')
|
||||
*str++ = '%';
|
||||
if (*fmt)
|
||||
*str++ = *fmt;
|
||||
else
|
||||
--fmt;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (qualifier == 'l')
|
||||
num = va_arg(args, unsigned long);
|
||||
else if (flags & SIGN)
|
||||
num = va_arg(args, int);
|
||||
else
|
||||
num = va_arg(args, unsigned int);
|
||||
|
||||
str = number(str, num, base, field_width, precision, flags);
|
||||
}
|
||||
|
||||
*str = '\0';
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
int ee_printf(const char *fmt, ...) {
|
||||
char buf[256], *p;
|
||||
va_list args;
|
||||
int n = 0;
|
||||
|
||||
va_start(args, fmt);
|
||||
ee_vsprintf(buf, fmt, args);
|
||||
va_end(args);
|
||||
p = buf;
|
||||
while (*p) {
|
||||
putchar(*p);
|
||||
n++;
|
||||
p++;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
25
vendor/patches/eembc_coremark/0001-no-minimum-run-time.patch
vendored
Normal file
25
vendor/patches/eembc_coremark/0001-no-minimum-run-time.patch
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
diff --git a/core_main.c b/core_main.c
|
||||
index 6161974..cca596c 100644
|
||||
--- a/core_main.c
|
||||
+++ b/core_main.c
|
||||
@@ -295,10 +295,16 @@ MAIN_RETURN_TYPE main(int argc, char *argv[]) {
|
||||
if (time_in_secs(total_time) > 0)
|
||||
ee_printf("Iterations/Sec : %d\n",default_num_contexts*results[0].iterations/time_in_secs(total_time));
|
||||
#endif
|
||||
- if (time_in_secs(total_time) < 10) {
|
||||
- ee_printf("ERROR! Must execute for at least 10 secs for a valid result!\n");
|
||||
- total_errors++;
|
||||
- }
|
||||
+ // Remove error report if execution time low.
|
||||
+ // On Ibex a few loops suffices to get an accurate result. With this check in
|
||||
+ // the verilator simulation of coremark will report failure (unless left running
|
||||
+ // for a significant number of iterations greatly increasing simulation time).
|
||||
+ // The other error checking is useful to determine if the benchmark has been
|
||||
+ // broken in some way which is masked if this check is left in.
|
||||
+ //if (time_in_secs(total_time) < 10) {
|
||||
+ // ee_printf("ERROR! Must execute for at least 10 secs for a valid result!\n");
|
||||
+ // total_errors++;
|
||||
+ //}
|
||||
|
||||
ee_printf("Iterations : %lu\n", (long unsigned) default_num_contexts*results[0].iterations);
|
||||
ee_printf("Compiler version : %s\n",COMPILER_VERSION);
|
Loading…
Add table
Add a link
Reference in a new issue