| # 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/base", | |
| "//sw/device/lib/runtime:hart", | |
| "//sw/device/lib/runtime:log", | |
| "//sw/device/lib/runtime:print", | |
| "//sw/device/lib/arch:device", | |
| "//sw/device/lib/testing/test_rom_ext", | |
| "//sw/device/lib/crt", | |
| "//sw/device/lib/testing/test_framework:ottf", | |
| "//sw/device/lib/testing/test_framework:ottf_isrs", | |
| "//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, | |
| ) |