3eeace91ff326600f51f2a77d59327cd95b39b01
14 # Note that gdb comes with its own testsuite. I was unable to figure out how to
15 # run that testsuite against the spike simulator.
18 for directory
in (os
.getcwd(), os
.path
.dirname(__file__
)):
19 fullpath
= os
.path
.join(directory
, path
)
20 relpath
= os
.path
.relpath(fullpath
)
21 if len(relpath
) >= len(fullpath
):
23 if os
.path
.exists(relpath
):
27 def compile(args
, xlen
=32): # pylint: disable=redefined-builtin
28 cc
= os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gcc")
31 cmd
.append("-march=rv32imac")
32 cmd
.append("-mabi=ilp32")
34 cmd
.append("-march=rv64imac")
35 cmd
.append("-mabi=lp64")
37 found
= find_file(arg
)
43 print "+", " ".join(cmd
)
44 process
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
,
45 stderr
=subprocess
.PIPE
)
46 stdout
, stderr
= process
.communicate()
47 if process
.returncode
:
51 raise Exception("Compile failed!")
54 # http://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python/2838309#2838309
56 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
58 port
= s
.getsockname()[1]
63 logname
= "spike-%d.log" % os
.getpid()
65 def __init__(self
, target
, halted
=False, timeout
=None, with_jtag_gdb
=True):
66 """Launch spike. Return tuple of its process and the port it's running
75 cmd
= self
.command(target
, harts
, halted
, timeout
, with_jtag_gdb
)
76 self
.infinite_loop
= target
.compile(harts
[0],
77 "programs/checksum.c", "programs/tiny-malloc.c",
78 "programs/infinite_loop.S", "-DDEFINE_MALLOC", "-DDEFINE_FREE")
79 cmd
.append(self
.infinite_loop
)
80 logfile
= open(self
.logname
, "w")
81 logfile
.write("+ %s\n" % " ".join(cmd
))
83 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
84 stdout
=logfile
, stderr
=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"
114 if harts
[0].xlen
== 32:
115 cmd
+= ["--isa", "RV32G"]
117 cmd
+= ["--isa", "RV64G"]
119 assert len(set(t
.ram
for t
in harts
)) == 1, \
120 "All spike harts must have the same RAM layout"
121 assert len(set(t
.ram_size
for t
in harts
)) == 1, \
122 "All spike harts must have the same RAM layout"
123 cmd
+= ["-m0x%x:0x%x" % (harts
[0].ram
, harts
[0].ram_size
)]
126 cmd
= ["timeout", str(timeout
)] + cmd
131 cmd
+= ['--rbb-port', '0']
132 os
.environ
['REMOTE_BITBANG_HOST'] = 'localhost'
144 def wait(self
, *args
, **kwargs
):
145 return self
.process
.wait(*args
, **kwargs
)
147 class VcsSim(object):
150 def __init__(self
, sim_cmd
=None, debug
=False):
152 cmd
= shlex
.split(sim_cmd
)
155 cmd
+= ["+jtag_vpi_enable"]
157 cmd
[0] = cmd
[0] + "-debug"
158 cmd
+= ["+vcdplusfile=output/gdbserver.vpd"]
159 logfile
= open(self
.logname
, "w")
160 logfile
.write("+ %s\n" % " ".join(cmd
))
162 listenfile
= open(self
.logname
, "r")
163 listenfile
.seek(0, 2)
164 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
165 stdout
=logfile
, stderr
=logfile
)
168 # Fail if VCS exits early
169 exit_code
= self
.process
.poll()
170 if exit_code
is not None:
171 raise RuntimeError('VCS simulator exited early with status %d'
174 line
= listenfile
.readline()
177 match
= re
.match(r
"^Listening on port (\d+)$", line
)
180 self
.port
= int(match
.group(1))
181 os
.environ
['JTAG_VPI_PORT'] = str(self
.port
)
190 class Openocd(object):
191 logfile
= tempfile
.NamedTemporaryFile(prefix
='openocd', suffix
='.log')
192 logname
= logfile
.name
193 print "OpenOCD Temporary Log File: %s" % logname
195 def __init__(self
, server_cmd
=None, config
=None, debug
=False, timeout
=60):
196 self
.timeout
= timeout
199 cmd
= shlex
.split(server_cmd
)
201 openocd
= os
.path
.expandvars("$RISCV/bin/openocd")
206 # This command needs to come before any config scripts on the command
207 # line, since they are executed in order.
209 # Tell OpenOCD to bind gdb to an unused, ephemeral port.
212 # Disable tcl and telnet servers, since they are unused and because
213 # the port numbers will conflict if multiple OpenOCD processes are
214 # running on the same server.
218 "telnet_port disabled",
222 f
= find_file(config
)
224 print "Unable to read file " + config
231 logfile
= open(Openocd
.logname
, "w")
232 logfile
.write("+ %s\n" % " ".join(cmd
))
237 self
.process
= self
.start(cmd
, logfile
)
239 def start(self
, cmd
, logfile
):
240 process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
241 stdout
=logfile
, stderr
=logfile
)
244 # Wait for OpenOCD to have made it through riscv_examine(). When
245 # using OpenOCD to communicate with a simulator this may take a
246 # long time, and gdb will time out when trying to connect if we
251 log
= open(Openocd
.logname
).read()
252 m
= re
.search(r
"Listening on port (\d+) for gdb connections",
256 self
.port
= int(m
.group(1))
257 self
.ports
.append(int(m
.group(1)))
259 if "telnet server disabled" in log
:
262 if not process
.poll() is None:
264 "OpenOCD exited before completing riscv_examine()")
265 if not messaged
and time
.time() - start
> 1:
267 print "Waiting for OpenOCD to start..."
268 if (time
.time() - start
) > self
.timeout
:
269 raise Exception("ERROR: Timed out waiting for OpenOCD to "
273 header("OpenOCD log")
274 sys
.stdout
.write(log
)
281 except (OSError, AttributeError):
284 class OpenocdCli(object):
285 def __init__(self
, port
=4444):
286 self
.child
= pexpect
.spawn(
287 "sh -c 'telnet localhost %d | tee openocd-cli.log'" % port
)
288 self
.child
.expect("> ")
290 def command(self
, cmd
):
291 self
.child
.sendline(cmd
)
292 self
.child
.expect(cmd
)
293 self
.child
.expect("\n")
294 self
.child
.expect("> ")
295 return self
.child
.before
.strip("\t\r\n \0")
297 def reg(self
, reg
=''):
298 output
= self
.command("reg %s" % reg
)
299 matches
= re
.findall(r
"(\w+) \(/\d+\): (0x[0-9A-F]+)", output
)
300 values
= {r
: int(v
, 0) for r
, v
in matches
}
305 def load_image(self
, image
):
306 output
= self
.command("load_image %s" % image
)
307 if 'invalid ELF file, only 32bits files are supported' in output
:
308 raise TestNotApplicable(output
)
310 class CannotAccess(Exception):
311 def __init__(self
, address
):
312 Exception.__init
__(self
)
313 self
.address
= address
315 Thread
= collections
.namedtuple('Thread', ('id', 'target_id', 'name',
319 logfile
= tempfile
.NamedTemporaryFile(prefix
="gdb", suffix
=".log")
320 logname
= logfile
.name
321 print "GDB Temporary Log File: %s" % logname
324 cmd
=os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb")):
325 self
.child
= pexpect
.spawn(cmd
)
326 self
.child
.logfile
= open(self
.logname
, "w")
327 self
.child
.logfile
.write("+ %s\n" % cmd
)
329 self
.command("set confirm off")
330 self
.command("set width 0")
331 self
.command("set height 0")
333 self
.command("set print entry-values no")
335 def select_hart(self
, hart
):
336 output
= self
.command("thread %d" % (hart
.index
+ 1))
337 assert "Unknown" not in output
340 """Wait for prompt."""
341 self
.child
.expect(r
"\(gdb\)")
343 def command(self
, command
, timeout
=6000):
344 """timeout is in seconds"""
345 self
.child
.sendline(command
)
346 self
.child
.expect("\n", timeout
=timeout
)
347 self
.child
.expect(r
"\(gdb\)", timeout
=timeout
)
348 return self
.child
.before
.strip()
350 def c(self
, wait
=True, timeout
=-1, async=False):
356 output
= self
.command("c%s" % async, timeout
=timeout
)
357 assert "Continuing" in output
360 self
.child
.sendline("c%s" % async)
361 self
.child
.expect("Continuing")
364 self
.child
.send("\003")
365 self
.child
.expect(r
"\(gdb\)", timeout
=6000)
366 return self
.child
.before
.strip()
368 def x(self
, address
, size
='w'):
369 output
= self
.command("x/%s %s" % (size
, address
))
370 value
= int(output
.split(':')[1].strip(), 0)
373 def p_raw(self
, obj
):
374 output
= self
.command("p %s" % obj
)
375 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
377 raise CannotAccess(int(m
.group(1), 0))
378 return output
.split('=')[-1].strip()
380 def parse_string(self
, text
):
382 if text
.startswith("{") and text
.endswith("}"):
384 return [self
.parse_string(t
) for t
in inner
.split(", ")]
385 elif text
.startswith('"') and text
.endswith('"'):
390 def p(self
, obj
, fmt
="/x"):
391 output
= self
.command("p%s %s" % (fmt
, obj
))
392 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
394 raise CannotAccess(int(m
.group(1), 0))
395 rhs
= output
.split('=')[-1]
396 return self
.parse_string(rhs
)
398 def p_string(self
, obj
):
399 output
= self
.command("p %s" % obj
)
400 value
= shlex
.split(output
.split('=')[-1].strip())[1]
404 output
= self
.command("stepi", timeout
=60)
408 output
= self
.command("load", timeout
=6000)
409 assert "failed" not in output
410 assert "Transfer rate" in output
412 def b(self
, location
):
413 output
= self
.command("b %s" % location
)
414 assert "not defined" not in output
415 assert "Breakpoint" in output
418 def hbreak(self
, location
):
419 output
= self
.command("hbreak %s" % location
)
420 assert "not defined" not in output
421 assert "Hardware assisted breakpoint" in output
425 output
= self
.command("info threads")
427 for line
in output
.splitlines():
429 r
"[\s\*]*(\d+)\s*Thread (\d+)\s*\(Name: ([^\)]+)\s*(.*)",
432 threads
.append(Thread(*m
.groups()))
434 threads
.append(Thread('1', '1', 'Default', '???'))
437 def thread(self
, thread
):
438 return self
.command("thread %s" % thread
.id)
440 def run_all_tests(module
, target
, parsed
):
441 if not os
.path
.exists(parsed
.logs
):
442 os
.makedirs(parsed
.logs
)
444 overall_start
= time
.time()
446 global gdb_cmd
# pylint: disable=global-statement
450 for hart
in target
.harts
:
452 hart
.misa
= int(parsed
.misaval
, 16)
453 print "Using $misa from command line: 0x%x" % hart
.misa
455 print "Using $misa from hart definition: 0x%x" % hart
.misa
457 todo
.append(("ExamineTarget", ExamineTarget
, hart
))
459 for name
in dir(module
):
460 definition
= getattr(module
, name
)
461 if type(definition
) == type and hasattr(definition
, 'test') and \
462 (not parsed
.test
or any(test
in name
for test
in parsed
.test
)):
463 todo
.append((name
, definition
, None))
465 results
, count
= run_tests(parsed
, target
, todo
)
467 header("ran %d tests in %.0fs" % (count
, time
.time() - overall_start
),
470 return print_results(results
)
472 good_results
= set(('pass', 'not_applicable'))
473 def run_tests(parsed
, target
, todo
):
477 for name
, definition
, hart
in todo
:
478 log_name
= os
.path
.join(parsed
.logs
, "%s-%s-%s.log" %
479 (time
.strftime("%Y%m%d-%H%M%S"), type(target
).__name
__, name
))
480 log_fd
= open(log_name
, 'w')
481 print "Running %s > %s ..." % (name
, log_name
),
482 instance
= definition(target
, hart
)
484 log_fd
.write("Test: %s\n" % name
)
485 log_fd
.write("Target: %s\n" % type(target
).__name
__)
487 real_stdout
= sys
.stdout
490 result
= instance
.run()
491 log_fd
.write("Result: %s\n" % result
)
493 sys
.stdout
= real_stdout
494 log_fd
.write("Time elapsed: %.2fs\n" % (time
.time() - start
))
495 print "%s in %.2fs" % (result
, time
.time() - start
)
496 if result
not in good_results
and parsed
.print_failures
:
497 sys
.stdout
.write(open(log_name
).read())
499 results
.setdefault(result
, []).append((name
, log_name
))
501 if result
not in good_results
and parsed
.fail_fast
:
504 return results
, count
506 def print_results(results
):
508 for key
, value
in results
.iteritems():
509 print "%d tests returned %s" % (len(value
), key
)
510 if key
not in good_results
:
512 for name
, log_name
in value
:
513 print " %s > %s" % (name
, log_name
)
517 def add_test_run_options(parser
):
518 parser
.add_argument("--logs", default
="logs",
519 help="Store logs in the specified directory.")
520 parser
.add_argument("--fail-fast", "-f", action
="store_true",
521 help="Exit as soon as any test fails.")
522 parser
.add_argument("--print-failures", action
="store_true",
523 help="When a test fails, print the log file to stdout.")
524 parser
.add_argument("test", nargs
='*',
525 help="Run only tests that are named here.")
526 parser
.add_argument("--gdb",
527 help="The command to use to start gdb.")
528 parser
.add_argument("--misaval",
529 help="Don't run ExamineTarget, just assume the misa value which is "
532 def header(title
, dash
='-', length
=78):
534 dashes
= dash
* (length
- 4 - len(title
))
535 before
= dashes
[:len(dashes
)/2]
536 after
= dashes
[len(dashes
)/2:]
537 print "%s[ %s ]%s" % (before
, title
, after
)
543 lines
= open(path
, "r").readlines()
548 class BaseTest(object):
551 def __init__(self
, target
, hart
=None):
556 self
.hart
= random
.choice(target
.harts
)
557 self
.hart
= target
.harts
[-1] #<<<
559 self
.target_process
= None
564 def early_applicable(self
):
565 """Return a false value if the test has determined it cannot run
566 without ever needing to talk to the target or server."""
567 # pylint: disable=no-self-use
574 compile_args
= getattr(self
, 'compile_args', None)
576 if compile_args
not in BaseTest
.compiled
:
577 # pylint: disable=star-args
578 BaseTest
.compiled
[compile_args
] = \
579 self
.target
.compile(self
.hart
, *compile_args
)
580 self
.binary
= BaseTest
.compiled
.get(compile_args
)
582 def classSetup(self
):
584 self
.target_process
= self
.target
.create()
585 if self
.target_process
:
586 self
.logs
.append(self
.target_process
.logname
)
588 self
.server
= self
.target
.server()
589 self
.logs
.append(self
.server
.logname
)
591 for log
in self
.logs
:
595 def classTeardown(self
):
597 del self
.target_process
599 def postMortem(self
):
604 If compile_args is set, compile a program and set self.binary.
608 Then call test() and return the result, displaying relevant information
609 if an exception is raised.
614 if not self
.early_applicable():
615 return "not_applicable"
617 self
.start
= time
.time()
622 result
= self
.test() # pylint: disable=no-member
623 except TestNotApplicable
:
624 result
= "not_applicable"
625 except Exception as e
: # pylint: disable=broad-except
626 if isinstance(e
, TestFailed
):
630 if isinstance(e
, TestFailed
):
634 traceback
.print_exc(file=sys
.stdout
)
639 for log
in self
.logs
:
641 header("End of logs")
649 class GdbTest(BaseTest
):
650 def __init__(self
, target
, hart
=None):
651 BaseTest
.__init
__(self
, target
, hart
=hart
)
654 def classSetup(self
):
655 BaseTest
.classSetup(self
)
658 self
.gdb
= Gdb(gdb_cmd
)
662 self
.logs
.append(self
.gdb
.logname
)
665 self
.gdb
.command("file %s" % self
.binary
)
667 self
.gdb
.command("set arch riscv:rv%d" % self
.hart
.xlen
)
668 self
.gdb
.command("set remotetimeout %d" % self
.target
.timeout_sec
)
671 "target extended-remote localhost:%d" % self
.server
.port
)
672 self
.gdb
.select_hart(self
.hart
)
674 for cmd
in self
.target
.gdb_setup
:
675 self
.gdb
.command(cmd
)
677 # FIXME: OpenOCD doesn't handle PRIV now
678 #self.gdb.p("$priv=3")
680 def postMortem(self
):
684 self
.gdb
.command("info registers all", timeout
=10)
686 def classTeardown(self
):
688 BaseTest
.classTeardown(self
)
690 class GdbSingleHartTest(GdbTest
):
691 def classSetup(self
):
692 GdbTest
.classSetup(self
)
694 for hart
in self
.target
.harts
:
695 # Park all harts that we're not using in a safe place.
696 if hart
!= self
.hart
:
697 self
.gdb
.select_hart(hart
)
698 self
.gdb
.p("$pc=loop_forever")
699 self
.gdb
.select_hart(self
.hart
)
701 class ExamineTarget(GdbTest
):
703 self
.hart
.misa
= self
.gdb
.p("$misa")
706 if (self
.hart
.misa
>> 30) == 1:
708 elif (self
.hart
.misa
>> 62) == 2:
710 elif (self
.hart
.misa
>> 126) == 3:
713 raise TestFailed("Couldn't determine XLEN from $misa (0x%x)" %
717 if self
.hart
.misa
& (1<<i
):
718 txt
+= chr(i
+ ord('A'))
721 class TestFailed(Exception):
722 def __init__(self
, message
):
723 Exception.__init
__(self
)
724 self
.message
= message
726 class TestNotApplicable(Exception):
727 def __init__(self
, message
):
728 Exception.__init
__(self
)
729 self
.message
= message
731 def assertEqual(a
, b
):
733 raise TestFailed("%r != %r" % (a
, b
))
735 def assertNotEqual(a
, b
):
737 raise TestFailed("%r == %r" % (a
, b
))
741 raise TestFailed("%r not in %r" % (a
, b
))
743 def assertNotIn(a
, b
):
745 raise TestFailed("%r in %r" % (a
, b
))
747 def assertGreater(a
, b
):
749 raise TestFailed("%r not greater than %r" % (a
, b
))
751 def assertLess(a
, b
):
753 raise TestFailed("%r not less than %r" % (a
, b
))
757 raise TestFailed("%r is not True" % a
)
759 def assertRegexpMatches(text
, regexp
):
760 if not re
.search(regexp
, text
):
761 raise TestFailed("can't find %r in %r" % (regexp
, text
))