"""Utility rules used to compile Verilator""" def _verilator_astgen_impl(ctx): args = ctx.actions.args() args.add("--astgen", ctx.file.astgen) args.add_all(ctx.files.srcs, format_each = "--src=%s") args.add_all(ctx.outputs.outs, format_each = "--out=%s") args.add("--") args.add_all(ctx.attr.args) ctx.actions.run( executable = ctx.executable._process_wrapper, mnemonic = "VerilatorASTgen", arguments = [args], inputs = ctx.files.srcs, outputs = ctx.outputs.outs, tools = [ctx.file.astgen], ) return [DefaultInfo( files = depset(ctx.outputs.outs), )] verilator_astgen = rule( doc = "Run Verilator's `astgen` tool and collect the requested outputs.", implementation = _verilator_astgen_impl, attrs = { "args": attr.string_list( doc = "The command line arugments for `astgen`.", ), "astgen": attr.label( doc = "The path to the `astgen` tool.", allow_single_file = True, mandatory = True, ), "outs": attr.output_list( doc = "The output sources generated by `astgen`.", allow_empty = False, mandatory = True, ), "srcs": attr.label_list( doc = "Input sources for `astgen`.", allow_files = True, ), "_process_wrapper": attr.label( cfg = "exec", executable = True, default = Label("//private:verilator_astgen"), ), }, ) def _correct_bison_env_for_action(env, bison): """Modify the Bison environment variables to work in an action that doesn't a have built bison runfiles directory. The `bison_toolchain.bison_env` parameter assumes that Bison will provided via an executable attribute and thus have built runfiles available to it. This is not the case for this action and any other actions trying to use bison as a tool via the toolchain. This function transforms existing environment variables to support running Bison as desired. Args: env (dict): The existing bison environment variables bison (File): The Bison executable Returns: Dict: Environment variables required for running Bison. """ bison_env = dict(env) # Convert the environment variables to non-runfiles forms bison_runfiles_dir = "{}.runfiles/{}".format( bison.path, bison.owner.workspace_name, ) bison_env["BISON_PKGDATADIR"] = bison_env["BISON_PKGDATADIR"].replace( bison_runfiles_dir, "external/{}".format(bison.owner.workspace_name), ) bison_env["M4"] = bison_env["M4"].replace( bison_runfiles_dir, "{}/external/{}".format(bison.root.path, bison.owner.workspace_name), ) return bison_env def _verilator_bisonpre_impl(ctx): bison_toolchain = ctx.toolchains["@rules_bison//bison:toolchain_type"].bison_toolchain args = ctx.actions.args() args.add(ctx.file.bisonpre) args.add("--yacc", bison_toolchain.bison_tool.executable) args.add("-d") args.add("-v") args.add("-o", ctx.outputs.out_src) args.add(ctx.file.yacc_src) outputs = [ ctx.outputs.out_src, ctx.outputs.out_hdr, ] tools = depset([ctx.file.bisonpre], transitive = [bison_toolchain.all_files]) bison_env = _correct_bison_env_for_action( env = bison_toolchain.bison_env, bison = bison_toolchain.bison_tool.executable, ) ctx.actions.run( outputs = outputs, inputs = [ctx.file.yacc_src], tools = tools, executable = ctx.executable._process_wrapper, arguments = [args], mnemonic = "VerilatorBisonPre", use_default_shell_env = False, env = bison_env, ) return DefaultInfo( files = depset(outputs), runfiles = ctx.runfiles(files = outputs), ) verilator_bisonpre = rule( doc = "Run Verilator's `bisonpre` tool and collect the requested outputs.", implementation = _verilator_bisonpre_impl, attrs = { "bisonpre": attr.label( doc = "The path to the `bisonpre` tool.", allow_single_file = True, mandatory = True, cfg = "exec", ), "out_hdr": attr.output( mandatory = True, doc = "Output files generated by the action.", ), "out_src": attr.output( mandatory = True, doc = "Output files generated by the action.", ), "yacc_src": attr.label( doc = "The yacc file to run on.", allow_single_file = True, mandatory = True, ), "_process_wrapper": attr.label( executable = True, cfg = "exec", default = Label("//private:verilator_bisonpre"), ), }, toolchains = [ "@rules_bison//bison:toolchain_type", "@rules_m4//m4:toolchain_type", ], ) def _find_flex_src(ctx): cc_srcs = ctx.attr.src[OutputGroupInfo].cc_srcs.to_list() if len(cc_srcs) != 1: fail("Unexpected number of cc sources generated in `{}`: {}".format( ctx.attr.src.label, cc_srcs, )) return cc_srcs[0] def _verilator_flexfix_impl(ctx): src = _find_flex_src(ctx) args = ctx.actions.args() args.add("--flexfix", ctx.file.flexfix) args.add("--src", src) args.add("--output", ctx.outputs.out) args.add("--") args.add_all(ctx.attr.args) ctx.actions.run( executable = ctx.executable._process_wrapper, mnemonic = "VerilatorFlexFix", outputs = [ctx.outputs.out], inputs = [src], tools = [ctx.file.flexfix], arguments = [args], ) return [DefaultInfo( files = depset([ctx.outputs.out]), )] verilator_flexfix = rule( doc = "Run Verilator's `flexfix` tool and collect the requested outputs.", implementation = _verilator_flexfix_impl, attrs = { "args": attr.string_list( doc = "The command line arugments for `flexfix`.", ), "flexfix": attr.label( doc = "The path to the `flexfix` tool.", cfg = "exec", allow_single_file = True, mandatory = True, ), "out": attr.output( doc = "The output source generated by `flexfix`.", mandatory = True, ), "src": attr.label( doc = "The source file to pass to `flexfix`.", mandatory = True, allow_files = True, ), "_process_wrapper": attr.label( cfg = "exec", executable = True, default = Label("//private:verilator_flexfix"), ), }, ) def _verilator_version_impl(ctx): output = ctx.actions.declare_file(ctx.label.name + ".txt") args = ctx.actions.args() args.add("--output", output) args.add("--changelog", ctx.file.changelog) ctx.actions.run( executable = ctx.executable._parser, mnemonic = "VerilatorVersion", outputs = [output], inputs = [ctx.file.changelog], arguments = [args], ) return [DefaultInfo( files = depset([output]), )] verilator_version = rule( doc = "A rule for parsing the current verilator version from the change log.", implementation = _verilator_version_impl, attrs = { "changelog": attr.label( doc = "The Verilator change log.", allow_single_file = True, mandatory = True, ), "_parser": attr.label( executable = True, cfg = "exec", default = Label("//private:verilator_version"), ), }, ) def _verilator_build_template_impl(ctx): output = ctx.outputs.out args = ctx.actions.args() args.add("--output", output) args.add("--version", ctx.file.version) args.add("--substitutions", json.encode(ctx.attr.substitutions)) args.add("--template", ctx.file.template) ctx.actions.run( executable = ctx.executable._generator, mnemonic = "VerilatorBuildTemplate", outputs = [output], inputs = [ctx.file.version, ctx.file.template], arguments = [args], ) return [DefaultInfo( files = depset([output]), )] verilator_build_template = rule( doc = "A rule for expanding verilator template files required for compiling.", implementation = _verilator_build_template_impl, attrs = { "out": attr.output( doc = "The output file", mandatory = True, ), "substitutions": attr.string_dict( doc = "A mapping of substitutions to apply on the template file.", mandatory = True, ), "template": attr.label( doc = "The base template to apply substitutions to", mandatory = True, allow_single_file = True, ), "version": attr.label( doc = ( "A file containing the current version of Verilator. In substitution " + "values, the `{VERILATOR_VERSION}` string will be replaced by the version " + "in this file." ), allow_single_file = True, mandatory = True, ), "_generator": attr.label( executable = True, cfg = "exec", default = Label("//private:verilator_build_template"), ), }, ) def _verilator_internal_test_impl(ctx): script = ctx.actions.declare_file(ctx.label.name + ".sh") # Get runfiles path to verilator binary repo = ctx.executable.verilator.owner.workspace_name or ctx.workspace_name verilator_rf = "{}/{}".format(repo, ctx.executable.verilator.short_path) # Build runfiles paths for sources def _rf(ctx, f): repo = f.owner.workspace_name or ctx.workspace_name return "{}/{}".format(repo, f.short_path) src_rfs = [_rf(ctx, f) for f in ctx.files.srcs] src_args = " ".join(['"${TEST_SRCDIR}/%s"' % s for s in src_rfs]) # Unique include dirs based on sources (also runfiles paths) def _rf_dir(ctx, f): repo = f.owner.workspace_name or ctx.workspace_name return repo if not f.dirname else "%s/%s" % (repo, f.dirname) seen = {} inc_dirs = [] for f in ctx.files.srcs: d = _rf_dir(ctx, f) if d not in seen: seen[d] = True inc_dirs.append(d) inc_flags = " ".join(['-I"${TEST_SRCDIR}/%s"' % d for d in inc_dirs]) # Arguments to verilator args = " ".join([a for a in ctx.attr.verilator_args]) # Always specify top module top = "--top-module {}".format(ctx.attr.top_module) content = "\n".join([ "#!/usr/bin/env bash", "set -euo pipefail", # Get correct path to binary, set VERILATOR_ROOT accordingly 'VERILATOR="${TEST_SRCDIR}/%s"' % verilator_rf, 'export VERILATOR_ROOT="$(dirname "$(dirname "$VERILATOR")")"', # Setup working directory and output directory 'WORKDIR="${TEST_TMPDIR}"', 'mkdir -p "${WORKDIR}"', 'cd "${WORKDIR}"', 'OUTDIR=obj_dir', 'mkdir "$OUTDIR"', '"${VERILATOR}" --Mdir "${OUTDIR}" -I${OUTDIR} ' + args + " " + inc_flags + " " + src_args + " " + top, ]) ctx.actions.write(output = script, content = content, is_executable = True) # Ensure the executable and its runfiles are present in the test’s runfiles. runfiles = ctx.runfiles(files = ctx.files.srcs + [ctx.executable.verilator]) runfiles = runfiles.merge(ctx.attr.verilator[DefaultInfo].default_runfiles) return DefaultInfo( executable = script, runfiles = runfiles ) verilator_internal_test = rule( doc = "Internal test rule for Verilator BCR release. Do not reuse.", implementation = _verilator_internal_test_impl, test = True, attrs = { "verilator": attr.label(executable = True, cfg = "target", default = "//:verilator_bin"), "srcs": attr.label_list(allow_files = [".sv", ".svh", ".v"]), "top_module": attr.string(doc = "The top module name"), "verilator_args": attr.string_list(), }, ) def _verilator_coverage_internal_test_impl(ctx): script = ctx.actions.declare_file(ctx.label.name + ".sh") def _rf(ctx, f): repo = f.owner.workspace_name or ctx.workspace_name return "{}/{}".format(repo, f.short_path) coverage_rf = _rf(ctx, ctx.executable.verilator_coverage) src_rfs = [_rf(ctx, f) for f in ctx.files.srcs] src_args = " ".join(['"${TEST_SRCDIR}/%s"' % s for s in src_rfs]) expected_rf = _rf(ctx, ctx.file.expected) content = "\n".join([ "#!/usr/bin/env bash", "set -euo pipefail", 'COVERAGE="${TEST_SRCDIR}/%s"' % coverage_rf, 'OUT="${TEST_TMPDIR}/coverage.info"', '"${COVERAGE}" --write-info "${OUT}" ' + src_args, 'diff -u "${TEST_SRCDIR}/%s" "${OUT}"' % expected_rf, ]) ctx.actions.write(output = script, content = content, is_executable = True) runfiles = ctx.runfiles( files = ctx.files.srcs + [ctx.file.expected, ctx.executable.verilator_coverage], ) runfiles = runfiles.merge(ctx.attr.verilator_coverage[DefaultInfo].default_runfiles) return DefaultInfo( executable = script, runfiles = runfiles, ) verilator_coverage_internal_test = rule( doc = "Internal coverage smoke test rule for Verilator BCR release.", implementation = _verilator_coverage_internal_test_impl, test = True, attrs = { "verilator_coverage": attr.label( executable = True, cfg = "target", default = "//:verilator_coverage_bin", ), "srcs": attr.label_list(allow_files = [".dat"]), "expected": attr.label( allow_single_file = True, mandatory = True, ), }, )