diff options
-rw-r--r-- | debug/Makefile | 4 | ||||
-rwxr-xr-x | debug/gdbserver.py | 153 | ||||
-rwxr-xr-x | debug/programs/entry.S | 53 | ||||
-rw-r--r-- | debug/programs/init.c | 23 | ||||
-rw-r--r-- | debug/programs/multicore.c | 81 | ||||
-rw-r--r-- | debug/programs/start.S | 12 | ||||
-rw-r--r-- | debug/targets.py | 113 | ||||
-rw-r--r-- | debug/targets/RISC-V/spike.cfg (renamed from debug/targets/RISC-V/spike32.cfg) | 5 | ||||
-rw-r--r-- | debug/targets/RISC-V/spike32-2.py | 12 | ||||
-rwxr-xr-x | debug/targets/RISC-V/spike32.lds | 2 | ||||
-rw-r--r-- | debug/targets/RISC-V/spike32.py | 8 | ||||
-rw-r--r-- | debug/targets/RISC-V/spike64-2.py | 12 | ||||
-rw-r--r-- | debug/targets/RISC-V/spike64.cfg | 19 | ||||
-rwxr-xr-x | debug/targets/RISC-V/spike64.lds | 2 | ||||
-rw-r--r-- | debug/targets/RISC-V/spike64.py | 8 | ||||
-rw-r--r-- | debug/targets/SiFive/Freedom/E300.py | 9 | ||||
-rw-r--r-- | debug/targets/SiFive/Freedom/E300Sim.py | 9 | ||||
-rw-r--r-- | debug/targets/SiFive/Freedom/Freedom.lds | 2 | ||||
-rw-r--r-- | debug/targets/SiFive/Freedom/U500.py | 7 | ||||
-rw-r--r-- | debug/targets/SiFive/Freedom/U500Sim.py | 11 | ||||
-rwxr-xr-x | debug/targets/SiFive/HiFive1.lds | 2 | ||||
-rw-r--r-- | debug/targets/SiFive/HiFive1.py | 5 | ||||
-rw-r--r-- | debug/testlib.py | 143 |
23 files changed, 486 insertions, 209 deletions
diff --git a/debug/Makefile b/debug/Makefile index 2d8d367..33988dd 100644 --- a/debug/Makefile +++ b/debug/Makefile @@ -4,9 +4,9 @@ XLEN ?= 64 src_dir ?= . GDBSERVER_PY = $(src_dir)/gdbserver.py -default: spike$(XLEN) +default: spike$(XLEN)-2 -all: pylint spike32 spike64 +all: pylint spike32 spike64 spike32-2 spike64-2 pylint: pylint --rcfile=pylint.rc `git ls-files '*.py'` diff --git a/debug/gdbserver.py b/debug/gdbserver.py index cbb1299..21eea4e 100755 --- a/debug/gdbserver.py +++ b/debug/gdbserver.py @@ -12,7 +12,7 @@ import targets import testlib from testlib import assertEqual, assertNotEqual, assertIn, assertNotIn from testlib import assertGreater, assertRegexpMatches, assertLess -from testlib import GdbTest +from testlib import GdbTest, GdbSingleHartTest, TestFailed MSTATUS_UIE = 0x00000001 MSTATUS_SIE = 0x00000002 @@ -66,8 +66,8 @@ def readable_binary_string(s): class SimpleRegisterTest(GdbTest): def check_reg(self, name): - a = random.randrange(1<<self.target.xlen) - b = random.randrange(1<<self.target.xlen) + a = random.randrange(1<<self.hart.xlen) + b = random.randrange(1<<self.hart.xlen) self.gdb.p("$%s=0x%x" % (name, a)) self.gdb.stepi() assertEqual(self.gdb.p("$%s" % name), a) @@ -77,12 +77,12 @@ class SimpleRegisterTest(GdbTest): def setup(self): # 0x13 is nop - self.gdb.command("p *((int*) 0x%x)=0x13" % self.target.ram) - self.gdb.command("p *((int*) 0x%x)=0x13" % (self.target.ram + 4)) - self.gdb.command("p *((int*) 0x%x)=0x13" % (self.target.ram + 8)) - self.gdb.command("p *((int*) 0x%x)=0x13" % (self.target.ram + 12)) - self.gdb.command("p *((int*) 0x%x)=0x13" % (self.target.ram + 16)) - self.gdb.p("$pc=0x%x" % self.target.ram) + self.gdb.command("p *((int*) 0x%x)=0x13" % self.hart.ram) + self.gdb.command("p *((int*) 0x%x)=0x13" % (self.hart.ram + 4)) + self.gdb.command("p *((int*) 0x%x)=0x13" % (self.hart.ram + 8)) + self.gdb.command("p *((int*) 0x%x)=0x13" % (self.hart.ram + 12)) + self.gdb.command("p *((int*) 0x%x)=0x13" % (self.hart.ram + 16)) + self.gdb.p("$pc=0x%x" % self.hart.ram) class SimpleS0Test(SimpleRegisterTest): def test(self): @@ -114,7 +114,7 @@ class SimpleF18Test(SimpleRegisterTest): assertLess(abs(float(self.gdb.p_raw("$%s" % name)) - b), .001) def early_applicable(self): - return self.target.extensionSupported('F') + return self.hart.extensionSupported('F') def test(self): self.check_reg("f18") @@ -124,8 +124,8 @@ class SimpleMemoryTest(GdbTest): assertEqual(self.gdb.p("sizeof(%s)" % data_type), size) a = 0x86753095555aaaa & ((1<<(size*8))-1) b = 0xdeadbeef12345678 & ((1<<(size*8))-1) - addrA = self.target.ram - addrB = self.target.ram + self.target.ram_size - size + addrA = self.hart.ram + addrB = self.hart.ram + self.hart.ram_size - size self.gdb.p("*((%s*)0x%x) = 0x%x" % (data_type, addrA, a)) self.gdb.p("*((%s*)0x%x) = 0x%x" % (data_type, addrB, b)) assertEqual(self.gdb.p("*((%s*)0x%x)" % (data_type, addrA)), a) @@ -157,7 +157,7 @@ class MemTest64(SimpleMemoryTest): # assert False, "Read should have failed." # except testlib.CannotAccess as e: # assertEqual(e.address, 0xdeadbeef) -# self.gdb.p("*((int*)0x%x)" % self.target.ram) +# self.gdb.p("*((int*)0x%x)" % self.hart.ram) # #class MemTestWriteInvalid(SimpleMemoryTest): # def test(self): @@ -168,24 +168,25 @@ class MemTest64(SimpleMemoryTest): # assert False, "Write should have failed." # except testlib.CannotAccess as e: # assertEqual(e.address, 0xdeadbeef) -# self.gdb.p("*((int*)0x%x)=6874742" % self.target.ram) +# self.gdb.p("*((int*)0x%x)=6874742" % self.hart.ram) class MemTestBlock(GdbTest): + length = 1024 + line_length = 16 + def test(self): - length = 1024 - line_length = 16 a = tempfile.NamedTemporaryFile(suffix=".ihex") data = "" - for i in range(length / line_length): + for i in range(self.length / self.line_length): line_data = "".join(["%c" % random.randrange(256) - for _ in range(line_length)]) + for _ in range(self.line_length)]) data += line_data - a.write(ihex_line(i * line_length, 0, line_data)) + a.write(ihex_line(i * self.line_length, 0, line_data)) a.flush() - self.gdb.command("restore %s 0x%x" % (a.name, self.target.ram)) - for offset in range(0, length, 19*4) + [length-4]: - value = self.gdb.p("*((int*)0x%x)" % (self.target.ram + offset)) + self.gdb.command("restore %s 0x%x" % (a.name, self.hart.ram)) + for offset in range(0, self.length, 19*4) + [self.length-4]: + value = self.gdb.p("*((int*)0x%x)" % (self.hart.ram + offset)) written = ord(data[offset]) | \ (ord(data[offset+1]) << 8) | \ (ord(data[offset+2]) << 16) | \ @@ -194,13 +195,16 @@ class MemTestBlock(GdbTest): b = tempfile.NamedTemporaryFile(suffix=".ihex") self.gdb.command("dump ihex memory %s 0x%x 0x%x" % (b.name, - self.target.ram, self.target.ram + length)) + self.hart.ram, self.hart.ram + self.length)) for line in b: record_type, address, line_data = ihex_parse(line) if record_type == 0: - assertEqual(readable_binary_string(line_data), - readable_binary_string( - data[address:address+len(line_data)])) + written_data = data[address:address+len(line_data)] + if line_data != written_data: + raise TestFailed( + "Data mismatch at 0x%x; wrote %s but read %s" % ( + address, readable_binary_string(written_data), + readable_binary_string(line_data))) class InstantHaltTest(GdbTest): def test(self): @@ -213,7 +217,7 @@ class InstantHaltTest(GdbTest): self.gdb.thread(t) pcs.append(self.gdb.p("$pc")) for pc in pcs: - assertEqual(self.target.reset_vector, pc) + assertEqual(self.hart.reset_vector, pc) # mcycle and minstret have no defined reset value. mstatus = self.gdb.p("$mstatus") assertEqual(mstatus & (MSTATUS_MIE | MSTATUS_MPRV | @@ -225,16 +229,16 @@ class InstantChangePc(GdbTest): # 0x13 is nop self.gdb.command("monitor reset halt") self.gdb.command("flushregs") - self.gdb.command("p *((int*) 0x%x)=0x13" % self.target.ram) - self.gdb.command("p *((int*) 0x%x)=0x13" % (self.target.ram + 4)) - self.gdb.command("p *((int*) 0x%x)=0x13" % (self.target.ram + 8)) - self.gdb.p("$pc=0x%x" % self.target.ram) + self.gdb.command("p *((int*) 0x%x)=0x13" % self.hart.ram) + self.gdb.command("p *((int*) 0x%x)=0x13" % (self.hart.ram + 4)) + self.gdb.command("p *((int*) 0x%x)=0x13" % (self.hart.ram + 8)) + self.gdb.p("$pc=0x%x" % self.hart.ram) self.gdb.stepi() - assertEqual((self.target.ram + 4), self.gdb.p("$pc")) + assertEqual((self.hart.ram + 4), self.gdb.p("$pc")) self.gdb.stepi() - assertEqual((self.target.ram + 8), self.gdb.p("$pc")) + assertEqual((self.hart.ram + 8), self.gdb.p("$pc")) -class DebugTest(GdbTest): +class DebugTest(GdbSingleHartTest): # Include malloc so that gdb can make function calls. I suspect this malloc # will silently blow through the memory set aside for it, so be careful. compile_args = ("programs/debug.c", "programs/checksum.c", @@ -325,10 +329,10 @@ class DebugBreakpoint(DebugTest): class Hwbp1(DebugTest): def test(self): - if self.target.instruction_hardware_breakpoint_count < 1: + if self.hart.instruction_hardware_breakpoint_count < 1: return 'not_applicable' - if not self.target.honors_tdata1_hmode: + if not self.hart.honors_tdata1_hmode: # Run to main before setting the breakpoint, because startup code # will otherwise clear the trigger that we set. self.gdb.b("main") @@ -345,7 +349,7 @@ class Hwbp1(DebugTest): class Hwbp2(DebugTest): def test(self): - if self.target.instruction_hardware_breakpoint_count < 2: + if self.hart.instruction_hardware_breakpoint_count < 2: return 'not_applicable' self.gdb.hbreak("main") @@ -415,21 +419,19 @@ class UserInterrupt(DebugTest): self.gdb.p("i=0") self.exit() -class MulticoreTest(GdbTest): - compile_args = ("programs/infinite_loop.S", ) +class MulticoreRegTest(GdbTest): + compile_args = ("programs/infinite_loop.S", "-DMULTICORE") + + def early_applicable(self): + return len(self.target.harts) > 1 def setup(self): self.gdb.load() - - def test(self): - threads = self.gdb.threads() - if len(threads) < 2: - return 'not_applicable' - - for t in threads: - self.gdb.thread(t) + for hart in self.target.harts: + self.gdb.select_hart(hart) self.gdb.p("$pc=_start") + def test(self): # Run to main self.gdb.b("main") self.gdb.c() @@ -456,18 +458,47 @@ class MulticoreTest(GdbTest): # Confirmed that we read different register values for different harts. # Write a new value to x1, and run through the add sequence again. - for t in threads: - self.gdb.thread(t) - self.gdb.p("$x1=0x%x" % (int(t.id) * 0x800)) + for hart in self.target.harts: + self.gdb.select_hart(hart) + self.gdb.p("$x1=0x%x" % (hart.index * 0x800)) self.gdb.p("$pc=main_post_csrr") self.gdb.c() for t in self.gdb.threads(): assertIn("main_end", t.frame) + for hart in self.target.harts: # Check register values. - self.gdb.thread(t) + self.gdb.select_hart(hart) for n in range(1, 32): value = self.gdb.p("$x%d" % n) - assertEqual(value, int(t.id) * 0x800 + n - 1) + assertEqual(value, hart.index * 0x800 + n - 1) + +class MulticoreRunHaltStepiTest(GdbTest): + compile_args = ("programs/multicore.c", "-DMULTICORE") + + def early_applicable(self): + return len(self.target.harts) > 1 + + def setup(self): + self.gdb.load() + for hart in self.target.harts: + self.gdb.select_hart(hart) + self.gdb.p("$pc=_start") + + def test(self): + previous_hart_count = [0 for h in self.target.harts] + for _ in range(10): + self.gdb.c(wait=False) + time.sleep(1) + self.gdb.interrupt() + self.gdb.p("buf", fmt="") + hart_count = self.gdb.p("hart_count") + for i, h in enumerate(self.target.harts): + assertGreater(hart_count[i], previous_hart_count[i]) + self.gdb.select_hart(h) + pc = self.gdb.p("$pc") + self.gdb.stepi() + stepped_pc = self.gdb.p("$pc") + assertNotEqual(pc, stepped_pc) class StepTest(GdbTest): compile_args = ("programs/step.S", ) @@ -479,7 +510,7 @@ class StepTest(GdbTest): def test(self): main_address = self.gdb.p("$pc") - if self.target.extensionSupported("c"): + if self.hart.extensionSupported("c"): sequence = (4, 8, 0xc, 0xe, 0x14, 0x18, 0x22, 0x1c, 0x24, 0x24) else: sequence = (4, 8, 0xc, 0x10, 0x18, 0x1c, 0x28, 0x20, 0x2c, 0x2c) @@ -558,16 +589,16 @@ class TriggerStoreAddressInstant(TriggerTest): class TriggerDmode(TriggerTest): def early_applicable(self): - return self.target.honors_tdata1_hmode + return self.hart.honors_tdata1_hmode def check_triggers(self, tdata1_lsbs, tdata2): - dmode = 1 << (self.target.xlen-5) + dmode = 1 << (self.hart.xlen-5) triggers = [] - if self.target.xlen == 32: + if self.hart.xlen == 32: xlen_type = 'int' - elif self.target.xlen == 64: + elif self.hart.xlen == 64: xlen_type = 'long long' else: raise NotImplementedError @@ -627,7 +658,7 @@ class WriteGprs(RegsTest): self.gdb.command("info registers") for n in range(len(regs)): assertEqual(self.gdb.x("data+%d" % (8*n), 'g'), - ((0xdeadbeef<<n)+17) & ((1<<self.target.xlen)-1)) + ((0xdeadbeef<<n)+17) & ((1<<self.hart.xlen)-1)) class WriteCsrs(RegsTest): def test(self): @@ -651,7 +682,7 @@ class WriteCsrs(RegsTest): class DownloadTest(GdbTest): def setup(self): # pylint: disable=attribute-defined-outside-init - length = min(2**10, self.target.ram_size - 2048) + length = min(2**10, self.hart.ram_size - 2048) self.download_c = tempfile.NamedTemporaryFile(prefix="download_", suffix=".c", delete=False) self.download_c.write("#include <stdint.h>\n") @@ -677,7 +708,7 @@ class DownloadTest(GdbTest): if self.crc < 0: self.crc += 2**32 - self.binary = self.target.compile(self.download_c.name, + self.binary = self.target.compile(self.hart, self.download_c.name, "programs/checksum.c") self.gdb.command("file %s" % self.binary) @@ -708,7 +739,7 @@ class DownloadTest(GdbTest): # # pylint: disable=attribute-defined-outside-init # self.gdb.load() # -# misa = self.target.misa +# misa = self.hart.misa # self.supported = set() # if misa & (1<<20): # self.supported.add(0) diff --git a/debug/programs/entry.S b/debug/programs/entry.S index ff8ae30..a2ea955 100755 --- a/debug/programs/entry.S +++ b/debug/programs/entry.S @@ -1,9 +1,8 @@ -#ifndef ENTRY_S -#define ENTRY_S - #include "encoding.h" -#define STACK_SIZE 512 +// Enough stack to store every register in case a trap handler is executed, +// plus 33 more values. +#define STACK_SIZE (64 * XLEN / 8) #if XLEN == 64 # define LREG ld @@ -61,8 +60,15 @@ handle_reset: la gp, __global_pointer$ .option pop - # initialize stack pointer - la sp, stack_top + # Initialize stack pointer. + # Give each hart STACK_SIZE of stack. + # Assume hart IDs are contiguous and start at 0. + csrr t0, CSR_MHARTID + addi t0, t0, 1 + li t1, STACK_SIZE + mul t0, t0, t1 + la sp, stack_bottom + add sp, sp, t0 # Clear all hardware triggers li t0, ~0 @@ -73,8 +79,33 @@ handle_reset: csrr t1, CSR_TSELECT beq t0, t1, 1b +#ifdef MULTICORE + csrr t0, CSR_MHARTID + bnez t0, wait_until_initialized +#endif + + la t0, __bss_start + la t1, __bss_end +1: + bge t0, t1, 2f + sb zero, 0(t0) + addi t0, t0, 1 + j 1b +2: +#ifdef MULTICORE + la t0, initialized + li t1, 1 + sw t1, 0(t0) + +wait_until_initialized: # Wait for hart 0 to perform initialization. + la t0, initialized +1: + lw t1, 0(t0) + beqz t1, 1b +#endif + # perform the rest of initialization in C - j _init + j _init trap_entry: @@ -157,9 +188,13 @@ trap_entry: addi sp, sp, 32*REGBYTES mret +loop_forever: + j loop_forever + // Fill the stack with data so we can see if it was overrun. .align 4 stack_bottom: - .fill STACK_SIZE/4, 4, 0x22446688 + .fill NHARTS * STACK_SIZE/4, 4, 0x22446688 stack_top: -#endif +initialized: + .word 0 diff --git a/debug/programs/init.c b/debug/programs/init.c index a2b41b0..9933c23 100644 --- a/debug/programs/init.c +++ b/debug/programs/init.c @@ -1,7 +1,30 @@ +#include "init.h" +#include "encoding.h" + int main(void); +trap_handler_t trap_handler[NHARTS] = {0}; + +void set_trap_handler(trap_handler_t handler) +{ + unsigned hartid = csr_read(mhartid); + trap_handler[hartid] = handler; +} + +void enable_timer_interrupts() +{ + set_csr(mie, MIP_MTIP); + set_csr(mstatus, MSTATUS_MIE); +} + void handle_trap(unsigned int mcause, unsigned int mepc, unsigned int sp) { + unsigned hartid = csr_read(mhartid); + if (trap_handler[hartid]) { + trap_handler[hartid](hartid, mcause, mepc, sp); + return; + } + while (1) ; } diff --git a/debug/programs/multicore.c b/debug/programs/multicore.c new file mode 100644 index 0000000..d7dd845 --- /dev/null +++ b/debug/programs/multicore.c @@ -0,0 +1,81 @@ +#include <stdint.h> + +typedef struct { + int counter; +} atomic_t; + +static inline int atomic_xchg(atomic_t *v, int n) +{ + register int c; + + __asm__ __volatile__ ( + "amoswap.w.aqrl %0, %2, %1" + : "=r" (c), "+A" (v->counter) + : "r" (n)); + return c; +} + +#define csr_read(csr) \ +({ \ + register unsigned long __v; \ + __asm__ __volatile__ ("csrr %0, " #csr \ + : "=r" (__v)); \ + __v; \ +}) + +static inline void mb(void) +{ + __asm__ __volatile__ ("fence"); +} + +void get_lock(atomic_t *lock) +{ + while (atomic_xchg(lock, 1) == 1) + ; + mb(); +} + +void put_lock(atomic_t *lock) +{ + mb(); + atomic_xchg(lock, 0); +} + +static atomic_t buf_lock = { .counter = 0 }; +static char buf[32]; +static int buf_initialized; +static unsigned hart_count[2]; + +static const char case_bit = 'a' - 'A'; +volatile int initialized; + +int main() +{ + uint32_t hartid = csr_read(mhartid); + hart_count[hartid] = 0; + + while (1) { + get_lock(&buf_lock); + hart_count[hartid]++; + + if (!buf_initialized) { + for (unsigned i = 0; i < sizeof(buf); i++) { + buf[i] = 'A' + (i % 26); + } + buf_initialized = 1; + } + + char first = buf[0]; + int offset = (first & ~0x20) - 'A'; + for (unsigned i = 0; i < sizeof(buf); i++) { + while (buf[i] != (first - offset + ((offset + i) % 26))) + ; + + if (hartid & 1) + buf[i] = 'A' + ((i + hartid + hart_count[hartid]) % 26); + else + buf[i] = 'a' + ((i + hartid + hart_count[hartid]) % 26); + } + put_lock(&buf_lock); + } +} diff --git a/debug/programs/start.S b/debug/programs/start.S deleted file mode 100644 index 76c37bb..0000000 --- a/debug/programs/start.S +++ /dev/null @@ -1,12 +0,0 @@ - .global _start - -_start: - la sp, stack_end - jal main -done: - j done - - .data -stack: - .fill 4096, 1, 0 -stack_end: diff --git a/debug/targets.py b/debug/targets.py index 7183a38..db8d917 100644 --- a/debug/targets.py +++ b/debug/targets.py @@ -5,51 +5,71 @@ import tempfile import testlib +class Hart(object): + # XLEN of the hart. May be overridden with --32 or --64 command line + # options. + xlen = 0 + + # Will be autodetected (by running ExamineTarget) if left unset. Set to + # save a little time. + misa = None + + # Path to linker script relative to the .py file where the target is + # defined. Defaults to <name>.lds. + link_script_path = None + + # Implements dmode in tdata1 as described in the spec. Harts that need + # this value set to False are not compliant with the spec (but still usable + # as long as running code doesn't try to mess with triggers set by an + # external debugger). + honors_tdata1_hmode = True + + # Address where a r/w/x block of RAM starts, together with its size. + ram = None + ram_size = None + + # Number of instruction triggers the hart supports. + instruction_hardware_breakpoint_count = 0 + + # Defaults to target-<index> + name = None + + def extensionSupported(self, letter): + # target.misa is set by testlib.ExamineTarget + if self.misa: + return self.misa & (1 << (ord(letter.upper()) - ord('A'))) + else: + return False + class Target(object): # pylint: disable=too-many-instance-attributes + # List of Hart object instances, one for each hart in the target. + harts = [] + # Name of the target. Defaults to the name of the class. name = None - # XLEN of the target. May be overridden with --32 or --64 command line - # options. - xlen = 0 - # GDB remotetimeout setting. timeout_sec = 2 - # Path to OpenOCD configuration file relative to the .py file where the - # target is defined. Defaults to <name>.cfg. - openocd_config_path = None - # Timeout waiting for the server to start up. This is different than the # GDB timeout, which is how long GDB waits for commands to execute. # The server_timeout is how long this script waits for the Server to be # ready for GDB connections. server_timeout_sec = 60 - # Path to linker script relative to the .py file where the target is - # defined. Defaults to <name>.lds. - link_script_path = None - - # Will be autodetected (by running ExamineTarget) if left unset. Set to - # save a little time. - misa = None + # Path to OpenOCD configuration file relative to the .py file where the + # target is defined. Defaults to <name>.cfg. + openocd_config_path = None # List of commands that should be executed in gdb after connecting but # before starting the test. gdb_setup = [] - # Implements dmode in tdata1 as described in the spec. Targets that need - # this value set to False are not compliant with the spec (but still usable - # as long as running code doesn't try to mess with triggers set by an - # external debugger). - honors_tdata1_hmode = True - # Internal variables: directory = None temporary_files = [] - temporary_binary = None def __init__(self, path, parsed): # Path to module. @@ -57,7 +77,8 @@ class Target(object): self.directory = os.path.dirname(path) self.server_cmd = parsed.server_cmd self.sim_cmd = parsed.sim_cmd - self.isolate = parsed.isolate + self.temporary_binary = None + Target.isolate = parsed.isolate if not self.name: self.name = type(self).__name__ # Default OpenOCD config file to <name>.cfg @@ -65,11 +86,15 @@ class Target(object): self.openocd_config_path = "%s.cfg" % self.name self.openocd_config_path = os.path.join(self.directory, self.openocd_config_path) - # Default link script to <name>.lds - if not self.link_script_path: - self.link_script_path = "%s.lds" % self.name - self.link_script_path = os.path.join(self.directory, - self.link_script_path) + for i, hart in enumerate(self.harts): + hart.index = i + if not hart.name: + hart.name = "%s-%d" % (self.name, i) + # Default link script to <name>.lds + if not hart.link_script_path: + hart.link_script_path = "%s.lds" % self.name + hart.link_script_path = os.path.join(self.directory, + hart.link_script_path) def create(self): """Create the target out of thin air, eg. start a simulator.""" @@ -78,42 +103,35 @@ class Target(object): def server(self): """Start the debug server that gdb connects to, eg. OpenOCD.""" return testlib.Openocd(server_cmd=self.server_cmd, - config=self.openocd_config_path, - timeout=self.server_timeout_sec) + config=self.openocd_config_path) - def compile(self, *sources): + def compile(self, hart, *sources): binary_name = "%s_%s-%d" % ( self.name, os.path.basename(os.path.splitext(sources[0])[0]), - self.xlen) - if self.isolate: + hart.xlen) + if Target.isolate: self.temporary_binary = tempfile.NamedTemporaryFile( prefix=binary_name + "_") binary_name = self.temporary_binary.name Target.temporary_files.append(self.temporary_binary) - march = "rv%dima" % self.xlen + march = "rv%dima" % hart.xlen for letter in "fdc": - if self.extensionSupported(letter): + if hart.extensionSupported(letter): march += letter testlib.compile(sources + ("programs/entry.S", "programs/init.c", + "-DNHARTS=%d" % len(self.harts), "-I", "../env", "-march=%s" % march, - "-T", self.link_script_path, + "-T", hart.link_script_path, "-nostartfiles", "-mcmodel=medany", - "-DXLEN=%d" % self.xlen, + "-DXLEN=%d" % hart.xlen, "-o", binary_name), - xlen=self.xlen) + xlen=hart.xlen) return binary_name - def extensionSupported(self, letter): - # target.misa is set by testlib.ExamineTarget - if self.misa: - return self.misa & (1 << (ord(letter.upper()) - ord('A'))) - else: - return False - def add_target_options(parser): parser.add_argument("target", help=".py file that contains definition for " "the target to test with.") @@ -149,4 +167,7 @@ def target(parsed): assert len(found) == 1, "%s does not define exactly one subclass of " \ "targets.Target" % parsed.target - return found[0](parsed.target, parsed) + t = found[0](parsed.target, parsed) + assert t.harts, "%s doesn't have any harts defined!" % t.name + + return t diff --git a/debug/targets/RISC-V/spike32.cfg b/debug/targets/RISC-V/spike.cfg index 2742335..9b1841c 100644 --- a/debug/targets/RISC-V/spike32.cfg +++ b/debug/targets/RISC-V/spike.cfg @@ -8,12 +8,9 @@ set _CHIPNAME riscv jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913 set _TARGETNAME $_CHIPNAME.cpu -#target create $_TARGETNAME riscv -chain-position $_TARGETNAME -rtos riscv -target create $_TARGETNAME riscv -chain-position $_TARGETNAME +target create $_TARGETNAME riscv -chain-position $_TARGETNAME -rtos riscv gdb_report_data_abort enable init reset halt - -echo "Ready for Remote Connections" diff --git a/debug/targets/RISC-V/spike32-2.py b/debug/targets/RISC-V/spike32-2.py new file mode 100644 index 0000000..6cf558d --- /dev/null +++ b/debug/targets/RISC-V/spike32-2.py @@ -0,0 +1,12 @@ +import targets +import testlib + +import spike32 # pylint: disable=import-error + +class spike32_2(targets.Target): + harts = [spike32.spike32_hart(), spike32.spike32_hart()] + openocd_config_path = "spike.cfg" + timeout_sec = 30 + + def create(self): + return testlib.Spike(self) diff --git a/debug/targets/RISC-V/spike32.lds b/debug/targets/RISC-V/spike32.lds index 01d0e3d..84216db 100755 --- a/debug/targets/RISC-V/spike32.lds +++ b/debug/targets/RISC-V/spike32.lds @@ -22,11 +22,13 @@ SECTIONS } /* bss segment */ + __bss_start = .; .sbss : { *(.sbss .sbss.* .gnu.linkonce.sb.*) *(.scommon) } .bss : { *(.bss) } + __bss_end = .; __malloc_start = .; . = . + 512; diff --git a/debug/targets/RISC-V/spike32.py b/debug/targets/RISC-V/spike32.py index 3bf8b47..665d7e9 100644 --- a/debug/targets/RISC-V/spike32.py +++ b/debug/targets/RISC-V/spike32.py @@ -1,12 +1,18 @@ import targets import testlib -class spike32(targets.Target): +class spike32_hart(targets.Hart): xlen = 32 ram = 0x10000000 ram_size = 0x10000000 instruction_hardware_breakpoint_count = 4 reset_vector = 0x1000 + link_script_path = "spike32.lds" + +class spike32(targets.Target): + harts = [spike32_hart()] + openocd_config_path = "spike.cfg" + timeout_sec = 30 def create(self): return testlib.Spike(self) diff --git a/debug/targets/RISC-V/spike64-2.py b/debug/targets/RISC-V/spike64-2.py new file mode 100644 index 0000000..c6321dc --- /dev/null +++ b/debug/targets/RISC-V/spike64-2.py @@ -0,0 +1,12 @@ +import targets +import testlib + +import spike64 # pylint: disable=import-error + +class spike64_2(targets.Target): + harts = [spike64.spike64_hart(), spike64.spike64_hart()] + openocd_config_path = "spike.cfg" + timeout_sec = 30 + + def create(self): + return testlib.Spike(self) diff --git a/debug/targets/RISC-V/spike64.cfg b/debug/targets/RISC-V/spike64.cfg deleted file mode 100644 index 2742335..0000000 --- a/debug/targets/RISC-V/spike64.cfg +++ /dev/null @@ -1,19 +0,0 @@ -adapter_khz 10000 - -interface remote_bitbang -remote_bitbang_host $::env(REMOTE_BITBANG_HOST) -remote_bitbang_port $::env(REMOTE_BITBANG_PORT) - -set _CHIPNAME riscv -jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913 - -set _TARGETNAME $_CHIPNAME.cpu -#target create $_TARGETNAME riscv -chain-position $_TARGETNAME -rtos riscv -target create $_TARGETNAME riscv -chain-position $_TARGETNAME - -gdb_report_data_abort enable - -init -reset halt - -echo "Ready for Remote Connections" diff --git a/debug/targets/RISC-V/spike64.lds b/debug/targets/RISC-V/spike64.lds index dc7cb63..2e7d65d 100755 --- a/debug/targets/RISC-V/spike64.lds +++ b/debug/targets/RISC-V/spike64.lds @@ -20,11 +20,13 @@ SECTIONS } /* bss segment */ + __bss_start = .; .sbss : { *(.sbss .sbss.* .gnu.linkonce.sb.*) *(.scommon) } .bss : { *(.bss) } + __bss_end = .; __malloc_start = .; . = . + 512; diff --git a/debug/targets/RISC-V/spike64.py b/debug/targets/RISC-V/spike64.py index c705857..6e3da89 100644 --- a/debug/targets/RISC-V/spike64.py +++ b/debug/targets/RISC-V/spike64.py @@ -1,12 +1,18 @@ import targets import testlib -class spike64(targets.Target): +class spike64_hart(targets.Hart): xlen = 64 ram = 0x1212340000 ram_size = 0x10000000 instruction_hardware_breakpoint_count = 4 reset_vector = 0x1000 + link_script_path = "spike64.lds" + +class spike64(targets.Target): + harts = [spike64_hart()] + openocd_config_path = "spike.cfg" + timeout_sec = 30 def create(self): return testlib.Spike(self) diff --git a/debug/targets/SiFive/Freedom/E300.py b/debug/targets/SiFive/Freedom/E300.py index 95ddcfd..170de40 100644 --- a/debug/targets/SiFive/Freedom/E300.py +++ b/debug/targets/SiFive/Freedom/E300.py @@ -1,9 +1,12 @@ import targets -class E300(targets.Target): +class E300Hart(targets.Hart): xlen = 32 ram = 0x80000000 - ram_size = 16 * 1024 + ram_size = 256 * 1024 * 1024 instruction_hardware_breakpoint_count = 2 - openocd_config_path = "Freedom.cfg" link_script_path = "Freedom.lds" + +class E300(targets.Target): + openocd_config_path = "Freedom.cfg" + harts = [E300Hart()] diff --git a/debug/targets/SiFive/Freedom/E300Sim.py b/debug/targets/SiFive/Freedom/E300Sim.py index 91be2e8..f9428d0 100644 --- a/debug/targets/SiFive/Freedom/E300Sim.py +++ b/debug/targets/SiFive/Freedom/E300Sim.py @@ -1,14 +1,17 @@ import targets import testlib -class E300Sim(targets.Target): +class E300Hart(targets.Hart): xlen = 32 - timeout_sec = 6000 ram = 0x80000000 ram_size = 256 * 1024 * 1024 instruction_hardware_breakpoint_count = 2 - openocd_config_path = "Freedom.cfg" link_script_path = "Freedom.lds" +class E300Sim(targets.Target): + timeout_sec = 6000 + openocd_config_path = "Freedom.cfg" + harts = [E300Hart()] + def create(self): return testlib.VcsSim(sim_cmd=self.sim_cmd, debug=False) diff --git a/debug/targets/SiFive/Freedom/Freedom.lds b/debug/targets/SiFive/Freedom/Freedom.lds index 1e0645a..9354d3f 100644 --- a/debug/targets/SiFive/Freedom/Freedom.lds +++ b/debug/targets/SiFive/Freedom/Freedom.lds @@ -20,11 +20,13 @@ SECTIONS } /* bss segment */ + __bss_start = .; .sbss : { *(.sbss .sbss.* .gnu.linkonce.sb.*) *(.scommon) } .bss : { *(.bss) } + __bss_end = .; __malloc_start = .; . = . + 512; diff --git a/debug/targets/SiFive/Freedom/U500.py b/debug/targets/SiFive/Freedom/U500.py index c22aa4c..6da3ac5 100644 --- a/debug/targets/SiFive/Freedom/U500.py +++ b/debug/targets/SiFive/Freedom/U500.py @@ -1,9 +1,12 @@ import targets -class U500(targets.Target): +class U500Hart(targets.Hart): xlen = 64 ram = 0x80000000 ram_size = 16 * 1024 instruction_hardware_breakpoint_count = 2 - openocd_config_path = "Freedom.cfg" link_script_path = "Freedom.lds" + +class U500(targets.Target): + openocd_config_path = "Freedom.cfg" + harts = [U500Hart()] diff --git a/debug/targets/SiFive/Freedom/U500Sim.py b/debug/targets/SiFive/Freedom/U500Sim.py index 62bc827..065ab08 100644 --- a/debug/targets/SiFive/Freedom/U500Sim.py +++ b/debug/targets/SiFive/Freedom/U500Sim.py @@ -1,14 +1,17 @@ import targets import testlib -class U500Sim(targets.Target): +class U500Hart(targets.Hart): xlen = 64 - timeout_sec = 6000 ram = 0x80000000 ram_size = 256 * 1024 * 1024 instruction_hardware_breakpoint_count = 2 - openocd_config_path = "Freedom.cfg" link_script_path = "Freedom.lds" - def create(self): +class U500Sim(targets.Target): + timeout_sec = 6000 + openocd_config_path = "Freedom.cfg" + harts = [U500Hart()] + + def target(self): return testlib.VcsSim(sim_cmd=self.sim_cmd, debug=False) diff --git a/debug/targets/SiFive/HiFive1.lds b/debug/targets/SiFive/HiFive1.lds index 1e0645a..9354d3f 100755 --- a/debug/targets/SiFive/HiFive1.lds +++ b/debug/targets/SiFive/HiFive1.lds @@ -20,11 +20,13 @@ SECTIONS } /* bss segment */ + __bss_start = .; .sbss : { *(.sbss .sbss.* .gnu.linkonce.sb.*) *(.scommon) } .bss : { *(.bss) } + __bss_end = .; __malloc_start = .; . = . + 512; diff --git a/debug/targets/SiFive/HiFive1.py b/debug/targets/SiFive/HiFive1.py index 813829e..3cb508c 100644 --- a/debug/targets/SiFive/HiFive1.py +++ b/debug/targets/SiFive/HiFive1.py @@ -1,8 +1,11 @@ import targets -class HiFive1(targets.Target): +class HiFive1Hart(targets.Hart): xlen = 32 ram = 0x80000000 ram_size = 16 * 1024 instruction_hardware_breakpoint_count = 2 misa = 0x40001105 + +class HiFive1(targets.Target): + harts = [HiFive1Hart()] diff --git a/debug/testlib.py b/debug/testlib.py index b76f320..8ac616e 100644 --- a/debug/testlib.py +++ b/debug/testlib.py @@ -17,8 +17,11 @@ import pexpect def find_file(path): for directory in (os.getcwd(), os.path.dirname(__file__)): fullpath = os.path.join(directory, path) - if os.path.exists(fullpath): - return fullpath + relpath = os.path.relpath(fullpath) + if len(relpath) >= len(fullpath): + relpath = fullpath + if os.path.exists(relpath): + return relpath return None def compile(args, xlen=32): # pylint: disable=redefined-builtin @@ -36,13 +39,12 @@ def compile(args, xlen=32): # pylint: disable=redefined-builtin cmd.append(found) else: cmd.append(arg) + header("Compile") + print "+", " ".join(cmd) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() if process.returncode: - print - header("Compile failed") - print "+", " ".join(cmd) print stdout, print stderr, header("") @@ -63,16 +65,34 @@ class Spike(object): def __init__(self, target, halted=False, timeout=None, with_jtag_gdb=True): """Launch spike. Return tuple of its process and the port it's running on.""" + self.process = None + + if target.harts: + harts = target.harts + else: + harts = [target] + if target.sim_cmd: cmd = shlex.split(target.sim_cmd) else: spike = os.path.expandvars("$RISCV/bin/spike") cmd = [spike] - if target.xlen == 32: + + cmd += ["-p%d" % len(harts)] + + assert len(set(t.xlen for t in harts)) == 1, \ + "All spike harts must have the same XLEN" + + if harts[0].xlen == 32: cmd += ["--isa", "RV32G"] else: cmd += ["--isa", "RV64G"] - cmd += ["-m0x%x:0x%x" % (target.ram, target.ram_size)] + + assert len(set(t.ram for t in harts)) == 1, \ + "All spike harts must have the same RAM layout" + assert len(set(t.ram_size for t in harts)) == 1, \ + "All spike harts must have the same RAM layout" + cmd += ["-m0x%x:0x%x" % (harts[0].ram, harts[0].ram_size)] if timeout: cmd = ["timeout", str(timeout)] + cmd @@ -82,7 +102,7 @@ class Spike(object): if with_jtag_gdb: cmd += ['--rbb-port', '0'] os.environ['REMOTE_BITBANG_HOST'] = 'localhost' - self.infinite_loop = target.compile( + self.infinite_loop = target.compile(harts[0], "programs/checksum.c", "programs/tiny-malloc.c", "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE") cmd.append(self.infinite_loop) @@ -106,11 +126,12 @@ class Spike(object): "connection" def __del__(self): - try: - self.process.kill() - self.process.wait() - except OSError: - pass + if self.process: + try: + self.process.kill() + self.process.wait() + except OSError: + pass def wait(self, *args, **kwargs): return self.process.wait(*args, **kwargs) @@ -164,6 +185,8 @@ class Openocd(object): print "OpenOCD Temporary Log File: %s" % logname def __init__(self, server_cmd=None, config=None, debug=False, timeout=60): + self.timeout = timeout + if server_cmd: cmd = shlex.split(server_cmd) else: @@ -200,7 +223,13 @@ class Openocd(object): logfile = open(Openocd.logname, "w") logfile.write("+ %s\n" % " ".join(cmd)) logfile.flush() - self.process = subprocess.Popen(cmd, stdin=subprocess.PIPE, + + self.ports = [] + self.port = None + self.process = self.start(cmd, logfile) + + def start(self, cmd, logfile): + process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=logfile, stderr=logfile) try: @@ -215,19 +244,23 @@ class Openocd(object): m = re.search(r"Listening on port (\d+) for gdb connections", log) if m: - self.port = int(m.group(1)) + if not self.ports: + self.port = int(m.group(1)) + self.ports.append(int(m.group(1))) + + if "telnet server disabled" in log: break - if not self.process.poll() is None: + if not process.poll() is None: raise Exception( "OpenOCD exited before completing riscv_examine()") if not messaged and time.time() - start > 1: messaged = True print "Waiting for OpenOCD to start..." - if (time.time() - start) > timeout: + if (time.time() - start) > self.timeout: raise Exception("ERROR: Timed out waiting for OpenOCD to " "listen for gdb") - + return process except Exception: header("OpenOCD log") sys.stdout.write(log) @@ -291,6 +324,10 @@ class Gdb(object): # Force consistency. self.command("set print entry-values no") + def select_hart(self, hart): + output = self.command("thread %d" % (hart.index + 1)) + assert "Unknown" not in output + def wait(self): """Wait for prompt.""" self.child.expect(r"\(gdb\)") @@ -331,13 +368,23 @@ class Gdb(object): raise CannotAccess(int(m.group(1), 0)) return output.split('=')[-1].strip() - def p(self, obj): - output = self.command("p/x %s" % obj) + def parse_string(self, text): + text = text.strip() + if text.startswith("{") and text.endswith("}"): + inner = text[1:-1] + return [self.parse_string(t) for t in inner.split(", ")] + elif text.startswith('"') and text.endswith('"'): + return text[1:-1] + else: + return int(text, 0) + + def p(self, obj, fmt="/x"): + output = self.command("p%s %s" % (fmt, obj)) m = re.search("Cannot access memory at address (0x[0-9a-f]+)", output) if m: raise CannotAccess(int(m.group(1), 0)) - value = int(output.split('=')[-1].strip(), 0) - return value + rhs = output.split('=')[-1] + return self.parse_string(rhs) def p_string(self, obj): output = self.command("p %s" % obj) @@ -391,19 +438,20 @@ def run_all_tests(module, target, parsed): gdb_cmd = parsed.gdb todo = [] - if parsed.misaval: - target.misa = int(parsed.misaval, 16) - print "Using $misa from command line: 0x%x" % target.misa - elif target.misa: - print "Using $misa from target definition: 0x%x" % target.misa - else: - todo.append(("ExamineTarget", ExamineTarget)) + for hart in target.harts: + if parsed.misaval: + hart.misa = int(parsed.misaval, 16) + print "Using $misa from command line: 0x%x" % hart.misa + elif hart.misa: + print "Using $misa from hart definition: 0x%x" % hart.misa + else: + todo.append(("ExamineTarget", ExamineTarget, hart)) for name in dir(module): definition = getattr(module, name) if type(definition) == type and hasattr(definition, 'test') and \ (not parsed.test or any(test in name for test in parsed.test)): - todo.append((name, definition)) + todo.append((name, definition, None)) results, count = run_tests(parsed, target, todo) @@ -417,12 +465,12 @@ def run_tests(parsed, target, todo): results = {} count = 0 - for name, definition in todo: - instance = definition(target) + for name, definition, hart in todo: log_name = os.path.join(parsed.logs, "%s-%s-%s.log" % (time.strftime("%Y%m%d-%H%M%S"), type(target).__name__, name)) log_fd = open(log_name, 'w') print "Running %s > %s ..." % (name, log_name), + instance = definition(target, hart) sys.stdout.flush() log_fd.write("Test: %s\n" % name) log_fd.write("Target: %s\n" % type(target).__name__) @@ -491,8 +539,13 @@ def print_log(path): class BaseTest(object): compiled = {} - def __init__(self, target): + def __init__(self, target, hart=None): self.target = target + if hart: + self.hart = hart + else: + self.hart = random.choice(target.harts) + self.hart = target.harts[-1] #<<< self.server = None self.target_process = None self.binary = None @@ -514,7 +567,7 @@ class BaseTest(object): if compile_args not in BaseTest.compiled: # pylint: disable=star-args BaseTest.compiled[compile_args] = \ - self.target.compile(*compile_args) + self.target.compile(self.hart, *compile_args) self.binary = BaseTest.compiled.get(compile_args) def classSetup(self): @@ -581,8 +634,8 @@ class BaseTest(object): gdb_cmd = None class GdbTest(BaseTest): - def __init__(self, target): - BaseTest.__init__(self, target) + def __init__(self, target, hart=None): + BaseTest.__init__(self, target, hart=hart) self.gdb = None def classSetup(self): @@ -598,15 +651,12 @@ class GdbTest(BaseTest): if self.binary: self.gdb.command("file %s" % self.binary) if self.target: - self.gdb.command("set arch riscv:rv%d" % self.target.xlen) + self.gdb.command("set arch riscv:rv%d" % self.hart.xlen) self.gdb.command("set remotetimeout %d" % self.target.timeout_sec) if self.server.port: self.gdb.command( "target extended-remote localhost:%d" % self.server.port) - # Select a random thread. - # TODO: Allow a command line option to force a specific thread. - thread = random.choice(self.gdb.threads()) - self.gdb.thread(thread) + self.gdb.select_hart(self.hart) for cmd in self.target.gdb_setup: self.gdb.command(cmd) @@ -618,6 +668,17 @@ class GdbTest(BaseTest): del self.gdb BaseTest.classTeardown(self) +class GdbSingleHartTest(GdbTest): + def classSetup(self): + GdbTest.classSetup(self) + + for hart in self.target.harts: + # Park all harts that we're not using in a safe place. + if hart != self.hart: + self.gdb.select_hart(hart) + self.gdb.p("$pc=loop_forever") + self.gdb.select_hart(self.hart) + class ExamineTarget(GdbTest): def test(self): self.target.misa = self.gdb.p("$misa") |