blob: 528f257760528c396bd383bacc26bc5ecad671af [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
'''Wrapper script to run the OTBN random instruction generator'''
import argparse
import json
import os
import random
import sys
from typing import Optional, cast
# Ensure that the OTBN utils directory is on sys.path. This means that RIG code
# can import modules like "shared.foo" and get the OTBN shared code.
_RIG_DIR = os.path.dirname(__file__)
_OTBN_DIR = os.path.normpath(os.path.join(_RIG_DIR, '../..'))
_OTBN_UTIL_DIR = os.path.join(_OTBN_DIR, 'util')
sys.path.append(_OTBN_UTIL_DIR)
from shared.insn_yaml import InsnsFile, load_insns_yaml # noqa: E402
from rig.config import Config # noqa: E402
from rig.init_data import InitData # noqa: E402
from rig.rig import gen_program # noqa: E402
from rig.snippet import Snippet # noqa: E402
def get_insns_file() -> Optional[InsnsFile]:
'''Load up insns.yml'''
try:
return load_insns_yaml()
except RuntimeError as err:
print(err, file=sys.stderr)
return None
def get_named_config(name: str) -> Optional[Config]:
cfg_dir = os.path.join(_RIG_DIR, 'rig/configs')
try:
return Config.load(cfg_dir, name)
except RuntimeError as err:
print(err, file=sys.stderr)
return None
def gen_main(args: argparse.Namespace) -> int:
'''Entry point for the gen subcommand'''
random.seed(args.seed)
insns_file = get_insns_file()
if insns_file is None:
return 1
config = get_named_config(args.config)
if config is None:
return 1
# Run the generator
try:
init_data, snippet, end_addr = gen_program(config,
args.size,
insns_file)
except RuntimeError as err:
print(err, file=sys.stderr)
return 1
# Write out the data and snippets to a JSON file
ser_data = init_data.as_json()
ser_snippet = snippet.to_json()
ser = [ser_data, ser_snippet, end_addr]
json.dump(ser, args.output)
# Add a newline at end of output: json.dump doesn't, and it makes a
# bit of a mess of some consoles.
args.output.write('\n')
return 0
def asm_main(args: argparse.Namespace) -> int:
'''Entry point for the asm subcommand'''
insns_file = get_insns_file()
if insns_file is None:
return 1
# Load JSON
try:
json_data = json.load(args.snippets)
except json.JSONDecodeError as err:
print('Snippets file at {!r} is not valid JSON: {}.'
.format(args.snippets.name, err),
file=sys.stderr)
return 1
# Parse these to proper init data and snippet objects.
try:
if not (isinstance(json_data, list) and len(json_data) == 3):
raise ValueError('Top-level structure should be a length 3 list.')
json_init_data, json_snippet, json_end_addr = json_data
init_data = InitData.read(json_init_data)
snippet = Snippet.from_json(insns_file, [], json_snippet)
if not isinstance(json_end_addr, int) or json_end_addr < 0:
raise ValueError('end_addr should be an integer.')
end_addr = json_end_addr
except ValueError as err:
print('Failed to parse snippets from {!r}: {}'
.format(args.snippets.name, err),
file=sys.stderr)
return 1
program = snippet.to_program()
dsegs = init_data.as_segs()
# Dump the assembly output, and the linker script too if we're writing to
# something other than stdout.
if args.output is None or args.output == '-':
program.dump_asm(sys.stdout, dsegs)
else:
try:
asm_path = args.output + '.s'
with open(asm_path, 'w') as out_file:
program.dump_asm(out_file, dsegs)
except OSError as err:
print('Failed to open asm output file {!r}: {}.'
.format(args.output, err),
file=sys.stderr)
return 1
try:
ld_path = args.output + '.ld'
with open(ld_path, 'w') as out_file:
program.dump_linker_script(out_file, dsegs, end_addr)
except OSError as err:
print('Failed to open ld script output file {!r}: {}.'
.format(ld_path, err),
file=sys.stderr)
return 1
return 0
def main() -> int:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
subparsers.required = True
gen = subparsers.add_parser('gen', help='Generate a random program')
asm = subparsers.add_parser('asm', help='Convert snippets to assembly')
gen.add_argument('--seed', type=int, default=0,
help='Random seed. Defaults to 0.')
gen.add_argument('--size', type=int, default=100,
help=('Max number of instructions in stream. '
'Defaults to 100.'))
gen.add_argument('--config', type=str, default='default',
help='Configuration to use')
gen.add_argument('--output', '-o',
metavar='out',
type=argparse.FileType('w', encoding='UTF-8'),
default=sys.stdout,
help='Output filename')
gen.set_defaults(func=gen_main)
asm.add_argument('--output', '-o',
metavar='out',
help=('Base path for output filenames. Will generate '
'out.s with an assembly listing and out.ld with '
'a linker script. If this is not supplied, the '
'assembly (but no linker script) will be dumped '
'to stdout.'))
asm.add_argument('snippets', metavar='path.json',
type=argparse.FileType('r'), nargs='?', default=sys.stdin,
help=('A JSON file of snippets, as generated by '
'otbn-rig gen.'))
asm.set_defaults(func=asm_main)
args = parser.parse_args()
return cast(int, args.func(args))
if __name__ == '__main__':
sys.exit(main())