WIP on debug testing.
authorTim Newsome <tim@sifive.com>
Sat, 4 Jun 2016 20:19:45 +0000 (13:19 -0700)
committerTim Newsome <tim@sifive.com>
Tue, 19 Jul 2016 01:51:54 +0000 (18:51 -0700)
./gdbserver.py --m2gl_m2s --openocd "$HOME/SiFive/openocd/src/openocd -s
$HOME/SiFive/openocd/tcl" -- RegsTest.test_write_gprs
doesn't fail in a completely crazy way.

12 files changed:
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/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/targets/m2gl_m2s/entry.S [new file with mode: 0755]
debug/targets/m2gl_m2s/link.lds [new file with mode: 0755]
debug/targets/m2gl_m2s/openocd.cfg [new file with mode: 0644]
debug/testlib.py [new file with mode: 0644]

diff --git a/debug/README.md b/debug/README.md
new file mode 100644 (file)
index 0000000..2be795b
--- /dev/null
@@ -0,0 +1,3 @@
+Debugging requires many of a system components to all work together.  The goal
+is to collect some tests that test gdb with spike, and gdb talking to real
+hardware through openocd.
diff --git a/debug/gdbserver.py b/debug/gdbserver.py
new file mode 100755 (executable)
index 0000000..8b7b562
--- /dev/null
@@ -0,0 +1,271 @@
+#!/usr/bin/python
+
+import os
+import sys
+import argparse
+import testlib
+import unittest
+import tempfile
+import time
+import random
+import binascii
+
+class DeleteServer(unittest.TestCase):
+    def tearDown(self):
+        del self.server
+
+class InstantHaltTest(DeleteServer):
+    def setUp(self):
+        self.binary = target.compile("debug.c")
+        self.server = target.server(self.binary, halted=True)
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+
+    def test_instant_halt(self):
+        self.assertEqual(0x1000, self.gdb.p("$pc"))
+        # For some reason instret resets to 0.
+        self.assertLess(self.gdb.p("$instret"), 8)
+        self.gdb.command("stepi")
+        self.assertNotEqual(0x1000, self.gdb.p("$pc"))
+
+    def test_change_pc(self):
+        """Change the PC right as we come out of reset."""
+        # 0x13 is nop
+        self.gdb.command("p *((int*) 0x80000000)=0x13")
+        self.gdb.command("p *((int*) 0x80000004)=0x13")
+        self.gdb.command("p *((int*) 0x80000008)=0x13")
+        self.gdb.command("p $pc=0x80000000")
+        self.gdb.command("stepi")
+        self.assertEqual(0x80000004, self.gdb.p("$pc"))
+        self.gdb.command("stepi")
+        self.assertEqual(0x80000008, self.gdb.p("$pc"))
+
+class DebugTest(DeleteServer):
+    def setUp(self):
+        self.binary = target.compile("debug.c")
+        self.server = target.server(self.binary, halted=False)
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+
+    def test_turbostep(self):
+        """Single step a bunch of times."""
+        self.gdb.command("p i=0");
+        last_pc = None
+        for _ in range(100):
+            self.gdb.command("stepi")
+            pc = self.gdb.command("p $pc")
+            self.assertNotEqual(last_pc, pc)
+            last_pc = pc
+
+    def test_exit(self):
+        self.gdb.command("p i=0");
+        output = self.gdb.command("c")
+        self.assertIn("Continuing", output)
+        self.assertIn("Remote connection closed", output)
+
+    def test_breakpoint(self):
+        self.gdb.command("p i=0");
+        self.gdb.command("b print_row")
+        # The breakpoint should be hit exactly 10 times.
+        for i in range(10):
+            output = self.gdb.command("c")
+            self.assertIn("Continuing", output)
+            self.assertIn("length=%d" % i, output)
+            self.assertIn("Breakpoint 1", output)
+        output = self.gdb.command("c")
+        self.assertIn("Continuing", output)
+        self.assertIn("Remote connection closed", output)
+
+    def test_registers(self):
+        self.gdb.command("p i=0");
+        # 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)
+        # 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")
+
+        # 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.command("stepi")
+
+    def test_interrupt(self):
+        """Sending gdb ^C while the program is running should cause it to halt."""
+        self.gdb.c(wait=False)
+        time.sleep(0.1)
+        self.gdb.interrupt()
+        self.gdb.command("p i=123");
+        self.gdb.c(wait=False)
+        time.sleep(0.1)
+        self.gdb.interrupt()
+        self.gdb.command("p i=0");
+        output = self.gdb.c()
+        self.assertIn("Continuing", output)
+        self.assertIn("Remote connection closed", output)
+
+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.command("load")
+
+    def test_write_gprs(self):
+        # Note a0 is missing from this list since it's used to hold the
+        # address.
+        regs = ("ra", "sp", "gp", "tp", "t0", "t1", "t2", "fp", "s1",
+                "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4",
+                "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5",
+                "t6")
+
+        self.gdb.command("p $pc=write_regs")
+        for i, r in enumerate(regs):
+            self.gdb.command("p $%s=%d" % (r, (0xdeadbeef<<i)+17))
+        self.gdb.command("p $a0=data")
+        self.gdb.command("b all_done")
+        output = self.gdb.command("c")
+        self.assertIn("Breakpoint 1", 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)
+
+    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 $fflags=9")
+        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(9, self.gdb.p("$fflags"))
+        self.assertEqual(9, self.gdb.p("$x1"))
+        self.assertEqual(9, self.gdb.p("$csr1"))
+
+class DownloadTest(DeleteServer):
+    def setUp(self):
+        length = 2**20
+        fd = file("data.c", "w")
+        fd.write("#include <stdint.h>\n")
+        fd.write("uint32_t length = %d;\n" % length)
+        fd.write("uint8_t d[%d] = {\n" % length)
+        self.crc = 0
+        for i in range(length / 16):
+            fd.write("  /* 0x%04x */ " % (i * 16));
+            for _ in range(16):
+                value = random.randrange(1<<8)
+                fd.write("%d, " % value)
+                self.crc = binascii.crc32("%c" % value, self.crc)
+            fd.write("\n");
+        fd.write("};\n");
+        fd.write("uint8_t *data = &d[0];\n");
+        fd.close()
+
+        self.binary = target.compile("checksum.c", "data.c", "start.S",
+                "-mcmodel=medany",
+                "-T", "standalone.lds",
+                "-nostartfiles"
+                )
+        self.server = target.server(None, halted=True)
+        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.command("load")
+        self.assertNotIn("failed", output)
+        self.assertIn("Transfer rate", output)
+        self.gdb.command("b done")
+        self.gdb.c()
+        result = self.gdb.p("$a0")
+        self.assertEqual(self.crc, result)
+
+class MprvTest(DeleteServer):
+    def setUp(self):
+        self.binary = target.compile("mprv.S", "-T", "standalone.lds",
+                "-nostartfiles")
+        self.server = target.server(None, halted=True)
+        self.gdb = testlib.Gdb()
+        self.gdb.command("file %s" % self.binary)
+        self.gdb.command("target extended-remote localhost:%d" % self.server.port)
+        self.gdb.command("load")
+
+    def test_mprv(self):
+        """Test that the debugger can access memory when MPRV is set."""
+        self.gdb.c(wait=False)
+        self.gdb.interrupt()
+        output = self.gdb.command("p/x *(int*)(((char*)&data)-0x80000000)")
+        self.assertIn("0xbead", output)
+
+class Target(object):
+    def server(self):
+        raise NotImplementedError
+
+    def compile(self, *sources):
+        return testlib.compile(*(sources +
+                ("targets/%s/entry.S" % self.name, "programs/init.c",
+                    "-I", "../env",
+                    "-T", "targets/%s/link.lds" % self.name,
+                    "-nostartfiles")))
+
+class SpikeTarget(Target):
+    name = "spike"
+
+class MicroSemiTarget(Target):
+    name = "m2gl_m2s"
+
+    def server(self):
+        return testlib.Openocd(cmd=parsed.openocd,
+                config="targets/%s/openocd.cfg" % self.name)
+
+targets = [
+        SpikeTarget,
+        MicroSemiTarget
+        ]
+
+def main():
+    parser = argparse.ArgumentParser()
+    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("--openocd", help="The OpenOCD command to use.",
+            default="openocd")
+    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..36152fc
--- /dev/null
@@ -0,0 +1,47 @@
+#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);
+}
+
+extern uint8_t *data;
+extern uint32_t length;
+
+uint32_t main()
+{
+  /* Compute a simple checksum. */
+  return crc32a(data, length);
+}
diff --git a/debug/programs/debug.c b/debug/programs/debug.c
new file mode 100644 (file)
index 0000000..2cad88f
--- /dev/null
@@ -0,0 +1,27 @@
+#include <stdio.h>
+
+char c = 'x';
+
+void print_row(int length)
+{
+    for (int x=0; x<length; x++) {
+        printf("%c", c);
+    }
+    printf("\n");
+}
+
+int main()
+{
+    volatile int i = 42;
+    const char *text = "constant\n";
+    int threshold = 7;
+
+    // Wait for the debugger to get us out of this loop.
+    while (i)
+        ;
+
+    printf("%s", text);
+    for (int y=0; y < 10; y++) {
+        print_row(y);
+    }
+}
diff --git a/debug/programs/init.c b/debug/programs/init.c
new file mode 100644 (file)
index 0000000..074bc21
--- /dev/null
@@ -0,0 +1,14 @@
+int main(void);
+
+void handle_trap(unsigned int mcause, unsigned int mepc, unsigned int sp)
+{
+    while (1)
+        ;
+}
+
+void _init()
+{
+    main();
+    while (1)
+        ;
+}
diff --git a/debug/programs/mprv.S b/debug/programs/mprv.S
new file mode 100644 (file)
index 0000000..df346b3
--- /dev/null
@@ -0,0 +1,38 @@
+#include "../riscv/encoding.h"
+#define PGSHIFT         12
+
+        .global         _start
+
+        .section        .text
+_start:
+        # 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
+        li      t0, (MSTATUS_MPRV | (VM_SV39 << 24))
+        #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:
+        .word   ((0x80000000 >> 2) | PTE_V | PTE_TYPE_URWX_SRWX)
+        .word   0
diff --git a/debug/programs/regs.S b/debug/programs/regs.S
new file mode 100644 (file)
index 0000000..e6456e1
--- /dev/null
@@ -0,0 +1,43 @@
+        .global main
+main:
+        j       main
+
+write_regs:
+        sd      x1, 0(a0)
+        sd      x2, 8(a0)
+        sd      x3, 16(a0)
+        sd      x4, 24(a0)
+        sd      x5, 32(a0)
+        sd      x6, 40(a0)
+        sd      x7, 48(a0)
+        sd      x8, 56(a0)
+        sd      x9, 64(a0)
+        sd      x11, 72(a0)
+        sd      x12, 80(a0)
+        sd      x13, 88(a0)
+        sd      x14, 96(a0)
+        sd      x15, 104(a0)
+        sd      x16, 112(a0)
+        sd      x17, 120(a0)
+        sd      x18, 128(a0)
+        sd      x19, 136(a0)
+        sd      x20, 144(a0)
+        sd      x21, 152(a0)
+        sd      x22, 160(a0)
+        sd      x23, 168(a0)
+        sd      x24, 176(a0)
+        sd      x25, 184(a0)
+        sd      x26, 192(a0)
+        sd      x27, 200(a0)
+        sd      x28, 208(a0)
+        sd      x29, 216(a0)
+        sd      x30, 224(a0)
+        sd      x31, 232(a0)
+
+        csrr    x1, 1   # fflags
+
+all_done:
+        j       all_done
+
+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/targets/m2gl_m2s/entry.S b/debug/targets/m2gl_m2s/entry.S
new file mode 100755 (executable)
index 0000000..ff49cf6
--- /dev/null
@@ -0,0 +1,131 @@
+#ifndef ENTRY_S
+#define ENTRY_S
+
+#include "encoding.h"
+
+#define STACK_SIZE ((1 << 12) - 128)
+
+#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
+
+  .bss
+  .align 4
+stack_bottom:
+  .skip STACK_SIZE
+stack_top:
+#endif
diff --git a/debug/targets/m2gl_m2s/link.lds b/debug/targets/m2gl_m2s/link.lds
new file mode 100755 (executable)
index 0000000..260b014
--- /dev/null
@@ -0,0 +1,35 @@
+OUTPUT_ARCH( "riscv" )
+
+SECTIONS
+{
+  . = 0x60040000;
+  .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.*)
+  }
+
+  . = 0x80000000;
+
+  /* bss segment */
+  .sbss : {
+    *(.sbss .sbss.* .gnu.linkonce.sb.*)
+    *(.scommon)
+  }
+  .bss : { *(.bss) }
+
+  /* End of uninitalized data segement */
+  _end = .;
+  _heap_end = .;
+}
+
diff --git a/debug/targets/m2gl_m2s/openocd.cfg b/debug/targets/m2gl_m2s/openocd.cfg
new file mode 100644 (file)
index 0000000..d58116b
--- /dev/null
@@ -0,0 +1,17 @@
+adapter_khz     100
+
+source [find interface/ftdi/olimex-arm-usb-tiny-h.cfg]
+
+set _CHIPNAME riscv
+jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1525600b
+
+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
diff --git a/debug/testlib.py b/debug/testlib.py
new file mode 100644 (file)
index 0000000..0902983
--- /dev/null
@@ -0,0 +1,140 @@
+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):
+    """Compile a single .c file into a binary."""
+    dst = os.path.splitext(args[0])[0]
+    cc = os.path.expandvars("$RISCV/bin/riscv64-unknown-elf-gcc")
+    cmd = [cc, "-g", "-O", "-o", dst]
+    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
+    return dst
+
+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, binary, halted=False, with_gdb=True, timeout=None):
+        """Launch spike. Return tuple of its process and the port it's running on."""
+        cmd = []
+        if timeout:
+            cmd += ["timeout", str(timeout)]
+
+        cmd += [find_file("spike")]
+        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")
+        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=True):
+        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")
+        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.wait()
+        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:
+            return self.command("c")
+        else:
+            self.child.sendline("c")
+            self.child.expect("Continuing")
+
+    def interrupt(self):
+        self.child.send("\003");
+        self.child.expect("\(gdb\)")
+
+    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 %s" % obj)
+        value = int(output.split('=')[-1].strip())
+        return value
+
+    def stepi(self):
+        return self.command("stepi")