Make the debug tests aware of multicore.
authorTim Newsome <tim@sifive.com>
Mon, 7 Aug 2017 19:51:42 +0000 (12:51 -0700)
committerTim Newsome <tim@sifive.com>
Mon, 28 Aug 2017 19:16:39 +0000 (12:16 -0700)
Targets now contain an array of harts. When running a regular test, one
hart is selected to run the test on while the remaining harts are parked
in a safe infinite loop.

There's currently only one test that tests multicore behavior, but there
could be more.

The infrastructure should be able to support heterogeneous multicore,
but I don't have a target like that to test with.

18 files changed:
debug/Makefile
debug/gdbserver.py
debug/programs/entry.S
debug/programs/start.S [deleted file]
debug/targets.py
debug/targets/RISC-V/spike.cfg [new file with mode: 0644]
debug/targets/RISC-V/spike32-2.py [new file with mode: 0644]
debug/targets/RISC-V/spike32.cfg [deleted file]
debug/targets/RISC-V/spike32.py
debug/targets/RISC-V/spike64-2.py [new file with mode: 0644]
debug/targets/RISC-V/spike64.cfg [deleted file]
debug/targets/RISC-V/spike64.py
debug/targets/SiFive/Freedom/E300.py
debug/targets/SiFive/Freedom/E300Sim.py
debug/targets/SiFive/Freedom/U500.py
debug/targets/SiFive/Freedom/U500Sim.py
debug/targets/SiFive/HiFive1.py
debug/testlib.py

index 2d8d36759d0aac813935b88d88771d9caca8ad32..33988dd02615fae0a40517bedc85f80f7b4a29cf 100644 (file)
@@ -4,9 +4,9 @@ XLEN ?= 64
 src_dir ?= .
 GDBSERVER_PY = $(src_dir)/gdbserver.py
 
 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'`
 
 pylint:
        pylint --rcfile=pylint.rc `git ls-files '*.py'`
index cbb1299ad9ecafc009df6c486a8e656f49fde011..092f0181907f31db82b9806c2e574bbfdef7c381 100755 (executable)
@@ -12,7 +12,7 @@ import targets
 import testlib
 from testlib import assertEqual, assertNotEqual, assertIn, assertNotIn
 from testlib import assertGreater, assertRegexpMatches, assertLess
 import testlib
 from testlib import assertEqual, assertNotEqual, assertIn, assertNotIn
 from testlib import assertGreater, assertRegexpMatches, assertLess
-from testlib import GdbTest
+from testlib import GdbTest, GdbSingleHartTest
 
 MSTATUS_UIE = 0x00000001
 MSTATUS_SIE = 0x00000002
 
 MSTATUS_UIE = 0x00000001
 MSTATUS_SIE = 0x00000002
@@ -66,8 +66,8 @@ def readable_binary_string(s):
 
 class SimpleRegisterTest(GdbTest):
     def check_reg(self, name):
 
 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)
         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
 
     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):
 
 class SimpleS0Test(SimpleRegisterTest):
     def test(self):
@@ -114,7 +114,8 @@ class SimpleF18Test(SimpleRegisterTest):
         assertLess(abs(float(self.gdb.p_raw("$%s" % name)) - b), .001)
 
     def early_applicable(self):
         assertLess(abs(float(self.gdb.p_raw("$%s" % name)) - b), .001)
 
     def early_applicable(self):
-        return self.target.extensionSupported('F')
+        print repr(self.hart)
+        return self.hart.extensionSupported('F')
 
     def test(self):
         self.check_reg("f18")
 
     def test(self):
         self.check_reg("f18")
@@ -157,7 +158,7 @@ class MemTest64(SimpleMemoryTest):
 #            assert False, "Read should have failed."
 #        except testlib.CannotAccess as e:
 #            assertEqual(e.address, 0xdeadbeef)
 #            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):
 #
 #class MemTestWriteInvalid(SimpleMemoryTest):
 #    def test(self):
@@ -168,7 +169,7 @@ class MemTest64(SimpleMemoryTest):
 #            assert False, "Write should have failed."
 #        except testlib.CannotAccess as e:
 #            assertEqual(e.address, 0xdeadbeef)
 #            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):
     def test(self):
 
 class MemTestBlock(GdbTest):
     def test(self):
@@ -183,9 +184,9 @@ class MemTestBlock(GdbTest):
             a.write(ihex_line(i * line_length, 0, line_data))
         a.flush()
 
             a.write(ihex_line(i * line_length, 0, line_data))
         a.flush()
 
-        self.gdb.command("restore %s 0x%x" % (a.name, self.target.ram))
+        self.gdb.command("restore %s 0x%x" % (a.name, self.hart.ram))
         for offset in range(0, length, 19*4) + [length-4]:
         for offset in range(0, length, 19*4) + [length-4]:
-            value = self.gdb.p("*((int*)0x%x)" % (self.target.ram + offset))
+            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) | \
             written = ord(data[offset]) | \
                     (ord(data[offset+1]) << 8) | \
                     (ord(data[offset+2]) << 16) | \
@@ -194,7 +195,7 @@ class MemTestBlock(GdbTest):
 
         b = tempfile.NamedTemporaryFile(suffix=".ihex")
         self.gdb.command("dump ihex memory %s 0x%x 0x%x" % (b.name,
 
         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 + length))
         for line in b:
             record_type, address, line_data = ihex_parse(line)
             if record_type == 0:
         for line in b:
             record_type, address, line_data = ihex_parse(line)
             if record_type == 0:
@@ -213,7 +214,7 @@ class InstantHaltTest(GdbTest):
             self.gdb.thread(t)
             pcs.append(self.gdb.p("$pc"))
         for pc in pcs:
             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 |
         # mcycle and minstret have no defined reset value.
         mstatus = self.gdb.p("$mstatus")
         assertEqual(mstatus & (MSTATUS_MIE | MSTATUS_MPRV |
@@ -225,16 +226,16 @@ class InstantChangePc(GdbTest):
         # 0x13 is nop
         self.gdb.command("monitor reset halt")
         self.gdb.command("flushregs")
         # 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()
         self.gdb.stepi()
-        assertEqual((self.target.ram + 4), self.gdb.p("$pc"))
+        assertEqual((self.hart.ram + 4), self.gdb.p("$pc"))
         self.gdb.stepi()
         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",
     # 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 +326,10 @@ class DebugBreakpoint(DebugTest):
 
 class Hwbp1(DebugTest):
     def test(self):
 
 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'
 
             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")
             # Run to main before setting the breakpoint, because startup code
             # will otherwise clear the trigger that we set.
             self.gdb.b("main")
@@ -345,7 +346,7 @@ class Hwbp1(DebugTest):
 
 class Hwbp2(DebugTest):
     def test(self):
 
 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")
             return 'not_applicable'
 
         self.gdb.hbreak("main")
@@ -418,16 +419,15 @@ class UserInterrupt(DebugTest):
 class MulticoreTest(GdbTest):
     compile_args = ("programs/infinite_loop.S", )
 
 class MulticoreTest(GdbTest):
     compile_args = ("programs/infinite_loop.S", )
 
+    def early_applicable(self):
+        return len(self.target.harts) > 1
+
     def setup(self):
         self.gdb.load()
 
     def test(self):
     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")
 
         # Run to main
             self.gdb.p("$pc=_start")
 
         # Run to main
@@ -456,18 +456,19 @@ 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.
 
         # 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)
             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.
             # 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)
             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 StepTest(GdbTest):
     compile_args = ("programs/step.S", )
 
 class StepTest(GdbTest):
     compile_args = ("programs/step.S", )
@@ -479,7 +480,7 @@ class StepTest(GdbTest):
 
     def test(self):
         main_address = self.gdb.p("$pc")
 
     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)
             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 +559,16 @@ class TriggerStoreAddressInstant(TriggerTest):
 
 class TriggerDmode(TriggerTest):
     def early_applicable(self):
 
 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):
 
     def check_triggers(self, tdata1_lsbs, tdata2):
-        dmode = 1 << (self.target.xlen-5)
+        dmode = 1 << (self.hart.xlen-5)
 
         triggers = []
 
 
         triggers = []
 
-        if self.target.xlen == 32:
+        if self.hart.xlen == 32:
             xlen_type = 'int'
             xlen_type = 'int'
-        elif self.target.xlen == 64:
+        elif self.hart.xlen == 64:
             xlen_type = 'long long'
         else:
             raise NotImplementedError
             xlen_type = 'long long'
         else:
             raise NotImplementedError
@@ -627,7 +628,7 @@ class WriteGprs(RegsTest):
         self.gdb.command("info registers")
         for n in range(len(regs)):
             assertEqual(self.gdb.x("data+%d" % (8*n), 'g'),
         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):
 
 class WriteCsrs(RegsTest):
     def test(self):
@@ -651,7 +652,7 @@ class WriteCsrs(RegsTest):
 class DownloadTest(GdbTest):
     def setup(self):
         # pylint: disable=attribute-defined-outside-init
 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")
         self.download_c = tempfile.NamedTemporaryFile(prefix="download_",
                 suffix=".c", delete=False)
         self.download_c.write("#include <stdint.h>\n")
@@ -677,7 +678,7 @@ class DownloadTest(GdbTest):
         if self.crc < 0:
             self.crc += 2**32
 
         if self.crc < 0:
             self.crc += 2**32
 
-        self.binary = self.target.compile(self.download_c.name,
+        self.binary = self.hart.compile(self.download_c.name,
                 "programs/checksum.c")
         self.gdb.command("file %s" % self.binary)
 
                 "programs/checksum.c")
         self.gdb.command("file %s" % self.binary)
 
@@ -708,7 +709,7 @@ class DownloadTest(GdbTest):
 #        # pylint: disable=attribute-defined-outside-init
 #        self.gdb.load()
 #
 #        # 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)
 #        self.supported = set()
 #        if misa & (1<<20):
 #            self.supported.add(0)
index ff8ae3092a517ad20ce2860b7b56ac2d1cd05b5a..c3be61108e4a129b0ab2b087754d78d9d0ed1972 100755 (executable)
@@ -157,9 +157,16 @@ trap_entry:
   addi sp, sp, 32*REGBYTES
   mret
 
   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
 stack_top:
   // Fill the stack with data so we can see if it was overrun.
   .align 4
 stack_bottom:
   .fill STACK_SIZE/4, 4, 0x22446688
 stack_top:
+  // Prevent stack_top from being identical to next symbol, which may cause gdb
+  // to report we're halted at stack_top which happens to be the same address
+  // as main.
+  .word 0
 #endif
 #endif
diff --git a/debug/programs/start.S b/debug/programs/start.S
deleted file mode 100644 (file)
index 76c37bb..0000000
+++ /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:
index 7183a38fe8011fc68f77d7e63be04abcbc4c7b84..8efa2559d8f72c2539100b2eddfa444e6cdb414f 100644 (file)
@@ -5,88 +5,44 @@ import tempfile
 
 import testlib
 
 
 import testlib
 
-class Target(object):
-    # pylint: disable=too-many-instance-attributes
-
-    # 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
+class Hart(object):
+    # XLEN of the hart. May be overridden with --32 or --64 command line
     # options.
     xlen = 0
 
     # 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
 
     # Will be autodetected (by running ExamineTarget) if left unset. Set to
     # save a little time.
     misa = None
 
-    # List of commands that should be executed in gdb after connecting but
-    # before starting the test.
-    gdb_setup = []
+    # 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. Targets that need
+    # 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
 
     # 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
+    # Address where a r/w/x block of RAM starts, together with its size.
+    ram = None
+    ram_size = None
 
 
-    def __init__(self, path, parsed):
-        # Path to module.
-        self.path = path
-        self.directory = os.path.dirname(path)
-        self.server_cmd = parsed.server_cmd
-        self.sim_cmd = parsed.sim_cmd
-        self.isolate = parsed.isolate
-        if not self.name:
-            self.name = type(self).__name__
-        # Default OpenOCD config file to <name>.cfg
-        if not self.openocd_config_path:
-            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)
+    # Number of instruction triggers the hart supports.
+    instruction_hardware_breakpoint_count = 0
 
 
-    def create(self):
-        """Create the target out of thin air, eg. start a simulator."""
-        pass
+    # Defaults to target-<index>
+    name = None
 
 
-    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)
+    def __init__(self):
+        self.temporary_binary = None
 
     def compile(self, *sources):
         binary_name = "%s_%s-%d" % (
                 self.name,
                 os.path.basename(os.path.splitext(sources[0])[0]),
                 self.xlen)
 
     def compile(self, *sources):
         binary_name = "%s_%s-%d" % (
                 self.name,
                 os.path.basename(os.path.splitext(sources[0])[0]),
                 self.xlen)
-        if self.isolate:
+        if Target.isolate:
             self.temporary_binary = tempfile.NamedTemporaryFile(
                     prefix=binary_name + "_")
             binary_name = self.temporary_binary.name
             self.temporary_binary = tempfile.NamedTemporaryFile(
                     prefix=binary_name + "_")
             binary_name = self.temporary_binary.name
@@ -114,6 +70,69 @@ class Target(object):
         else:
             return False
 
         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
+
+    # GDB remotetimeout setting.
+    timeout_sec = 2
+
+    # 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 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 = []
+
+    # Internal variables:
+    directory = None
+    temporary_files = []
+
+    def __init__(self, path, parsed):
+        # Path to module.
+        self.path = path
+        self.directory = os.path.dirname(path)
+        self.server_cmd = parsed.server_cmd
+        self.sim_cmd = parsed.sim_cmd
+        Target.isolate = parsed.isolate
+        if not self.name:
+            self.name = type(self).__name__
+        # Default OpenOCD config file to <name>.cfg
+        if not self.openocd_config_path:
+            self.openocd_config_path = "%s.cfg" % self.name
+        self.openocd_config_path = os.path.join(self.directory,
+                self.openocd_config_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."""
+        pass
+
+    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)
+
 def add_target_options(parser):
     parser.add_argument("target", help=".py file that contains definition for "
             "the target to test with.")
 def add_target_options(parser):
     parser.add_argument("target", help=".py file that contains definition for "
             "the target to test with.")
@@ -149,4 +168,7 @@ def target(parsed):
     assert len(found) == 1, "%s does not define exactly one subclass of " \
             "targets.Target" % parsed.target
 
     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/spike.cfg b/debug/targets/RISC-V/spike.cfg
new file mode 100644 (file)
index 0000000..9b1841c
--- /dev/null
@@ -0,0 +1,16 @@
+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
+
+gdb_report_data_abort enable
+
+init
+reset halt
diff --git a/debug/targets/RISC-V/spike32-2.py b/debug/targets/RISC-V/spike32-2.py
new file mode 100644 (file)
index 0000000..3f87d26
--- /dev/null
@@ -0,0 +1,11 @@
+import targets
+import testlib
+
+import spike32
+
+class spike32_2(targets.Target):
+    harts = [spike32.spike32_hart(), spike32.spike32_hart()]
+    openocd_config_path = "spike.cfg"
+
+    def create(self):
+        return testlib.Spike(self)
diff --git a/debug/targets/RISC-V/spike32.cfg b/debug/targets/RISC-V/spike32.cfg
deleted file mode 100644 (file)
index 2742335..0000000
+++ /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"
index 3bf8b4783c4578bb5f671b9036c5a08de37c7ab4..e80f60a07a51e6ae38ee0b2afad51f201e1f0146 100644 (file)
@@ -1,12 +1,17 @@
 import targets
 import testlib
 
 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
     xlen = 32
     ram = 0x10000000
     ram_size = 0x10000000
     instruction_hardware_breakpoint_count = 4
     reset_vector = 0x1000
+    link_script_path = "spike64.lds"
+
+class spike32(targets.Target):
+    harts = [spike32_hart()]
+    openocd_config_path = "spike.cfg"
 
     def create(self):
         return testlib.Spike(self)
 
     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 (file)
index 0000000..e1df6d2
--- /dev/null
@@ -0,0 +1,11 @@
+import targets
+import testlib
+
+import spike64
+
+class spike64_2(targets.Target):
+    harts = [spike64.spike64_hart(), spike64.spike64_hart()]
+    openocd_config_path = "spike.cfg"
+
+    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 (file)
index 2742335..0000000
+++ /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"
index c70585782622610b4513cbaedbff5fb1fc49af1d..84586e33f46d682d18ab63db92ec4b5c83562da5 100644 (file)
@@ -1,12 +1,17 @@
 import targets
 import testlib
 
 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
     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"
 
     def create(self):
         return testlib.Spike(self)
 
     def create(self):
         return testlib.Spike(self)
index 95ddcfd6273a598a6aecf52e0000b0aac8e8a514..170de400df202221b7834678bcb300d89f2ba10d 100644 (file)
@@ -1,9 +1,12 @@
 import targets
 
 import targets
 
-class E300(targets.Target):
+class E300Hart(targets.Hart):
     xlen = 32
     ram = 0x80000000
     xlen = 32
     ram = 0x80000000
-    ram_size = 16 * 1024
+    ram_size = 256 * 1024 * 1024
     instruction_hardware_breakpoint_count = 2
     instruction_hardware_breakpoint_count = 2
-    openocd_config_path = "Freedom.cfg"
     link_script_path = "Freedom.lds"
     link_script_path = "Freedom.lds"
+
+class E300(targets.Target):
+    openocd_config_path = "Freedom.cfg"
+    harts = [E300Hart()]
index 91be2e8901778cb3c6ea65d0b7fd3623d67a8391..f9428d077650d62c3483dcfe987dd7810ea24216 100644 (file)
@@ -1,14 +1,17 @@
 import targets
 import testlib
 
 import targets
 import testlib
 
-class E300Sim(targets.Target):
+class E300Hart(targets.Hart):
     xlen = 32
     xlen = 32
-    timeout_sec = 6000
     ram = 0x80000000
     ram_size = 256 * 1024 * 1024
     instruction_hardware_breakpoint_count = 2
     ram = 0x80000000
     ram_size = 256 * 1024 * 1024
     instruction_hardware_breakpoint_count = 2
-    openocd_config_path = "Freedom.cfg"
     link_script_path = "Freedom.lds"
 
     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)
     def create(self):
         return testlib.VcsSim(sim_cmd=self.sim_cmd, debug=False)
index c22aa4c6907fd3b280550e812f558dc05b627812..6da3ac509c5b53c7d75034983404b19159d6a945 100644 (file)
@@ -1,9 +1,12 @@
 import targets
 
 import targets
 
-class U500(targets.Target):
+class U500Hart(targets.Hart):
     xlen = 64
     ram = 0x80000000
     ram_size = 16 * 1024
     instruction_hardware_breakpoint_count = 2
     xlen = 64
     ram = 0x80000000
     ram_size = 16 * 1024
     instruction_hardware_breakpoint_count = 2
-    openocd_config_path = "Freedom.cfg"
     link_script_path = "Freedom.lds"
     link_script_path = "Freedom.lds"
+
+class U500(targets.Target):
+    openocd_config_path = "Freedom.cfg"
+    harts = [U500Hart()]
index 62bc8278ab6efd20f30e9ce8c324883aab89d5a1..5c500c4842c7a6009a5dca06576d72a19843a133 100644 (file)
@@ -1,14 +1,17 @@
 import targets
 import testlib
 
 import targets
 import testlib
 
-class U500Sim(targets.Target):
+class U500Hart(targets.Hart):
     xlen = 64
     xlen = 64
-    timeout_sec = 6000
     ram = 0x80000000
     ram_size = 256 * 1024 * 1024
     instruction_hardware_breakpoint_count = 2
     ram = 0x80000000
     ram_size = 256 * 1024 * 1024
     instruction_hardware_breakpoint_count = 2
-    openocd_config_path = "Freedom.cfg"
     link_script_path = "Freedom.lds"
 
     link_script_path = "Freedom.lds"
 
-    def create(self):
+class U500Sim(Target):
+    timeout_sec = 6000
+    openocd_config_path = "Freedom.cfg"
+    harts = [U500Hart()]
+
+    def target(self):
         return testlib.VcsSim(sim_cmd=self.sim_cmd, debug=False)
         return testlib.VcsSim(sim_cmd=self.sim_cmd, debug=False)
index 813829e415116ec15a76c463924f168c14f83c87..3cb508cd0e1e358797ce7f836ffc6fdcc5046ad7 100644 (file)
@@ -1,8 +1,11 @@
 import targets
 
 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
     xlen = 32
     ram = 0x80000000
     ram_size = 16 * 1024
     instruction_hardware_breakpoint_count = 2
     misa = 0x40001105
+
+class HiFive1(targets.Target):
+    harts = [HiFive1Hart()]
index b76f320a7071cc4fb00c02cd0aaacade8066c9e2..d6c7f8a0c0dd3b3e545abdb68ba2ffc14a36a940 100644 (file)
@@ -36,13 +36,12 @@ def compile(args, xlen=32): # pylint: disable=redefined-builtin
             cmd.append(found)
         else:
             cmd.append(arg)
             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:
     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("")
         print stdout,
         print stderr,
         header("")
@@ -63,16 +62,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."""
     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.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 += ["--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
 
         if timeout:
             cmd = ["timeout", str(timeout)] + cmd
@@ -82,7 +99,7 @@ class Spike(object):
         if with_jtag_gdb:
             cmd += ['--rbb-port', '0']
             os.environ['REMOTE_BITBANG_HOST'] = 'localhost'
         if with_jtag_gdb:
             cmd += ['--rbb-port', '0']
             os.environ['REMOTE_BITBANG_HOST'] = 'localhost'
-        self.infinite_loop = target.compile(
+        self.infinite_loop = harts[0].compile(
                 "programs/checksum.c", "programs/tiny-malloc.c",
                 "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
         cmd.append(self.infinite_loop)
                 "programs/checksum.c", "programs/tiny-malloc.c",
                 "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
         cmd.append(self.infinite_loop)
@@ -106,11 +123,12 @@ class Spike(object):
                     "connection"
 
     def __del__(self):
                     "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)
 
     def wait(self, *args, **kwargs):
         return self.process.wait(*args, **kwargs)
@@ -291,6 +309,10 @@ class Gdb(object):
         # Force consistency.
         self.command("set print entry-values no")
 
         # 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\)")
     def wait(self):
         """Wait for prompt."""
         self.child.expect(r"\(gdb\)")
@@ -391,19 +413,20 @@ def run_all_tests(module, target, parsed):
     gdb_cmd = parsed.gdb
 
     todo = []
     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)):
 
     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)
 
 
     results, count = run_tests(parsed, target, todo)
 
@@ -417,12 +440,12 @@ def run_tests(parsed, target, todo):
     results = {}
     count = 0
 
     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),
         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__)
         sys.stdout.flush()
         log_fd.write("Test: %s\n" % name)
         log_fd.write("Target: %s\n" % type(target).__name__)
@@ -491,8 +514,12 @@ def print_log(path):
 class BaseTest(object):
     compiled = {}
 
 class BaseTest(object):
     compiled = {}
 
-    def __init__(self, target):
+    def __init__(self, target, hart=None):
         self.target = target
         self.target = target
+        if hart:
+            self.hart = hart
+        else:
+            self.hart = random.choice(target.harts)
         self.server = None
         self.target_process = None
         self.binary = None
         self.server = None
         self.target_process = None
         self.binary = None
@@ -514,7 +541,7 @@ class BaseTest(object):
             if compile_args not in BaseTest.compiled:
                 # pylint: disable=star-args
                 BaseTest.compiled[compile_args] = \
             if compile_args not in BaseTest.compiled:
                 # pylint: disable=star-args
                 BaseTest.compiled[compile_args] = \
-                        self.target.compile(*compile_args)
+                        self.hart.compile(*compile_args)
         self.binary = BaseTest.compiled.get(compile_args)
 
     def classSetup(self):
         self.binary = BaseTest.compiled.get(compile_args)
 
     def classSetup(self):
@@ -581,8 +608,8 @@ class BaseTest(object):
 
 gdb_cmd = None
 class GdbTest(BaseTest):
 
 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):
         self.gdb = None
 
     def classSetup(self):
@@ -598,15 +625,12 @@ class GdbTest(BaseTest):
         if self.binary:
             self.gdb.command("file %s" % self.binary)
         if self.target:
         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)
             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)
 
         for cmd in self.target.gdb_setup:
             self.gdb.command(cmd)
@@ -618,6 +642,17 @@ class GdbTest(BaseTest):
         del self.gdb
         BaseTest.classTeardown(self)
 
         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")
 class ExamineTarget(GdbTest):
     def test(self):
         self.target.misa = self.gdb.p("$misa")