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,
60 isa
=None, progbufsize
=None):
61 """Launch spike. Return tuple of its process and the port it's running
65 self
.progbufsize
= progbufsize
72 cmd
= self
.command(target
, harts
, halted
, timeout
, with_jtag_gdb
)
73 self
.infinite_loop
= target
.compile(harts
[0],
74 "programs/checksum.c", "programs/tiny-malloc.c",
75 "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
76 cmd
.append(self
.infinite_loop
)
77 self
.logfile
= tempfile
.NamedTemporaryFile(prefix
="spike-",
79 self
.logname
= self
.logfile
.name
81 real_stdout
.write("Temporary spike log: %s\n" % self
.logname
)
82 self
.logfile
.write("+ %s\n" % " ".join(cmd
))
84 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
85 stdout
=self
.logfile
, stderr
=self
.logfile
)
90 m
= re
.search(r
"Listening for remote bitbang connection on "
91 r
"port (\d+).", open(self
.logname
).read())
93 self
.port
= int(m
.group(1))
94 os
.environ
['REMOTE_BITBANG_PORT'] = m
.group(1)
98 print_log(self
.logname
)
99 raise Exception("Didn't get spike message about bitbang "
102 def command(self
, target
, harts
, halted
, timeout
, with_jtag_gdb
):
103 # pylint: disable=no-self-use
105 cmd
= shlex
.split(target
.sim_cmd
)
107 spike
= os
.path
.expandvars("$RISCV/bin/spike")
110 cmd
+= ["-p%d" % len(harts
)]
112 assert len(set(t
.xlen
for t
in harts
)) == 1, \
113 "All spike harts must have the same XLEN"
118 isa
= "RV%dG" % harts
[0].xlen
120 cmd
+= ["--isa", isa
]
122 if not self
.progbufsize
is None:
123 cmd
+= ["--progsize", str(self
.progbufsize
)]
124 cmd
+= ["--debug-sba", "32"]
126 assert len(set(t
.ram
for t
in harts
)) == 1, \
127 "All spike harts must have the same RAM layout"
128 assert len(set(t
.ram_size
for t
in harts
)) == 1, \
129 "All spike harts must have the same RAM layout"
130 cmd
+= ["-m0x%x:0x%x" % (harts
[0].ram
, harts
[0].ram_size
)]
133 cmd
= ["timeout", str(timeout
)] + cmd
138 cmd
+= ['--rbb-port', '0']
139 os
.environ
['REMOTE_BITBANG_HOST'] = 'localhost'
151 def wait(self
, *args
, **kwargs
):
152 return self
.process
.wait(*args
, **kwargs
)
154 class VcsSim(object):
155 logfile
= tempfile
.NamedTemporaryFile(prefix
='simv', suffix
='.log')
156 logname
= logfile
.name
158 def __init__(self
, sim_cmd
=None, debug
=False, timeout
=300):
160 cmd
= shlex
.split(sim_cmd
)
163 cmd
+= ["+jtag_vpi_enable"]
165 cmd
[0] = cmd
[0] + "-debug"
166 cmd
+= ["+vcdplusfile=output/gdbserver.vpd"]
168 logfile
= open(self
.logname
, "w")
170 real_stdout
.write("Temporary VCS log: %s\n" % self
.logname
)
171 logfile
.write("+ %s\n" % " ".join(cmd
))
174 listenfile
= open(self
.logname
, "r")
175 listenfile
.seek(0, 2)
176 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
177 stdout
=logfile
, stderr
=logfile
)
181 # Fail if VCS exits early
182 exit_code
= self
.process
.poll()
183 if exit_code
is not None:
184 raise RuntimeError('VCS simulator exited early with status %d'
187 line
= listenfile
.readline()
190 match
= re
.match(r
"^Listening on port (\d+)$", line
)
193 self
.port
= int(match
.group(1))
194 os
.environ
['JTAG_VPI_PORT'] = str(self
.port
)
196 if (time
.time() - start
) > timeout
:
197 raise Exception("Timed out waiting for VCS to listen for JTAG "
207 class Openocd(object):
208 logfile
= tempfile
.NamedTemporaryFile(prefix
='openocd', suffix
='.log')
209 logname
= logfile
.name
211 def __init__(self
, server_cmd
=None, config
=None, debug
=False, timeout
=60):
212 self
.timeout
= timeout
215 cmd
= shlex
.split(server_cmd
)
217 openocd
= os
.path
.expandvars("$RISCV/bin/openocd")
222 # This command needs to come before any config scripts on the command
223 # line, since they are executed in order.
225 # Tell OpenOCD to bind gdb to an unused, ephemeral port.
228 # Disable tcl and telnet servers, since they are unused and because
229 # the port numbers will conflict if multiple OpenOCD processes are
230 # running on the same server.
234 "telnet_port disabled",
238 f
= find_file(config
)
240 print "Unable to read file " + config
247 logfile
= open(Openocd
.logname
, "w")
249 real_stdout
.write("Temporary OpenOCD log: %s\n" % Openocd
.logname
)
250 env_entries
= ("REMOTE_BITBANG_HOST", "REMOTE_BITBANG_PORT")
251 env_entries
= [key
for key
in env_entries
if key
in os
.environ
]
252 logfile
.write("+ %s%s\n" % (
253 "".join("%s=%s " % (key
, os
.environ
[key
]) for key
in env_entries
),
254 " ".join(map(pipes
.quote
, cmd
))))
258 self
.process
= self
.start(cmd
, logfile
)
260 def start(self
, cmd
, logfile
):
261 process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
262 stdout
=logfile
, stderr
=logfile
)
265 # Wait for OpenOCD to have made it through riscv_examine(). When
266 # using OpenOCD to communicate with a simulator this may take a
267 # long time, and gdb will time out when trying to connect if we
271 fd
= open(Openocd
.logname
, "r")
275 if not process
.poll() is None:
276 raise Exception("OpenOCD exited early.")
280 m
= re
.search(r
"Listening on port (\d+) for gdb connections",
283 self
.gdb_ports
.append(int(m
.group(1)))
285 if "telnet server disabled" in line
:
288 if not messaged
and time
.time() - start
> 1:
290 print "Waiting for OpenOCD to start..."
291 if (time
.time() - start
) > self
.timeout
:
292 raise Exception("Timed out waiting for OpenOCD to "
296 print_log(Openocd
.logname
)
303 except (OSError, AttributeError):
306 class OpenocdCli(object):
307 def __init__(self
, port
=4444):
308 self
.child
= pexpect
.spawn(
309 "sh -c 'telnet localhost %d | tee openocd-cli.log'" % port
)
310 self
.child
.expect("> ")
312 def command(self
, cmd
):
313 self
.child
.sendline(cmd
)
314 self
.child
.expect(cmd
)
315 self
.child
.expect("\n")
316 self
.child
.expect("> ")
317 return self
.child
.before
.strip("\t\r\n \0")
319 def reg(self
, reg
=''):
320 output
= self
.command("reg %s" % reg
)
321 matches
= re
.findall(r
"(\w+) \(/\d+\): (0x[0-9A-F]+)", output
)
322 values
= {r
: int(v
, 0) for r
, v
in matches
}
327 def load_image(self
, image
):
328 output
= self
.command("load_image %s" % image
)
329 if 'invalid ELF file, only 32bits files are supported' in output
:
330 raise TestNotApplicable(output
)
332 class CannotAccess(Exception):
333 def __init__(self
, address
):
334 Exception.__init
__(self
)
335 self
.address
= address
337 class CouldNotFetch(Exception):
338 def __init__(self
, regname
, explanation
):
339 Exception.__init
__(self
)
340 self
.regname
= regname
341 self
.explanation
= explanation
343 Thread
= collections
.namedtuple('Thread', ('id', 'description', 'target_id',
347 """A single gdb class which can interact with one or more gdb instances."""
349 # pylint: disable=too-many-public-methods
350 # pylint: disable=too-many-instance-attributes
352 def __init__(self
, ports
,
353 cmd
=os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb"),
354 timeout
=60, binary
=None):
359 self
.timeout
= timeout
368 logfile
= tempfile
.NamedTemporaryFile(prefix
="gdb@%d-" % port
,
370 self
.logfiles
.append(logfile
)
372 real_stdout
.write("Temporary gdb log: %s\n" % logfile
.name
)
373 child
= pexpect
.spawn(cmd
)
374 child
.logfile
= logfile
375 child
.logfile
.write("+ %s\n" % cmd
)
376 self
.children
.append(child
)
377 self
.active_child
= self
.children
[0]
380 for port
, child
in zip(self
.ports
, self
.children
):
381 self
.select_child(child
)
383 self
.command("set confirm off")
384 self
.command("set width 0")
385 self
.command("set height 0")
387 self
.command("set print entry-values no")
388 self
.command("set remotetimeout %d" % self
.timeout
)
389 self
.command("target extended-remote localhost:%d" % port
)
391 self
.command("file %s" % self
.binary
)
392 threads
= self
.threads()
396 m
= re
.search(r
"Hart (\d+)", t
.name
)
398 hartid
= int(m
.group(1))
401 hartid
= max(self
.harts
) + 1
404 # solo: True iff this is the only thread on this child
405 self
.harts
[hartid
] = {'child': child
,
407 'solo': len(threads
) == 1}
410 for child
in self
.children
:
413 def one_hart_per_gdb(self
):
414 return all(h
['solo'] for h
in self
.harts
.itervalues())
417 return [logfile
.name
for logfile
in self
.logfiles
]
419 def select_child(self
, child
):
420 self
.active_child
= child
422 def select_hart(self
, hart
):
423 h
= self
.harts
[hart
.id]
424 self
.select_child(h
['child'])
426 output
= self
.command("thread %s" % h
['thread'].id, timeout
=10)
427 assert "Unknown" not in output
429 def push_state(self
):
431 'active_child': self
.active_child
435 state
= self
.stack
.pop()
436 self
.active_child
= state
['active_child']
439 """Wait for prompt."""
440 self
.active_child
.expect(r
"\(gdb\)")
442 def command(self
, command
, timeout
=6000):
443 """timeout is in seconds"""
444 self
.active_child
.sendline(command
)
445 self
.active_child
.expect("\n", timeout
=timeout
)
446 self
.active_child
.expect(r
"\(gdb\)", timeout
=timeout
)
447 return self
.active_child
.before
.strip()
449 def global_command(self
, command
):
450 """Execute this command on every gdb that we control."""
451 with
PrivateState(self
):
452 for child
in self
.children
:
453 self
.select_child(child
)
454 self
.command(command
)
456 def c(self
, wait
=True, timeout
=-1, async=False):
459 In RTOS mode, gdb will resume all harts.
460 In multi-gdb mode, this command will just go to the current gdb, so
461 will only resume one hart.
468 output
= self
.command("c%s" % async, timeout
=timeout
)
469 assert "Continuing" in output
472 self
.active_child
.sendline("c%s" % async)
473 self
.active_child
.expect("Continuing")
479 This function works fine when using multiple gdb sessions, but the
480 caller must be careful when using it nonetheless. gdb's behavior is to
481 not set breakpoints until just before the hart is resumed, and then
482 clears them as soon as the hart halts. That means that you can't set
483 one software breakpoint, and expect multiple harts to hit it. It's
484 possible that the first hart completes set/run/halt/clear before the
485 second hart even gets to resume, so it will never hit the breakpoint.
487 with
PrivateState(self
):
488 for child
in self
.children
:
490 child
.expect("Continuing")
492 # Now wait for them all to halt
493 for child
in self
.children
:
494 child
.expect(r
"\(gdb\)")
497 self
.active_child
.send("\003")
498 self
.active_child
.expect(r
"\(gdb\)", timeout
=6000)
499 return self
.active_child
.before
.strip()
501 def x(self
, address
, size
='w'):
502 output
= self
.command("x/%s %s" % (size
, address
))
503 value
= int(output
.split(':')[1].strip(), 0)
506 def p_raw(self
, obj
):
507 output
= self
.command("p %s" % obj
)
508 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
510 raise CannotAccess(int(m
.group(1), 0))
511 return output
.split('=')[-1].strip()
513 def parse_string(self
, text
):
515 if text
.startswith("{") and text
.endswith("}"):
517 return [self
.parse_string(t
) for t
in inner
.split(", ")]
518 elif text
.startswith('"') and text
.endswith('"'):
523 def p(self
, obj
, fmt
="/x"):
524 output
= self
.command("p%s %s" % (fmt
, obj
))
525 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
527 raise CannotAccess(int(m
.group(1), 0))
528 m
= re
.search(r
"Could not fetch register \"(\w
+)\"; (.*)$
", output)
530 raise CouldNotFetch(m.group(1), m.group(2))
531 rhs = output.split('=')[-1]
532 return self.parse_string(rhs)
534 def p_string(self, obj):
535 output = self.command("p
%s" % obj)
536 value = shlex.split(output.split('=')[-1].strip())[1]
540 output = self.command("stepi
", timeout=60)
544 output = self.command("load
", timeout=6000)
545 assert "failed
" not in output
546 assert "Transfer rate
" in output
548 def b(self, location):
549 output = self.command("b
%s" % location)
550 assert "not defined
" not in output
551 assert "Breakpoint
" in output
554 def hbreak(self, location):
555 output = self.command("hbreak
%s" % location)
556 assert "not defined
" not in output
557 assert "Hardware assisted breakpoint
" in output
561 output = self.command("info threads
")
563 for line in output.splitlines():
566 r"(Remote target|
Thread (\d
+)\s
*\
(Name
: ([^\
)]+))"
569 threads.append(Thread(*m.groups()))
572 #>>> threads.append(Thread('1', '1', 'Default', '???'))
575 def thread(self, thread):
576 return self.command("thread
%s" % thread.id)
579 return self.command("where
1")
581 class PrivateState(object):
582 def __init__(self, gdb):
586 self.gdb.push_state()
588 def __exit__(self, _type, _value, _traceback):
591 def run_all_tests(module, target, parsed):
592 if not os.path.exists(parsed.logs):
593 os.makedirs(parsed.logs)
595 overall_start = time.time()
597 global gdb_cmd # pylint: disable=global-statement
601 examine_added = False
602 for hart in target.harts:
604 hart.misa = int(parsed.misaval, 16)
605 print "Using $misa
from command line
: 0x
%x" % hart.misa
607 print "Using $misa
from hart definition
: 0x
%x" % hart.misa
608 elif not examine_added:
609 todo.append(("ExamineTarget
", ExamineTarget, None))
612 for name in dir(module):
613 definition = getattr(module, name)
614 if isinstance(definition, type) and hasattr(definition, 'test') and \
615 (not parsed.test or any(test in name for test in parsed.test)):
616 todo.append((name, definition, None))
618 results, count = run_tests(parsed, target, todo)
620 header("ran
%d tests
in %.0fs
" % (count, time.time() - overall_start),
623 return print_results(results)
625 good_results = set(('pass', 'not_applicable'))
626 def run_tests(parsed, target, todo):
630 for name, definition, hart in todo:
631 log_name = os.path.join(parsed.logs, "%s-%s-%s.log
" %
632 (time.strftime("%Y
%m
%d-%H
%M
%S
"), type(target).__name__, name))
633 log_fd = open(log_name, 'w')
634 print "[%s] Starting
> %s" % (name, log_name)
635 instance = definition(target, hart)
637 log_fd.write("Test
: %s\n" % name)
638 log_fd.write("Target
: %s\n" % type(target).__name__)
640 global real_stdout # pylint: disable=global-statement
641 real_stdout = sys.stdout
644 result = instance.run()
645 log_fd.write("Result
: %s\n" % result)
646 log_fd.write("Logfile
: %s\n" % log_name)
648 sys.stdout = real_stdout
649 log_fd.write("Time elapsed
: %.2fs
\n" % (time.time() - start))
651 print "[%s] %s in %.2fs
" % (name, result, time.time() - start)
652 if result not in good_results and parsed.print_failures:
653 sys.stdout.write(open(log_name).read())
655 results.setdefault(result, []).append((name, log_name))
657 if result not in good_results and parsed.fail_fast:
660 return results, count
662 def print_results(results):
664 for key, value in results.iteritems():
665 print "%d tests returned
%s" % (len(value), key)
666 if key not in good_results:
668 for name, log_name in value:
669 print " %s > %s" % (name, log_name)
673 def add_test_run_options(parser):
674 parser.add_argument("--logs
", default="logs
",
675 help="Store logs
in the specified directory
.")
676 parser.add_argument("--fail
-fast
", "-f
", action="store_true
",
677 help="Exit
as soon
as any test fails
.")
678 parser.add_argument("--print-failures
", action="store_true
",
679 help="When a test fails
, print the log
file to stdout
.")
680 parser.add_argument("--print-log
-names
", "--pln
", action="store_true
",
681 help="Print names of temporary log files
as soon
as they are
"
683 parser.add_argument("test
", nargs='*',
684 help="Run only tests that are named here
.")
685 parser.add_argument("--gdb
",
686 help="The command to use to start gdb
.")
687 parser.add_argument("--misaval
",
688 help="Don
't run ExamineTarget, just assume the misa value which is "
691 def header(title, dash='-', length=78):
693 dashes = dash * (length - 4 - len(title))
694 before = dashes[:len(dashes)/2]
695 after = dashes[len(dashes)/2:]
696 print "%s[ %s ]%s" % (before, title, after)
702 for l in open(path, "r"):
706 class BaseTest(object):
709 def __init__(self, target, hart=None):
714 self.hart = random.choice(target.harts)
715 self.hart = target.harts[-1] #<<<
717 self.target_process = None
722 def early_applicable(self):
723 """Return a false value if the test has determined it cannot run
724 without ever needing to talk to the target or server."""
725 # pylint: disable=no-self-use
732 compile_args = getattr(self, 'compile_args
', None)
734 if compile_args not in BaseTest.compiled:
735 BaseTest.compiled[compile_args] = \
736 self.target.compile(self.hart, *compile_args)
737 self.binary = BaseTest.compiled.get(compile_args)
739 def classSetup(self):
741 self.target_process = self.target.create()
742 if self.target_process:
743 self.logs.append(self.target_process.logname)
745 self.server = self.target.server()
746 self.logs.append(self.server.logname)
748 for log in self.logs:
752 def classTeardown(self):
754 del self.target_process
756 def postMortem(self):
761 If compile_args is set, compile a program and set self.binary.
765 Then call test() and return the result, displaying relevant information
766 if an exception is raised.
771 if not self.early_applicable():
772 return "not_applicable"
774 self.start = time.time()
779 result = self.test() # pylint: disable=no-member
780 except TestNotApplicable:
781 result = "not_applicable"
782 except Exception as e: # pylint: disable=broad-except
783 if isinstance(e, TestFailed):
787 if isinstance(e, TestFailed):
791 traceback.print_exc(file=sys.stdout)
794 except Exception as e: # pylint: disable=broad-except
795 header("postMortem Exception")
797 traceback.print_exc(file=sys.stdout)
801 for log in self.logs:
803 header("End of logs")
811 class GdbTest(BaseTest):
812 def __init__(self, target, hart=None):
813 BaseTest.__init__(self, target, hart=hart)
816 def classSetup(self):
817 BaseTest.classSetup(self)
820 self.gdb = Gdb(self.server.gdb_ports, gdb_cmd,
821 timeout=self.target.timeout_sec, binary=self.binary)
823 self.gdb = Gdb(self.server.gdb_ports,
824 timeout=self.target.timeout_sec, binary=self.binary)
826 self.logs += self.gdb.lognames()
829 self.gdb.global_command("set remotetimeout %d" %
830 self.target.timeout_sec)
832 for cmd in self.target.gdb_setup:
833 self.gdb.command(cmd)
835 self.gdb.select_hart(self.hart)
837 # FIXME: OpenOCD doesn't handle PRIV now
838 #self.gdb.p("$priv=3")
840 def postMortem(self
):
844 self
.gdb
.command("disassemble")
845 self
.gdb
.command("info registers all", timeout
=10)
847 def classTeardown(self
):
849 BaseTest
.classTeardown(self
)
851 class GdbSingleHartTest(GdbTest
):
852 def classSetup(self
):
853 GdbTest
.classSetup(self
)
855 for hart
in self
.target
.harts
:
856 # Park all harts that we're not using in a safe place.
857 if hart
!= self
.hart
:
858 self
.gdb
.select_hart(hart
)
859 self
.gdb
.p("$pc=loop_forever")
860 self
.gdb
.select_hart(self
.hart
)
862 class ExamineTarget(GdbTest
):
864 for hart
in self
.target
.harts
:
865 self
.gdb
.select_hart(hart
)
867 hart
.misa
= self
.gdb
.p("$misa")
871 if ((hart
.misa
& 0xFFFFFFFF) >> 30) == 1:
873 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFF) >> 62) == 2:
875 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) >> 126) == 3:
878 raise TestFailed("Couldn't determine XLEN from $misa (0x%x)" %
881 if misa_xlen
!= hart
.xlen
:
882 raise TestFailed("MISA reported XLEN of %d but we were "\
883 "expecting XLEN of %d\n" % (misa_xlen
, hart
.xlen
))
885 txt
+= ("%d" % misa_xlen
)
888 if hart
.misa
& (1<<i
):
889 txt
+= chr(i
+ ord('A'))
892 class TestFailed(Exception):
893 def __init__(self
, message
):
894 Exception.__init
__(self
)
895 self
.message
= message
897 class TestNotApplicable(Exception):
898 def __init__(self
, message
):
899 Exception.__init
__(self
)
900 self
.message
= message
902 def assertEqual(a
, b
):
904 raise TestFailed("%r != %r" % (a
, b
))
906 def assertNotEqual(a
, b
):
908 raise TestFailed("%r == %r" % (a
, b
))
912 raise TestFailed("%r not in %r" % (a
, b
))
914 def assertNotIn(a
, b
):
916 raise TestFailed("%r in %r" % (a
, b
))
918 def assertGreater(a
, b
):
920 raise TestFailed("%r not greater than %r" % (a
, b
))
922 def assertLess(a
, b
):
924 raise TestFailed("%r not less than %r" % (a
, b
))
928 raise TestFailed("%r is not True" % a
)
930 def assertRegexpMatches(text
, regexp
):
931 if not re
.search(regexp
, text
):
932 raise TestFailed("can't find %r in %r" % (regexp
, text
))