|  | # Copyright lowRISC contributors. | 
|  | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | # TODO(drewmacrae) this should be in rules_cc | 
|  | # pending resolution of https://github.com/bazelbuild/rules_cc/issues/75 | 
|  | load("//rules:bugfix.bzl", "find_cc_toolchain") | 
|  |  | 
|  | """Rules to build OpenTitan for the RiscV target""" | 
|  |  | 
|  | OPENTITAN_CPU = "@bazel_embedded//constraints/cpu:riscv32" | 
|  | OPENTITAN_PLATFORM = "@bazel_embedded//platforms:opentitan_rv32imc" | 
|  |  | 
|  | _targets_compatible_with = { | 
|  | OPENTITAN_PLATFORM: [OPENTITAN_CPU], | 
|  | } | 
|  |  | 
|  | def _opentitan_transition_impl(settings, attr): | 
|  | return {"//command_line_option:platforms": attr.platform} | 
|  |  | 
|  | opentitan_transition = transition( | 
|  | implementation = _opentitan_transition_impl, | 
|  | inputs = [], | 
|  | outputs = ["//command_line_option:platforms"], | 
|  | ) | 
|  |  | 
|  | def _obj_transform(ctx): | 
|  | cc_toolchain = find_cc_toolchain(ctx) | 
|  | outputs = [] | 
|  | for src in ctx.files.srcs: | 
|  | binary = ctx.actions.declare_file("{}.{}".format(src.basename, ctx.attr.suffix)) | 
|  | outputs.append(binary) | 
|  | ctx.actions.run( | 
|  | outputs = [binary], | 
|  | inputs = [src] + cc_toolchain.all_files.to_list(), | 
|  | arguments = [ | 
|  | "--output-target", | 
|  | ctx.attr.format, | 
|  | src.path, | 
|  | binary.path, | 
|  | ], | 
|  | executable = cc_toolchain.objcopy_executable, | 
|  | ) | 
|  | return [DefaultInfo(files = depset(outputs), data_runfiles = ctx.runfiles(files = outputs))] | 
|  |  | 
|  | obj_transform = rule( | 
|  | implementation = _obj_transform, | 
|  | cfg = opentitan_transition, | 
|  | attrs = { | 
|  | "srcs": attr.label_list(allow_files = True), | 
|  | "suffix": attr.string(default = "bin"), | 
|  | "format": attr.string(default = "binary"), | 
|  | "platform": attr.string(default = OPENTITAN_PLATFORM), | 
|  | "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), | 
|  | "_allowlist_function_transition": attr.label( | 
|  | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | 
|  | ), | 
|  | }, | 
|  | toolchains = ["@rules_cc//cc:toolchain_type"], | 
|  | ) | 
|  |  | 
|  | def _elf_to_disassembly(ctx): | 
|  | cc_toolchain = find_cc_toolchain(ctx) | 
|  | outputs = [] | 
|  | for src in ctx.files.srcs: | 
|  | disassembly = ctx.actions.declare_file("{}.dis".format(src.basename)) | 
|  | outputs.append(disassembly) | 
|  | ctx.actions.run_shell( | 
|  | outputs = [disassembly], | 
|  | inputs = [src] + cc_toolchain.all_files.to_list(), | 
|  | arguments = [ | 
|  | cc_toolchain.objdump_executable, | 
|  | src.path, | 
|  | disassembly.path, | 
|  | ], | 
|  | command = "$1 --disassemble --headers --line-numbers --source $2 > $3", | 
|  | ) | 
|  | return [DefaultInfo(files = depset(outputs), data_runfiles = ctx.runfiles(files = outputs))] | 
|  |  | 
|  | elf_to_disassembly = rule( | 
|  | implementation = _elf_to_disassembly, | 
|  | cfg = opentitan_transition, | 
|  | attrs = { | 
|  | "srcs": attr.label_list(allow_files = True), | 
|  | "platform": attr.string(default = OPENTITAN_PLATFORM), | 
|  | "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), | 
|  | "_allowlist_function_transition": attr.label( | 
|  | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | 
|  | ), | 
|  | }, | 
|  | toolchains = ["@rules_cc//cc:toolchain_type"], | 
|  | incompatible_use_toolchain_transition = True, | 
|  | ) | 
|  |  | 
|  | def _elf_to_scrambled(ctx): | 
|  | outputs = [] | 
|  | for src in ctx.files.srcs: | 
|  | scrambled = ctx.actions.declare_file("{}.scr.40.vmem".format(src.basename)) | 
|  | outputs.append(scrambled) | 
|  | ctx.actions.run( | 
|  | outputs = [scrambled], | 
|  | inputs = [ | 
|  | src, | 
|  | ctx.files._tool[0], | 
|  | ctx.files._config[0], | 
|  | ], | 
|  | arguments = [ | 
|  | ctx.files._config[0].path, | 
|  | src.path, | 
|  | scrambled.path, | 
|  | ], | 
|  | executable = ctx.files._tool[0].path, | 
|  | ) | 
|  | return [DefaultInfo(files = depset(outputs), data_runfiles = ctx.runfiles(files = outputs))] | 
|  |  | 
|  | elf_to_scrambled = rule( | 
|  | implementation = _elf_to_scrambled, | 
|  | cfg = opentitan_transition, | 
|  | attrs = { | 
|  | "srcs": attr.label_list(allow_files = True), | 
|  | "platform": attr.string(default = OPENTITAN_PLATFORM), | 
|  | "_tool": attr.label(default = "//hw/ip/rom_ctrl/util:scramble_image.py", allow_files = True), | 
|  | "_config": attr.label(default = "//hw/top_earlgrey/data:autogen/top_earlgrey.gen.hjson", allow_files = True), | 
|  | "_allowlist_function_transition": attr.label( | 
|  | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | 
|  | ), | 
|  | }, | 
|  | ) | 
|  |  | 
|  | def opentitan_binary( | 
|  | name, | 
|  | platform = OPENTITAN_PLATFORM, | 
|  | per_device_deps = { | 
|  | "verilator": ["//sw/device/lib/arch:sim_verilator"], | 
|  | "dv": ["//sw/device/lib/arch:sim_dv"], | 
|  | "fpga_nexysvideo": ["//sw/device/lib/arch:fpga_nexysvideo"], | 
|  | "cw310": ["//sw/device/lib/arch:fpga_cw310"], | 
|  | }, | 
|  | output_bin = True, | 
|  | output_disassembly = True, | 
|  | output_scrambled = False, | 
|  | **kwargs): | 
|  | """A helper macro for generating OpenTitan binary artifacts. | 
|  | This macro is mostly a wrapper around cc_binary, but creates artifacts | 
|  | for each of the keys in `per_device_deps`.  The actual artifacts | 
|  | created are an ELF file, a BIN file, the disassembly and the scrambled | 
|  | ROM image.  Each of these output targets performs a bazel transition to | 
|  | the RV32I toolchain to build the target under the correct compiler. | 
|  | Args: | 
|  | @param name: The name of this rule. | 
|  | @param platform: The target platform for the artifacts. | 
|  | @param per_device_deps: The deps for each of the execution environments. | 
|  | @param output_bin: Whether or not to emit a BIN file. | 
|  | @param output_disassembly: Whether or not to emit a disassembly file. | 
|  | @param output_scrambled: Whether or not to emit a SCR file. | 
|  | @param **kwargs: Arguments to forward to `cc_binary`. | 
|  | Emits rules: | 
|  | For each device in per_device_deps entry: | 
|  | cc_binary     named: name_device | 
|  | obj_transform named: name_device_elf | 
|  | optionally: | 
|  | obj_transform       named: name_device_bin | 
|  | elf_to_dissassembly named: name_device_dis | 
|  | elf_to_scrambled    named: name_device_scr | 
|  | filegroup named: name | 
|  | with all the generated rules | 
|  | """ | 
|  |  | 
|  | copts = kwargs.pop("copts", []) + [ | 
|  | "-nostdlib", | 
|  | "-ffreestanding", | 
|  | ] | 
|  | linkopts = kwargs.pop("linkopts", []) + [ | 
|  | "-nostartfiles", | 
|  | "-nostdlib", | 
|  | ] | 
|  | deps = kwargs.pop("deps", []) | 
|  | targets = [] | 
|  | for (device, dev_deps) in per_device_deps.items(): | 
|  | devname = "{}_{}".format(name, device) | 
|  | native.cc_binary( | 
|  | name = devname, | 
|  | deps = deps + dev_deps, | 
|  | target_compatible_with = _targets_compatible_with[platform], | 
|  | copts = copts, | 
|  | linkopts = linkopts, | 
|  | **kwargs | 
|  | ) | 
|  | targets.append(":" + devname + "_elf") | 
|  | obj_transform( | 
|  | name = devname + "_elf", | 
|  | srcs = [devname], | 
|  | format = "elf32-little", | 
|  | suffix = "elf", | 
|  | platform = platform, | 
|  | ) | 
|  |  | 
|  | if output_bin: | 
|  | targets.append(":" + devname + "_bin") | 
|  | obj_transform( | 
|  | name = devname + "_bin", | 
|  | srcs = [devname], | 
|  | platform = platform, | 
|  | ) | 
|  |  | 
|  | if output_disassembly: | 
|  | targets.append(":" + devname + "_dis") | 
|  | elf_to_disassembly( | 
|  | name = devname + "_dis", | 
|  | srcs = [devname], | 
|  | platform = platform, | 
|  | ) | 
|  | if output_scrambled: | 
|  | targets.append(":" + devname + "_scr") | 
|  | elf_to_scrambled( | 
|  | name = devname + "_scr", | 
|  | srcs = [devname], | 
|  | platform = platform, | 
|  | ) | 
|  |  | 
|  | native.filegroup( | 
|  | name = name, | 
|  | srcs = targets, | 
|  | ) | 
|  |  | 
|  | def verilator_params( | 
|  | rom = "//sw/device/lib/testing/test_rom:test_rom_verilator_scr", | 
|  | otp = "//hw/ip/otp_ctrl/data:rma_image_verilator", | 
|  | tags = [ | 
|  | "cpu:4", | 
|  | ], | 
|  | timeout = "moderate", | 
|  | local = True, | 
|  | args = [ | 
|  | "console", | 
|  | "--exit-failure=FAIL", | 
|  | "--exit-success=PASS", | 
|  | "--timeout=3600", | 
|  | ], | 
|  | data = [], | 
|  | **kwargs): | 
|  | """A macro to create verilator parameters for OpenTitan functional tests. | 
|  |  | 
|  | This macro emits a dictionary of parameters which are pasted into the | 
|  | verilator specific test rule. | 
|  |  | 
|  | Args: | 
|  | @param rom: The ROM to use when booting verilator. | 
|  | @param otp: The OTP image to use when booting verilator. | 
|  | @param tags: The test tags to apply to the test rule. | 
|  | @param timeout: The timeout to apply to the test rule. | 
|  | @param local: Whether the test should be run locally and without sandboxing. | 
|  | @param args: Arguments to pass to the test. | 
|  | @param data: Data dependencies of the test. | 
|  | """ | 
|  | kwargs.update(rom = rom, otp = otp, tags = tags + ["verilator"], timeout = timeout, local = local, args = args, data = data) | 
|  | return kwargs | 
|  |  | 
|  | def cw310_params( | 
|  | tags = [], | 
|  | timeout = "moderate", | 
|  | local = True, | 
|  | args = [ | 
|  | "--exec=\"console -q -t0\"", | 
|  | "--exec=\"bootstrap $(location {test_bin})\"", | 
|  | "console", | 
|  | "--exit-failure=FAIL", | 
|  | "--exit-success=PASS", | 
|  | "--timeout=3600", | 
|  | ], | 
|  | data = [], | 
|  | **kwargs): | 
|  | """A macro to create CW310 parameters for OpenTitan functional tests. | 
|  |  | 
|  | This macro emits a dictionary of parameters which are pasted into the | 
|  | ChipWhisperer-310 specific test rule. | 
|  |  | 
|  | Args: | 
|  | @param tags: The test tags to apply to the test rule. | 
|  | @param timeout: The timeout to apply to the test rule. | 
|  | @param local: Whether the test should be run locally and without sandboxing. | 
|  | @param args: Arguments to pass to the test. | 
|  | @param data: Data dependencies of the test. | 
|  | """ | 
|  | kwargs.update(tags = tags + ["cw310", "exclusive"], timeout = timeout, local = local, args = args, data = data) | 
|  | return kwargs | 
|  |  | 
|  | def _format_list(name, list1, datadict, **kwargs): | 
|  | """Concatenate and format list items. | 
|  |  | 
|  | This is used to prepare substitutions in user-supplied args to the | 
|  | various test invocations (ie: the location of test_bin). | 
|  | Args: | 
|  | @param name: The name of the item in `datadict`. | 
|  | @param list1: A list of items to prepend to the list item from datadict. | 
|  | @param datadict: A dictionary of per-test parameters. | 
|  | @param **kwargs: Values to pass to the format function. | 
|  | Returns: | 
|  | list[str] | 
|  | """ | 
|  | return [x.format(**kwargs) for x in list1 + datadict.pop(name, [])] | 
|  |  | 
|  | _OTTF_DEPS = [ | 
|  | "//sw/device/lib/arch:device", | 
|  | "//sw/device/lib/base", | 
|  | "//sw/device/lib/runtime:hart", | 
|  | "//sw/device/lib/runtime:log", | 
|  | "//sw/device/lib/runtime:print", | 
|  | "//sw/device/lib/crt", | 
|  | "//sw/device/lib/testing/test_framework:ottf_start", | 
|  | "//sw/device/lib/testing/test_framework:ottf", | 
|  | "//sw/device/lib/base:mmio", | 
|  | ] | 
|  |  | 
|  | def _unique_deps(*deplists): | 
|  | uniq = {} | 
|  | for deplist in deplists: | 
|  | for dep in deplist: | 
|  | uniq[dep] = True | 
|  | return uniq.keys() | 
|  |  | 
|  | def opentitan_functest( | 
|  | name, | 
|  | platform = OPENTITAN_PLATFORM, | 
|  | targets = ["verilator", "cw310"], | 
|  | args = [], | 
|  | data = [], | 
|  | ottf = _OTTF_DEPS, | 
|  | verilator = None, | 
|  | cw310 = None, | 
|  | **kwargs): | 
|  | """A helper macro for generating OpenTitan functional tests. | 
|  | This macro is mostly a wrapper around opentitan_binary, but creates | 
|  | testing artifacts for each of the keys in `per_device_deps`. | 
|  | The testing artifacts are then given to an `sh_test` rule which | 
|  | dispatches the test via opentitantool. | 
|  | Args: | 
|  | @param name: The name of this rule. | 
|  | @param platform: The target platform for the artifacts. | 
|  | @param targets: A list of targets on which to dispatch tests. | 
|  | @param args: Extra arguments to pass to `opentitantool`. | 
|  | @param data: Extra data dependencies needed while executing the test. | 
|  | @param ottf: Default dependencies for OTTF tests.  Set to empty list if | 
|  | your test doesn't use the OTTF. | 
|  | @param **kwargs: Arguments to forward to `opentitan_binary`. | 
|  |  | 
|  | This macro emits the following rules: | 
|  | opentitan_binary named: {name}_prog (and all of its emitted rules). | 
|  | sh_test named:          verilator_{name} | 
|  | sh_test named:          cw310_{name} | 
|  | test_suite named:       {name} | 
|  | """ | 
|  |  | 
|  | deps = _unique_deps(kwargs.pop("deps", []), ottf) | 
|  | opentitan_binary( | 
|  | name = name + "_prog", | 
|  | platform = platform, | 
|  | output_disassembly = True, | 
|  | deps = deps, | 
|  | **kwargs | 
|  | ) | 
|  |  | 
|  | all_tests = [] | 
|  |  | 
|  | if "verilator" in targets: | 
|  | test_name = "verilator_{}".format(name) | 
|  | test_bin = "{}_prog_verilator_elf".format(name) | 
|  |  | 
|  | if verilator == None: | 
|  | verilator = verilator_params() | 
|  | rom = verilator.pop("rom") | 
|  | otp = verilator.pop("otp") | 
|  | vargs = _format_list("args", args, verilator, test_bin = test_bin) | 
|  | vdata = _format_list("data", data, verilator, test_bin = test_bin) | 
|  |  | 
|  | if "manual" not in verilator.get("tags", []): | 
|  | all_tests.append(test_name) | 
|  |  | 
|  | native.sh_test( | 
|  | name = test_name, | 
|  | srcs = ["//util:opentitantool_test_runner.sh"], | 
|  | args = [ | 
|  | "--rcfile=", | 
|  | "--logging=info", | 
|  | "--interface=verilator", | 
|  | "--conf=sw/host/opentitantool/config/opentitan_verilator.json", | 
|  | "--verilator-bin=$(location //hw:verilator)/sim-verilator/Vchip_sim_tb", | 
|  | "--verilator-rom=$(location {})".format(rom), | 
|  | "--verilator-flash=$(location {})".format(test_bin), | 
|  | "--verilator-otp=$(location {})".format(otp), | 
|  | ] + vargs, | 
|  | data = [ | 
|  | test_bin, | 
|  | rom, | 
|  | otp, | 
|  | "//sw/host/opentitantool:test_resources", | 
|  | "//hw:verilator", | 
|  | ] + vdata, | 
|  | **verilator | 
|  | ) | 
|  |  | 
|  | if "cw310" in targets: | 
|  | test_name = "cw310_{}".format(name) | 
|  | test_bin = "{}_prog_cw310_bin".format(name) | 
|  |  | 
|  | if cw310 == None: | 
|  | cw310 = cw310_params() | 
|  | cargs = _format_list("args", args, cw310, test_bin = test_bin) | 
|  | cdata = _format_list("data", data, cw310, test_bin = test_bin) | 
|  |  | 
|  | if "manual" not in verilator.get("tags", []): | 
|  | all_tests.append(test_name) | 
|  |  | 
|  | native.sh_test( | 
|  | name = test_name, | 
|  | srcs = ["//util:opentitantool_test_runner.sh"], | 
|  | args = [ | 
|  | "--rcfile=", | 
|  | "--logging=info", | 
|  | "--interface=cw310", | 
|  | "--conf=sw/host/opentitantool/config/opentitan_cw310.json", | 
|  | ] + cargs, | 
|  | data = [ | 
|  | test_bin, | 
|  | "//sw/host/opentitantool:test_resources", | 
|  | ] + cdata, | 
|  | **cw310 | 
|  | ) | 
|  |  | 
|  | native.test_suite( | 
|  | name = name, | 
|  | tests = all_tests, | 
|  | ) |