16 print_log_names
= False
17 real_stdout
= sys
.stdout
19 # Note that gdb comes with its own testsuite. I was unable to figure out how to
20 # run that testsuite against the spike simulator.
23 for directory
in (os
.getcwd(), os
.path
.dirname(__file__
)):
24 fullpath
= os
.path
.join(directory
, path
)
25 relpath
= os
.path
.relpath(fullpath
)
26 if len(relpath
) >= len(fullpath
):
28 if os
.path
.exists(relpath
):
32 def compile(args
, xlen
=32): # pylint: disable=redefined-builtin
33 cc
= os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gcc")
36 cmd
.append("-march=rv32imac")
37 cmd
.append("-mabi=ilp32")
39 cmd
.append("-march=rv64imac")
40 cmd
.append("-mabi=lp64")
42 found
= find_file(arg
)
48 print "+", " ".join(cmd
)
49 process
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
,
50 stderr
=subprocess
.PIPE
)
51 stdout
, stderr
= process
.communicate()
52 if process
.returncode
:
56 raise Exception("Compile failed!")
59 def __init__(self
, target
, halted
=False, timeout
=None, with_jtag_gdb
=True,
61 """Launch spike. Return tuple of its process and the port it's running
71 cmd
= self
.command(target
, harts
, halted
, timeout
, with_jtag_gdb
)
72 self
.infinite_loop
= target
.compile(harts
[0],
73 "programs/checksum.c", "programs/tiny-malloc.c",
74 "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
75 cmd
.append(self
.infinite_loop
)
76 self
.logfile
= tempfile
.NamedTemporaryFile(prefix
="spike-",
78 self
.logname
= self
.logfile
.name
80 real_stdout
.write("Temporary spike log: %s\n" % self
.logname
)
81 self
.logfile
.write("+ %s\n" % " ".join(cmd
))
83 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
84 stdout
=self
.logfile
, stderr
=self
.logfile
)
89 m
= re
.search(r
"Listening for remote bitbang connection on "
90 r
"port (\d+).", open(self
.logname
).read())
92 self
.port
= int(m
.group(1))
93 os
.environ
['REMOTE_BITBANG_PORT'] = m
.group(1)
97 print_log(self
.logname
)
98 raise Exception("Didn't get spike message about bitbang "
101 def command(self
, target
, harts
, halted
, timeout
, with_jtag_gdb
):
102 # pylint: disable=no-self-use
104 cmd
= shlex
.split(target
.sim_cmd
)
106 spike
= os
.path
.expandvars("$RISCV/bin/spike")
109 cmd
+= ["-p%d" % len(harts
)]
111 assert len(set(t
.xlen
for t
in harts
)) == 1, \
112 "All spike harts must have the same XLEN"
117 isa
= "RV%dG" % harts
[0].xlen
119 cmd
+= ["--isa", isa
]
121 assert len(set(t
.ram
for t
in harts
)) == 1, \
122 "All spike harts must have the same RAM layout"
123 assert len(set(t
.ram_size
for t
in harts
)) == 1, \
124 "All spike harts must have the same RAM layout"
125 cmd
+= ["-m0x%x:0x%x" % (harts
[0].ram
, harts
[0].ram_size
)]
128 cmd
= ["timeout", str(timeout
)] + cmd
133 cmd
+= ['--rbb-port', '0']
134 os
.environ
['REMOTE_BITBANG_HOST'] = 'localhost'
146 def wait(self
, *args
, **kwargs
):
147 return self
.process
.wait(*args
, **kwargs
)
149 class VcsSim(object):
150 logfile
= tempfile
.NamedTemporaryFile(prefix
='simv', suffix
='.log')
151 logname
= logfile
.name
153 def __init__(self
, sim_cmd
=None, debug
=False, timeout
=300):
155 cmd
= shlex
.split(sim_cmd
)
158 cmd
+= ["+jtag_vpi_enable"]
160 cmd
[0] = cmd
[0] + "-debug"
161 cmd
+= ["+vcdplusfile=output/gdbserver.vpd"]
163 logfile
= open(self
.logname
, "w")
165 real_stdout
.write("Temporary VCS log: %s\n" % self
.logname
)
166 logfile
.write("+ %s\n" % " ".join(cmd
))
169 listenfile
= open(self
.logname
, "r")
170 listenfile
.seek(0, 2)
171 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
172 stdout
=logfile
, stderr
=logfile
)
176 # Fail if VCS exits early
177 exit_code
= self
.process
.poll()
178 if exit_code
is not None:
179 raise RuntimeError('VCS simulator exited early with status %d'
182 line
= listenfile
.readline()
185 match
= re
.match(r
"^Listening on port (\d+)$", line
)
188 self
.port
= int(match
.group(1))
189 os
.environ
['JTAG_VPI_PORT'] = str(self
.port
)
191 if (time
.time() - start
) > timeout
:
192 raise Exception("Timed out waiting for VCS to listen for JTAG "
202 class Openocd(object):
203 logfile
= tempfile
.NamedTemporaryFile(prefix
='openocd', suffix
='.log')
204 logname
= logfile
.name
206 def __init__(self
, server_cmd
=None, config
=None, debug
=False, timeout
=60):
207 self
.timeout
= timeout
210 cmd
= shlex
.split(server_cmd
)
212 openocd
= os
.path
.expandvars("$RISCV/bin/openocd")
217 # This command needs to come before any config scripts on the command
218 # line, since they are executed in order.
220 # Tell OpenOCD to bind gdb to an unused, ephemeral port.
223 # Disable tcl and telnet servers, since they are unused and because
224 # the port numbers will conflict if multiple OpenOCD processes are
225 # running on the same server.
229 "telnet_port disabled",
233 f
= find_file(config
)
235 print "Unable to read file " + config
242 logfile
= open(Openocd
.logname
, "w")
244 real_stdout
.write("Temporary OpenOCD log: %s\n" % Openocd
.logname
)
245 env_entries
= ("REMOTE_BITBANG_HOST", "REMOTE_BITBANG_PORT")
246 env_entries
= [key
for key
in env_entries
if key
in os
.environ
]
247 logfile
.write("+ %s%s\n" % (
248 "".join("%s=%s " % (key
, os
.environ
[key
]) for key
in env_entries
),
249 " ".join(map(pipes
.quote
, cmd
))))
253 self
.process
= self
.start(cmd
, logfile
)
255 def start(self
, cmd
, logfile
):
256 process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
257 stdout
=logfile
, stderr
=logfile
)
260 # Wait for OpenOCD to have made it through riscv_examine(). When
261 # using OpenOCD to communicate with a simulator this may take a
262 # long time, and gdb will time out when trying to connect if we
266 fd
= open(Openocd
.logname
, "r")
270 if not process
.poll() is None:
271 raise Exception("OpenOCD exited early.")
275 m
= re
.search(r
"Listening on port (\d+) for gdb connections",
278 self
.gdb_ports
.append(int(m
.group(1)))
280 if "telnet server disabled" in line
:
283 if not messaged
and time
.time() - start
> 1:
285 print "Waiting for OpenOCD to start..."
286 if (time
.time() - start
) > self
.timeout
:
287 raise Exception("Timed out waiting for OpenOCD to "
291 print_log(Openocd
.logname
)
298 except (OSError, AttributeError):
301 class OpenocdCli(object):
302 def __init__(self
, port
=4444):
303 self
.child
= pexpect
.spawn(
304 "sh -c 'telnet localhost %d | tee openocd-cli.log'" % port
)
305 self
.child
.expect("> ")
307 def command(self
, cmd
):
308 self
.child
.sendline(cmd
)
309 self
.child
.expect(cmd
)
310 self
.child
.expect("\n")
311 self
.child
.expect("> ")
312 return self
.child
.before
.strip("\t\r\n \0")
314 def reg(self
, reg
=''):
315 output
= self
.command("reg %s" % reg
)
316 matches
= re
.findall(r
"(\w+) \(/\d+\): (0x[0-9A-F]+)", output
)
317 values
= {r
: int(v
, 0) for r
, v
in matches
}
322 def load_image(self
, image
):
323 output
= self
.command("load_image %s" % image
)
324 if 'invalid ELF file, only 32bits files are supported' in output
:
325 raise TestNotApplicable(output
)
327 class CannotAccess(Exception):
328 def __init__(self
, address
):
329 Exception.__init
__(self
)
330 self
.address
= address
332 Thread
= collections
.namedtuple('Thread', ('id', 'description', 'target_id',
336 """A single gdb class which can interact with one or more gdb instances."""
338 # pylint: disable=too-many-public-methods
339 # pylint: disable=too-many-instance-attributes
341 def __init__(self
, ports
,
342 cmd
=os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb"),
343 timeout
=60, binary
=None):
348 self
.timeout
= timeout
357 logfile
= tempfile
.NamedTemporaryFile(prefix
="gdb@%d-" % port
,
359 self
.logfiles
.append(logfile
)
361 real_stdout
.write("Temporary gdb log: %s\n" % logfile
.name
)
362 child
= pexpect
.spawn(cmd
)
363 child
.logfile
= logfile
364 child
.logfile
.write("+ %s\n" % cmd
)
365 self
.children
.append(child
)
366 self
.active_child
= self
.children
[0]
369 for port
, child
in zip(self
.ports
, self
.children
):
370 self
.select_child(child
)
372 self
.command("set confirm off")
373 self
.command("set width 0")
374 self
.command("set height 0")
376 self
.command("set print entry-values no")
377 self
.command("set remotetimeout %d" % self
.timeout
)
378 self
.command("target extended-remote localhost:%d" % port
)
380 self
.command("file %s" % self
.binary
)
381 threads
= self
.threads()
385 m
= re
.search(r
"Hart (\d+)", t
.name
)
387 hartid
= int(m
.group(1))
390 hartid
= max(self
.harts
) + 1
393 # solo: True iff this is the only thread on this child
394 self
.harts
[hartid
] = {'child': child
,
396 'solo': len(threads
) == 1}
399 for child
in self
.children
:
402 def one_hart_per_gdb(self
):
403 return all(h
['solo'] for h
in self
.harts
.itervalues())
406 return [logfile
.name
for logfile
in self
.logfiles
]
408 def select_child(self
, child
):
409 self
.active_child
= child
411 def select_hart(self
, hart
):
412 h
= self
.harts
[hart
.id]
413 self
.select_child(h
['child'])
415 output
= self
.command("thread %s" % h
['thread'].id, timeout
=10)
416 assert "Unknown" not in output
418 def push_state(self
):
420 'active_child': self
.active_child
424 state
= self
.stack
.pop()
425 self
.active_child
= state
['active_child']
428 """Wait for prompt."""
429 self
.active_child
.expect(r
"\(gdb\)")
431 def command(self
, command
, timeout
=6000):
432 """timeout is in seconds"""
433 self
.active_child
.sendline(command
)
434 self
.active_child
.expect("\n", timeout
=timeout
)
435 self
.active_child
.expect(r
"\(gdb\)", timeout
=timeout
)
436 return self
.active_child
.before
.strip()
438 def global_command(self
, command
):
439 """Execute this command on every gdb that we control."""
440 with
PrivateState(self
):
441 for child
in self
.children
:
442 self
.select_child(child
)
443 self
.command(command
)
445 def c(self
, wait
=True, timeout
=-1, async=False):
448 In RTOS mode, gdb will resume all harts.
449 In multi-gdb mode, this command will just go to the current gdb, so
450 will only resume one hart.
457 output
= self
.command("c%s" % async, timeout
=timeout
)
458 assert "Continuing" in output
461 self
.active_child
.sendline("c%s" % async)
462 self
.active_child
.expect("Continuing")
468 This function works fine when using multiple gdb sessions, but the
469 caller must be careful when using it nonetheless. gdb's behavior is to
470 not set breakpoints until just before the hart is resumed, and then
471 clears them as soon as the hart halts. That means that you can't set
472 one software breakpoint, and expect multiple harts to hit it. It's
473 possible that the first hart completes set/run/halt/clear before the
474 second hart even gets to resume, so it will never hit the breakpoint.
476 with
PrivateState(self
):
477 for child
in self
.children
:
479 child
.expect("Continuing")
481 # Now wait for them all to halt
482 for child
in self
.children
:
483 child
.expect(r
"\(gdb\)")
486 self
.active_child
.send("\003")
487 self
.active_child
.expect(r
"\(gdb\)", timeout
=6000)
488 return self
.active_child
.before
.strip()
490 def x(self
, address
, size
='w'):
491 output
= self
.command("x/%s %s" % (size
, address
))
492 value
= int(output
.split(':')[1].strip(), 0)
495 def p_raw(self
, obj
):
496 output
= self
.command("p %s" % obj
)
497 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
499 raise CannotAccess(int(m
.group(1), 0))
500 return output
.split('=')[-1].strip()
502 def parse_string(self
, text
):
504 if text
.startswith("{") and text
.endswith("}"):
506 return [self
.parse_string(t
) for t
in inner
.split(", ")]
507 elif text
.startswith('"') and text
.endswith('"'):
512 def p(self
, obj
, fmt
="/x"):
513 output
= self
.command("p%s %s" % (fmt
, obj
))
514 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
516 raise CannotAccess(int(m
.group(1), 0))
517 rhs
= output
.split('=')[-1]
518 return self
.parse_string(rhs
)
520 def p_string(self
, obj
):
521 output
= self
.command("p %s" % obj
)
522 value
= shlex
.split(output
.split('=')[-1].strip())[1]
526 output
= self
.command("stepi", timeout
=60)
530 output
= self
.command("load", timeout
=6000)
531 assert "failed" not in output
532 assert "Transfer rate" in output
534 def b(self
, location
):
535 output
= self
.command("b %s" % location
)
536 assert "not defined" not in output
537 assert "Breakpoint" in output
540 def hbreak(self
, location
):
541 output
= self
.command("hbreak %s" % location
)
542 assert "not defined" not in output
543 assert "Hardware assisted breakpoint" in output
547 output
= self
.command("info threads")
549 for line
in output
.splitlines():
552 r
"(Remote target|Thread (\d+)\s*\(Name: ([^\)]+))"
555 threads
.append(Thread(*m
.groups()))
558 #>>> threads.append(Thread('1', '1', 'Default', '???'))
561 def thread(self
, thread
):
562 return self
.command("thread %s" % thread
.id)
565 return self
.command("where 1")
567 class PrivateState(object):
568 def __init__(self
, gdb
):
572 self
.gdb
.push_state()
574 def __exit__(self
, _type
, _value
, _traceback
):
577 def run_all_tests(module
, target
, parsed
):
578 if not os
.path
.exists(parsed
.logs
):
579 os
.makedirs(parsed
.logs
)
581 overall_start
= time
.time()
583 global gdb_cmd
# pylint: disable=global-statement
587 examine_added
= False
588 for hart
in target
.harts
:
590 hart
.misa
= int(parsed
.misaval
, 16)
591 print "Using $misa from command line: 0x%x" % hart
.misa
593 print "Using $misa from hart definition: 0x%x" % hart
.misa
594 elif not examine_added
:
595 todo
.append(("ExamineTarget", ExamineTarget
, None))
598 for name
in dir(module
):
599 definition
= getattr(module
, name
)
600 if isinstance(definition
, type) and hasattr(definition
, 'test') and \
601 (not parsed
.test
or any(test
in name
for test
in parsed
.test
)):
602 todo
.append((name
, definition
, None))
604 results
, count
= run_tests(parsed
, target
, todo
)
606 header("ran %d tests in %.0fs" % (count
, time
.time() - overall_start
),
609 return print_results(results
)
611 good_results
= set(('pass', 'not_applicable'))
612 def run_tests(parsed
, target
, todo
):
616 for name
, definition
, hart
in todo
:
617 log_name
= os
.path
.join(parsed
.logs
, "%s-%s-%s.log" %
618 (time
.strftime("%Y%m%d-%H%M%S"), type(target
).__name
__, name
))
619 log_fd
= open(log_name
, 'w')
620 print "[%s] Starting > %s" % (name
, log_name
)
621 instance
= definition(target
, hart
)
623 log_fd
.write("Test: %s\n" % name
)
624 log_fd
.write("Target: %s\n" % type(target
).__name
__)
626 global real_stdout
# pylint: disable=global-statement
627 real_stdout
= sys
.stdout
630 result
= instance
.run()
631 log_fd
.write("Result: %s\n" % result
)
633 sys
.stdout
= real_stdout
634 log_fd
.write("Time elapsed: %.2fs\n" % (time
.time() - start
))
636 print "[%s] %s in %.2fs" % (name
, result
, time
.time() - start
)
637 if result
not in good_results
and parsed
.print_failures
:
638 sys
.stdout
.write(open(log_name
).read())
640 results
.setdefault(result
, []).append((name
, log_name
))
642 if result
not in good_results
and parsed
.fail_fast
:
645 return results
, count
647 def print_results(results
):
649 for key
, value
in results
.iteritems():
650 print "%d tests returned %s" % (len(value
), key
)
651 if key
not in good_results
:
653 for name
, log_name
in value
:
654 print " %s > %s" % (name
, log_name
)
658 def add_test_run_options(parser
):
659 parser
.add_argument("--logs", default
="logs",
660 help="Store logs in the specified directory.")
661 parser
.add_argument("--fail-fast", "-f", action
="store_true",
662 help="Exit as soon as any test fails.")
663 parser
.add_argument("--print-failures", action
="store_true",
664 help="When a test fails, print the log file to stdout.")
665 parser
.add_argument("--print-log-names", "--pln", action
="store_true",
666 help="Print names of temporary log files as soon as they are "
668 parser
.add_argument("test", nargs
='*',
669 help="Run only tests that are named here.")
670 parser
.add_argument("--gdb",
671 help="The command to use to start gdb.")
672 parser
.add_argument("--misaval",
673 help="Don't run ExamineTarget, just assume the misa value which is "
676 def header(title
, dash
='-', length
=78):
678 dashes
= dash
* (length
- 4 - len(title
))
679 before
= dashes
[:len(dashes
)/2]
680 after
= dashes
[len(dashes
)/2:]
681 print "%s[ %s ]%s" % (before
, title
, after
)
687 for l
in open(path
, "r"):
691 class BaseTest(object):
694 def __init__(self
, target
, hart
=None):
699 self
.hart
= random
.choice(target
.harts
)
700 self
.hart
= target
.harts
[-1] #<<<
702 self
.target_process
= None
707 def early_applicable(self
):
708 """Return a false value if the test has determined it cannot run
709 without ever needing to talk to the target or server."""
710 # pylint: disable=no-self-use
717 compile_args
= getattr(self
, 'compile_args', None)
719 if compile_args
not in BaseTest
.compiled
:
720 BaseTest
.compiled
[compile_args
] = \
721 self
.target
.compile(self
.hart
, *compile_args
)
722 self
.binary
= BaseTest
.compiled
.get(compile_args
)
724 def classSetup(self
):
726 self
.target_process
= self
.target
.create()
727 if self
.target_process
:
728 self
.logs
.append(self
.target_process
.logname
)
730 self
.server
= self
.target
.server()
731 self
.logs
.append(self
.server
.logname
)
733 for log
in self
.logs
:
737 def classTeardown(self
):
739 del self
.target_process
741 def postMortem(self
):
746 If compile_args is set, compile a program and set self.binary.
750 Then call test() and return the result, displaying relevant information
751 if an exception is raised.
756 if not self
.early_applicable():
757 return "not_applicable"
759 self
.start
= time
.time()
764 result
= self
.test() # pylint: disable=no-member
765 except TestNotApplicable
:
766 result
= "not_applicable"
767 except Exception as e
: # pylint: disable=broad-except
768 if isinstance(e
, TestFailed
):
772 if isinstance(e
, TestFailed
):
776 traceback
.print_exc(file=sys
.stdout
)
779 except Exception as e
: # pylint: disable=broad-except
780 header("postMortem Exception")
782 traceback
.print_exc(file=sys
.stdout
)
786 for log
in self
.logs
:
788 header("End of logs")
796 class GdbTest(BaseTest
):
797 def __init__(self
, target
, hart
=None):
798 BaseTest
.__init
__(self
, target
, hart
=hart
)
801 def classSetup(self
):
802 BaseTest
.classSetup(self
)
805 self
.gdb
= Gdb(self
.server
.gdb_ports
, gdb_cmd
,
806 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
808 self
.gdb
= Gdb(self
.server
.gdb_ports
,
809 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
811 self
.logs
+= self
.gdb
.lognames()
814 self
.gdb
.global_command("set remotetimeout %d" %
815 self
.target
.timeout_sec
)
817 for cmd
in self
.target
.gdb_setup
:
818 self
.gdb
.command(cmd
)
820 self
.gdb
.select_hart(self
.hart
)
822 # FIXME: OpenOCD doesn't handle PRIV now
823 #self.gdb.p("$priv=3")
825 def postMortem(self
):
829 self
.gdb
.command("info registers all", timeout
=10)
831 def classTeardown(self
):
833 BaseTest
.classTeardown(self
)
835 class GdbSingleHartTest(GdbTest
):
836 def classSetup(self
):
837 GdbTest
.classSetup(self
)
839 for hart
in self
.target
.harts
:
840 # Park all harts that we're not using in a safe place.
841 if hart
!= self
.hart
:
842 self
.gdb
.select_hart(hart
)
843 self
.gdb
.p("$pc=loop_forever")
844 self
.gdb
.select_hart(self
.hart
)
846 class ExamineTarget(GdbTest
):
848 for hart
in self
.target
.harts
:
849 self
.gdb
.select_hart(hart
)
851 hart
.misa
= self
.gdb
.p("$misa")
855 if ((hart
.misa
& 0xFFFFFFFF) >> 30) == 1:
857 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFF) >> 62) == 2:
859 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) >> 126) == 3:
862 raise TestFailed("Couldn't determine XLEN from $misa (0x%x)" %
865 if misa_xlen
!= hart
.xlen
:
866 raise TestFailed("MISA reported XLEN of %d but we were "\
867 "expecting XLEN of %d\n" % (misa_xlen
, hart
.xlen
))
869 txt
+= ("%d" % misa_xlen
)
872 if hart
.misa
& (1<<i
):
873 txt
+= chr(i
+ ord('A'))
876 class TestFailed(Exception):
877 def __init__(self
, message
):
878 Exception.__init
__(self
)
879 self
.message
= message
881 class TestNotApplicable(Exception):
882 def __init__(self
, message
):
883 Exception.__init
__(self
)
884 self
.message
= message
886 def assertEqual(a
, b
):
888 raise TestFailed("%r != %r" % (a
, b
))
890 def assertNotEqual(a
, b
):
892 raise TestFailed("%r == %r" % (a
, b
))
896 raise TestFailed("%r not in %r" % (a
, b
))
898 def assertNotIn(a
, b
):
900 raise TestFailed("%r in %r" % (a
, b
))
902 def assertGreater(a
, b
):
904 raise TestFailed("%r not greater than %r" % (a
, b
))
906 def assertLess(a
, b
):
908 raise TestFailed("%r not less than %r" % (a
, b
))
912 raise TestFailed("%r is not True" % a
)
914 def assertRegexpMatches(text
, regexp
):
915 if not re
.search(regexp
, text
):
916 raise TestFailed("can't find %r in %r" % (regexp
, text
))