39a6fc4b9e1c936acb7893b186d163bfc3e360b1
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 """Launch spike. Return tuple of its process and the port it's running
69 cmd
= self
.command(target
, harts
, halted
, timeout
, with_jtag_gdb
)
70 self
.infinite_loop
= target
.compile(harts
[0],
71 "programs/checksum.c", "programs/tiny-malloc.c",
72 "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
73 cmd
.append(self
.infinite_loop
)
74 self
.logfile
= tempfile
.NamedTemporaryFile(prefix
="spike-",
76 self
.logname
= self
.logfile
.name
78 real_stdout
.write("Temporary spike log: %s\n" % self
.logname
)
79 self
.logfile
.write("+ %s\n" % " ".join(cmd
))
81 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
82 stdout
=self
.logfile
, stderr
=self
.logfile
)
87 m
= re
.search(r
"Listening for remote bitbang connection on "
88 r
"port (\d+).", open(self
.logname
).read())
90 self
.port
= int(m
.group(1))
91 os
.environ
['REMOTE_BITBANG_PORT'] = m
.group(1)
95 print_log(self
.logname
)
96 raise Exception("Didn't get spike message about bitbang "
99 def command(self
, target
, harts
, halted
, timeout
, with_jtag_gdb
):
100 # pylint: disable=no-self-use
102 cmd
= shlex
.split(target
.sim_cmd
)
104 spike
= os
.path
.expandvars("$RISCV/bin/spike")
107 cmd
+= ["-p%d" % len(harts
)]
109 assert len(set(t
.xlen
for t
in harts
)) == 1, \
110 "All spike harts must have the same XLEN"
112 if harts
[0].xlen
== 32:
113 cmd
+= ["--isa", "RV32G"]
115 cmd
+= ["--isa", "RV64G"]
117 assert len(set(t
.ram
for t
in harts
)) == 1, \
118 "All spike harts must have the same RAM layout"
119 assert len(set(t
.ram_size
for t
in harts
)) == 1, \
120 "All spike harts must have the same RAM layout"
121 cmd
+= ["-m0x%x:0x%x" % (harts
[0].ram
, harts
[0].ram_size
)]
124 cmd
= ["timeout", str(timeout
)] + cmd
129 cmd
+= ['--rbb-port', '0']
130 os
.environ
['REMOTE_BITBANG_HOST'] = 'localhost'
142 def wait(self
, *args
, **kwargs
):
143 return self
.process
.wait(*args
, **kwargs
)
145 class VcsSim(object):
146 logfile
= tempfile
.NamedTemporaryFile(prefix
='simv', suffix
='.log')
147 logname
= logfile
.name
149 def __init__(self
, sim_cmd
=None, debug
=False, timeout
=300):
151 cmd
= shlex
.split(sim_cmd
)
154 cmd
+= ["+jtag_vpi_enable"]
156 cmd
[0] = cmd
[0] + "-debug"
157 cmd
+= ["+vcdplusfile=output/gdbserver.vpd"]
159 logfile
= open(self
.logname
, "w")
161 real_stdout
.write("Temporary VCS log: %s\n" % self
.logname
)
162 logfile
.write("+ %s\n" % " ".join(cmd
))
165 listenfile
= open(self
.logname
, "r")
166 listenfile
.seek(0, 2)
167 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
168 stdout
=logfile
, stderr
=logfile
)
172 # Fail if VCS exits early
173 exit_code
= self
.process
.poll()
174 if exit_code
is not None:
175 raise RuntimeError('VCS simulator exited early with status %d'
178 line
= listenfile
.readline()
181 match
= re
.match(r
"^Listening on port (\d+)$", line
)
184 self
.port
= int(match
.group(1))
185 os
.environ
['JTAG_VPI_PORT'] = str(self
.port
)
187 if (time
.time() - start
) > timeout
:
188 raise Exception("Timed out waiting for VCS to listen for JTAG "
198 class Openocd(object):
199 logfile
= tempfile
.NamedTemporaryFile(prefix
='openocd', suffix
='.log')
200 logname
= logfile
.name
202 def __init__(self
, server_cmd
=None, config
=None, debug
=False, timeout
=60):
203 self
.timeout
= timeout
206 cmd
= shlex
.split(server_cmd
)
208 openocd
= os
.path
.expandvars("$RISCV/bin/openocd")
213 # This command needs to come before any config scripts on the command
214 # line, since they are executed in order.
216 # Tell OpenOCD to bind gdb to an unused, ephemeral port.
219 # Disable tcl and telnet servers, since they are unused and because
220 # the port numbers will conflict if multiple OpenOCD processes are
221 # running on the same server.
225 "telnet_port disabled",
229 f
= find_file(config
)
231 print "Unable to read file " + config
238 logfile
= open(Openocd
.logname
, "w")
240 real_stdout
.write("Temporary OpenOCD log: %s\n" % Openocd
.logname
)
241 env_entries
= ("REMOTE_BITBANG_HOST", "REMOTE_BITBANG_PORT")
242 env_entries
= [key
for key
in env_entries
if key
in os
.environ
]
243 logfile
.write("+ %s%s\n" % (
244 "".join("%s=%s " % (key
, os
.environ
[key
]) for key
in env_entries
),
245 " ".join(map(pipes
.quote
, cmd
))))
249 self
.process
= self
.start(cmd
, logfile
)
251 def start(self
, cmd
, logfile
):
252 process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
253 stdout
=logfile
, stderr
=logfile
)
256 # Wait for OpenOCD to have made it through riscv_examine(). When
257 # using OpenOCD to communicate with a simulator this may take a
258 # long time, and gdb will time out when trying to connect if we
262 fd
= open(Openocd
.logname
, "r")
266 if not process
.poll() is None:
267 raise Exception("OpenOCD exited early.")
271 m
= re
.search(r
"Listening on port (\d+) for gdb connections",
274 self
.gdb_ports
.append(int(m
.group(1)))
276 if "telnet server disabled" in line
:
279 if not messaged
and time
.time() - start
> 1:
281 print "Waiting for OpenOCD to start..."
282 if (time
.time() - start
) > self
.timeout
:
283 raise Exception("Timed out waiting for OpenOCD to "
287 print_log(Openocd
.logname
)
294 except (OSError, AttributeError):
297 class OpenocdCli(object):
298 def __init__(self
, port
=4444):
299 self
.child
= pexpect
.spawn(
300 "sh -c 'telnet localhost %d | tee openocd-cli.log'" % port
)
301 self
.child
.expect("> ")
303 def command(self
, cmd
):
304 self
.child
.sendline(cmd
)
305 self
.child
.expect(cmd
)
306 self
.child
.expect("\n")
307 self
.child
.expect("> ")
308 return self
.child
.before
.strip("\t\r\n \0")
310 def reg(self
, reg
=''):
311 output
= self
.command("reg %s" % reg
)
312 matches
= re
.findall(r
"(\w+) \(/\d+\): (0x[0-9A-F]+)", output
)
313 values
= {r
: int(v
, 0) for r
, v
in matches
}
318 def load_image(self
, image
):
319 output
= self
.command("load_image %s" % image
)
320 if 'invalid ELF file, only 32bits files are supported' in output
:
321 raise TestNotApplicable(output
)
323 class CannotAccess(Exception):
324 def __init__(self
, address
):
325 Exception.__init
__(self
)
326 self
.address
= address
328 Thread
= collections
.namedtuple('Thread', ('id', 'description', 'target_id',
332 """A single gdb class which can interact with one or more gdb instances."""
334 # pylint: disable=too-many-public-methods
335 # pylint: disable=too-many-instance-attributes
337 def __init__(self
, ports
,
338 cmd
=os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb"),
339 timeout
=60, binary
=None):
344 self
.timeout
= timeout
353 logfile
= tempfile
.NamedTemporaryFile(prefix
="gdb@%d-" % port
,
355 self
.logfiles
.append(logfile
)
357 real_stdout
.write("Temporary gdb log: %s\n" % logfile
.name
)
358 child
= pexpect
.spawn(cmd
)
359 child
.logfile
= logfile
360 child
.logfile
.write("+ %s\n" % cmd
)
361 self
.children
.append(child
)
362 self
.active_child
= self
.children
[0]
365 for port
, child
in zip(self
.ports
, self
.children
):
366 self
.select_child(child
)
368 self
.command("set confirm off")
369 self
.command("set width 0")
370 self
.command("set height 0")
372 self
.command("set print entry-values no")
373 self
.command("set remotetimeout %d" % self
.timeout
)
374 self
.command("target extended-remote localhost:%d" % port
)
376 self
.command("file %s" % self
.binary
)
377 threads
= self
.threads()
381 m
= re
.search(r
"Hart (\d+)", t
.name
)
383 hartid
= int(m
.group(1))
386 hartid
= max(self
.harts
) + 1
389 self
.harts
[hartid
] = (child
, t
)
392 for child
in self
.children
:
396 return [logfile
.name
for logfile
in self
.logfiles
]
398 def select_child(self
, child
):
399 self
.active_child
= child
401 def select_hart(self
, hart
):
402 child
, thread
= self
.harts
[hart
.id]
403 self
.select_child(child
)
404 output
= self
.command("thread %s" % thread
.id)
405 assert "Unknown" not in output
407 def push_state(self
):
409 'active_child': self
.active_child
413 state
= self
.stack
.pop()
414 self
.active_child
= state
['active_child']
417 """Wait for prompt."""
418 self
.active_child
.expect(r
"\(gdb\)")
420 def command(self
, command
, timeout
=6000):
421 """timeout is in seconds"""
422 self
.active_child
.sendline(command
)
423 self
.active_child
.expect("\n", timeout
=timeout
)
424 self
.active_child
.expect(r
"\(gdb\)", timeout
=timeout
)
425 return self
.active_child
.before
.strip()
427 def global_command(self
, command
):
428 """Execute this command on every gdb that we control."""
429 with
PrivateState(self
):
430 for child
in self
.children
:
431 self
.select_child(child
)
432 self
.command(command
)
434 def c(self
, wait
=True, timeout
=-1, async=False):
437 In RTOS mode, gdb will resume all harts.
438 In multi-gdb mode, this command will just go to the current gdb, so
439 will only resume one hart.
446 output
= self
.command("c%s" % async, timeout
=timeout
)
447 assert "Continuing" in output
450 self
.active_child
.sendline("c%s" % async)
451 self
.active_child
.expect("Continuing")
457 This function works fine when using multiple gdb sessions, but the
458 caller must be careful when using it nonetheless. gdb's behavior is to
459 not set breakpoints until just before the hart is resumed, and then
460 clears them as soon as the hart halts. That means that you can't set
461 one software breakpoint, and expect multiple harts to hit it. It's
462 possible that the first hart completes set/run/halt/clear before the
463 second hart even gets to resume, so it will never hit the breakpoint.
465 with
PrivateState(self
):
466 for child
in self
.children
:
468 child
.expect("Continuing")
470 # Now wait for them all to halt
471 for child
in self
.children
:
472 child
.expect(r
"\(gdb\)")
475 self
.active_child
.send("\003")
476 self
.active_child
.expect(r
"\(gdb\)", timeout
=6000)
477 return self
.active_child
.before
.strip()
479 def x(self
, address
, size
='w'):
480 output
= self
.command("x/%s %s" % (size
, address
))
481 value
= int(output
.split(':')[1].strip(), 0)
484 def p_raw(self
, obj
):
485 output
= self
.command("p %s" % obj
)
486 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
488 raise CannotAccess(int(m
.group(1), 0))
489 return output
.split('=')[-1].strip()
491 def parse_string(self
, text
):
493 if text
.startswith("{") and text
.endswith("}"):
495 return [self
.parse_string(t
) for t
in inner
.split(", ")]
496 elif text
.startswith('"') and text
.endswith('"'):
501 def p(self
, obj
, fmt
="/x"):
502 output
= self
.command("p%s %s" % (fmt
, obj
))
503 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
505 raise CannotAccess(int(m
.group(1), 0))
506 rhs
= output
.split('=')[-1]
507 return self
.parse_string(rhs
)
509 def p_string(self
, obj
):
510 output
= self
.command("p %s" % obj
)
511 value
= shlex
.split(output
.split('=')[-1].strip())[1]
515 output
= self
.command("stepi", timeout
=60)
519 output
= self
.command("load", timeout
=6000)
520 assert "failed" not in output
521 assert "Transfer rate" in output
523 def b(self
, location
):
524 output
= self
.command("b %s" % location
)
525 assert "not defined" not in output
526 assert "Breakpoint" in output
529 def hbreak(self
, location
):
530 output
= self
.command("hbreak %s" % location
)
531 assert "not defined" not in output
532 assert "Hardware assisted breakpoint" in output
536 output
= self
.command("info threads")
538 for line
in output
.splitlines():
541 r
"(Remote target|Thread (\d+)\s*\(Name: ([^\)]+))"
544 threads
.append(Thread(*m
.groups()))
547 #>>> threads.append(Thread('1', '1', 'Default', '???'))
550 def thread(self
, thread
):
551 return self
.command("thread %s" % thread
.id)
554 return self
.command("where 1")
556 class PrivateState(object):
557 def __init__(self
, gdb
):
561 self
.gdb
.push_state()
563 def __exit__(self
, _type
, _value
, _traceback
):
566 def run_all_tests(module
, target
, parsed
):
567 if not os
.path
.exists(parsed
.logs
):
568 os
.makedirs(parsed
.logs
)
570 overall_start
= time
.time()
572 global gdb_cmd
# pylint: disable=global-statement
576 examine_added
= False
577 for hart
in target
.harts
:
579 hart
.misa
= int(parsed
.misaval
, 16)
580 print "Using $misa from command line: 0x%x" % hart
.misa
582 print "Using $misa from hart definition: 0x%x" % hart
.misa
583 elif not examine_added
:
584 todo
.append(("ExamineTarget", ExamineTarget
, None))
587 for name
in dir(module
):
588 definition
= getattr(module
, name
)
589 if isinstance(definition
, type) and hasattr(definition
, 'test') and \
590 (not parsed
.test
or any(test
in name
for test
in parsed
.test
)):
591 todo
.append((name
, definition
, None))
593 results
, count
= run_tests(parsed
, target
, todo
)
595 header("ran %d tests in %.0fs" % (count
, time
.time() - overall_start
),
598 return print_results(results
)
600 good_results
= set(('pass', 'not_applicable'))
601 def run_tests(parsed
, target
, todo
):
605 for name
, definition
, hart
in todo
:
606 log_name
= os
.path
.join(parsed
.logs
, "%s-%s-%s.log" %
607 (time
.strftime("%Y%m%d-%H%M%S"), type(target
).__name
__, name
))
608 log_fd
= open(log_name
, 'w')
609 print "[%s] Starting > %s" % (name
, log_name
)
610 instance
= definition(target
, hart
)
612 log_fd
.write("Test: %s\n" % name
)
613 log_fd
.write("Target: %s\n" % type(target
).__name
__)
615 global real_stdout
# pylint: disable=global-statement
616 real_stdout
= sys
.stdout
619 result
= instance
.run()
620 log_fd
.write("Result: %s\n" % result
)
622 sys
.stdout
= real_stdout
623 log_fd
.write("Time elapsed: %.2fs\n" % (time
.time() - start
))
625 print "[%s] %s in %.2fs" % (name
, result
, time
.time() - start
)
626 if result
not in good_results
and parsed
.print_failures
:
627 sys
.stdout
.write(open(log_name
).read())
629 results
.setdefault(result
, []).append((name
, log_name
))
631 if result
not in good_results
and parsed
.fail_fast
:
634 return results
, count
636 def print_results(results
):
638 for key
, value
in results
.iteritems():
639 print "%d tests returned %s" % (len(value
), key
)
640 if key
not in good_results
:
642 for name
, log_name
in value
:
643 print " %s > %s" % (name
, log_name
)
647 def add_test_run_options(parser
):
648 parser
.add_argument("--logs", default
="logs",
649 help="Store logs in the specified directory.")
650 parser
.add_argument("--fail-fast", "-f", action
="store_true",
651 help="Exit as soon as any test fails.")
652 parser
.add_argument("--print-failures", action
="store_true",
653 help="When a test fails, print the log file to stdout.")
654 parser
.add_argument("--print-log-names", "--pln", action
="store_true",
655 help="Print names of temporary log files as soon as they are "
657 parser
.add_argument("test", nargs
='*',
658 help="Run only tests that are named here.")
659 parser
.add_argument("--gdb",
660 help="The command to use to start gdb.")
661 parser
.add_argument("--misaval",
662 help="Don't run ExamineTarget, just assume the misa value which is "
665 def header(title
, dash
='-', length
=78):
667 dashes
= dash
* (length
- 4 - len(title
))
668 before
= dashes
[:len(dashes
)/2]
669 after
= dashes
[len(dashes
)/2:]
670 print "%s[ %s ]%s" % (before
, title
, after
)
676 for l
in open(path
, "r"):
680 class BaseTest(object):
683 def __init__(self
, target
, hart
=None):
688 self
.hart
= random
.choice(target
.harts
)
689 self
.hart
= target
.harts
[-1] #<<<
691 self
.target_process
= None
696 def early_applicable(self
):
697 """Return a false value if the test has determined it cannot run
698 without ever needing to talk to the target or server."""
699 # pylint: disable=no-self-use
706 compile_args
= getattr(self
, 'compile_args', None)
708 if compile_args
not in BaseTest
.compiled
:
709 BaseTest
.compiled
[compile_args
] = \
710 self
.target
.compile(self
.hart
, *compile_args
)
711 self
.binary
= BaseTest
.compiled
.get(compile_args
)
713 def classSetup(self
):
715 self
.target_process
= self
.target
.create()
716 if self
.target_process
:
717 self
.logs
.append(self
.target_process
.logname
)
719 self
.server
= self
.target
.server()
720 self
.logs
.append(self
.server
.logname
)
722 for log
in self
.logs
:
726 def classTeardown(self
):
728 del self
.target_process
730 def postMortem(self
):
735 If compile_args is set, compile a program and set self.binary.
739 Then call test() and return the result, displaying relevant information
740 if an exception is raised.
745 if not self
.early_applicable():
746 return "not_applicable"
748 self
.start
= time
.time()
753 result
= self
.test() # pylint: disable=no-member
754 except TestNotApplicable
:
755 result
= "not_applicable"
756 except Exception as e
: # pylint: disable=broad-except
757 if isinstance(e
, TestFailed
):
761 if isinstance(e
, TestFailed
):
765 traceback
.print_exc(file=sys
.stdout
)
768 except Exception as e
: # pylint: disable=broad-except
769 header("postMortem Exception")
771 traceback
.print_exc(file=sys
.stdout
)
775 for log
in self
.logs
:
777 header("End of logs")
785 class GdbTest(BaseTest
):
786 def __init__(self
, target
, hart
=None):
787 BaseTest
.__init
__(self
, target
, hart
=hart
)
790 def classSetup(self
):
791 BaseTest
.classSetup(self
)
794 self
.gdb
= Gdb(self
.server
.gdb_ports
, gdb_cmd
,
795 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
797 self
.gdb
= Gdb(self
.server
.gdb_ports
,
798 timeout
=self
.target
.timeout_sec
, binary
=self
.binary
)
800 self
.logs
+= self
.gdb
.lognames()
803 self
.gdb
.global_command("set arch riscv:rv%d" % self
.hart
.xlen
)
804 self
.gdb
.global_command("set remotetimeout %d" %
805 self
.target
.timeout_sec
)
807 for cmd
in self
.target
.gdb_setup
:
808 self
.gdb
.command(cmd
)
810 self
.gdb
.select_hart(self
.hart
)
812 # FIXME: OpenOCD doesn't handle PRIV now
813 #self.gdb.p("$priv=3")
815 def postMortem(self
):
819 self
.gdb
.command("info registers all", timeout
=10)
821 def classTeardown(self
):
823 BaseTest
.classTeardown(self
)
825 class GdbSingleHartTest(GdbTest
):
826 def classSetup(self
):
827 GdbTest
.classSetup(self
)
829 for hart
in self
.target
.harts
:
830 # Park all harts that we're not using in a safe place.
831 if hart
!= self
.hart
:
832 self
.gdb
.select_hart(hart
)
833 self
.gdb
.p("$pc=loop_forever")
834 self
.gdb
.select_hart(self
.hart
)
836 class ExamineTarget(GdbTest
):
838 for hart
in self
.target
.harts
:
839 self
.gdb
.select_hart(hart
)
841 hart
.misa
= self
.gdb
.p("$misa")
845 if ((hart
.misa
& 0xFFFFFFFF) >> 30) == 1:
847 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFF) >> 62) == 2:
849 elif ((hart
.misa
& 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) >> 126) == 3:
852 raise TestFailed("Couldn't determine XLEN from $misa (0x%x)" %
855 if misa_xlen
!= hart
.xlen
:
856 raise TestFailed("MISA reported XLEN of %d but we were "\
857 "expecting XLEN of %d\n" % (misa_xlen
, hart
.xlen
))
859 txt
+= ("%d" % misa_xlen
)
862 if hart
.misa
& (1<<i
):
863 txt
+= chr(i
+ ord('A'))
866 class TestFailed(Exception):
867 def __init__(self
, message
):
868 Exception.__init
__(self
)
869 self
.message
= message
871 class TestNotApplicable(Exception):
872 def __init__(self
, message
):
873 Exception.__init
__(self
)
874 self
.message
= message
876 def assertEqual(a
, b
):
878 raise TestFailed("%r != %r" % (a
, b
))
880 def assertNotEqual(a
, b
):
882 raise TestFailed("%r == %r" % (a
, b
))
886 raise TestFailed("%r not in %r" % (a
, b
))
888 def assertNotIn(a
, b
):
890 raise TestFailed("%r in %r" % (a
, b
))
892 def assertGreater(a
, b
):
894 raise TestFailed("%r not greater than %r" % (a
, b
))
896 def assertLess(a
, b
):
898 raise TestFailed("%r not less than %r" % (a
, b
))
902 raise TestFailed("%r is not True" % a
)
904 def assertRegexpMatches(text
, regexp
):
905 if not re
.search(regexp
, text
):
906 raise TestFailed("can't find %r in %r" % (regexp
, text
))