| #!/usr/bin/env python3 |
| """Runs test within Spike, Qemu and Renode simulators.""" |
| import argparse |
| import os |
| import re |
| import sys |
| import tempfile |
| |
| import io |
| import pexpect |
| |
| |
| parser = argparse.ArgumentParser( |
| description="Run a springbok test on an simulator.") |
| |
| parser.add_argument("simulator", |
| help="Select a simulator", |
| choices=["renode", "qemu", "spike"]) |
| parser.add_argument("elf", |
| help="Elf to execute on a simulator") |
| parser.add_argument("--renode-path", |
| help="Path to renode simulator") |
| parser.add_argument("--trace-output", |
| help="Path to trace output file") |
| parser.add_argument("--qemu-path", |
| help="Path to qemu simulator") |
| parser.add_argument("--spike-path", |
| help="Path to spike simulator") |
| parser.add_argument("--timeout", type=int, |
| help="Timeout for test", default=1000) |
| parser.add_argument("--quick_test", |
| help="allow quickest test time", action="store_true") |
| |
| args = parser.parse_args() |
| |
| class Simulation: # pylint: disable=too-few-public-methods |
| """ Base class for simulation """ |
| def __init__(self, simulator_cmd): |
| self.simulator_cmd = simulator_cmd |
| self.buffer = io.StringIO() |
| self.child = None |
| self.termination_strings = [ |
| "main returned", |
| "Exception occurred", |
| "ReadByte from non existing peripheral", |
| ] |
| |
| def run(self, timeout=1000): |
| """ Run the simulation command and quit the simulation.""" |
| self.child = pexpect.spawn(self.simulator_cmd, encoding="utf-8") |
| self.child.logfile = self.buffer |
| self.child.expect(self.termination_strings, timeout=timeout) |
| self.child.send("\nq\n") |
| self.child.expect(pexpect.EOF, timeout=timeout) |
| self.child.close() |
| self.buffer.seek(0) |
| return self.buffer.read() |
| |
| class QemuSimulation(Simulation): # pylint: disable=too-few-public-methods |
| """ Qemu simulation """ |
| def __init__(self, path, elf): |
| self.qemu_simulator_cmd = ( |
| "%(sim)s -M springbok -nographic -d springbok -device loader,file=%(elf)s") |
| self.sim_params = {"sim": path, "elf": elf} |
| super().__init__(self.qemu_simulator_cmd % self.sim_params) |
| |
| |
| class RenodeSimulation(Simulation): # pylint: disable=too-few-public-methods |
| """ Renode Simulation """ |
| def __init__(self, path, elf): |
| # Get the ROOTDIR path if it exists |
| self.rootdir = os.environ.get("ROOTDIR", default=None) |
| if self.rootdir is None: |
| parser.error("ROOTDIR environment variable not set.") |
| renode_script = """ |
| $bin=@%(elf)s |
| path set @%(rootdir)s |
| include @sim/config/springbok.resc""" |
| |
| if args.quick_test: |
| renode_script += """ |
| sysbus.cpu2 PerformanceInMips 2000 |
| emulation SetGlobalQuantum "1" """ |
| |
| if args.trace_output: |
| renode_script += """ |
| sysbus.cpu2 EnableExecutionTracing @%(trace_file)s PCAndOpcode """ |
| |
| renode_script += """ |
| start |
| sysbus.vec_controlblock WriteDoubleWord 0xc 0""" |
| self.script_params = { |
| "elf": os.path.realpath(elf), |
| "rootdir": self.rootdir, |
| "trace_file": os.path.realpath(args.trace_output) if args.trace_output else "" |
| } |
| self.renode_script = renode_script % self.script_params |
| self.renode_args = [ |
| "%s" % path, |
| "--disable-xwt", |
| " --console", |
| "--plain", |
| ] |
| self.renode_simulator_cmd = " ".join(self.renode_args) |
| super().__init__(self.renode_simulator_cmd) |
| |
| def run(self, timeout=120): |
| file_desc, script_path = tempfile.mkstemp(suffix=".resc") |
| try: |
| with os.fdopen(file_desc, "w") as tmp: |
| tmp.write(self.renode_script) |
| tmp.flush() |
| self.simulator_cmd += " %s" % script_path |
| test_output = super().run(timeout=timeout) |
| finally: |
| os.remove(script_path) |
| return test_output |
| |
| class SpikeSimulation(Simulation): # pylint: disable=too-few-public-methods |
| """ Spike Simulation """ |
| def __init__(self, path, elf): |
| self.sim_params = { |
| "path": path, |
| "elf": elf, |
| "trace_file": os.path.realpath(args.trace_output) if args.trace_output else "" |
| } |
| self.spike_simulator_cmd = "%(path)s -m0x32000000:0x100000,0x34000000:0x1000000 --pc=0x32000000 " |
| |
| if args.trace_output: |
| self.spike_simulator_cmd += " -l --log=%(trace_file)s " |
| |
| self.spike_simulator_cmd += " %(elf)s" |
| super().__init__(self.spike_simulator_cmd % self.sim_params) |
| |
| Simulators = { |
| "qemu": QemuSimulation, |
| "renode": RenodeSimulation, |
| "spike": SpikeSimulation, |
| } |
| |
| simulators_paths = { |
| "renode": args.renode_path, |
| "qemu": args.qemu_path, |
| "spike": args.spike_path, |
| } |
| |
| |
| def main(): |
| """ Run a test and check for Pass or Fail """ |
| simulator_path = simulators_paths[args.simulator] |
| if simulator_path is None: |
| parser.error( |
| "Must provide path to simulator %s, use argument --%s-path" % (args.simulator, |
| args.simulator)) |
| |
| simulator_class = Simulators[args.simulator] |
| simulator = simulator_class(simulator_path, args.elf) |
| output = simulator.run(timeout=args.timeout) |
| # mono API generates escape characters at the termination. Need to clean up. |
| # TODO(hcindyl): Remove this when Renode fix the mono call. |
| ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") |
| output = ansi_escape.sub("", output) |
| print(output) |
| failure_strings = [ |
| "FAILED", |
| "Exception occurred", |
| "ReadByte from non existing peripheral" |
| ] |
| if any(x in output for x in failure_strings): |
| sys.exit(1) |
| # Grab the return code from the output string with regex |
| # Syntax: "main returned: ", <code> (<hex_code>) |
| return_string = re.compile( |
| r"\"main returned:\s\",(?P<ret_code>\s[0-9]+\s*)") |
| code = return_string.search(output) |
| sys.exit(int(code.group(1))) |
| |
| |
| if __name__ == "__main__": |
| main() |