11 # Note that gdb comes with its own testsuite. I was unable to figure out how to
12 # run that testsuite against the spike simulator.
15 for directory
in (os
.getcwd(), os
.path
.dirname(__file__
)):
16 fullpath
= os
.path
.join(directory
, path
)
17 if os
.path
.exists(fullpath
):
21 def compile(args
, xlen
=32): # pylint: disable=redefined-builtin
22 cc
= os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gcc")
25 cmd
.append("-march=rv32imac")
26 cmd
.append("-mabi=ilp32")
28 cmd
.append("-march=rv64imac")
29 cmd
.append("-mabi=lp64")
31 found
= find_file(arg
)
36 process
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
,
37 stderr
=subprocess
.PIPE
)
38 stdout
, stderr
= process
.communicate()
39 if process
.returncode
:
41 header("Compile failed")
42 print "+", " ".join(cmd
)
46 raise Exception("Compile failed!")
49 # http://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python/2838309#2838309
51 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
53 port
= s
.getsockname()[1]
60 def __init__(self
, sim_cmd
, binary
=None, halted
=False, with_jtag_gdb
=True,
61 timeout
=None, xlen
=64):
62 """Launch spike. Return tuple of its process and the port it's running
65 cmd
= shlex
.split(sim_cmd
)
67 spike
= os
.path
.expandvars("$RISCV/bin/spike")
70 cmd
+= ["--isa", "RV32G"]
72 cmd
+= ["--isa", "RV64G"]
75 cmd
= ["timeout", str(timeout
)] + cmd
80 cmd
+= ['--rbb-port', '0']
81 os
.environ
['REMOTE_BITBANG_HOST'] = 'localhost'
82 cmd
.append('programs/infinite_loop')
85 logfile
= open(self
.logname
, "w")
86 logfile
.write("+ %s\n" % " ".join(cmd
))
88 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
89 stdout
=logfile
, stderr
=logfile
)
94 m
= re
.search(r
"Listening for remote bitbang connection on "
95 r
"port (\d+).", open(self
.logname
).read())
97 self
.port
= int(m
.group(1))
98 os
.environ
['REMOTE_BITBANG_PORT'] = m
.group(1)
101 assert self
.port
, "Didn't get spike message about bitbang " \
111 def wait(self
, *args
, **kwargs
):
112 return self
.process
.wait(*args
, **kwargs
)
114 class VcsSim(object):
115 def __init__(self
, sim_cmd
=None, debug
=False):
117 cmd
= shlex
.split(sim_cmd
)
120 cmd
+= ["+jtag_vpi_enable"]
122 cmd
[0] = cmd
[0] + "-debug"
123 cmd
+= ["+vcdplusfile=output/gdbserver.vpd"]
124 logfile
= open("simv.log", "w")
125 logfile
.write("+ %s\n" % " ".join(cmd
))
127 listenfile
= open("simv.log", "r")
128 listenfile
.seek(0, 2)
129 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
130 stdout
=logfile
, stderr
=logfile
)
133 line
= listenfile
.readline()
136 match
= re
.match(r
"^Listening on port (\d+)$", line
)
139 self
.port
= int(match
.group(1))
140 os
.environ
['JTAG_VPI_PORT'] = str(self
.port
)
149 class Openocd(object):
150 logname
= "openocd.log"
152 def __init__(self
, server_cmd
=None, config
=None, debug
=False):
154 cmd
= shlex
.split(server_cmd
)
156 openocd
= os
.path
.expandvars("$RISCV/bin/riscv-openocd")
161 # This command needs to come before any config scripts on the command
162 # line, since they are executed in order.
164 # Tell OpenOCD to bind gdb to an unused, ephemeral port.
167 # Disable tcl and telnet servers, since they are unused and because
168 # the port numbers will conflict if multiple OpenOCD processes are
169 # running on the same server.
173 "telnet_port disabled",
177 f
= find_file(config
)
179 print("Unable to read file " + config
)
186 logfile
= open(Openocd
.logname
, "w")
187 logfile
.write("+ %s\n" % " ".join(cmd
))
189 self
.process
= subprocess
.Popen(cmd
, stdin
=subprocess
.PIPE
,
190 stdout
=logfile
, stderr
=logfile
)
192 # Wait for OpenOCD to have made it through riscv_examine(). When using
193 # OpenOCD to communicate with a simulator this may take a long time,
194 # and gdb will time out when trying to connect if we attempt too early.
198 log
= open(Openocd
.logname
).read()
199 if "Ready for Remote Connections" in log
:
201 if not self
.process
.poll() is None:
203 "OpenOCD exited before completing riscv_examine()")
204 if not messaged
and time
.time() - start
> 1:
206 print "Waiting for OpenOCD to examine RISCV core..."
208 self
.port
= self
._get
_gdb
_server
_port
()
210 def _get_gdb_server_port(self
):
211 """Get port that OpenOCD's gdb server is listening on."""
213 PORT_REGEX
= re
.compile(r
'(?P<port>\d+) \(LISTEN\)')
214 for _
in range(MAX_ATTEMPTS
):
215 with
open(os
.devnull
, 'w') as devnull
:
217 output
= subprocess
.check_output([
219 '-a', # Take the AND of the following selectors
220 '-p{}'.format(self
.process
.pid
), # Filter on PID
221 '-iTCP', # Filter only TCP sockets
223 except subprocess
.CalledProcessError
:
225 matches
= list(PORT_REGEX
.finditer(output
))
226 matches
= [m
for m
in matches
227 if m
.group('port') not in ('6666', '4444')]
231 "OpenOCD listening on multiple ports. Cannot uniquely "
232 "identify gdb server port.")
235 return int(match
.group('port'))
237 raise Exception("Timed out waiting for gdb server to obtain port.")
246 class OpenocdCli(object):
247 def __init__(self
, port
=4444):
248 self
.child
= pexpect
.spawn(
249 "sh -c 'telnet localhost %d | tee openocd-cli.log'" % port
)
250 self
.child
.expect("> ")
252 def command(self
, cmd
):
253 self
.child
.sendline(cmd
)
254 self
.child
.expect(cmd
)
255 self
.child
.expect("\n")
256 self
.child
.expect("> ")
257 return self
.child
.before
.strip("\t\r\n \0")
259 def reg(self
, reg
=''):
260 output
= self
.command("reg %s" % reg
)
261 matches
= re
.findall(r
"(\w+) \(/\d+\): (0x[0-9A-F]+)", output
)
262 values
= {r
: int(v
, 0) for r
, v
in matches
}
267 def load_image(self
, image
):
268 output
= self
.command("load_image %s" % image
)
269 if 'invalid ELF file, only 32bits files are supported' in output
:
270 raise TestNotApplicable(output
)
272 class CannotAccess(Exception):
273 def __init__(self
, address
):
274 Exception.__init
__(self
)
275 self
.address
= address
279 cmd
=os
.path
.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb")):
280 self
.child
= pexpect
.spawn(cmd
)
281 self
.child
.logfile
= open("gdb.log", "w")
282 self
.child
.logfile
.write("+ %s\n" % cmd
)
284 self
.command("set confirm off")
285 self
.command("set width 0")
286 self
.command("set height 0")
288 self
.command("set print entry-values no")
291 """Wait for prompt."""
292 self
.child
.expect(r
"\(gdb\)")
294 def command(self
, command
, timeout
=6000):
295 self
.child
.sendline(command
)
296 self
.child
.expect("\n", timeout
=timeout
)
297 self
.child
.expect(r
"\(gdb\)", timeout
=timeout
)
298 return self
.child
.before
.strip()
300 def c(self
, wait
=True, timeout
=-1):
302 output
= self
.command("c", timeout
=timeout
)
303 assert "Continuing" in output
306 self
.child
.sendline("c")
307 self
.child
.expect("Continuing")
310 self
.child
.send("\003")
311 self
.child
.expect(r
"\(gdb\)", timeout
=6000)
312 return self
.child
.before
.strip()
314 def x(self
, address
, size
='w'):
315 output
= self
.command("x/%s %s" % (size
, address
))
316 value
= int(output
.split(':')[1].strip(), 0)
319 def p_raw(self
, obj
):
320 output
= self
.command("p %s" % obj
)
321 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
323 raise CannotAccess(int(m
.group(1), 0))
324 return output
.split('=')[-1].strip()
327 output
= self
.command("p/x %s" % obj
)
328 m
= re
.search("Cannot access memory at address (0x[0-9a-f]+)", output
)
330 raise CannotAccess(int(m
.group(1), 0))
331 value
= int(output
.split('=')[-1].strip(), 0)
334 def p_string(self
, obj
):
335 output
= self
.command("p %s" % obj
)
336 value
= shlex
.split(output
.split('=')[-1].strip())[1]
340 output
= self
.command("stepi")
344 output
= self
.command("load", timeout
=6000)
345 assert "failed" not in output
346 assert "Transfer rate" in output
348 def b(self
, location
):
349 output
= self
.command("b %s" % location
)
350 assert "not defined" not in output
351 assert "Breakpoint" in output
354 def hbreak(self
, location
):
355 output
= self
.command("hbreak %s" % location
)
356 assert "not defined" not in output
357 assert "Hardware assisted breakpoint" in output
360 def run_all_tests(module
, target
, parsed
):
361 good_results
= set(('pass', 'not_applicable'))
368 global gdb_cmd
# pylint: disable=global-statement
371 todo
= [("ExamineTarget", ExamineTarget
)]
372 for name
in dir(module
):
373 definition
= getattr(module
, name
)
374 if type(definition
) == type and hasattr(definition
, 'test') and \
375 (not parsed
.test
or any(test
in name
for test
in parsed
.test
)):
376 todo
.append((name
, definition
))
378 for name
, definition
in todo
:
379 instance
= definition(target
)
380 result
= instance
.run()
381 results
.setdefault(result
, []).append(name
)
383 if result
not in good_results
and parsed
.fail_fast
:
386 header("ran %d tests in %.0fs" % (count
, time
.time() - start
), dash
=':')
389 for key
, value
in results
.iteritems():
390 print "%d tests returned %s" % (len(value
), key
)
391 if key
not in good_results
:
398 def add_test_run_options(parser
):
399 parser
.add_argument("--fail-fast", "-f", action
="store_true",
400 help="Exit as soon as any test fails.")
401 parser
.add_argument("test", nargs
='*',
402 help="Run only tests that are named here.")
403 parser
.add_argument("--gdb",
404 help="The command to use to start gdb.")
406 def header(title
, dash
='-'):
408 dashes
= dash
* (36 - len(title
))
409 before
= dashes
[:len(dashes
)/2]
410 after
= dashes
[len(dashes
)/2:]
411 print "%s[ %s ]%s" % (before
, title
, after
)
417 lines
= open(path
, "r").readlines()
418 if len(lines
) > 1000:
419 for l
in lines
[:500]:
422 for l
in lines
[-500:]:
428 class BaseTest(object):
431 def __init__(self
, target
):
434 self
.target_process
= None
439 def early_applicable(self
):
440 """Return a false value if the test has determined it cannot run
441 without ever needing to talk to the target or server."""
442 # pylint: disable=no-self-use
449 compile_args
= getattr(self
, 'compile_args', None)
451 if compile_args
not in BaseTest
.compiled
:
452 # pylint: disable=star-args
453 BaseTest
.compiled
[compile_args
] = \
454 self
.target
.compile(*compile_args
)
455 self
.binary
= BaseTest
.compiled
.get(compile_args
)
457 def classSetup(self
):
459 self
.target_process
= self
.target
.target()
460 self
.server
= self
.target
.server()
461 self
.logs
.append(self
.server
.logname
)
463 def classTeardown(self
):
465 del self
.target_process
469 If compile_args is set, compile a program and set self.binary.
473 Then call test() and return the result, displaying relevant information
474 if an exception is raised.
477 print "Running", type(self
).__name
__, "...",
480 if not self
.early_applicable():
481 print "not_applicable"
482 return "not_applicable"
484 self
.start
= time
.time()
490 result
= self
.test() # pylint: disable=no-member
491 except TestNotApplicable
:
492 result
= "not_applicable"
493 except Exception as e
: # pylint: disable=broad-except
494 if isinstance(e
, TestFailed
):
498 print "%s in %.2fs" % (result
, time
.time() - self
.start
)
500 if isinstance(e
, TestFailed
):
504 traceback
.print_exc(file=sys
.stdout
)
505 for log
in self
.logs
:
515 print "%s in %.2fs" % (result
, time
.time() - self
.start
)
519 class GdbTest(BaseTest
):
520 def __init__(self
, target
):
521 BaseTest
.__init
__(self
, target
)
524 def classSetup(self
):
525 BaseTest
.classSetup(self
)
526 self
.logs
.append("gdb.log")
529 self
.gdb
= Gdb(gdb_cmd
)
534 self
.gdb
.command("file %s" % self
.binary
)
536 self
.gdb
.command("set arch riscv:rv%d" % self
.target
.xlen
)
537 self
.gdb
.command("set remotetimeout %d" % self
.target
.timeout_sec
)
540 "target extended-remote localhost:%d" % self
.server
.port
)
542 # FIXME: OpenOCD doesn't handle PRIV now
543 #self.gdb.p("$priv=3")
545 def classTeardown(self
):
547 BaseTest
.classTeardown(self
)
549 class ExamineTarget(GdbTest
):
551 self
.target
.misa
= self
.gdb
.p("$misa")
554 if (self
.target
.misa
>> 30) == 1:
556 elif (self
.target
.misa
>> 62) == 2:
558 elif (self
.target
.misa
>> 126) == 3:
564 if self
.target
.misa
& (1<<i
):
565 txt
+= chr(i
+ ord('A'))
568 class TestFailed(Exception):
569 def __init__(self
, message
):
570 Exception.__init
__(self
)
571 self
.message
= message
573 class TestNotApplicable(Exception):
574 def __init__(self
, message
):
575 Exception.__init
__(self
)
576 self
.message
= message
578 def assertEqual(a
, b
):
580 raise TestFailed("%r != %r" % (a
, b
))
582 def assertNotEqual(a
, b
):
584 raise TestFailed("%r == %r" % (a
, b
))
588 raise TestFailed("%r not in %r" % (a
, b
))
590 def assertNotIn(a
, b
):
592 raise TestFailed("%r in %r" % (a
, b
))
594 def assertGreater(a
, b
):
596 raise TestFailed("%r not greater than %r" % (a
, b
))
598 def assertLess(a
, b
):
600 raise TestFailed("%r not less than %r" % (a
, b
))
604 raise TestFailed("%r is not True" % a
)
606 def assertRegexpMatches(text
, regexp
):
607 if not re
.search(regexp
, text
):
608 raise TestFailed("can't find %r in %r" % (regexp
, text
))