15 print_log_names
= False
16 real_stdout
= sys
.stdout
18 # Note that gdb comes with its own testsuite. I was unable to figure out how to
19 # run that testsuite against the spike simulator.
22 for directory
in (os
.getcwd(), os
.path
.dirname(__file__
)):
23 fullpath
= os
.path
.join(directory
, path
)
24 relpath
= os
.path
.relpath(fullpath
)
25 if len(relpath
) >= len(fullpath
):
27 if os
.path
.exists(relpath
):
31 def compile(args
, xlen
=32): # pylint: disable=redefined-builtin
32 cc
= os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gcc")
35 cmd
.append("-march=rv32imac")
36 cmd
.append("-mabi=ilp32")
38 cmd
.append("-march=rv64imac")
39 cmd
.append("-mabi=lp64")
41 found
= find_file(arg
)
47 print "+", " ".join(cmd
)
48 process
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
,
49 stderr
=subprocess
.PIPE
)
50 stdout
, stderr
= process
.communicate()
51 if process
.returncode
:
55 raise Exception("Compile failed!")
58 def __init__(self
, target
, halted
=False, timeout
=None, with_jtag_gdb
=True):
59 """Launch spike. Return tuple of its process and the port it's running
68 cmd
= self
.command(target
, harts
, halted
, timeout
, with_jtag_gdb
)
69 self
.infinite_loop
= target
.compile(harts
[0],
70 "programs/checksum.c", "programs/tiny-malloc.c",
71 "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
72 cmd
.append(self
.infinite_loop
)
73 self
.logfile
= tempfile
.NamedTemporaryFile(prefix
="spike-",
75 self
.logname
= self
.logfile
.name
77 real_stdout
.write("Temporary spike log: %s\n" % self
.logname
)
78 self
.logfile
.write("+ %s\n" % " ".join(cmd
))
80 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
81 stdout
=self
.logfile
, stderr
=self
.logfile
)
86 m
= re
.search(r
"Listening for remote bitbang connection on "
87 r
"port (\d+).", open(self
.logname
).read())
89 self
.port
= int(m
.group(1))
90 os
.environ
['REMOTE_BITBANG_PORT'] = m
.group(1)
94 print_log(self
.logname
)
95 raise Exception("Didn't get spike message about bitbang "
98 def command(self
, target
, harts
, halted
, timeout
, with_jtag_gdb
):
99 # pylint: disable=no-self-use
101 cmd
= shlex
.split(target
.sim_cmd
)
103 spike
= os
.path
.expandvars("$RISCV/bin/spike")
106 cmd
+= ["-p%d" % len(harts
)]
108 assert len(set(t
.xlen
for t
in harts
)) == 1, \
109 "All spike harts must have the same XLEN"
111 if harts
[0].xlen
== 32:
112 cmd
+= ["--isa", "RV32G"]
114 cmd
+= ["--isa", "RV64G"]
116 assert len(set(t
.ram
for t
in harts
)) == 1, \
117 "All spike harts must have the same RAM layout"
118 assert len(set(t
.ram_size
for t
in harts
)) == 1, \
119 "All spike harts must have the same RAM layout"
120 cmd
+= ["-m0x%x:0x%x" % (harts
[0].ram
, harts
[0].ram_size
)]
123 cmd
= ["timeout", str(timeout
)] + cmd
128 cmd
+= ['--rbb-port', '0']
129 os
.environ
['REMOTE_BITBANG_HOST'] = 'localhost'
141 def wait(self
, *args
, **kwargs
):
142 return self
.process
.wait(*args
, **kwargs
)
144 class VcsSim(object):
147 def __init__(self
, sim_cmd
=None, debug
=False):
149 cmd
= shlex
.split(sim_cmd
)
152 cmd
+= ["+jtag_vpi_enable"]
154 cmd
[0] = cmd
[0] + "-debug"
155 cmd
+= ["+vcdplusfile=output/gdbserver.vpd"]
156 logfile
= open(self
.logname
, "w")
157 logfile
.write("+ %s\n" % " ".join(cmd
))
159 listenfile
= open(self
.logname
, "r")
160 listenfile
.seek(0, 2)
161 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
162 stdout
=logfile
, stderr
=logfile
)
165 # Fail if VCS exits early
166 exit_code
= self
.process
.poll()
167 if exit_code
is not None:
168 raise RuntimeError('VCS simulator exited early with status %d'
171 line
= listenfile
.readline()
174 match
= re
.match(r
"^Listening on port (\d+)$", line
)
177 self
.port
= int(match
.group(1))
178 os
.environ
['JTAG_VPI_PORT'] = str(self
.port
)
187 class Openocd(object):
188 logfile
= tempfile
.NamedTemporaryFile(prefix
='openocd', suffix
='.log')
189 logname
= logfile
.name
191 def __init__(self
, server_cmd
=None, config
=None, debug
=False, timeout
=60):
192 self
.timeout
= timeout
195 cmd
= shlex
.split(server_cmd
)
197 openocd
= os
.path
.expandvars("$RISCV/bin/openocd")
202 # This command needs to come before any config scripts on the command
203 # line, since they are executed in order.
205 # Tell OpenOCD to bind gdb to an unused, ephemeral port.
208 # Disable tcl and telnet servers, since they are unused and because
209 # the port numbers will conflict if multiple OpenOCD processes are
210 # running on the same server.
214 "telnet_port disabled",
218 f
= find_file(config
)
220 print "Unable to read file " + config
227 logfile
= open(Openocd
.logname
, "w")
229 real_stdout
.write("Temporary OpenOCD log: %s\n" % Openocd
.logname
)
230 logfile
.write("+ %s\n" % " ".join(cmd
))
234 self
.process
= self
.start(cmd
, logfile
)
236 def start(self
, cmd
, logfile
):
237 process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
238 stdout
=logfile
, stderr
=logfile
)
241 # Wait for OpenOCD to have made it through riscv_examine(). When
242 # using OpenOCD to communicate with a simulator this may take a
243 # long time, and gdb will time out when trying to connect if we
247 fd
= open(Openocd
.logname
, "r")
251 if not process
.poll() is None:
252 raise Exception("OpenOCD exited early.")
256 m
= re
.search(r
"Listening on port (\d+) for gdb connections",
259 self
.gdb_ports
.append(int(m
.group(1)))
261 if "telnet server disabled" in line
:
264 if not messaged
and time
.time() - start
> 1:
266 print "Waiting for OpenOCD to start..."
267 if (time
.time() - start
) > self
.timeout
:
268 raise Exception("Timed out waiting for OpenOCD to "
272 print_log(Openocd
.logname
)
279 except (OSError, AttributeError):
282 class OpenocdCli(object):
283 def __init__(self
, port
=4444):
284 self
.child
= pexpect
.spawn(
285 "sh -c 'telnet localhost %d | tee openocd-cli.log'" % port
)
286 self
.child
.expect("> ")
288 def command(self
, cmd
):
289 self
.child
.sendline(cmd
)
290 self
.child
.expect(cmd
)
291 self
.child
.expect("\n")
292 self
.child
.expect("> ")
293 return self
.child
.before
.strip("\t\r\n \0")
295 def reg(self
, reg
=''):
296 output
= self
.command("reg %s" % reg
)
297 matches
= re
.findall(r
"(\w+) \(/\d+\): (0x[0-9A-F]+)", output
)
298 values
= {r
: int(v
, 0) for r
, v
in matches
}
303 def load_image(self
, image
):
304 output
= self
.command("load_image %s" % image
)
305 if 'invalid ELF file, only 32bits files are supported' in output
:
306 raise TestNotApplicable(output
)
308 class CannotAccess(Exception):
309 def __init__(self
, address
):
310 Exception.__init
__(self
)
311 self
.address
= address
313 Thread
= collections
.namedtuple('Thread', ('id', 'description', 'target_id',
317 """A single gdb class which can interact with one or more gdb instances."""
319 # pylint: disable=too-many-public-methods
320 # pylint: disable=too-many-instance-attributes
322 def __init__(self
, ports
,
323 cmd
=os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb"),
324 timeout
=60, binary
=None):
329 self
.timeout
= timeout
338 logfile
= tempfile
.NamedTemporaryFile(prefix
="gdb@%d-" % port
,
340 self
.logfiles
.append(logfile
)
342 real_stdout
.write("Temporary gdb log: %s\n" % logfile
.name
)
343 child
= pexpect
.spawn(cmd
)
344 child
.logfile
= logfile
345 child
.logfile
.write("+ %s\n" % cmd
)
346 self
.children
.append(child
)
347 self
.active_child
= self
.children
[0]
350 for port
, child
in zip(self
.ports
, self
.children
):
351 self
.select_child(child
)
353 self
.command("set confirm off")
354 self
.command("set width 0")
355 self
.command("set height 0")
357 self
.command("set print entry-values no")
358 self
.command("set remotetimeout %d" % self
.timeout
)
359 self
.command("target extended-remote localhost:%d" % port
)
361 self
.command("file %s" % self
.binary
)
362 threads
= self
.threads()
366 m
= re
.search(r
"Hart (\d+)", t
.name
)
368 hartid
= int(m
.group(1))
371 hartid
= max(self
.harts
) + 1
374 self
.harts
[hartid
] = (child
, t
)
377 for child
in self
.children
:
381 return [logfile
.name
for logfile
in self
.logfiles
]
383 def select_child(self
, child
):
384 self
.active_child
= child
386 def select_hart(self
, hart
):
387 child
, thread
= self
.harts
[hart
.id]
388 self
.select_child(child
)
389 output
= self
.command("thread %s" % thread
.id)
390 assert "Unknown" not in output
392 def push_state(self
):
394 'active_child': self
.active_child
398 state
= self
.stack
.pop()
399 self
.active_child
= state
['active_child']
402 """Wait for prompt."""
403 self
.active_child
.expect(r
"\(gdb\)")
405 def command(self
, command
, timeout
=6000):
406 """timeout is in seconds"""
407 self
.active_child
.sendline(command
)
408 self
.active_child
.expect("\n", timeout
=timeout
)
409 self
.active_child
.expect(r
"\(gdb\)", timeout
=timeout
)
410 return self
.active_child
.before
.strip()
412 def global_command(self
, command
):
413 """Execute this command on every gdb that we control."""
414 with
PrivateState(self
):
415 for child
in self
.children
:
416 self
.select_child(child
)
417 self
.command(command
)
419 def c(self
, wait
=True, timeout
=-1, async=False):
422 In RTOS mode, gdb will resume all harts.
423 In multi-gdb mode, this command will just go to the current gdb, so
424 will only resume one hart.
431 output
= self
.command("c%s" % async, timeout
=timeout
)
432 assert "Continuing" in output
435 self
.active_child
.sendline("c%s" % async)
436 self
.active_child
.expect("Continuing")
442 This function works fine when using multiple gdb sessions, but the
443 caller must be careful when using it nonetheless. gdb's behavior is to
444 not set breakpoints until just before the hart is resumed, and then
445 clears them as soon as the hart halts. That means that you can't set
446 one software breakpoint, and expect multiple harts to hit it. It's
447 possible that the first hart completes set/run/halt/clear before the
448 second hart even gets to resume, so it will never hit the breakpoint.
450 with
PrivateState(self
):
451 for child
in self
.children
:
453 child
.expect("Continuing")
455 # Now wait for them all to halt
456 for child
in self
.children
:
457 child
.expect(r
"\(gdb\)")
460 self
.active_child
.send("\003")
461 self
.active_child
.expect(r
"\(gdb\)", timeout
=6000)
462 return self
.active_child
.before
.strip()
464 def x(self
, address
, size
='w'):
465 output
= self
.command("x/%s %s" % (size
, address
))
466 value
= int(output
.split(':')[1].strip(), 0)
469 def p_raw(self
, obj
):
470 output
= self
.command("p %s" % obj
)
471 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
473 raise CannotAccess(int(m
.group(1), 0))
474 return output
.split('=')[-1].strip()
476 def parse_string(self
, text
):
478 if text
.startswith("{") and text
.endswith("}"):
480 return [self
.parse_string(t
) for t
in inner
.split(", ")]
481 elif text
.startswith('"') and text
.endswith('"'):
486 def p(self
, obj
, fmt
="/x"):
487 output
= self
.command("p%s %s" % (fmt
, obj
))
488 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
490 raise CannotAccess(int(m
.group(1), 0))
491 rhs
= output
.split('=')[-1]
492 return self
.parse_string(rhs
)
494 def p_string(self
, obj
):
495 output
= self
.command("p %s" % obj
)
496 value
= shlex
.split(output
.split('=')[-1].strip())[1]
500 output
= self
.command("stepi", timeout
=60)
504 output
= self
.command("load", timeout
=6000)
505 assert "failed" not in output
506 assert "Transfer rate" in output
508 def b(self
, location
):
509 output
= self
.command("b %s" % location
)
510 assert "not defined" not in output
511 assert "Breakpoint" in output
514 def hbreak(self
, location
):
515 output
= self
.command("hbreak %s" % location
)
516 assert "not defined" not in output
517 assert "Hardware assisted breakpoint" in output
521 output
= self
.command("info threads")
523 for line
in output
.splitlines():
526 r
"(Remote target|Thread (\d+)\s*\(Name: ([^\)]+))"
529 threads
.append(Thread(*m
.groups()))
532 #>>> threads.append(Thread('1', '1', 'Default', '???'))
535 def thread(self
, thread
):
536 return self
.command("thread %s" % thread
.id)
539 return self
.command("where 1")
541 class PrivateState(object):
542 def __init__(self
, gdb
):
546 self
.gdb
.push_state()
548 def __exit__(self
, _type
, _value
, _traceback
):
551 def run_all_tests(module
, target
, parsed
):
552 if not os
.path
.exists(parsed
.logs
):
553 os
.makedirs(parsed
.logs
)
555 overall_start
= time
.time()
557 global gdb_cmd
# pylint: disable=global-statement
561 examine_added
= False
562 for hart
in target
.harts
:
564 hart
.misa
= int(parsed
.misaval
, 16)
565 print "Using $misa from command line: 0x%x" % hart
.misa
567 print "Using $misa from hart definition: 0x%x" % hart
.misa
568 elif not examine_added
:
569 todo
.append(("ExamineTarget", ExamineTarget
, None))
572 for name
in dir(module
):
573 definition
= getattr(module
, name
)
574 if isinstance(definition
, type) and hasattr(definition
, 'test') and \
575 (not parsed
.test
or any(test
in name
for test
in parsed
.test
)):
576 todo
.append((name
, definition
, None))
578 results
, count
= run_tests(parsed
, target
, todo
)
580 header("ran %d tests in %.0fs" % (count
, time
.time() - overall_start
),
583 return print_results(results
)
585 good_results
= set(('pass', 'not_applicable'))
586 def run_tests(parsed
, target
, todo
):
590 for name
, definition
, hart
in todo
:
591 log_name
= os
.path
.join(parsed
.logs
, "%s-%s-%s.log" %
592 (time
.strftime("%Y%m%d-%H%M%S"), type(target
).__name
__, name
))
593 log_fd
= open(log_name
, 'w')
594 print "[%s] Starting > %s" % (name
, log_name
)
595 instance
= definition(target
, hart
)
597 log_fd
.write("Test: %s\n" % name
)
598 log_fd
.write("Target: %s\n" % type(target
).__name
__)
600 global real_stdout
# pylint: disable=global-statement
601 real_stdout
= sys
.stdout
604 result
= instance
.run()
605 log_fd
.write("Result: %s\n" % result
)
607 sys
.stdout
= real_stdout
608 log_fd
.write("Time elapsed: %.2fs\n" % (time
.time() - start
))
610 print "[%s] %s in %.2fs" % (name
, result
, time
.time() - start
)
611 if result
not in good_results
and parsed
.print_failures
:
612 sys
.stdout
.write(open(log_name
).read())
614 results
.setdefault(result
, []).append((name
, log_name
))
616 if result
not in good_results
and parsed
.fail_fast
:
619 return results
, count
621 def print_results(results
):
623 for key
, value
in results
.iteritems():
624 print "%d tests returned %s" % (len(value
), key
)
625 if key
not in good_results
:
627 for name
, log_name
in value
:
628 print " %s > %s" % (name
, log_name
)
632 def add_test_run_options(parser
):
633 parser
.add_argument("--logs", default
="logs",
634 help="Store logs in the specified directory.")
635 parser
.add_argument("--fail-fast", "-f", action
="store_true",
636 help="Exit as soon as any test fails.")
637 parser
.add_argument("--print-failures", action
="store_true",
638 help="When a test fails, print the log file to stdout.")
639 parser
.add_argument("--print-log-names", "--pln", action
="store_true",
640 help="Print names of temporary log files as soon as they are "
642 parser
.add_argument("test", nargs
='*',
643 help="Run only tests that are named here.")
644 parser
.add_argument("--gdb",
645 help="The command to use to start gdb.")
646 parser
.add_argument("--misaval",
647 help="Don't run ExamineTarget, just assume the misa value which is "
650 def header(title
, dash
='-', length
=78):
652 dashes
= dash
* (length
- 4 - len(title
))
653 before
= dashes
[:len(dashes
)/2]
654 after
= dashes
[len(dashes
)/2:]
655 print "%s[ %s ]%s" % (before
, title
, after
)
661 for l
in open(path
, "r"):
665 class BaseTest(object):
668 def __init__(self
, target
, hart
=None):
673 self
.hart
= random
.choice(target
.harts
)
674 self
.hart
= target
.harts
[-1] #<<<
676 self
.target_process
= None
681 def early_applicable(self
):
682 """Return a false value if the test has determined it cannot run
683 without ever needing to talk to the target or server."""
684 # pylint: disable=no-self-use
691 compile_args
= getattr(self
, 'compile_args', None)
693 if compile_args
not in BaseTest
.compiled
:
694 BaseTest
.compiled
[compile_args
] = \
695 self
.target
.compile(self
.hart
, *compile_args
)
696 self
.binary
= BaseTest
.compiled
.get(compile_args
)
698 def classSetup(self
):
700 self
.target_process
= self
.target
.create()
701 if self
.target_process
:
702 self
.logs
.append(self
.target_process
.logname
)
704 self
.server
= self
.target
.server()
705 self
.logs
.append(self
.server
.logname
)
707 for log
in self
.logs
:
711 def classTeardown(self
):
713 del self
.target_process
715 def postMortem(self
):
720 If compile_args is set, compile a program and set self.binary.
724 Then call test() and return the result, displaying relevant information
725 if an exception is raised.
730 if not self
.early_applicable():
731 return "not_applicable"
733 self
.start
= time
.time()
738 result
= self
.test() # pylint: disable=no-member
739 except TestNotApplicable
:
740 result
= "not_applicable"
741 except Exception as e
: # pylint: disable=broad-except
742 if isinstance(e
, TestFailed
):
746 if isinstance(e
, TestFailed
):
750 traceback
.print_exc(file=sys
.stdout
)
753 except Exception as e
: # pylint: disable=broad-except
754 header("postMortem Exception")
756 traceback
.print_exc(file=sys
.stdout
)
760 for log
in self
.logs
:
762 header("End of logs")
770 class GdbTest(BaseTest
):
771 def __init__(self
, target
, hart
=None):
772 BaseTest
.__init
__(self
, target
, hart
=hart
)
775 def classSetup(self
):
776 BaseTest
.classSetup(self
)
779 self
.gdb
= Gdb(self
.server
.gdb_ports
, gdb_cmd
,
780 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
782 self
.gdb
= Gdb(self
.server
.gdb_ports
,
783 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
785 self
.logs
+= self
.gdb
.lognames()
788 self
.gdb
.global_command("set arch riscv:rv%d" % self
.hart
.xlen
)
789 self
.gdb
.global_command("set remotetimeout %d" %
790 self
.target
.timeout_sec
)
792 for cmd
in self
.target
.gdb_setup
:
793 self
.gdb
.command(cmd
)
795 self
.gdb
.select_hart(self
.hart
)
797 # FIXME: OpenOCD doesn't handle PRIV now
798 #self.gdb.p("$priv=3")
800 def postMortem(self
):
804 self
.gdb
.command("info registers all", timeout
=10)
806 def classTeardown(self
):
808 BaseTest
.classTeardown(self
)
810 class GdbSingleHartTest(GdbTest
):
811 def classSetup(self
):
812 GdbTest
.classSetup(self
)
814 for hart
in self
.target
.harts
:
815 # Park all harts that we're not using in a safe place.
816 if hart
!= self
.hart
:
817 self
.gdb
.select_hart(hart
)
818 self
.gdb
.p("$pc=loop_forever")
819 self
.gdb
.select_hart(self
.hart
)
821 class ExamineTarget(GdbTest
):
823 for hart
in self
.target
.harts
:
824 self
.gdb
.select_hart(hart
)
826 hart
.misa
= self
.gdb
.p("$misa")
830 if ((hart
.misa
& 0xFFFFFFFF) >> 30) == 1:
832 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFF) >> 62) == 2:
834 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) >> 126) == 3:
837 raise TestFailed("Couldn't determine XLEN from $misa (0x%x)" %
840 if misa_xlen
!= hart
.xlen
:
841 raise TestFailed("MISA reported XLEN of %d but we were "\
842 "expecting XLEN of %d\n" % (misa_xlen
, hart
.xlen
))
844 txt
+= ("%d" % misa_xlen
)
847 if hart
.misa
& (1<<i
):
848 txt
+= chr(i
+ ord('A'))
851 class TestFailed(Exception):
852 def __init__(self
, message
):
853 Exception.__init
__(self
)
854 self
.message
= message
856 class TestNotApplicable(Exception):
857 def __init__(self
, message
):
858 Exception.__init
__(self
)
859 self
.message
= message
861 def assertEqual(a
, b
):
863 raise TestFailed("%r != %r" % (a
, b
))
865 def assertNotEqual(a
, b
):
867 raise TestFailed("%r == %r" % (a
, b
))
871 raise TestFailed("%r not in %r" % (a
, b
))
873 def assertNotIn(a
, b
):
875 raise TestFailed("%r in %r" % (a
, b
))
877 def assertGreater(a
, b
):
879 raise TestFailed("%r not greater than %r" % (a
, b
))
881 def assertLess(a
, b
):
883 raise TestFailed("%r not less than %r" % (a
, b
))
887 raise TestFailed("%r is not True" % a
)
889 def assertRegexpMatches(text
, regexp
):
890 if not re
.search(regexp
, text
):
891 raise TestFailed("can't find %r in %r" % (regexp
, text
))