blob: 13e79dd60c0866dff0e63c9277acdd6fc9b8132d [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
'''A handy wrapper that uses gen-binaries.sh and a Verilated binary
Use this with a command line like
run-some.py --size=1500 --count=10 XXX
which will generate 10 OTBN binaries, each with up to 1500 instructions in
their respective traces. It will also build a Verilated model of OTBN (using
otbn_top_sim) and run the model on each binary.
'''
import argparse
import os
import shlex
import subprocess
import sys
from typing import TextIO
_SCRIPT_DIR = os.path.dirname(__file__)
def find_gen_binaries() -> str:
'''Find the path to gen-binaries.py'''
path = os.path.join(_SCRIPT_DIR, '../uvm/gen-binaries.py')
if not os.path.exists(path):
raise RuntimeError(f'No such file: {path}')
return os.path.normpath(path)
def get_projdir() -> str:
'''Return the path to the top of the project'''
path = os.path.join(_SCRIPT_DIR, '../../../../..')
assert os.path.exists(os.path.join(path, '.git'))
return os.path.normpath(path)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--count', type=int, default=10,
help='Number of binaries to generate and run')
parser.add_argument('--seed', type=int, default=1)
parser.add_argument('--size', type=int, default=100)
parser.add_argument('destdir', help='Destination directory')
args = parser.parse_args()
# Run gen-binaries.py in --gen-only mode, which will produce a
# build.ninja.gen describing how to generate the binaries.
gb_flags = ['--gen-only',
'--ninja-suffix=gen',
'--count={}'.format(args.count),
'--seed={}'.format(args.seed),
'--size={}'.format(args.size)]
gb_cmd = [find_gen_binaries()] + gb_flags + [args.destdir]
if subprocess.run(gb_cmd, check=False).returncode:
print('Failed to run generate-binaries (command: {})'
.format(' '.join([shlex.quote(arg) for arg in gb_cmd])),
file=sys.stderr)
return 1
# Next, we make our own build.ninja, which says how to compile and run the
# verilated testbench
with open(os.path.join(args.destdir, 'build.ninja'), 'w') as ninja_handle:
write_ninja(ninja_handle, args.destdir, args.seed, args.count)
# Finally, use ninja to run everything, continuing on error (so that you
# can run 100 seeds and see what proportion fails).
ninja_cmd = ['ninja', '-C', args.destdir, '-k0', 'run']
return subprocess.run(ninja_cmd, check=False).returncode
def write_ninja(handle: TextIO,
destdir: str,
seed: int,
count: int) -> None:
handle.write('include build.ninja.gen\n\n')
# Find the project directory, as viewed from destdir
projdir_from_here = get_projdir()
projdir_from_destdir = os.path.relpath(projdir_from_here, destdir)
# The rule to build the Verilated system
handle.write('rule fusesoc\n'
f' command = fusesoc --cores-root={projdir_from_destdir} '
'run --target=sim --setup --build lowrisc:ip:otbn_top_sim '
'>fusesoc.log 2>&1\n\n')
handle.write('tb = build/lowrisc_ip_otbn_top_sim_0.1/'
'sim-verilator/Votbn_top_sim\n\n')
handle.write('build $tb: fusesoc\n\n')
# Collect up all the generated files
basenames = [str(seed + off) for off in range(count)]
# Rules to run them
handle.write(f'rule run\n'
f' command = REPO_TOP={projdir_from_destdir} '
f'$tb --load-elf $in >$out\n\n')
for name in basenames:
handle.write(f'build {name}.out: run {name}.elf | $tb\n')
handle.write('\n')
# A phony rule to run everything
handle.write('build run: phony {}\n\n'
.format(' '.join([f'{name}.out' for name in basenames])))
if __name__ == '__main__':
sys.exit(main())