"""Utility rules used to compile Verilator""" load("@rules_venv//python:py_test.bzl", "py_test") 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 _collect_tool_files(target): """Collect files and default runfiles from a target into a depset for tools.""" info = target[DefaultInfo] transitive = [info.files] if info.default_runfiles: transitive.append(info.default_runfiles.files) return depset(transitive = transitive) def _resolve_pkgdatadir(files): """Derive the bison PKGDATADIR path from the provided files.""" if len(files) == 1 and files[0].is_directory: return files[0].path for f in files: idx = f.path.find("/data/") if idx >= 0: return f.path[:idx + 5] if f.path.startswith("data/"): return "data" fail("Could not determine BISON_PKGDATADIR: no 'data' directory found in provided files") def _verilator_bisonpre_impl(ctx): m4 = ctx.file.m4 pkgdatadir = _resolve_pkgdatadir(ctx.files.bison_pkgdatadir) env = { "BISON_PKGDATADIR": pkgdatadir, "M4": m4.path, } args = ctx.actions.args() args.add(ctx.file.bisonpre) args.add("--yacc", ctx.executable.bison) 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 = [ _collect_tool_files(ctx.attr.bison), _collect_tool_files(ctx.attr.m4), _collect_tool_files(ctx.attr.bison_pkgdatadir), ], ) 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 = 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 = { "bison": attr.label( doc = "The bison binary to use.", default = Label("@bison"), cfg = "exec", executable = True, ), "bison_pkgdatadir": attr.label( doc = "Bison's data directory (containing m4sugar, skeletons, etc.).", default = Label("@bison//:bison_pkgdatadir"), allow_files = True, ), "bisonpre": attr.label( doc = "The path to the `bisonpre` tool.", allow_single_file = True, mandatory = True, cfg = "exec", ), "m4": attr.label( doc = "The m4 binary to use.", default = Label("@bison//:m4"), cfg = "exec", allow_single_file = True, ), "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"), ), }, ) def _find_flex_src(ctx): srcs = ctx.files.src if len(srcs) != 1: fail("Expected exactly one source file from `{}`, got: {}".format( ctx.attr.src.label, srcs, )) return 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_test(*, name, verilator_args, srcs = [], data = [], **kwargs): """Smoke test macro that invokes verilator with the given args. Args: name: Test target name. verilator_args: List of args to pass to the verilator binary. srcs: Verilog source files to pass to verilator. data: Additional data dependencies. **kwargs: Passed through to py_test. """ py_test( name = name, main = "//private:verilator_smoke_test.py", srcs = ["//private:verilator_smoke_test.py"], data = [ "//:verilator", "//:verilator_includes", "//:include/verilated_std.sv", ] + srcs + data, args = verilator_args + ["$(rlocationpath " + s + ")" for s in srcs], env = { "VERILATOR_RLOCATIONPATH": "$(rlocationpath //:verilator)", "VERILATOR_STD_SV_RLOCATIONPATH": "$(rlocationpath //:include/verilated_std.sv)", }, deps = ["@rules_venv//python/runfiles"] + kwargs.pop("deps", []), **kwargs )