#!/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('--no-smoke', action='store_true',
                        help=('Do not generate the smoke test (useful if '
                              'generating exactly one binary)'))
    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)]
    if args.no_smoke:
        gb_flags.append('--no-smoke')

    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.no_smoke, 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,
                no_smoke: bool,
                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 = [] if no_smoke else ['smoke']
    for off in range(count if no_smoke else count - 1):
        basenames.append(str(seed + off))

    # Rules to run them
    handle.write('rule run\n'
                 '  command = $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())

@@ -3988,24 +3988,25 @@
 andle.write(
+f
 'rule run%5Cn'
@@ -4023,16 +4023,17 @@
         
+f
 '  comma
@@ -4037,16 +4037,69 @@
 mmand = 
+REPO_TOP=%7Bprojdir_from_destdir%7D '%0A                 f'
 $tb --lo

Fix run-some.py when destdir doesn't lie in project