From 86bb902dbdeef66cdb079f5187ffce2c58e086ef Mon Sep 17 00:00:00 2001 From: Emil Tywoniak Date: Tue, 31 Oct 2023 17:53:22 +0000 Subject: [PATCH 1/4] Add spike RISC-V simulator support --- config/riscv32/boards/spike/board.cfg | 62 ++++++++ config/riscv32/boards/spike/boardsupport.c | 39 +++++ config/riscv32/boards/spike/crt0.S | 67 ++++++++ config/riscv32/boards/spike/link.ld | 93 ++++++++++++ config/riscv32/boards/spike/ns16550.h | 41 +++++ config/riscv32/boards/spike/stub.c | 169 +++++++++++++++++++++ config/riscv32/boards/spike/util.c | 62 ++++++++ config/riscv32/boards/spike/util.h | 28 ++++ pylib/run_spike.py | 72 +++++++++ 9 files changed, 633 insertions(+) create mode 100644 config/riscv32/boards/spike/board.cfg create mode 100644 config/riscv32/boards/spike/boardsupport.c create mode 100644 config/riscv32/boards/spike/crt0.S create mode 100644 config/riscv32/boards/spike/link.ld create mode 100644 config/riscv32/boards/spike/ns16550.h create mode 100644 config/riscv32/boards/spike/stub.c create mode 100644 config/riscv32/boards/spike/util.c create mode 100644 config/riscv32/boards/spike/util.h create mode 100644 pylib/run_spike.py diff --git a/config/riscv32/boards/spike/board.cfg b/config/riscv32/boards/spike/board.cfg new file mode 100644 index 00000000..791115ee --- /dev/null +++ b/config/riscv32/boards/spike/board.cfg @@ -0,0 +1,62 @@ +# Board configuration for riscv-isa-sim (spike +# +# Copyright (C) 2022 Embecosm Limited and University of Bristol +# +# Contributor TODO +# +# SPDX-License-Identifier: GPL-3.0-or-later + +# This is a python setting of parameters for the board. The following +# parameters may be set (other keys are silently ignored). Defaults are shown +# in brackets +# cc = 'riscv32-unknown-elf-gcc' +# - ld (same value as for cc) +# cflags = (['-c', '-Os', '-ffunction-sections', '-nostdlib', '-march=rv32imac', '-mabi=ilp32']) +# ldflags = (['-Wl,-gc-sections', '-nostdlib', '-march=rv32imac', '-mabi=ilp32', '-T../../../config/riscv32/boards/rv32wallyverilog/link.ld']) +# cflags = (['-c', '-Os', '-ffunction-sections', '-nostartfiles', '-march=rv32imac', '-mabi=ilp32']) +# ldflags = (['-Wl,-gc-sections', '-nostartfiles', '-march=rv32imac', '-mabi=ilp32', '-T../../../config/riscv32/boards/rv32wallyverilog/link.ld']) +cflags = (['-c', '-march=rv32imafdc', '-mabi=ilp32d', '-DSPIKE']) +ldflags = (['-march=rv32imafdc', '-mabi=ilp32d', '-T../../../config/riscv32/boards/spike/link.ld']) +# - cc_define_pattern ('-D{0}') +# - cc_incdir_pattern ('-I{0}') +# - cc_input_pattern ('{0}') +# - cc_output_pattern ('-o {0}') +# - ld_input_pattern ('{0}') +# - ld_output_pattern ('-o {0}') +user_libs = (['-lc']) +# dummy_libs = (['libm']) +# dummy_libs = (['libgcc', 'libm', 'libc']) +# - cpu_mhz (1) +# - warmup_heat (1) + +# The "flags" and "libs" parameters (cflags, ldflags, user_libs, dummy_libs) +# should be lists of arguments to be passed to the compile or link line as +# appropriate. Patterns are Python format patterns used to create arguments. +# Thus for GCC or Clang/LLVM defined constants can be passed using the prefix +# '-D', and the pattern '-D{0}' would be appropriate (which happens to be the +# default). + +# "user_libs" may be absolute file names or arguments to the linker. In the +# latter case corresponding arguments in ldflags may be needed. For example +# with GCC or Clang/LLVM is "-l" flags are used in "user_libs", the "-L" flags +# may be needed in "ldflags". + +# Dummy libs have their source in the "support" subdirectory. Thus if 'crt0' +# is specified, there should be a source file 'dummy-crt0.c' in the support +# directory. + +# There is no need to set an unused parameter, and this file may be empty to +# set no flags. + +# Parameter values which are duplicated in architecture, board, chip or +# command line are used in the following order of priority +# - default value +# - architecture specific value +# - chip specific value +# - board specific value +# - command line value + +# For flags, this priority is applied to individual flags, not the complete +# list of flags. + +cpu_mhz = 1 diff --git a/config/riscv32/boards/spike/boardsupport.c b/config/riscv32/boards/spike/boardsupport.c new file mode 100644 index 00000000..8fc4c5a1 --- /dev/null +++ b/config/riscv32/boards/spike/boardsupport.c @@ -0,0 +1,39 @@ +/* Copyright HighTec EDV-Systeme GmbH 2023 + + This file is part of Embench. + + SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 */ + +#include + +unsigned long long start; +extern void _exit(int i); + +#define CORETIMETYPE unsigned long long +#define read_csr(reg) ({ unsigned long __tmp; \ + asm volatile ("csrr %0, " #reg : "=r"(__tmp)); \ + __tmp; }) + + +void __attribute__ ((noinline)) __attribute__ ((externally_visible)) +start_trigger () +{ + unsigned long hi = read_csr(mcycleh); + unsigned long lo = read_csr(mcycle); + start = (unsigned long long)(((CORETIMETYPE)hi) << 32) | lo; +} + +void __attribute__ ((noinline)) __attribute__ ((externally_visible)) +stop_trigger () +{ + unsigned long hi = read_csr(mcycleh); + unsigned long lo = read_csr(mcycle); + unsigned long long end = (unsigned long long)(((CORETIMETYPE)hi) << 32) | lo; + printf("Spike mcycle timer delta: %llu\n", end - start); + _exit(0); +} + +void __attribute__ ((noinline)) +initialise_board () +{ +} diff --git a/config/riscv32/boards/spike/crt0.S b/config/riscv32/boards/spike/crt0.S new file mode 100644 index 00000000..b73d9942 --- /dev/null +++ b/config/riscv32/boards/spike/crt0.S @@ -0,0 +1,67 @@ +/* Startup code for spike RISC-V ISA simulator with FPU enabled + + Copyright (C) 2017 SiFive Inc. All rights reserved. + + This file is part of Embench. + + SPDX-License-Identifier: GPL-3.0-or-later OR BSD-2-Clause */ + +; #include "newlib.h" + +#define MSTATUS_FS 0x00006000 +#define MSTATUS_XS 0x00018000 + +#========================================================================= +# crt0.S : Entry point for RISC-V user programs +#========================================================================= + + .text + .global _start + .type _start, @function + .section .text.startup +_start: + # Initialize global pointer +.option push +.option norelax +1:auipc gp, %pcrel_hi(__global_pointer$) + addi gp, gp, %pcrel_lo(1b) +.option pop + + # enable FPU and accelerator if present + li t0, MSTATUS_FS | MSTATUS_XS + csrs mstatus, t0 + + # Clear the bss segment + la sp, __ram_end__ + la a0, __bss_start__ + la a2, _end + sub a2, a2, a0 + li a1, 0 + call memset +#ifdef HAS_ATEXIT +#ifdef _LITE_EXIT + # Make reference to atexit weak to avoid unconditionally pulling in + # support code. Refer to comments in __atexit.c for more details. + .weak atexit + la a0, atexit + beqz a0, .Lweak_atexit + .weak __libc_fini_array +#endif + + la a0, __libc_fini_array # Register global termination functions + call atexit # to be called upon exit +#ifdef _LITE_EXIT +.Lweak_atexit: +#endif +#endif + call __libc_init_array # Run global initialization functions + + #lw a0, 0(sp) # a0 = argc + #addi a1, sp, __SIZEOF_POINTER__ # a1 = argv + #li a2, 0 # a2 = envp = NULL + li a0, 0 + li a1, 0 + li a2, 0 + call main + tail exit + .size _start, .-_start diff --git a/config/riscv32/boards/spike/link.ld b/config/riscv32/boards/spike/link.ld new file mode 100644 index 00000000..56956a37 --- /dev/null +++ b/config/riscv32/boards/spike/link.ld @@ -0,0 +1,93 @@ +/* Linker script for spike RISC-V ISA simulator + + Copyright (C) 2014-2020 Free Software Foundation, Inc. + + This file is part of Embench. + + SPDX-License-Identifier: GPL-3.0-or-later OR FSFAP */ + +OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv") +OUTPUT_ARCH(riscv) + +MEMORY +{ + /* qemu-system-risc32 virt machine */ + RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 1M +} + +ENTRY(_start) /* this will cause an error if the symbol _start is not present */ + +SECTIONS +{ + . = ORIGIN(RAM); + + PROVIDE(__ram_origin__ = ORIGIN(RAM)); + PROVIDE(__ram_end__ = ORIGIN(RAM) + LENGTH(RAM)); + + .text.startup : { *(.text.startup) } + . = ALIGN(0x1000); + .tohost : { *(.htif) } + . = ALIGN(0x1000); + .text : { *(.text*) } + . = ALIGN(0x1000); + + /*. = ALIGN(0x10);*/ /* putting this here does not cause the followng section to move */ + + .rodata : /*ALIGN(0x10):*/ /* this will the align section but others can overlap */ + { + . = ALIGN(0x10); /* aligning here will align the section & update the loc counter */ + __rodata_start = .; + *(.rodata.*) + *(.srodata.*) + __rodata_end = .; + } + + .data : + { + . = ALIGN(0x1000); + __data_start = .; + *(.data.*) + *(.sdata*) /* small data objects */ + __data_end = .; + } + + __global_pointer$ = (__data_start + 0x800); /* stick this somewhere potentially useful */ + + +/**/ + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } +/**/ + + .bss : + { + . = ALIGN(0x100); + __bss_start = .; + __bss_start__ = .; + *(.sbss*) /* small data objects */ + *(.bss*) + __bss_end = .; + __bss_end__ = .; + } + + _end = .; +} + diff --git a/config/riscv32/boards/spike/ns16550.h b/config/riscv32/boards/spike/ns16550.h new file mode 100644 index 00000000..8c42b398 --- /dev/null +++ b/config/riscv32/boards/spike/ns16550.h @@ -0,0 +1,41 @@ +/* RISC-V rv32 tutorial examples + + Copyright (C) 2021 John Winans + + This file is part of Embench. + + SPDX-License-Identifier: GPL-3.0-or-later */ + + +#ifndef ns16550_H +#define ns16550_H + +#include + +// inferred from the qemu-system-riscv32 dtb values +#define NS16550_THR (*((volatile uint8_t *)0x10000000)) +#define NS16550_RBR (*((volatile uint8_t *)0x10000000)) +#define NS16550_IER (*((volatile uint8_t *)0x10000001)) +#define NS16550_IIR (*((volatile uint8_t *)0x10000002)) +#define NS16550_FCR (*((volatile uint8_t *)0x10000002)) +#define NS16550_LCR (*((volatile uint8_t *)0x10000003)) +#define NS16550_MCR (*((volatile uint8_t *)0x10000004)) +#define NS16550_LSR (*((volatile uint8_t *)0x10000005)) +#define NS16550_MSR (*((volatile uint8_t *)0x10000006)) +#define NS16550_SCR (*((volatile uint8_t *)0x10000007)) + +#define NS16550_LSR_THRE (1<<5) + +/** +* Wait for the TX FIFO to have room for a byte and send it. +***************************************************************************/ +inline __attribute__((always_inline)) void ns16550_tx(uint8_t ch) +{ + // be careful about order of operations here... + while((NS16550_LSR & NS16550_LSR_THRE) == 0) + ; + NS16550_THR = ch; +} + +#endif + diff --git a/config/riscv32/boards/spike/stub.c b/config/riscv32/boards/spike/stub.c new file mode 100644 index 00000000..e582cdd5 --- /dev/null +++ b/config/riscv32/boards/spike/stub.c @@ -0,0 +1,169 @@ +/* RISC-V rv32 tutorial examples + + Copyright (C) 2021 John Winans + + This file is part of Embench. + + SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "ns16550.h" +#include "util.h" +#include +#include + +#undef errno +extern int errno; + + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-noreturn" +void _exit(int i) +{ + tohost_exit(0); + asm volatile (" ebreak "); +} +#pragma GCC diagnostic pop + +/** +* It is assumed that there is exactly only process running and that +* it does not support signals. Therefore calling this is effectively +* illegal and will therefore terminate the program. +*****************************************************************/ +void _kill(int pid) +{ +#if 1 + asm volatile (" ebreak "); +#else + return; // arguably, this might also be acceptable +#endif +} + +/** +* This returns the process ID of the runnung program. +* This library assumes that there is only process that +* can ever run. +* +* @return 1 +*****************************************************************/ +int _getpid(void) +{ + return 1; +} + + +/** +* This library does not support any file I/O of any kind. +* +* @return -1 Indicating that file could not be closed. +*****************************************************************/ +int _close(int file) +{ + errno = EBADF; + return -1; +} + +/** +* This library does not support any file I/O of any kind. +* This call will return a status indicating that the file +* in question is a character device. +* +* @return 0 Indicating that the call has succeeded. +*****************************************************************/ +int _fstat(int file, struct stat *st) +{ + st->st_mode = S_IFCHR; + return 0; +} +/** +* This library does not support any file I/O of any kind. +* +* @return 1 Indicating that file is a tty device (a terminal.) +*****************************************************************/ +int _isatty(int file) +{ + return 1; +} + +/** +* This library does not support any file I/O of any kind. +* +* @return 0 Indicating that the request has succeeded. +*****************************************************************/ +int _lseek(int file, int ptr, int dir) +{ + return 0; +} + +/** +* This library does not support any file I/O of any kind. +* +* @return -1 (error codition.) +*****************************************************************/ +int _open(const char *name, int flags, int mode) +{ + errno = ENFILE; // The system-wide limit (0) on total open files has been reached. + return -1; +} + +/** +* This library does not support any file I/O of any kind. +* +* @return EOF. +*****************************************************************/ +int _read(int file, char *ptr, int len) +{ + return 0; +} + +/** +* This function should satify the caller by simply returning len +* indicating that the write has succeeded as requested in spite +* of the fact that the data is simply ignored/discarded. +* +* @return len +*****************************************************************/ +int _write(int file, char *ptr, int len) +{ +#ifdef QEMU + // qemu-system-riscv32 -machine virt has a 16550 at address 0x10000000 + //volatile char *thr = (volatile char *)0x10000000; + for (int i=0; i +#include + +void *memset(void *s, int c, size_t n); +void *memcpy(void *dest, const void *src, size_t n); +int strcmp(const char *s1, const char *s2); +unsigned int strlen(const char *s); + +void print(const char *s); +void print_hex(size_t x); +void printn(const char *s, int len); + +#define SYS_exit 93 +#define SYS_read 63 +#define SYS_write 64 + +uintptr_t syscall(uintptr_t n, uintptr_t a0, uintptr_t a1, uintptr_t a2, + uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6); + +void shutdown(int code); + +void tohost_exit(uintptr_t code); diff --git a/pylib/run_spike.py b/pylib/run_spike.py new file mode 100644 index 00000000..b1e93f68 --- /dev/null +++ b/pylib/run_spike.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 + +# Python module to run programs natively. + +# Copyright (C) 2019 Clemson University +# +# Contributor: Ola Jeppsson +# +# This file is part of Embench. + +# SPDX-License-Identifier: GPL-3.0-or-later + +""" +Embench module to run benchmark programs. + +This version is suitable for running programs natively. +""" + +__all__ = [ + 'get_target_args', + 'build_benchmark_cmd', + 'decode_results', +] + +import argparse +import re + +from embench_core import log + + +def get_target_args(remnant): + """Parse left over arguments""" + parser = argparse.ArgumentParser(description='Get target specific args') + + # No target arguments + return parser.parse_args(remnant) + + +def build_benchmark_cmd(bench, args): + """Construct the command to run the benchmark. "args" is a + namespace with target specific arguments""" + + # Due to way the target interface currently works we need to construct + # a command that records both the return value and execution time to + # stdin/stdout. Obviously using time will not be very precise. + return ['spike', '--isa=RV32GC', bench] + + +def decode_results(stdout_str, stderr_str): + """Extract the results from the output string of the run. Return the + elapsed time in milliseconds or zero if the run failed.""" + # See above in build_benchmark_cmd how we record the return value and + # execution time. Return code is in standard output. Execution time is in + # standard error. + + # Match "RET=rc" + rcstr = re.search('^RET=(\d+)', stdout_str, re.S | re.M) + if not rcstr: + log.debug('Warning: Failed to find return code') + return 0.0 + + # Match "real s.mm?m?" + time = re.search('^real (\d+)[.](\d+)', stderr_str, re.S) + if time: + ms_elapsed = int(time.group(1)) * 1000 + \ + int(time.group(2).ljust(3,'0')) # 0-pad + # Return value cannot be zero (will be interpreted as error) + return max(float(ms_elapsed), 0.001) + + # We must have failed to find a time + log.debug('Warning: Failed to find timing') + return 0.0 From 0c808d4af1777ff758637d4b105af7c948100e59 Mon Sep 17 00:00:00 2001 From: Emil Tywoniak Date: Wed, 1 Nov 2023 14:43:09 +0000 Subject: [PATCH 2/4] Add spike cycle count parsing --- pylib/run_spike.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pylib/run_spike.py b/pylib/run_spike.py index b1e93f68..9b0760df 100644 --- a/pylib/run_spike.py +++ b/pylib/run_spike.py @@ -43,7 +43,8 @@ def build_benchmark_cmd(bench, args): # Due to way the target interface currently works we need to construct # a command that records both the return value and execution time to # stdin/stdout. Obviously using time will not be very precise. - return ['spike', '--isa=RV32GC', bench] + # Hacky workaround for https://github.com/riscv-software-src/riscv-isa-sim/issues/1493 + return ['script', '-c', f'spike --isa=RV32GC {bench}'] def decode_results(stdout_str, stderr_str): @@ -54,16 +55,18 @@ def decode_results(stdout_str, stderr_str): # standard error. # Match "RET=rc" - rcstr = re.search('^RET=(\d+)', stdout_str, re.S | re.M) - if not rcstr: - log.debug('Warning: Failed to find return code') - return 0.0 + # rcstr = re.search('^RET=(\d+)', stdout_str, re.S | re.M) + # if not rcstr: + # log.debug('Warning: Failed to find return code') + # return 0.0 + + time = re.search('Spike mcycle timer delta: (\d+)', stdout_str, re.S) + fake_freq = 1e8 # 100 MHz + fake_period = 1.0 / fake_freq - # Match "real s.mm?m?" - time = re.search('^real (\d+)[.](\d+)', stderr_str, re.S) if time: - ms_elapsed = int(time.group(1)) * 1000 + \ - int(time.group(2).ljust(3,'0')) # 0-pad + s_elapsed = int(time.group(1)) * fake_period + ms_elapsed = s_elapsed * 1000 # Return value cannot be zero (will be interpreted as error) return max(float(ms_elapsed), 0.001) From 3d1171d9ea010e0f1f6c1ffedf50f97c1cec5c67 Mon Sep 17 00:00:00 2001 From: Emil Tywoniak Date: Wed, 1 Nov 2023 15:15:36 +0000 Subject: [PATCH 3/4] Fix fake frequency, fix license header, make script inherit spike's ret code, fail on non-zero benchmark ret code --- config/riscv32/boards/spike/stub.c | 2 +- pylib/run_spike.py | 21 +++++++-------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/config/riscv32/boards/spike/stub.c b/config/riscv32/boards/spike/stub.c index e582cdd5..11613c93 100644 --- a/config/riscv32/boards/spike/stub.c +++ b/config/riscv32/boards/spike/stub.c @@ -19,7 +19,7 @@ extern int errno; #pragma GCC diagnostic ignored "-Winvalid-noreturn" void _exit(int i) { - tohost_exit(0); + tohost_exit(i); asm volatile (" ebreak "); } #pragma GCC diagnostic pop diff --git a/pylib/run_spike.py b/pylib/run_spike.py index 9b0760df..224dfb14 100644 --- a/pylib/run_spike.py +++ b/pylib/run_spike.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -# Python module to run programs natively. +# Python module to run programs with RISC-V ISA simulator spike. -# Copyright (C) 2019 Clemson University +# Copyright (C) 2023 HighTec edV-Systeme GmbH # -# Contributor: Ola Jeppsson +# Contributor: Emil J. Tywoniak # # This file is part of Embench. @@ -13,7 +13,7 @@ """ Embench module to run benchmark programs. -This version is suitable for running programs natively. +This version is suitable for running programs with RISC-V ISA simulator spike. """ __all__ = [ @@ -44,24 +44,17 @@ def build_benchmark_cmd(bench, args): # a command that records both the return value and execution time to # stdin/stdout. Obviously using time will not be very precise. # Hacky workaround for https://github.com/riscv-software-src/riscv-isa-sim/issues/1493 - return ['script', '-c', f'spike --isa=RV32GC {bench}'] + return ['script', '-c', f'spike --isa=RV32GC {bench}', '-e'] def decode_results(stdout_str, stderr_str): """Extract the results from the output string of the run. Return the elapsed time in milliseconds or zero if the run failed.""" # See above in build_benchmark_cmd how we record the return value and - # execution time. Return code is in standard output. Execution time is in - # standard error. - - # Match "RET=rc" - # rcstr = re.search('^RET=(\d+)', stdout_str, re.S | re.M) - # if not rcstr: - # log.debug('Warning: Failed to find return code') - # return 0.0 + # execution time. time = re.search('Spike mcycle timer delta: (\d+)', stdout_str, re.S) - fake_freq = 1e8 # 100 MHz + fake_freq = 1e6 # 1 MHz fake_period = 1.0 / fake_freq if time: From f3100fe459f93d78adedbe3fff6f85bd1dd90f31 Mon Sep 17 00:00:00 2001 From: Emil Tywoniak Date: Tue, 28 Nov 2023 10:07:08 +0100 Subject: [PATCH 4/4] Fix result verification --- config/riscv32/boards/spike/boardsupport.c | 1 - 1 file changed, 1 deletion(-) diff --git a/config/riscv32/boards/spike/boardsupport.c b/config/riscv32/boards/spike/boardsupport.c index 8fc4c5a1..945cc466 100644 --- a/config/riscv32/boards/spike/boardsupport.c +++ b/config/riscv32/boards/spike/boardsupport.c @@ -30,7 +30,6 @@ stop_trigger () unsigned long lo = read_csr(mcycle); unsigned long long end = (unsigned long long)(((CORETIMETYPE)hi) << 32) | lo; printf("Spike mcycle timer delta: %llu\n", end - start); - _exit(0); } void __attribute__ ((noinline))