Merge pull request #17 from timsifive/debug
authorAndrew Waterman <waterman@eecs.berkeley.edu>
Wed, 20 Jul 2016 00:10:24 +0000 (17:10 -0700)
committerGitHub <noreply@github.com>
Wed, 20 Jul 2016 00:10:24 +0000 (17:10 -0700)
Add end-to-end debug tests

17 files changed:
Makefile.in
debug/Makefile [new file with mode: 0644]
debug/README.md [new file with mode: 0644]
debug/gdbserver.py [new file with mode: 0755]
debug/programs/checksum.c [new file with mode: 0644]
debug/programs/debug.c [new file with mode: 0644]
debug/programs/entry.S [new file with mode: 0755]
debug/programs/init.c [new file with mode: 0644]
debug/programs/mprv.S [new file with mode: 0644]
debug/programs/regs.S [new file with mode: 0644]
debug/programs/start.S [new file with mode: 0644]
debug/programs/step.S [new file with mode: 0644]
debug/programs/tiny-malloc.c [new file with mode: 0644]
debug/targets/m2gl_m2s/link.lds [new file with mode: 0755]
debug/targets/m2gl_m2s/openocd.cfg [new file with mode: 0644]
debug/targets/spike/link.lds [new file with mode: 0755]
debug/testlib.py [new file with mode: 0644]

index 62a95b24bab9bdbdba5099b9206bc19d0d6130fb..d2b088e85efe53a5d66abb62e7b6663b84c21f64 100644 (file)
@@ -4,8 +4,9 @@ XLEN            := @XLEN@
 instbasedir     := $(DESTDIR)$(prefix)
 bmarkdir        := $(abs_top_src_dir)/benchmarks
 isa_src_dir     := $(abs_top_src_dir)/isa
+debug_src_dir   := $(abs_top_src_dir)/debug
 
-all: benchmarks isa
+all: benchmarks isa debug
 
 install: all
        install -d $(instbasedir)/share/riscv-tests/isa
@@ -21,9 +22,14 @@ isa:
        mkdir -p isa
        $(MAKE) -C isa -f $(isa_src_dir)/Makefile src_dir=$(isa_src_dir) XLEN=$(XLEN)
 
+debug:
+       mkdir -p debug
+       $(MAKE) -C debug -f $(debug_src_dir)/Makefile src_dir=$(debug_src_dir) XLEN=$(XLEN)
+
 clean:
        $(MAKE) -C isa -f $(isa_src_dir)/Makefile src_dir=$(isa_src_dir) clean
        $(MAKE) -C benchmarks -f $(bmarkdir)/Makefile src_dir=$(bmarkdir) clean
+       $(MAKE) -C debug -f $(bmarkdir)/Makefile src_dir=$(debug_src_dir) clean
 
 .PHONY: benchmarks isa clean
 
diff --git a/debug/Makefile b/debug/Makefile
new file mode 100644 (file)
index 0000000..1d90e12
--- /dev/null
@@ -0,0 +1,18 @@
+RISCV_SIM ?= spike
+XLEN ?= 64
+
+src_dir ?= .
+GDBSERVER_PY = $(src_dir)/gdbserver.py
+
+default:       spike$(XLEN).log
+
+all:   spike32.log spike64.log
+
+spike32.log:
+       $(GDBSERVER_PY) --isolate --spike32 --cmd $(RISCV_SIM) -- -v > $@ 2>&1
+
+spike64.log:
+       $(GDBSERVER_PY) --isolate --spike --cmd $(RISCV_SIM) -- -v > $@ 2>&1
+
+clean:
+       rm -f *.log
diff --git a/debug/README.md b/debug/README.md
new file mode 100644 (file)
index 0000000..829a285
--- /dev/null
@@ -0,0 +1,38 @@
+Debug Tests
+===========
+
+Debugging requires many system components to all work together. The tests here
+perform an end-to-end test, communicating only with gdb. If a simulator or
+hardware passes all these tests, then you can be pretty confident that the
+actual debug interface is functioning correctly.
+
+Targets
+=======
+
+64-bit Spike
+------------
+
+`./gdbserver.py --spike --cmd $RISCV/bin/spike`
+
+32-bit Spike
+------------
+
+`./gdbserver.py --spike32 --cmd $RISCV/bin/spike`
+
+32-bit SiFive Core on Microsemi FPGA board
+------------------------------------------
+
+`./gdbserver.py --m2gl_m2s`
+
+Debug Tips
+==========
+
+You can run just a single test by specifying <class>.<function> on the command
+line, eg: `./gdbserver.py --spike --cmd $RISCV/bin/spike
+SimpleRegisterTest.test_s0`.
+Once that test has failed, you can look at gdb.log and (in this case) spike.log
+to get an idea of what might have gone wrong.
+
+You can see what spike is doing by add `-l` to the spike command, eg.:
+`./gdbserver.py --spike32 --cmd "$RISCV/bin/spike -l"
+DebugTest.test_breakpoint`. (Then look at spike.log.)
diff --git a/debug/gdbserver.py b/debug/gdbserver.py
new file mode 100755 (executable)
index 0000000..0ae75a7
--- /dev/null
@@ -0,0 +1,558 @@
+#!/usr/bin/python
+
+import os
+import sys
+import argparse
+import testlib
+import unittest
+import tempfile
+import time
+import random
+import binascii
+
+MSTATUS_UIE = 0x00000001
+MSTATUS_SIE = 0x00000002
+MSTATUS_HIE = 0x00000004
+MSTATUS_MIE = 0x00000008
+MSTATUS_UPIE = 0x00000010
+MSTATUS_SPIE = 0x00000020
+MSTATUS_HPIE = 0x00000040
+MSTATUS_MPIE = 0x00000080
+MSTATUS_SPP = 0x00000100
+MSTATUS_HPP = 0x00000600
+MSTATUS_MPP = 0x00001800
+MSTATUS_FS = 0x00006000
+MSTATUS_XS = 0x00018000
+MSTATUS_MPRV = 0x00020000
+MSTATUS_PUM = 0x00040000
+MSTATUS_MXR = 0x00080000
+MSTATUS_VM = 0x1F000000
+MSTATUS32_SD = 0x80000000
+MSTATUS64_SD = 0x8000000000000000
+
+def ihex_line(address, record_type, data):
+    assert len(data) < 128
+    line = ":%02X%04X%02X" % (len(data), address, record_type)
+    check = len(data)
+    check += address % 256
+    check += address >> 8
+    check += record_type
+    for char in data:
+        value = ord(char)
+        check += value
+        line += "%02X" % value
+    line += "%02X\n" % ((256-check)%256)
+    return line
+
+def ihex_parse(line):
+    assert line.startswith(":")
+    line = line[1:]
+    data_len = int(line[:2], 16)
+    address = int(line[2:6], 16)
+    record_type = int(line[6:8], 16)
+    data = ""
+    for i in range(data_len):
+        data += "%c" % int(line[8+2*i:10+2*i], 16)
+    return record_type, address, data
+
+class DeleteServer(unittest.TestCase):
+    def tearDown(self):
+        del self.server
+
+class SimpleRegisterTest(DeleteServer):
+    def setUp(self):
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        # For now gdb has to be told what the architecture is when it's not
+        # given an ELF file.
+        self.gdb.command("set arch riscv:rv%d" % target.xlen)
+
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+
+        # 0x13 is nop
+        self.gdb.command("p *((int*) 0x%x)=0x13" % target.ram)
+        self.gdb.command("p *((int*) 0x%x)=0x13" % (target.ram + 4))
+        self.gdb.command("p *((int*) 0x%x)=0x13" % (target.ram + 8))
+        self.gdb.p("$pc=0x%x" % target.ram)
+
+    def check_reg(self, name):
+        a = random.randrange(1<<target.xlen)
+        b = random.randrange(1<<target.xlen)
+        self.gdb.p("$%s=0x%x" % (name, a))
+        self.gdb.stepi()
+        self.assertEqual(self.gdb.p("$%s" % name), a)
+        self.gdb.p("$%s=0x%x" % (name, b))
+        self.gdb.stepi()
+        self.assertEqual(self.gdb.p("$%s" % name), b)
+
+    def test_s0(self):
+        # S0 is saved/restored in DSCRATCH
+        self.check_reg("s0")
+
+    def test_s1(self):
+        # S1 is saved/restored in Debug RAM
+        self.check_reg("s1")
+
+    def test_t0(self):
+        # T0 is not saved/restored at all
+        self.check_reg("t2")
+
+    def test_t2(self):
+        # T2 is not saved/restored at all
+        self.check_reg("t2")
+
+class SimpleMemoryTest(DeleteServer):
+    def setUp(self):
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        self.gdb.command("set arch riscv:rv%d" % target.xlen)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+
+    def access_test(self, size, data_type):
+        self.assertEqual(self.gdb.p("sizeof(%s)" % data_type),
+                size)
+        a = 0x86753095555aaaa & ((1<<(size*8))-1)
+        b = 0xdeadbeef12345678 & ((1<<(size*8))-1)
+        self.gdb.p("*((%s*)0x%x) = 0x%x" % (data_type, target.ram, a))
+        self.gdb.p("*((%s*)0x%x) = 0x%x" % (data_type, target.ram + size, b))
+        self.assertEqual(self.gdb.p("*((%s*)0x%x)" % (data_type, target.ram)), a)
+        self.assertEqual(self.gdb.p("*((%s*)0x%x)" % (data_type, target.ram + size)), b)
+
+    def test_8(self):
+        self.access_test(1, 'char')
+
+    def test_16(self):
+        self.access_test(2, 'short')
+
+    def test_32(self):
+        self.access_test(4, 'int')
+
+    def test_64(self):
+        self.access_test(8, 'long long')
+
+    def test_block(self):
+        length = 1024
+        line_length = 16
+        a = tempfile.NamedTemporaryFile(suffix=".ihex")
+        data = ""
+        for i in range(length / line_length):
+            line_data = "".join(["%c" % random.randrange(256) for _ in range(line_length)])
+            data += line_data
+            a.write(ihex_line(i * line_length, 0, line_data))
+        a.flush()
+
+        self.gdb.command("restore %s 0x%x" % (a.name, target.ram))
+        for offset in range(0, length, 19*4) + [length-4]:
+            value = self.gdb.p("*((int*)0x%x)" % (target.ram + offset))
+            written = ord(data[offset]) | \
+                    (ord(data[offset+1]) << 8) | \
+                    (ord(data[offset+2]) << 16) | \
+                    (ord(data[offset+3]) << 24)
+            self.assertEqual(value, written)
+
+        b = tempfile.NamedTemporaryFile(suffix=".ihex")
+        self.gdb.command("dump ihex memory %s 0x%x 0x%x" % (b.name, target.ram,
+            target.ram + length))
+        for line in b:
+            record_type, address, line_data = ihex_parse(line)
+            if (record_type == 0):
+                self.assertEqual(line_data, data[address:address+len(line_data)])
+
+class InstantHaltTest(DeleteServer):
+    def setUp(self):
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        self.gdb.command("set arch riscv:rv%d" % target.xlen)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+
+    def test_instant_halt(self):
+        self.assertEqual(target.reset_vector, self.gdb.p("$pc"))
+        # mcycle and minstret have no defined reset value.
+        mstatus = self.gdb.p("$mstatus")
+        self.assertEqual(mstatus & (MSTATUS_MIE | MSTATUS_MPRV |
+            MSTATUS_VM), 0)
+
+    def test_change_pc(self):
+        """Change the PC right as we come out of reset."""
+        # 0x13 is nop
+        self.gdb.command("p *((int*) 0x%x)=0x13" % target.ram)
+        self.gdb.command("p *((int*) 0x%x)=0x13" % (target.ram + 4))
+        self.gdb.command("p *((int*) 0x%x)=0x13" % (target.ram + 8))
+        self.gdb.p("$pc=0x%x" % target.ram)
+        self.gdb.stepi()
+        self.assertEqual((target.ram + 4), self.gdb.p("$pc"))
+        self.gdb.stepi()
+        self.assertEqual((target.ram + 8), self.gdb.p("$pc"))
+
+class DebugTest(DeleteServer):
+    def setUp(self):
+        # 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.
+        self.binary = target.compile("programs/debug.c", "programs/checksum.c",
+                "programs/tiny-malloc.c", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+        self.gdb.load()
+        self.gdb.b("_exit")
+
+    def exit(self, expected_result = 0xc86455d4):
+        output = self.gdb.c()
+        self.assertIn("Breakpoint", output)
+        self.assertIn("_exit", output)
+        self.assertEqual(self.gdb.p("status"), expected_result)
+
+    def test_function_call(self):
+        self.gdb.b("main:start")
+        self.gdb.c()
+        text = "Howdy, Earth!"
+        gdb_length = self.gdb.p('strlen("%s")' % text)
+        self.assertEqual(gdb_length, len(text))
+        self.exit()
+
+    def test_change_string(self):
+        text = "This little piggy went to the market."
+        self.gdb.b("main:start")
+        self.gdb.c()
+        self.gdb.p('fox = "%s"' % text)
+        self.exit(0x43b497b8)
+
+    def test_turbostep(self):
+        """Single step a bunch of times."""
+        self.gdb.command("p i=0");
+        last_pc = None
+        advances = 0
+        jumps = 0
+        for _ in range(100):
+            self.gdb.stepi()
+            pc = self.gdb.p("$pc")
+            self.assertNotEqual(last_pc, pc)
+            if (last_pc and pc > last_pc and pc - last_pc <= 4):
+                advances += 1
+            else:
+                jumps += 1
+            last_pc = pc
+        # Some basic sanity that we're not running between breakpoints or
+        # something.
+        self.assertGreater(jumps, 10)
+        self.assertGreater(advances, 50)
+
+    def test_exit(self):
+        self.exit()
+
+    def test_symbols(self):
+        self.gdb.b("main")
+        self.gdb.b("rot13")
+        output = self.gdb.c()
+        self.assertIn(", main ", output)
+        output = self.gdb.c()
+        self.assertIn(", rot13 ", output)
+
+    def test_breakpoint(self):
+        self.gdb.b("rot13")
+        # The breakpoint should be hit exactly 2 times.
+        for i in range(2):
+            output = self.gdb.c()
+            self.gdb.p("$pc")
+            self.assertIn("Breakpoint ", output)
+            #TODO self.assertIn("rot13 ", output)
+        self.exit()
+
+    def test_hwbp_1(self):
+        if target.instruction_hardware_breakpoint_count < 1:
+            return
+
+        self.gdb.hbreak("rot13")
+        # The breakpoint should be hit exactly 2 times.
+        for i in range(2):
+            output = self.gdb.c()
+            self.gdb.p("$pc")
+            self.assertIn("Breakpoint ", output)
+            #TODO self.assertIn("rot13 ", output)
+        self.exit()
+
+    def test_hwbp_2(self):
+        if target.instruction_hardware_breakpoint_count < 2:
+            return
+
+        self.gdb.hbreak("main")
+        self.gdb.hbreak("rot13")
+        # We should hit 3 breakpoints.
+        for i in range(3):
+            output = self.gdb.c()
+            self.gdb.p("$pc")
+            self.assertIn("Breakpoint ", output)
+            #TODO self.assertIn("rot13 ", output)
+        self.exit()
+
+    def test_too_many_hwbp(self):
+        for i in range(30):
+            self.gdb.hbreak("*rot13 + %d" % (i * 4))
+
+        output = self.gdb.c()
+        self.assertIn("Cannot insert hardware breakpoint", output)
+        # Clean up, otherwise the hardware breakpoints stay set and future
+        # tests may fail.
+        self.gdb.command("D")
+
+    def test_registers(self):
+        # Get to a point in the code where some registers have actually been
+        # used.
+        self.gdb.b("rot13")
+        self.gdb.c()
+        self.gdb.c()
+        # Try both forms to test gdb.
+        for cmd in ("info all-registers", "info registers all"):
+            output = self.gdb.command(cmd)
+            self.assertNotIn("Could not", output)
+            for reg in ('zero', 'ra', 'sp', 'gp', 'tp'):
+                self.assertIn(reg, output)
+
+        #TODO
+        # mcpuid is one of the few registers that should have the high bit set
+        # (for rv64).
+        # Leave this commented out until gdb and spike agree on the encoding of
+        # mcpuid (which is going to be renamed to misa in any case).
+        #self.assertRegexpMatches(output, ".*mcpuid *0x80")
+
+        #TODO:
+        # The instret register should always be changing.
+        #last_instret = None
+        #for _ in range(5):
+        #    instret = self.gdb.p("$instret")
+        #    self.assertNotEqual(instret, last_instret)
+        #    last_instret = instret
+        #    self.gdb.stepi()
+
+        self.exit()
+
+    def test_interrupt(self):
+        """Sending gdb ^C while the program is running should cause it to halt."""
+        self.gdb.b("main:start")
+        self.gdb.c()
+        self.gdb.p("i=123");
+        self.gdb.c(wait=False)
+        time.sleep(0.1)
+        output = self.gdb.interrupt()
+        #TODO: assert "main" in output
+        self.assertGreater(self.gdb.p("j"), 10)
+        self.gdb.p("i=0");
+        self.exit()
+
+class StepTest(DeleteServer):
+    def setUp(self):
+        self.binary = target.compile("programs/step.S")
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+        self.gdb.load()
+        self.gdb.b("main")
+        self.gdb.c()
+
+    def test_step(self):
+        main = self.gdb.p("$pc")
+        for expected in (4, 8, 0xc, 0x10, 0x18, 0x1c, 0x28, 0x20, 0x2c, 0x2c):
+            self.gdb.stepi()
+            pc = self.gdb.p("$pc")
+            self.assertEqual("%x" % pc, "%x" % (expected + main))
+
+class RegsTest(DeleteServer):
+    def setUp(self):
+        self.binary = target.compile("programs/regs.S")
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+        self.gdb.load()
+        self.gdb.b("main")
+        self.gdb.b("handle_trap")
+        self.gdb.c()
+
+    def test_write_gprs(self):
+        regs = [("x%d" % n) for n in range(2, 32)]
+
+        self.gdb.p("$pc=write_regs")
+        for i, r in enumerate(regs):
+            self.gdb.command("p $%s=%d" % (r, (0xdeadbeef<<i)+17))
+        self.gdb.command("p $x1=data")
+        self.gdb.command("b all_done")
+        output = self.gdb.c()
+        self.assertIn("Breakpoint ", output)
+
+        # Just to get this data in the log.
+        self.gdb.command("x/30gx data")
+        self.gdb.command("info registers")
+        for n in range(len(regs)):
+            self.assertEqual(self.gdb.x("data+%d" % (8*n), 'g'),
+                    ((0xdeadbeef<<n)+17) & ((1<<target.xlen)-1))
+
+    def test_write_csrs(self):
+        # As much a test of gdb as of the simulator.
+        self.gdb.p("$mscratch=0")
+        self.gdb.stepi()
+        self.assertEqual(self.gdb.p("$mscratch"), 0)
+        self.gdb.p("$mscratch=123")
+        self.gdb.stepi()
+        self.assertEqual(self.gdb.p("$mscratch"), 123)
+
+        self.gdb.command("p $pc=write_regs")
+        self.gdb.command("p $a0=data")
+        self.gdb.command("b all_done")
+        self.gdb.command("c")
+
+        self.assertEqual(123, self.gdb.p("$mscratch"))
+        self.assertEqual(123, self.gdb.p("$x1"))
+        self.assertEqual(123, self.gdb.p("$csr832"))
+
+class DownloadTest(DeleteServer):
+    def setUp(self):
+        length = min(2**20, target.ram_size - 2048)
+        download_c = tempfile.NamedTemporaryFile(prefix="download_", suffix=".c")
+        download_c.write("#include <stdint.h>\n")
+        download_c.write("unsigned int crc32a(uint8_t *message, unsigned int size);\n")
+        download_c.write("uint32_t length = %d;\n" % length)
+        download_c.write("uint8_t d[%d] = {\n" % length)
+        self.crc = 0
+        for i in range(length / 16):
+            download_c.write("  /* 0x%04x */ " % (i * 16));
+            for _ in range(16):
+                value = random.randrange(1<<8)
+                download_c.write("%d, " % value)
+                self.crc = binascii.crc32("%c" % value, self.crc)
+            download_c.write("\n");
+        download_c.write("};\n");
+        download_c.write("uint8_t *data = &d[0];\n");
+        download_c.write("uint32_t main() { return crc32a(data, length); }\n")
+        download_c.flush()
+
+        if self.crc < 0:
+            self.crc += 2**32
+
+        self.binary = target.compile(download_c.name, "programs/checksum.c")
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+
+    def test_download(self):
+        output = self.gdb.load()
+        self.gdb.command("b _exit")
+        self.gdb.c()
+        self.assertEqual(self.gdb.p("status"), self.crc)
+
+class MprvTest(DeleteServer):
+    def setUp(self):
+        self.binary = target.compile("programs/mprv.S")
+        self.server = target.server()
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+        self.gdb.load()
+
+    def test_mprv(self):
+        """Test that the debugger can access memory when MPRV is set."""
+        self.gdb.c(wait=False)
+        time.sleep(0.5)
+        self.gdb.interrupt()
+        output = self.gdb.command("p/x *(int*)(((char*)&data)-0x80000000)")
+        self.assertIn("0xbead", output)
+
+class Target(object):
+    directory = None
+
+    def server(self):
+        raise NotImplementedError
+
+    def compile(self, *sources):
+        binary_name = "%s_%s" % (
+                self.name,
+                os.path.basename(os.path.splitext(sources[0])[0]))
+        if parsed.isolate:
+            self.temporary_binary = tempfile.NamedTemporaryFile(
+                    prefix=binary_name + "_")
+            binary_name = self.temporary_binary.name
+        testlib.compile(sources +
+                ("programs/entry.S", "programs/init.c",
+                    "-I", "../env",
+                    "-T", "targets/%s/link.lds" % (self.directory or self.name),
+                    "-nostartfiles",
+                    "-mcmodel=medany",
+                    "-o", binary_name),
+                xlen=self.xlen)
+        return binary_name
+
+class Spike64Target(Target):
+    name = "spike"
+    xlen = 64
+    ram = 0x80010000
+    ram_size = 5 * 1024 * 1024
+    instruction_hardware_breakpoint_count = 0
+    reset_vector = 0x1000
+
+    def server(self):
+        return testlib.Spike(parsed.cmd, halted=True)
+
+class Spike32Target(Target):
+    name = "spike32"
+    directory = "spike"
+    xlen = 32
+    ram = 0x80010000
+    ram_size = 5 * 1024 * 1024
+    instruction_hardware_breakpoint_count = 0
+    reset_vector = 0x1000
+
+    def server(self):
+        return testlib.Spike(parsed.cmd, halted=True, xlen=32)
+
+class MicroSemiTarget(Target):
+    name = "m2gl_m2s"
+    xlen = 32
+    ram = 0x80000000
+    ram_size = 16 * 1024
+    instruction_hardware_breakpoint_count = 2
+
+    def server(self):
+        return testlib.Openocd(cmd=parsed.cmd,
+                config="targets/%s/openocd.cfg" % self.name)
+
+targets = [
+        Spike32Target,
+        Spike64Target,
+        MicroSemiTarget
+        ]
+
+def main():
+    parser = argparse.ArgumentParser(
+            epilog="""
+            Example command line from the real world:
+            Run all RegsTest cases against a MicroSemi m2gl_m2s board, with custom openocd command:
+            ./gdbserver.py --m2gl_m2s --cmd "$HOME/SiFive/openocd/src/openocd -s $HOME/SiFive/openocd/tcl -d" -- -vf RegsTest
+            """)
+    group = parser.add_mutually_exclusive_group(required=True)
+    for t in targets:
+        group.add_argument("--%s" % t.name, action="store_const", const=t,
+                dest="target")
+    parser.add_argument("--cmd",
+            help="The command to use to start the debug server.")
+    parser.add_argument("--isolate", action="store_true",
+            help="Try to run in such a way that multiple instances can run at "
+            "the same time. This may make it harder to debug a failure if it "
+            "does occur.")
+    parser.add_argument("unittest", nargs="*")
+    global parsed
+    parsed = parser.parse_args()
+
+    global target
+    target = parsed.target()
+    unittest.main(argv=[sys.argv[0]] + parsed.unittest)
+
+# TROUBLESHOOTING TIPS
+# If a particular test fails, run just that one test, eg.:
+# ./tests/gdbserver.py MprvTest.test_mprv
+# Then inspect gdb.log and spike.log to see what happened in more detail.
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/debug/programs/checksum.c b/debug/programs/checksum.c
new file mode 100644 (file)
index 0000000..e076f8a
--- /dev/null
@@ -0,0 +1,38 @@
+#include <stdint.h>
+
+// CRC code from http://www.hackersdelight.org/hdcodetxt/crc.c.txt
+
+// Reverses (reflects) bits in a 32-bit word.
+unsigned reverse(unsigned x) {
+   x = ((x & 0x55555555) <<  1) | ((x >>  1) & 0x55555555);
+   x = ((x & 0x33333333) <<  2) | ((x >>  2) & 0x33333333);
+   x = ((x & 0x0F0F0F0F) <<  4) | ((x >>  4) & 0x0F0F0F0F);
+   x = (x << 24) | ((x & 0xFF00) << 8) |
+       ((x >> 8) & 0xFF00) | (x >> 24);
+   return x;
+}
+
+// ----------------------------- crc32a --------------------------------
+
+/* This is the basic CRC algorithm with no optimizations. It follows the
+logic circuit as closely as possible. */
+
+unsigned int crc32a(uint8_t *message, unsigned int size) {
+   int i, j;
+   unsigned int byte, crc;
+
+   i = 0;
+   crc = 0xFFFFFFFF;
+   while (i < size) {
+      byte = message[i];            // Get next byte.
+      byte = reverse(byte);         // 32-bit reversal.
+      for (j = 0; j <= 7; j++) {    // Do eight times.
+         if ((int)(crc ^ byte) < 0)
+              crc = (crc << 1) ^ 0x04C11DB7;
+         else crc = crc << 1;
+         byte = byte << 1;          // Ready next msg bit.
+      }
+      i = i + 1;
+   }
+   return reverse(~crc);
+}
diff --git a/debug/programs/debug.c b/debug/programs/debug.c
new file mode 100644 (file)
index 0000000..20b1cdc
--- /dev/null
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+unsigned int crc32a(uint8_t *message, unsigned int size);
+
+void rot13(char *buf)
+{
+    while (*buf) {
+        if ((*buf >= 'a' && *buf <= 'm') ||
+                (*buf >= 'A' && *buf <= 'M')) {
+            *buf += 13;
+        } else if ((*buf >= 'n' && *buf <= 'z') ||
+                (*buf >= 'N' && *buf <= 'Z')) {
+            *buf -= 13;
+        }
+        buf++;
+    }
+}
+
+size_t strlen(const char *buf)
+{
+    int len = 0;
+    while (buf[len])
+        len++;
+    return len;
+}
+
+extern void *__malloc_freelist;
+
+int main()
+{
+    __malloc_freelist = 0;
+
+    volatile int i = 0;
+    int j = 0;
+    char *fox = "The quick brown fox jumps of the lazy dog.";
+    unsigned int checksum = 0;
+
+start:
+    while (i)
+        j++;
+
+    rot13(fox);
+    checksum ^= crc32a(fox, strlen(fox));
+    rot13(fox);
+    checksum ^= crc32a(fox, strlen(fox));
+
+    return checksum;
+}
diff --git a/debug/programs/entry.S b/debug/programs/entry.S
new file mode 100755 (executable)
index 0000000..80904cd
--- /dev/null
@@ -0,0 +1,132 @@
+#ifndef ENTRY_S
+#define ENTRY_S
+
+#include "encoding.h"
+
+#define STACK_SIZE 512
+
+#ifdef __riscv64
+# define LREG ld
+# define SREG sd
+# define REGBYTES 8
+#else
+# define LREG lw
+# define SREG sw
+# define REGBYTES 4
+#endif
+
+  .section      .text.entry
+  .globl _start
+_start:
+  j handle_reset
+
+nmi_vector:
+  j nmi_vector
+
+trap_vector:
+  j trap_entry
+
+handle_reset:
+  la t0, trap_entry
+  csrw mtvec, t0
+  csrwi mstatus, 0
+  csrwi mideleg, 0
+  csrwi medeleg, 0
+  csrwi mie, 0
+
+  # initialize global pointer
+  la gp, _gp
+
+  # initialize stack pointer
+  la sp, stack_top
+
+  # perform the rest of initialization in C
+  j _init
+
+
+trap_entry:
+  addi sp, sp, -32*REGBYTES
+
+  SREG x1, 1*REGBYTES(sp)
+  SREG x2, 2*REGBYTES(sp)
+  SREG x3, 3*REGBYTES(sp)
+  SREG x4, 4*REGBYTES(sp)
+  SREG x5, 5*REGBYTES(sp)
+  SREG x6, 6*REGBYTES(sp)
+  SREG x7, 7*REGBYTES(sp)
+  SREG x8, 8*REGBYTES(sp)
+  SREG x9, 9*REGBYTES(sp)
+  SREG x10, 10*REGBYTES(sp)
+  SREG x11, 11*REGBYTES(sp)
+  SREG x12, 12*REGBYTES(sp)
+  SREG x13, 13*REGBYTES(sp)
+  SREG x14, 14*REGBYTES(sp)
+  SREG x15, 15*REGBYTES(sp)
+  SREG x16, 16*REGBYTES(sp)
+  SREG x17, 17*REGBYTES(sp)
+  SREG x18, 18*REGBYTES(sp)
+  SREG x19, 19*REGBYTES(sp)
+  SREG x20, 20*REGBYTES(sp)
+  SREG x21, 21*REGBYTES(sp)
+  SREG x22, 22*REGBYTES(sp)
+  SREG x23, 23*REGBYTES(sp)
+  SREG x24, 24*REGBYTES(sp)
+  SREG x25, 25*REGBYTES(sp)
+  SREG x26, 26*REGBYTES(sp)
+  SREG x27, 27*REGBYTES(sp)
+  SREG x28, 28*REGBYTES(sp)
+  SREG x29, 29*REGBYTES(sp)
+  SREG x30, 30*REGBYTES(sp)
+  SREG x31, 31*REGBYTES(sp)
+
+  csrr a0, mcause
+  csrr a1, mepc
+  mv a2, sp
+  jal handle_trap
+  csrw mepc, a0
+
+  # Remain in M-mode after mret
+  li t0, MSTATUS_MPP
+  csrs mstatus, t0
+
+  LREG x1, 1*REGBYTES(sp)
+  LREG x2, 2*REGBYTES(sp)
+  LREG x3, 3*REGBYTES(sp)
+  LREG x4, 4*REGBYTES(sp)
+  LREG x5, 5*REGBYTES(sp)
+  LREG x6, 6*REGBYTES(sp)
+  LREG x7, 7*REGBYTES(sp)
+  LREG x8, 8*REGBYTES(sp)
+  LREG x9, 9*REGBYTES(sp)
+  LREG x10, 10*REGBYTES(sp)
+  LREG x11, 11*REGBYTES(sp)
+  LREG x12, 12*REGBYTES(sp)
+  LREG x13, 13*REGBYTES(sp)
+  LREG x14, 14*REGBYTES(sp)
+  LREG x15, 15*REGBYTES(sp)
+  LREG x16, 16*REGBYTES(sp)
+  LREG x17, 17*REGBYTES(sp)
+  LREG x18, 18*REGBYTES(sp)
+  LREG x19, 19*REGBYTES(sp)
+  LREG x20, 20*REGBYTES(sp)
+  LREG x21, 21*REGBYTES(sp)
+  LREG x22, 22*REGBYTES(sp)
+  LREG x23, 23*REGBYTES(sp)
+  LREG x24, 24*REGBYTES(sp)
+  LREG x25, 25*REGBYTES(sp)
+  LREG x26, 26*REGBYTES(sp)
+  LREG x27, 27*REGBYTES(sp)
+  LREG x28, 28*REGBYTES(sp)
+  LREG x29, 29*REGBYTES(sp)
+  LREG x30, 30*REGBYTES(sp)
+  LREG x31, 31*REGBYTES(sp)
+
+  addi sp, sp, 32*REGBYTES
+  mret
+
+  // 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:
+#endif
diff --git a/debug/programs/init.c b/debug/programs/init.c
new file mode 100644 (file)
index 0000000..a2b41b0
--- /dev/null
@@ -0,0 +1,24 @@
+int main(void);
+
+void handle_trap(unsigned int mcause, unsigned int mepc, unsigned int sp)
+{
+    while (1)
+        ;
+}
+
+void _exit(int status)
+{
+    // Make sure gcc doesn't inline _exit, so we can actually set a breakpoint
+    // on it.
+    volatile int i = 42;
+    while (i)
+        ;
+    // _exit isn't supposed to return.
+    while (1)
+        ;
+}
+
+void _init()
+{
+    _exit(main());
+}
diff --git a/debug/programs/mprv.S b/debug/programs/mprv.S
new file mode 100644 (file)
index 0000000..115ccb5
--- /dev/null
@@ -0,0 +1,46 @@
+#include "../../env/encoding.h"
+#define PGSHIFT         12
+
+        .global         main
+
+        .section        .text
+main:
+        # Set up a page table entry that maps 0x0... to 0x8...
+        la      t0, page_table
+        srli    t0, t0, PGSHIFT
+        csrw    CSR_SPTBR, t0
+
+        # update mstatus
+        csrr    t1, CSR_MSTATUS
+#ifdef __riscv32
+        li      t0, (MSTATUS_MPRV | (VM_SV32 << 24))
+#else
+        li      t0, (MSTATUS_MPRV | (VM_SV39 << 24))
+#endif
+        #li      t0, ((VM_SV39 << 24))
+        or      t1, t0, t1
+        csrw    CSR_MSTATUS, t1
+
+        la      t0, (loop - 0x80000000)
+        csrw    CSR_MEPC, t0
+
+        # Exit supervisor mode, entering user mode at loop.
+        mret
+
+loop:
+        la      t0, data
+        lw      t1, 0(t0)
+        j       loop
+
+        .section        .data
+data:
+        .word   0xbead
+
+        .balign 0x1000
+page_table:
+#ifdef __riscv32
+        .word   ((0x80000000 >> 2) | PTE_V | PTE_R | PTE_W | PTE_X | PTE_G | PTE_U)
+#else
+        .word   ((0x80000000 >> 2) | PTE_V | PTE_R | PTE_W | PTE_X | PTE_G | PTE_U)
+        .word   0
+#endif
diff --git a/debug/programs/regs.S b/debug/programs/regs.S
new file mode 100644 (file)
index 0000000..2cacd4f
--- /dev/null
@@ -0,0 +1,57 @@
+#ifdef __riscv64
+# define LREG ld
+# define SREG sd
+# define REGBYTES 8
+#else
+# define LREG lw
+# define SREG sw
+# define REGBYTES 4
+#endif
+
+#include "../../env/encoding.h"
+
+        .global main
+main:
+        nop
+        j       main
+
+write_regs:
+        SREG    x2, 0(x1)
+        SREG    x3, 8(x1)
+        SREG    x4, 16(x1)
+        SREG    x5, 24(x1)
+        SREG    x6, 32(x1)
+        SREG    x7, 40(x1)
+        SREG    x8, 48(x1)
+        SREG    x9, 56(x1)
+        SREG    x10, 64(x1)
+        SREG    x11, 72(x1)
+        SREG    x12, 80(x1)
+        SREG    x13, 88(x1)
+        SREG    x14, 96(x1)
+        SREG    x15, 104(x1)
+        SREG    x16, 112(x1)
+        SREG    x17, 120(x1)
+        SREG    x18, 128(x1)
+        SREG    x19, 136(x1)
+        SREG    x20, 144(x1)
+        SREG    x21, 152(x1)
+        SREG    x22, 160(x1)
+        SREG    x23, 168(x1)
+        SREG    x24, 176(x1)
+        SREG    x25, 184(x1)
+        SREG    x26, 192(x1)
+        SREG    x27, 200(x1)
+        SREG    x28, 208(x1)
+        SREG    x29, 216(x1)
+        SREG    x30, 224(x1)
+        SREG    x31, 232(x1)
+
+        csrr    x1, CSR_MSCRATCH
+
+all_done:
+        j       all_done
+
+        .balign  16
+data:
+        .fill   64, 8, 0
diff --git a/debug/programs/start.S b/debug/programs/start.S
new file mode 100644 (file)
index 0000000..76c37bb
--- /dev/null
@@ -0,0 +1,12 @@
+        .global         _start
+
+_start:
+        la      sp, stack_end
+        jal     main
+done:
+        j       done
+
+        .data
+stack:
+        .fill   4096, 1, 0
+stack_end:
diff --git a/debug/programs/step.S b/debug/programs/step.S
new file mode 100644 (file)
index 0000000..6601548
--- /dev/null
@@ -0,0 +1,24 @@
+// Test stepping over a variety of instructions.
+
+        .global main
+
+main:
+        la      t0, trap_entry          // 0, 4
+        csrw    mtvec, t0               // 0x8
+
+        li      t0, 5                   // 0xc
+        beq     zero, zero, one         // 0x10
+        nop                             // 0x14
+one:
+        beq     zero, t0, one           // 0x18
+        jal     two                     // 0x1c
+
+three:
+        .word   0                       // 0x20
+        nop                             // 0x24
+
+two:
+        ret                             // 0x28
+
+trap_entry:
+        j       trap_entry              // 0x2c
diff --git a/debug/programs/tiny-malloc.c b/debug/programs/tiny-malloc.c
new file mode 100644 (file)
index 0000000..699660c
--- /dev/null
@@ -0,0 +1,600 @@
+// https://github.com/32bitmicro/newlib-nano-1.0/blob/master/newlib/libc/machine/xstormy16/tiny-malloc.c
+
+/* A replacement malloc with:
+   - Much reduced code size;
+   - Smaller RAM footprint;
+   - The ability to handle downward-growing heaps;
+   but
+   - Slower;
+   - Probably higher memory fragmentation;
+   - Doesn't support threads (but, if it did support threads,
+     it wouldn't need a global lock, only a compare-and-swap instruction);
+   - Assumes the maximum alignment required is the alignment of a pointer;
+   - Assumes that memory is already there and doesn't need to be allocated.
+
+* Synopsis of public routines
+
+  malloc(size_t n);
+     Return a pointer to a newly allocated chunk of at least n bytes, or null
+     if no space is available.
+  free(void* p);
+     Release the chunk of memory pointed to by p, or no effect if p is null.
+  realloc(void* p, size_t n);
+     Return a pointer to a chunk of size n that contains the same data
+     as does chunk p up to the minimum of (n, p's size) bytes, or null
+     if no space is available. The returned pointer may or may not be
+     the same as p. If p is null, equivalent to malloc.  Unless the
+     #define REALLOC_ZERO_BYTES_FREES below is set, realloc with a
+     size argument of zero (re)allocates a minimum-sized chunk.
+  memalign(size_t alignment, size_t n);
+     Return a pointer to a newly allocated chunk of n bytes, aligned
+     in accord with the alignment argument, which must be a power of
+     two.  Will fail if 'alignment' is too large.
+  calloc(size_t unit, size_t quantity);
+     Returns a pointer to quantity * unit bytes, with all locations
+     set to zero.
+  cfree(void* p);
+     Equivalent to free(p).
+  malloc_trim(size_t pad);
+     Release all but pad bytes of freed top-most memory back 
+     to the system. Return 1 if successful, else 0.
+  malloc_usable_size(void* p);
+     Report the number usable allocated bytes associated with allocated
+     chunk p. This may or may not report more bytes than were requested,
+     due to alignment and minimum size constraints.
+  malloc_stats();
+     Prints brief summary statistics on stderr.
+  mallinfo()
+     Returns (by copy) a struct containing various summary statistics.
+  mallopt(int parameter_number, int parameter_value)
+     Changes one of the tunable parameters described below. Returns
+     1 if successful in changing the parameter, else 0.  Actually, returns 0
+     always, as no parameter can be changed.
+*/
+
+#ifdef __xstormy16__
+#define MALLOC_DIRECTION -1
+#endif
+
+#ifndef MALLOC_DIRECTION
+#define MALLOC_DIRECTION 1
+#endif
+
+#include <stddef.h>
+
+void* malloc(size_t);
+void    free(void*);
+void* realloc(void*, size_t);
+void* memalign(size_t, size_t);
+void* valloc(size_t);
+void* pvalloc(size_t);
+void* calloc(size_t, size_t);
+void    cfree(void*);
+int     malloc_trim(size_t);
+size_t  malloc_usable_size(void*);
+void    malloc_stats(void);
+int     mallopt(int, int);
+struct mallinfo mallinfo(void);
+
+typedef struct freelist_entry {
+  size_t size;
+  struct freelist_entry *next;
+} *fle;
+
+extern void * __malloc_end;
+extern fle __malloc_freelist;
+
+/* Return the number of bytes that need to be added to X to make it
+   aligned to an ALIGN boundary.  ALIGN must be a power of 2.  */
+#define M_ALIGN(x, align) (-(size_t)(x) & ((align) - 1))
+
+/* Return the number of bytes that need to be subtracted from X to make it
+   aligned to an ALIGN boundary.  ALIGN must be a power of 2.  */
+#define M_ALIGN_SUB(x, align) ((size_t)(x) & ((align) - 1))
+
+extern char *__malloc_start;
+
+/* This is the minimum gap allowed between __malloc_end and the top of
+   the stack.  This is only checked for when __malloc_end is
+   decreased; if instead the stack grows into the heap, silent data
+   corruption will result.  */
+#define MALLOC_MINIMUM_GAP 32
+
+#ifdef __xstormy16__
+register void * stack_pointer asm ("r15");
+#define MALLOC_LIMIT stack_pointer
+#else
+#define MALLOC_LIMIT __builtin_frame_address (0)
+#endif
+
+#if MALLOC_DIRECTION < 0
+#define CAN_ALLOC_P(required)                          \
+  (((size_t) __malloc_end - (size_t)MALLOC_LIMIT       \
+    - MALLOC_MINIMUM_GAP) >= (required))
+#else
+#define CAN_ALLOC_P(required)                          \
+  (((size_t)MALLOC_LIMIT - (size_t) __malloc_end       \
+    - MALLOC_MINIMUM_GAP) >= (required))
+#endif
+
+/* real_size is the size we actually have to allocate, allowing for
+   overhead and alignment.  */
+#define REAL_SIZE(sz)                                          \
+  ((sz) < sizeof (struct freelist_entry) - sizeof (size_t)     \
+   ? sizeof (struct freelist_entry)                            \
+   : sz + sizeof (size_t) + M_ALIGN(sz, sizeof (size_t)))
+
+#ifdef DEFINE_MALLOC
+
+void * __malloc_end = &__malloc_start;
+fle __malloc_freelist;
+
+void *
+malloc (size_t sz)
+{
+  fle *nextfree;
+  fle block;
+
+  /* real_size is the size we actually have to allocate, allowing for
+     overhead and alignment.  */
+  size_t real_size = REAL_SIZE (sz);
+
+  /* Look for the first block on the freelist that is large enough.  */
+  for (nextfree = &__malloc_freelist; 
+       *nextfree; 
+       nextfree = &(*nextfree)->next)  
+    {
+      block = *nextfree;
+      
+      if (block->size >= real_size)
+       {
+         /* If the block found is just the right size, remove it from
+            the free list.  Otherwise, split it.  */
+         if (block->size < real_size + sizeof (struct freelist_entry))
+           {
+             *nextfree = block->next;
+             return (void *)&block->next;
+           }
+         else
+           {
+             size_t newsize = block->size - real_size;
+             fle newnext = block->next;
+             *nextfree = (fle)((size_t)block + real_size);
+             (*nextfree)->size = newsize;
+             (*nextfree)->next = newnext;
+             goto done;
+           }
+       }
+
+      /* If this is the last block on the freelist, and it was too small,
+        enlarge it.  */
+      if (! block->next
+         && __malloc_end == (void *)((size_t)block + block->size))
+       {
+         size_t moresize = real_size - block->size;
+         if (! CAN_ALLOC_P (moresize))
+           return NULL;
+         
+         *nextfree = NULL;
+         if (MALLOC_DIRECTION < 0)
+           {
+             block = __malloc_end = (void *)((size_t)block - moresize);
+           }
+         else
+           {
+             __malloc_end = (void *)((size_t)block + real_size);
+           }
+
+         goto done;
+       }
+    }
+
+  /* No free space at the end of the free list.  Allocate new space
+     and use that.  */
+
+  if (! CAN_ALLOC_P (real_size))
+    return NULL;
+
+  if (MALLOC_DIRECTION > 0)
+    {
+      block = __malloc_end;
+      __malloc_end = (void *)((size_t)__malloc_end + real_size);
+    }
+  else
+    {
+      block = __malloc_end = (void *)((size_t)__malloc_end - real_size);
+    }
+ done:
+  block->size = real_size;
+  return (void *)&block->next;
+}
+
+#endif
+
+#ifdef DEFINE_FREE
+
+void
+free (void *block_p)
+{
+  fle *nextfree;
+  fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
+
+  if (block_p == NULL)
+    return;
+  
+  /* Look on the freelist to see if there's a free block just before
+     or just after this block.  */
+  for (nextfree = &__malloc_freelist; 
+       *nextfree; 
+       nextfree = &(*nextfree)->next)
+    {
+      fle thisblock = *nextfree;
+      if ((size_t)thisblock + thisblock->size == (size_t) block)
+       {
+         thisblock->size += block->size;
+         if (MALLOC_DIRECTION > 0
+             && thisblock->next
+             && (size_t) block + block->size == (size_t) thisblock->next)
+           {
+             thisblock->size += thisblock->next->size;
+             thisblock->next = thisblock->next->next;
+           }
+         return;
+       }
+      else if ((size_t) thisblock == (size_t) block + block->size)
+       {
+         if (MALLOC_DIRECTION < 0
+             && thisblock->next
+             && (size_t) block == ((size_t) thisblock->next 
+                                   + thisblock->next->size))
+           {
+             *nextfree = thisblock->next;
+             thisblock->next->size += block->size + thisblock->size;
+           }
+         else
+           {
+             block->size += thisblock->size;
+             block->next = thisblock->next;
+             *nextfree = block;
+           }
+         return;
+       }
+      else if ((MALLOC_DIRECTION > 0
+               && (size_t) thisblock > (size_t) block)
+              || (MALLOC_DIRECTION < 0
+                  && (size_t) thisblock < (size_t) block))
+       break;
+    }
+
+  block->next = *nextfree;
+  *nextfree = block;
+  return;
+}
+#endif
+
+#ifdef DEFINE_REALLOC
+void *
+realloc (void *block_p, size_t sz)
+{
+  fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
+  size_t real_size = REAL_SIZE (sz);
+  size_t old_real_size;
+
+  if (block_p == NULL)
+    return malloc (sz);
+
+  old_real_size = block->size;
+
+  /* Perhaps we need to allocate more space.  */
+  if (old_real_size < real_size)
+    {
+      void *result;
+      size_t old_size = old_real_size - sizeof (size_t);
+      
+      /* Need to allocate, copy, and free.  */
+      result = malloc (sz);
+      if (result == NULL)
+       return NULL;
+      memcpy (result, block_p, old_size < sz ? old_size : sz);
+      free (block_p);
+      return result;
+    }
+  /* Perhaps we can free some space.  */
+  if (old_real_size - real_size >= sizeof (struct freelist_entry))
+    {
+      fle newblock = (fle)((size_t)block + real_size);
+      block->size = real_size;
+      newblock->size = old_real_size - real_size;
+      free (&newblock->next);
+    }
+  return block_p;
+}
+#endif
+
+#ifdef DEFINE_CALLOC
+void *
+calloc (size_t n, size_t elem_size)
+{
+  void *result;
+  size_t sz = n * elem_size;
+  result = malloc (sz);
+  if (result != NULL)
+    memset (result, 0, sz);
+  return result;
+}
+#endif
+
+#ifdef DEFINE_CFREE
+void
+cfree (void *p)
+{
+  free (p);
+}
+#endif
+
+#ifdef DEFINE_MEMALIGN
+void *
+memalign (size_t align, size_t sz)
+{
+  fle *nextfree;
+  fle block;
+
+  /* real_size is the size we actually have to allocate, allowing for
+     overhead and alignment.  */
+  size_t real_size = REAL_SIZE (sz);
+
+  /* Some sanity checking on 'align'. */
+  if ((align & (align - 1)) != 0
+      || align <= 0)
+    return NULL;
+
+  /* Look for the first block on the freelist that is large enough.  */
+  /* One tricky part is this: We want the result to be a valid pointer
+     to free.  That means that there has to be room for a size_t
+     before the block.  If there's additional space before the block,
+     it should go on the freelist, or it'll be lost---we could add it
+     to the size of the block before it in memory, but finding the
+     previous block is expensive.  */
+  for (nextfree = &__malloc_freelist; 
+       ; 
+       nextfree = &(*nextfree)->next)  
+    {
+      size_t before_size;
+      size_t old_size;
+
+      /* If we've run out of free blocks, allocate more space.  */
+      if (! *nextfree)
+       {
+         old_size = real_size;
+         if (MALLOC_DIRECTION < 0)
+           {
+             old_size += M_ALIGN_SUB (((size_t)__malloc_end 
+                                       - old_size + sizeof (size_t)),
+                                      align);
+             if (! CAN_ALLOC_P (old_size))
+               return NULL;
+             block = __malloc_end = (void *)((size_t)__malloc_end - old_size);
+           }
+         else
+           {
+             block = __malloc_end;
+             old_size += M_ALIGN ((size_t)__malloc_end + sizeof (size_t),
+                                  align);
+             if (! CAN_ALLOC_P (old_size))
+               return NULL;
+             __malloc_end = (void *)((size_t)__malloc_end + old_size);
+           }
+         *nextfree = block;
+         block->size = old_size;
+         block->next = NULL;
+       }
+      else
+       {
+         block = *nextfree;
+         old_size = block->size;
+       }
+      
+
+      before_size = M_ALIGN (&block->next, align);
+      if (before_size != 0)
+       before_size = sizeof (*block) + M_ALIGN (&(block+1)->next, align);
+
+      /* If this is the last block on the freelist, and it is too small,
+        enlarge it.  */
+      if (! block->next
+         && old_size < real_size + before_size
+         && __malloc_end == (void *)((size_t)block + block->size))
+       {
+         if (MALLOC_DIRECTION < 0)
+           {
+             size_t moresize = real_size - block->size;
+             moresize += M_ALIGN_SUB ((size_t)&block->next - moresize, align);
+             if (! CAN_ALLOC_P (moresize))
+               return NULL;
+             block = __malloc_end = (void *)((size_t)block - moresize);
+             block->next = NULL;
+             block->size = old_size = old_size + moresize;
+             before_size = 0;
+           }
+         else
+           {
+             if (! CAN_ALLOC_P (before_size + real_size - block->size))
+               return NULL;
+             __malloc_end = (void *)((size_t)block + before_size + real_size);
+             block->size = old_size = before_size + real_size;
+           }
+
+         /* Two out of the four cases below will now be possible; which
+            two depends on MALLOC_DIRECTION.  */
+       }
+
+      if (old_size >= real_size + before_size)
+       {
+         /* This block will do.  If there needs to be space before it, 
+            split the block.  */
+         if (before_size != 0)
+           {
+             fle old_block = block;
+
+             old_block->size = before_size;
+             block = (fle)((size_t)block + before_size);
+             
+             /* If there's no space after the block, we're now nearly
+                 done; just make a note of the size required.  
+                Otherwise, we need to create a new free space block.  */
+             if (old_size - before_size 
+                 <= real_size + sizeof (struct freelist_entry))
+               {
+                 block->size = old_size - before_size;
+                 return (void *)&block->next;
+               }
+             else 
+               {
+                 fle new_block;
+                 new_block = (fle)((size_t)block + real_size);
+                 new_block->size = old_size - before_size - real_size;
+                 if (MALLOC_DIRECTION > 0)
+                   {
+                     new_block->next = old_block->next;
+                     old_block->next = new_block;
+                   }
+                 else
+                   {
+                     new_block->next = old_block;
+                     *nextfree = new_block;
+                   }
+                 goto done;
+               }
+           }
+         else
+           {
+             /* If the block found is just the right size, remove it from
+                the free list.  Otherwise, split it.  */
+             if (old_size <= real_size + sizeof (struct freelist_entry))
+               {
+                 *nextfree = block->next;
+                 return (void *)&block->next;
+               }
+             else
+               {
+                 size_t newsize = old_size - real_size;
+                 fle newnext = block->next;
+                 *nextfree = (fle)((size_t)block + real_size);
+                 (*nextfree)->size = newsize;
+                 (*nextfree)->next = newnext;
+                 goto done;
+               }
+           }
+       }
+    }
+
+ done:
+  block->size = real_size;
+  return (void *)&block->next;
+}
+#endif
+
+#ifdef DEFINE_VALLOC
+void *
+valloc (size_t sz)
+{
+  return memalign (128, sz);
+}
+#endif
+#ifdef DEFINE_PVALLOC
+void *
+pvalloc (size_t sz)
+{
+  return memalign (128, sz + M_ALIGN (sz, 128));
+}
+#endif
+
+#ifdef DEFINE_MALLINFO
+#include "malloc.h"
+
+struct mallinfo 
+mallinfo (void)
+{
+  struct mallinfo r;
+  fle fr;
+  size_t free_size;
+  size_t total_size;
+  size_t free_blocks;
+
+  memset (&r, 0, sizeof (r));
+
+  free_size = 0;
+  free_blocks = 0;
+  for (fr = __malloc_freelist; fr; fr = fr->next)
+    {
+      free_size += fr->size;
+      free_blocks++;
+      if (! fr->next)
+       {
+         int atend;
+         if (MALLOC_DIRECTION > 0)
+           atend = (void *)((size_t)fr + fr->size) == __malloc_end;
+         else
+           atend = (void *)fr == __malloc_end;
+         if (atend)
+           r.keepcost = fr->size;
+       }
+    }
+
+  if (MALLOC_DIRECTION > 0)
+    total_size = (char *)__malloc_end - (char *)&__malloc_start;
+  else
+    total_size = (char *)&__malloc_start - (char *)__malloc_end;
+  
+#ifdef DEBUG
+  /* Fixme: should walk through all the in-use blocks and see if
+     they're valid.  */
+#endif
+
+  r.arena = total_size;
+  r.fordblks = free_size;
+  r.uordblks = total_size - free_size;
+  r.ordblks = free_blocks;
+  return r;
+}
+#endif
+
+#ifdef DEFINE_MALLOC_STATS
+#include "malloc.h"
+#include <stdio.h>
+
+void 
+malloc_stats(void)
+{
+  struct mallinfo i;
+  FILE *fp;
+  
+  fp = stderr;
+  i = mallinfo();
+  fprintf (fp, "malloc has reserved %u bytes between %p and %p\n",
+          i.arena, &__malloc_start, __malloc_end);
+  fprintf (fp, "there are %u bytes free in %u chunks\n",
+          i.fordblks, i.ordblks);
+  fprintf (fp, "of which %u bytes are at the end of the reserved space\n",
+          i.keepcost);
+  fprintf (fp, "and %u bytes are in use.\n", i.uordblks);
+}
+#endif
+
+#ifdef DEFINE_MALLOC_USABLE_SIZE
+size_t 
+malloc_usable_size (void *block_p)
+{
+  fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
+  return block->size - sizeof (size_t);
+}
+#endif
+
+#ifdef DEFINE_MALLOPT
+int
+mallopt (int n, int v)
+{
+  (void)n; (void)v;
+  return 0;
+}
+#endif
diff --git a/debug/targets/m2gl_m2s/link.lds b/debug/targets/m2gl_m2s/link.lds
new file mode 100755 (executable)
index 0000000..1dbb99c
--- /dev/null
@@ -0,0 +1,34 @@
+OUTPUT_ARCH( "riscv" )
+
+SECTIONS
+{
+  . = 0x80000000;
+  .text : 
+  {
+    *(.text.entry)
+    *(.text)
+  }
+
+  /* data segment */
+  .data : { *(.data) }
+
+  .sdata : {
+    _gp = . + 0x800;
+    *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2)
+    *(.srodata*)
+    *(.sdata .sdata.* .gnu.linkonce.s.*)
+  }
+
+  /* bss segment */
+  .sbss : {
+    *(.sbss .sbss.* .gnu.linkonce.sb.*)
+    *(.scommon)
+  }
+  .bss : { *(.bss) }
+
+  __malloc_start = .;
+  . = . + 512;
+
+  /* End of uninitalized data segement */
+  _end = .;
+}
diff --git a/debug/targets/m2gl_m2s/openocd.cfg b/debug/targets/m2gl_m2s/openocd.cfg
new file mode 100644 (file)
index 0000000..3884a3e
--- /dev/null
@@ -0,0 +1,19 @@
+adapter_khz     10000
+
+source [find interface/ftdi/olimex-arm-usb-tiny-h.cfg]
+
+set _CHIPNAME riscv
+jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913
+
+set _TARGETNAME $_CHIPNAME.cpu
+target create $_TARGETNAME riscv -chain-position $_TARGETNAME
+
+#reset_config trst_and_srst separate
+# Stupid long so I can see the LEDs
+#adapter_nsrst_delay 2000
+#jtag_ntrst_delay 1000
+#
+init
+#reset
+
+halt
diff --git a/debug/targets/spike/link.lds b/debug/targets/spike/link.lds
new file mode 100755 (executable)
index 0000000..52e4472
--- /dev/null
@@ -0,0 +1,36 @@
+OUTPUT_ARCH( "riscv" )
+
+SECTIONS
+{
+  /* Leave some space for pk's data structures, which includes tohost/fromhost
+   * which are special addresses we ought to leave alone.  */
+  . = 0x80010000;
+  .text : 
+  {
+    *(.text.entry)
+    *(.text)
+  }
+
+  /* data segment */
+  .data : { *(.data) }
+
+  .sdata : {
+    _gp = . + 0x800;
+    *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2)
+    *(.srodata*)
+    *(.sdata .sdata.* .gnu.linkonce.s.*)
+  }
+
+  /* bss segment */
+  .sbss : {
+    *(.sbss .sbss.* .gnu.linkonce.sb.*)
+    *(.scommon)
+  }
+  .bss : { *(.bss) }
+
+  __malloc_start = .;
+  . = . + 512;
+
+  /* End of uninitalized data segement */
+  _end = .;
+}
diff --git a/debug/testlib.py b/debug/testlib.py
new file mode 100644 (file)
index 0000000..5e7f366
--- /dev/null
@@ -0,0 +1,170 @@
+import os.path
+import pexpect
+import shlex
+import subprocess
+import tempfile
+import testlib
+import unittest
+
+# Note that gdb comes with its own testsuite. I was unable to figure out how to
+# run that testsuite against the spike simulator.
+
+def find_file(path):
+    for directory in (os.getcwd(), os.path.dirname(testlib.__file__)):
+        fullpath = os.path.join(directory, path)
+        if os.path.exists(fullpath):
+            return fullpath
+    return None
+
+def compile(args, xlen=32):
+    cc = os.path.expandvars("$RISCV/bin/riscv%d-unknown-elf-gcc" % xlen)
+    cmd = [cc, "-g"]
+    for arg in args:
+        found = find_file(arg)
+        if found:
+            cmd.append(found)
+        else:
+            cmd.append(arg)
+    cmd = " ".join(cmd)
+    result = os.system(cmd)
+    assert result == 0, "%r failed" % cmd
+
+def unused_port():
+    # http://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python/2838309#2838309
+    import socket
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.bind(("",0))
+    port = s.getsockname()[1]
+    s.close()
+    return port
+
+class Spike(object):
+    def __init__(self, cmd, binary=None, halted=False, with_gdb=True, timeout=None,
+            xlen=64):
+        """Launch spike. Return tuple of its process and the port it's running on."""
+        if cmd:
+            cmd = shlex.split(cmd)
+        else:
+            cmd = ["spike"]
+        if (xlen == 32):
+            cmd += ["--isa", "RV32"]
+
+        if timeout:
+            cmd = ["timeout", str(timeout)] + cmd
+
+        if halted:
+            cmd.append('-H')
+        if with_gdb:
+            self.port = unused_port()
+            cmd += ['--gdb-port', str(self.port)]
+        cmd.append('pk')
+        if binary:
+            cmd.append(binary)
+        logfile = open("spike.log", "w")
+        logfile.write("+ %s\n" % " ".join(cmd))
+        logfile.flush()
+        self.process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=logfile,
+                stderr=logfile)
+
+    def __del__(self):
+        try:
+            self.process.kill()
+            self.process.wait()
+        except OSError:
+            pass
+
+    def wait(self, *args, **kwargs):
+        return self.process.wait(*args, **kwargs)
+
+class Openocd(object):
+    def __init__(self, cmd=None, config=None, debug=False):
+        if cmd:
+            cmd = shlex.split(cmd)
+        else:
+            cmd = ["openocd"]
+        if config:
+            cmd += ["-f", find_file(config)]
+        if debug:
+            cmd.append("-d")
+        logfile = open("openocd.log", "w")
+        logfile.write("+ %s\n" % " ".join(cmd))
+        self.process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=logfile,
+                stderr=logfile)
+        # TODO: Pick a random port
+        self.port = 3333
+
+    def __del__(self):
+        try:
+            self.process.kill()
+            self.process.wait()
+        except OSError:
+            pass
+
+class Gdb(object):
+    def __init__(self,
+            path=os.path.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb")):
+        self.child = pexpect.spawn(path)
+        self.child.logfile = file("gdb.log", "w")
+        self.child.logfile.write("+ %s\n" % path)
+        self.wait()
+        self.command("set confirm off")
+        self.command("set width 0")
+        self.command("set height 0")
+        # Force consistency.
+        self.command("set print entry-values no")
+
+    def wait(self):
+        """Wait for prompt."""
+        self.child.expect("\(gdb\)")
+
+    def command(self, command, timeout=-1):
+        self.child.sendline(command)
+        self.child.expect("\n", timeout=timeout)
+        self.child.expect("\(gdb\)", timeout=timeout)
+        return self.child.before.strip()
+
+    def c(self, wait=True):
+        if wait:
+            output = self.command("c")
+            assert "Continuing" in output
+            return output
+        else:
+            self.child.sendline("c")
+            self.child.expect("Continuing")
+
+    def interrupt(self):
+        self.child.send("\003");
+        self.child.expect("\(gdb\)")
+        return self.child.before.strip()
+
+    def x(self, address, size='w'):
+        output = self.command("x/%s %s" % (size, address))
+        value = int(output.split(':')[1].strip(), 0)
+        return value
+
+    def p(self, obj):
+        output = self.command("p/x %s" % obj)
+        value = int(output.split('=')[-1].strip(), 0)
+        return value
+
+    def stepi(self):
+        output = self.command("stepi")
+        assert "Cannot" not in output
+        return output
+
+    def load(self):
+        output = self.command("load", timeout=60)
+        assert "failed" not in  output
+        assert "Transfer rate" in output
+
+    def b(self, location):
+        output = self.command("b %s" % location)
+        assert "not defined" not in output
+        assert "Breakpoint" in output
+        return output
+
+    def hbreak(self, location):
+        output = self.command("hbreak %s" % location)
+        assert "not defined" not in output
+        assert "Hardware assisted breakpoint" in output
+        return output