|  | #!/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 helper script to generate a default set of binaries for OTBN testing | 
|  |  | 
|  | This is intended for use with dvsim, which should call this script as | 
|  | part of the test build phase. | 
|  |  | 
|  | ''' | 
|  |  | 
|  | import argparse | 
|  | import os | 
|  | import subprocess | 
|  | import sys | 
|  | from typing import Dict, List, Optional, TextIO, Union | 
|  |  | 
|  |  | 
|  | def read_positive(val: str) -> int: | 
|  | ival = -1 | 
|  | try: | 
|  | ival = int(val, 0) | 
|  | except ValueError: | 
|  | pass | 
|  |  | 
|  | if ival <= 0: | 
|  | raise argparse.ArgumentTypeError('{!r} is not a positive integer.' | 
|  | .format(val)) | 
|  | return ival | 
|  |  | 
|  |  | 
|  | def read_jobs(val: Optional[str]) -> Optional[Union[str, int]]: | 
|  | if val == 'unlimited': | 
|  | return 'unlimited' | 
|  | if val is None: | 
|  | return None | 
|  |  | 
|  | return read_positive(val) | 
|  |  | 
|  |  | 
|  | class Toolchain: | 
|  | def __init__(self, env_data: Dict[str, str]) -> None: | 
|  | self.otbn_as = self.get_tool(env_data, 'OTBN_AS') | 
|  | self.otbn_ld = self.get_tool(env_data, 'OTBN_LD') | 
|  | self.rv32_tool_as = self.get_tool(env_data, 'RV32_TOOL_AS') | 
|  | self.rv32_tool_ld = self.get_tool(env_data, 'RV32_TOOL_LD') | 
|  |  | 
|  | @staticmethod | 
|  | def get_tool(env_data: Dict[str, str], tool: str) -> str: | 
|  | path = env_data.get(tool) | 
|  | if path is None: | 
|  | raise RuntimeError('No entry for {} in .env file'.format(tool)) | 
|  | return path | 
|  |  | 
|  | def run(self, cmd: List[str]) -> None: | 
|  | '''Wrapper around subprocess.run that sets needed environment vars''' | 
|  | env = os.environ.copy() | 
|  | env['RV32_TOOL_AS'] = self.rv32_tool_as | 
|  | env['RV32_TOOL_LD'] = self.rv32_tool_ld | 
|  | subprocess.run(cmd, env=env) | 
|  |  | 
|  |  | 
|  | def take_env_line(dst: Dict[str, str], | 
|  | path: str, line_number: int, line: str) -> None: | 
|  | '''Read one line from a .env file, updating dst.''' | 
|  | line = line.split('#', 1)[0].strip() | 
|  | if not line: | 
|  | return | 
|  |  | 
|  | parts = line.split('=', 1) | 
|  | if len(parts) != 2: | 
|  | raise RuntimeError('{}:{}: No equals sign in line {!r}.' | 
|  | .format(path, line_number, line)) | 
|  |  | 
|  | dst[parts[0]] = parts[1] | 
|  |  | 
|  |  | 
|  | def read_toolchain(obj_dir_arg: Optional[str], otbn_dir: str) -> Toolchain: | 
|  | '''Read Meson's dumped .env file to get toolchain info''' | 
|  | if obj_dir_arg is not None: | 
|  | obj_dir = obj_dir_arg | 
|  | source = 'specified by an --obj-dir argument' | 
|  | else: | 
|  | obj_dir_env = os.getenv('OBJ_DIR') | 
|  | if obj_dir_env is not None: | 
|  | obj_dir = obj_dir_env | 
|  | source = ('inferred from OBJ_DIR environment variable; ' | 
|  | 'have you run meson_init.sh?') | 
|  | else: | 
|  | git_dir = os.path.normpath(os.path.join(otbn_dir, '../' * 3)) | 
|  | obj_dir = os.path.normpath(os.path.join(git_dir, 'build-out')) | 
|  | source = ('inferred from script location; ' | 
|  | 'have you run meson_init.sh?') | 
|  |  | 
|  | env_path = os.path.join(obj_dir, '.env') | 
|  | try: | 
|  | with open(env_path) as env_file: | 
|  | env_dict = {}  # type: Dict[str, str] | 
|  | for idx, line in enumerate(env_file): | 
|  | take_env_line(env_dict, env_path, idx + 1, line) | 
|  |  | 
|  | return Toolchain(env_dict) | 
|  | except OSError as ose: | 
|  | raise RuntimeError('Failed to read .env file at {!r} ' | 
|  | '(at a path {}): {}.' | 
|  | .format(env_path, source, ose)) from None | 
|  |  | 
|  |  | 
|  | def main() -> int: | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument('--count', type=read_positive, default=10, | 
|  | help='Number of binaries to generate') | 
|  | parser.add_argument('--obj-dir', | 
|  | help=('Object directory configured with Meson (used ' | 
|  | 'to find tool configuration). If not supplied, ' | 
|  | 'defaults to the OBJ_DIR environment variable ' | 
|  | 'if set, or build-out at the top of the ' | 
|  | 'repository if not.')) | 
|  | parser.add_argument('--seed', type=read_positive, default=0) | 
|  | parser.add_argument('--size', type=read_positive, default=100) | 
|  | parser.add_argument('--verbose', '-v', action='store_true') | 
|  | parser.add_argument('--jobs', '-j', type=read_jobs, nargs='?', | 
|  | const='unlimited', help='Number of parallel jobs.') | 
|  | parser.add_argument('destdir', | 
|  | help='Destination directory') | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | script_dir = os.path.dirname(__file__) | 
|  | otbn_dir = os.path.normpath(os.path.join(script_dir, '../' * 2)) | 
|  |  | 
|  | try: | 
|  | toolchain = read_toolchain(args.obj_dir, otbn_dir) | 
|  | except RuntimeError as err: | 
|  | print(err, file=sys.stderr) | 
|  | return 1 | 
|  |  | 
|  | rig_count = args.count - 1 | 
|  |  | 
|  | os.makedirs(args.destdir, exist_ok=True) | 
|  |  | 
|  | with open(os.path.join(args.destdir, 'build.ninja'), 'w') as ninja_handle: | 
|  | write_ninja(ninja_handle, rig_count, | 
|  | args.seed, args.size, toolchain, otbn_dir) | 
|  |  | 
|  | # Handle the -j argument like Make does, defaulting to 1 thread. This | 
|  | # behaves a bit more reasonably than ninja's default (# cores) if we're | 
|  | # running underneath something else. | 
|  | if args.jobs is None: | 
|  | j_arg = 1 | 
|  | elif args.jobs == 'unlimited': | 
|  | j_arg = 0 | 
|  | else: | 
|  | j_arg = args.jobs | 
|  |  | 
|  | cmd = ['ninja', '-j', str(j_arg)] | 
|  | if args.verbose: | 
|  | cmd.append('-v') | 
|  |  | 
|  | return subprocess.run(cmd, cwd=args.destdir, check=False).returncode | 
|  |  | 
|  |  | 
|  | def write_ninja(handle: TextIO, rig_count: int, start_seed: int, size: int, | 
|  | toolchain: Toolchain, otbn_dir: str) -> None: | 
|  | '''Write a build.ninja to build rig_count random binaries and a smoke test | 
|  |  | 
|  | The rules build everything in the same directory as the build.ninja file. | 
|  | OTBN tooling is found in util_dir. | 
|  |  | 
|  | ''' | 
|  | assert start_seed >= 0 | 
|  | assert size > 0 | 
|  |  | 
|  | otbn_rig = os.path.join(otbn_dir, 'dv/rig/otbn-rig') | 
|  | smoke_src_dir = os.path.join(otbn_dir, 'dv/smoke') | 
|  |  | 
|  | seeds = [start_seed + idx for idx in range(rig_count)] | 
|  |  | 
|  | handle.write('rule rig-gen\n' | 
|  | '  command = {rig} gen --size {size} --seed $seed >$out\n' | 
|  | .format(rig=otbn_rig, size=size)) | 
|  |  | 
|  | handle.write('rule rig-asm\n' | 
|  | '  command = {rig} asm -o $seed $in\n' | 
|  | .format(rig=otbn_rig)) | 
|  |  | 
|  | handle.write('rule as\n' | 
|  | '  command = RV32_TOOL_AS={rv32_as} {otbn_as} -o $out $in\n' | 
|  | .format(rv32_as=toolchain.rv32_tool_as, | 
|  | otbn_as=toolchain.otbn_as)) | 
|  |  | 
|  | handle.write('rule ld\n' | 
|  | '  command = RV32_TOOL_LD={rv32_ld} ' | 
|  | '{otbn_ld} -o $out -T $ldscript $in\n' | 
|  | .format(rv32_ld=toolchain.rv32_tool_ld, | 
|  | otbn_ld=toolchain.otbn_ld)) | 
|  |  | 
|  | handle.write('rule ld1\n' | 
|  | '  command = RV32_TOOL_LD={rv32_ld} {otbn_ld} -o $out $in\n\n' | 
|  | .format(rv32_ld=toolchain.rv32_tool_ld, | 
|  | otbn_ld=toolchain.otbn_ld)) | 
|  |  | 
|  | for seed in seeds: | 
|  | # Generate the .s and .ld files. | 
|  | handle.write('build {seed}.json: rig-gen\n' | 
|  | '  seed = {seed}\n' | 
|  | .format(seed=seed)) | 
|  |  | 
|  | handle.write('build {seed}.s {seed}.ld: rig-asm {seed}.json\n' | 
|  | '  seed = {seed}\n' | 
|  | .format(seed=seed)) | 
|  |  | 
|  | # Assemble the asm file to an object | 
|  | handle.write('build {seed}.o: as {seed}.s\n'.format(seed=seed)) | 
|  |  | 
|  | # Link the object to an ELF, using the relevant LD file | 
|  | handle.write('build {seed}.elf: ld {seed}.o\n' | 
|  | '  ldscript = {seed}.ld\n\n' | 
|  | .format(seed=seed)) | 
|  |  | 
|  | # Rules to build the smoke test. | 
|  | smoke_src = os.path.join(os.path.abspath(smoke_src_dir), 'smoke_test.s') | 
|  | handle.write('build smoke.o: as {smoke_src}\n'.format(smoke_src=smoke_src)) | 
|  | handle.write('build smoke.elf: ld1 smoke.o\n\n') | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |