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
))
609 print "[%s] %s in %.2fs" % (name
, result
, time
.time() - start
)
610 if result
not in good_results
and parsed
.print_failures
:
611 sys
.stdout
.write(open(log_name
).read())
613 results
.setdefault(result
, []).append((name
, log_name
))
615 if result
not in good_results
and parsed
.fail_fast
:
618 return results
, count
620 def print_results(results
):
622 for key
, value
in results
.iteritems():
623 print "%d tests returned %s" % (len(value
), key
)
624 if key
not in good_results
:
626 for name
, log_name
in value
:
627 print " %s > %s" % (name
, log_name
)
631 def add_test_run_options(parser
):
632 parser
.add_argument("--logs", default
="logs",
633 help="Store logs in the specified directory.")
634 parser
.add_argument("--fail-fast", "-f", action
="store_true",
635 help="Exit as soon as any test fails.")
636 parser
.add_argument("--print-failures", action
="store_true",
637 help="When a test fails, print the log file to stdout.")
638 parser
.add_argument("--print-log-names", "--pln", action
="store_true",
639 help="Print names of temporary log files as soon as they are "
641 parser
.add_argument("test", nargs
='*',
642 help="Run only tests that are named here.")
643 parser
.add_argument("--gdb",
644 help="The command to use to start gdb.")
645 parser
.add_argument("--misaval",
646 help="Don't run ExamineTarget, just assume the misa value which is "
649 def header(title
, dash
='-', length
=78):
651 dashes
= dash
* (length
- 4 - len(title
))
652 before
= dashes
[:len(dashes
)/2]
653 after
= dashes
[len(dashes
)/2:]
654 print "%s[ %s ]%s" % (before
, title
, after
)
660 for l
in open(path
, "r"):
664 class BaseTest(object):
667 def __init__(self
, target
, hart
=None):
672 self
.hart
= random
.choice(target
.harts
)
673 self
.hart
= target
.harts
[-1] #<<<
675 self
.target_process
= None
680 def early_applicable(self
):
681 """Return a false value if the test has determined it cannot run
682 without ever needing to talk to the target or server."""
683 # pylint: disable=no-self-use
690 compile_args
= getattr(self
, 'compile_args', None)
692 if compile_args
not in BaseTest
.compiled
:
693 BaseTest
.compiled
[compile_args
] = \
694 self
.target
.compile(self
.hart
, *compile_args
)
695 self
.binary
= BaseTest
.compiled
.get(compile_args
)
697 def classSetup(self
):
699 self
.target_process
= self
.target
.create()
700 if self
.target_process
:
701 self
.logs
.append(self
.target_process
.logname
)
703 self
.server
= self
.target
.server()
704 self
.logs
.append(self
.server
.logname
)
706 for log
in self
.logs
:
710 def classTeardown(self
):
712 del self
.target_process
714 def postMortem(self
):
719 If compile_args is set, compile a program and set self.binary.
723 Then call test() and return the result, displaying relevant information
724 if an exception is raised.
729 if not self
.early_applicable():
730 return "not_applicable"
732 self
.start
= time
.time()
737 result
= self
.test() # pylint: disable=no-member
738 except TestNotApplicable
:
739 result
= "not_applicable"
740 except Exception as e
: # pylint: disable=broad-except
741 if isinstance(e
, TestFailed
):
745 if isinstance(e
, TestFailed
):
749 traceback
.print_exc(file=sys
.stdout
)
752 except Exception as e
: # pylint: disable=broad-except
753 header("postMortem Exception")
755 traceback
.print_exc(file=sys
.stdout
)
759 for log
in self
.logs
:
761 header("End of logs")
769 class GdbTest(BaseTest
):
770 def __init__(self
, target
, hart
=None):
771 BaseTest
.__init
__(self
, target
, hart
=hart
)
774 def classSetup(self
):
775 BaseTest
.classSetup(self
)
778 self
.gdb
= Gdb(self
.server
.gdb_ports
, gdb_cmd
,
779 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
781 self
.gdb
= Gdb(self
.server
.gdb_ports
,
782 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
784 self
.logs
+= self
.gdb
.lognames()
787 self
.gdb
.global_command("set arch riscv:rv%d" % self
.hart
.xlen
)
788 self
.gdb
.global_command("set remotetimeout %d" %
789 self
.target
.timeout_sec
)
791 for cmd
in self
.target
.gdb_setup
:
792 self
.gdb
.command(cmd
)
794 self
.gdb
.select_hart(self
.hart
)
796 # FIXME: OpenOCD doesn't handle PRIV now
797 #self.gdb.p("$priv=3")
799 def postMortem(self
):
803 self
.gdb
.command("info registers all", timeout
=10)
805 def classTeardown(self
):
807 BaseTest
.classTeardown(self
)
809 class GdbSingleHartTest(GdbTest
):
810 def classSetup(self
):
811 GdbTest
.classSetup(self
)
813 for hart
in self
.target
.harts
:
814 # Park all harts that we're not using in a safe place.
815 if hart
!= self
.hart
:
816 self
.gdb
.select_hart(hart
)
817 self
.gdb
.p("$pc=loop_forever")
818 self
.gdb
.select_hart(self
.hart
)
820 class ExamineTarget(GdbTest
):
822 for hart
in self
.target
.harts
:
823 self
.gdb
.select_hart(hart
)
825 hart
.misa
= self
.gdb
.p("$misa")
828 if (hart
.misa
>> 30) == 1:
830 elif (hart
.misa
>> 62) == 2:
832 elif (hart
.misa
>> 126) == 3:
835 raise TestFailed("Couldn't determine XLEN from $misa (0x%x)" %
839 if hart
.misa
& (1<<i
):
840 txt
+= chr(i
+ ord('A'))
843 class TestFailed(Exception):
844 def __init__(self
, message
):
845 Exception.__init
__(self
)
846 self
.message
= message
848 class TestNotApplicable(Exception):
849 def __init__(self
, message
):
850 Exception.__init
__(self
)
851 self
.message
= message
853 def assertEqual(a
, b
):
855 raise TestFailed("%r != %r" % (a
, b
))
857 def assertNotEqual(a
, b
):
859 raise TestFailed("%r == %r" % (a
, b
))
863 raise TestFailed("%r not in %r" % (a
, b
))
865 def assertNotIn(a
, b
):
867 raise TestFailed("%r in %r" % (a
, b
))
869 def assertGreater(a
, b
):
871 raise TestFailed("%r not greater than %r" % (a
, b
))
873 def assertLess(a
, b
):
875 raise TestFailed("%r not less than %r" % (a
, b
))
879 raise TestFailed("%r is not True" % a
)
881 def assertRegexpMatches(text
, regexp
):
882 if not re
.search(regexp
, text
):
883 raise TestFailed("can't find %r in %r" % (regexp
, text
))