|  | # Copyright lowRISC contributors. | 
|  | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | load("//rules:rv.bzl", "rv_rule") | 
|  | load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain") | 
|  |  | 
|  | def _get_assembler(cc_toolchain): | 
|  | """Find the path to riscv-unknown-elf-as.""" | 
|  |  | 
|  | # Note: the toolchain config doesn"t appear to have a good way to get | 
|  | # access to the assembler.  We should be able to access it via the | 
|  | # the compiler, but I had trouble with //hw/ip/otbn/util/otbn_as.py invoking | 
|  | # the compiler as assembler. | 
|  | return [f for f in cc_toolchain.all_files.to_list() if f.basename.endswith("as")][0] | 
|  |  | 
|  | def _otbn_assemble_sources(ctx): | 
|  | """Helper function that, for each source file in the provided context, adds | 
|  | an action to the context that invokes the otbn assember (otbn_as.py), | 
|  | producing a corresponding object file. Returns a list of all object files | 
|  | that will be generated by these actions. | 
|  | """ | 
|  | cc_toolchain = find_cc_toolchain(ctx).cc | 
|  | assembler = _get_assembler(cc_toolchain) | 
|  |  | 
|  | objs = [] | 
|  | for src in ctx.files.srcs: | 
|  | obj = ctx.actions.declare_file(src.basename.replace("." + src.extension, ".o")) | 
|  | objs.append(obj) | 
|  | ctx.actions.run( | 
|  | outputs = [obj], | 
|  | inputs = ([src] + | 
|  | cc_toolchain.all_files.to_list() + | 
|  | [ctx.executable._otbn_as]), | 
|  | env = { | 
|  | "RV32_TOOL_AS": assembler.path, | 
|  | }, | 
|  | arguments = ["-o", obj.path, src.path], | 
|  | executable = ctx.executable._otbn_as, | 
|  | ) | 
|  |  | 
|  | return objs | 
|  |  | 
|  | def _otbn_library(ctx): | 
|  | """Produces a collection of object files, one per source file, that can be | 
|  | used as a dependency for otbn binaries.""" | 
|  | objs = _otbn_assemble_sources(ctx) | 
|  |  | 
|  | return [ | 
|  | DefaultInfo( | 
|  | files = depset(objs), | 
|  | data_runfiles = ctx.runfiles(files = objs), | 
|  | ), | 
|  | ] | 
|  |  | 
|  | def _otbn_binary(ctx): | 
|  | """The build process for otbn resources currently invokes | 
|  | `//hw/ip/otbn/util/otbn_{as,ld,...}.py` to build the otbn resource. | 
|  | These programs are python scripts which translate otbn special | 
|  | instructions into the proper opcode sequences and _then_ invoke the normal | 
|  | `rv32-{as,ld,...}` programs to produce the resource.  These "native" | 
|  | otbn resources are the `otbn_objs` and `elf` output groups. | 
|  |  | 
|  | In order to make the otbn resource useful to the the main CPU, the | 
|  | otbn resource needs to be included as a blob of data that the main | 
|  | CPU can dump into the otbn `imem` area and ask otbn to execute it. | 
|  | `util/otbn-build.py` does this with some objcopy-fu, emitting | 
|  | `foo.rv32embed.o`.  Bazel's `cc_*` rules really want dependency objects | 
|  | expressed as archives rather than raw object files, so I've modified | 
|  | `otbn-build` to also emit an archive file. | 
|  |  | 
|  | _Morally_, the otbn resource is a data dependency.  However the | 
|  | practical meaning of a `data` dependency in bazel is a file made | 
|  | available at runtime, which is not how we're using the otbn resource. | 
|  | The closest analog is something like `cc_embed_data`, which is like | 
|  | a data dependency that needs to be linked into the main program. | 
|  | We achieve by having `otbn_build.py` emit a conventional RV32I library | 
|  | that other rules can depend on in their `deps`. | 
|  | """ | 
|  | cc_toolchain = find_cc_toolchain(ctx).cc | 
|  | assembler = _get_assembler(cc_toolchain) | 
|  |  | 
|  | # Run the otbn assembler on source files to produce object (.o) files. | 
|  | objs = _otbn_assemble_sources(ctx) | 
|  |  | 
|  | # Declare output files. | 
|  | elf = ctx.actions.declare_file(ctx.attr.name + ".elf") | 
|  | rv32embed = ctx.actions.declare_file(ctx.attr.name + ".rv32embed.o") | 
|  | archive = ctx.actions.declare_file(ctx.attr.name + ".rv32embed.a") | 
|  |  | 
|  | deps = [f for dep in ctx.attr.deps for f in dep.files.to_list()] | 
|  |  | 
|  | # Run the otbn_build.py script to link object files from the sources and | 
|  | # dependencies. | 
|  | ctx.actions.run( | 
|  | outputs = [elf, rv32embed, archive], | 
|  | inputs = (objs + | 
|  | deps + | 
|  | cc_toolchain.all_files.to_list() + | 
|  | ctx.files._otbn_data + | 
|  | [ctx.executable._wrapper]), | 
|  | env = { | 
|  | "RV32_TOOL_AS": assembler.path, | 
|  | "RV32_TOOL_AR": cc_toolchain.ar_executable, | 
|  | "RV32_TOOL_LD": cc_toolchain.ld_executable, | 
|  | "RV32_TOOL_OBJCOPY": cc_toolchain.objcopy_executable, | 
|  | }, | 
|  | arguments = [ | 
|  | "--app-name={}".format(ctx.attr.name), | 
|  | "--archive", | 
|  | "--no-assembler", | 
|  | "--out-dir={}".format(elf.dirname), | 
|  | ] + [obj.path for obj in (objs + deps)], | 
|  | executable = ctx.executable._wrapper, | 
|  | ) | 
|  |  | 
|  | feature_configuration = cc_common.configure_features( | 
|  | ctx = ctx, | 
|  | cc_toolchain = cc_toolchain, | 
|  | requested_features = ctx.features, | 
|  | unsupported_features = ctx.disabled_features, | 
|  | ) | 
|  |  | 
|  | outputs = objs + [elf, rv32embed, archive] | 
|  | return [ | 
|  | DefaultInfo(files = depset(outputs), data_runfiles = ctx.runfiles(files = outputs)), | 
|  | OutputGroupInfo( | 
|  | otbn_objs = depset(objs + deps), | 
|  | elf = depset([elf]), | 
|  | rv32embed = depset([rv32embed]), | 
|  | archive = depset([archive]), | 
|  | ), | 
|  | # Emit a CcInfo provider so that this rule can be a dependency in other | 
|  | # cc_* rules. | 
|  | CcInfo( | 
|  | linking_context = cc_common.create_linking_context( | 
|  | linker_inputs = depset([cc_common.create_linker_input( | 
|  | owner = ctx.label, | 
|  | libraries = depset([cc_common.create_library_to_link( | 
|  | actions = ctx.actions, | 
|  | feature_configuration = feature_configuration, | 
|  | cc_toolchain = cc_toolchain, | 
|  | static_library = archive, | 
|  | )]), | 
|  | )]), | 
|  | ), | 
|  | ), | 
|  | ] | 
|  |  | 
|  | def _otbn_sim_test(ctx): | 
|  | """This rule is for standalone OTBN unit tests, which are run on the host | 
|  | via the OTBN simulator. | 
|  |  | 
|  | It first generates binaries using the same method as otbn_binary, then runs | 
|  | them on the simulator. Tests are expected to count failures in the w0 | 
|  | register; the test checks that w0=0 to determine if the test passed. | 
|  | """ | 
|  | providers = _otbn_binary(ctx) | 
|  |  | 
|  | # Extract the output .elf file from the output group. | 
|  | elf = providers[1].elf.to_list()[0] | 
|  |  | 
|  | exp_file = ctx.file.exp | 
|  |  | 
|  | # Create a simple script that runs the OTBN test wrapper on the .elf file | 
|  | # using the provided simulator path. | 
|  | sim_test_wrapper = ctx.executable._sim_test_wrapper | 
|  | simulator = ctx.executable._simulator | 
|  | ctx.actions.write( | 
|  | output = ctx.outputs.executable, | 
|  | content = "{} {} {} {}".format(sim_test_wrapper.short_path, simulator.short_path, exp_file.short_path, elf.short_path), | 
|  | ) | 
|  |  | 
|  | # Runfiles include sources, the .elf file, the simulator and test wrapper | 
|  | # themselves, and all the simulator and test wrapper runfiles. | 
|  | runfiles = ctx.runfiles(files = (ctx.files.srcs + [elf, exp_file, ctx.executable._simulator, ctx.executable._sim_test_wrapper])) | 
|  | runfiles = runfiles.merge(ctx.attr._simulator[DefaultInfo].default_runfiles) | 
|  | runfiles = runfiles.merge(ctx.attr._sim_test_wrapper[DefaultInfo].default_runfiles) | 
|  | return [ | 
|  | DefaultInfo(runfiles = runfiles), | 
|  | providers[1], | 
|  | ] | 
|  |  | 
|  | def _otbn_consttime_test_impl(ctx): | 
|  | """This rule checks if a program or subroutine is constant-time. | 
|  |  | 
|  | There are some limitations to this check; see the Python script's | 
|  | documentation for details. In particular, the check may not be able to | 
|  | determine that a program runs in constant-time when in fact it does. | 
|  | However, if the check passes, the program should always run in constant | 
|  | time; that is, the check can produce false negatives but never false | 
|  | positives. | 
|  |  | 
|  | This rule expects one dependency of an otbn_binary or otbn_sim_test type, | 
|  | which should provide exactly one `.elf` file. | 
|  | """ | 
|  |  | 
|  | # Extract the output .elf file from the output group. | 
|  | elf = [f for t in ctx.attr.deps for f in t[OutputGroupInfo].elf.to_list()] | 
|  | if len(elf) != 1: | 
|  | fail("Expected only one .elf file in dependencies, got: " + str(elf)) | 
|  | elf = elf[0] | 
|  |  | 
|  | # Write a very simple script that runs the checker. | 
|  | script_content = "{} {} --verbose".format(ctx.executable._checker.short_path, elf.short_path) | 
|  | if ctx.attr.subroutine: | 
|  | script_content += " --subroutine {}".format(ctx.attr.subroutine) | 
|  | if ctx.attr.secrets: | 
|  | script_content += " --secrets {}".format(" ".join(ctx.attr.secrets)) | 
|  | if ctx.attr.initial_constants: | 
|  | script_content += " --constants {}".format(" ".join(ctx.attr.initial_constants)) | 
|  | ctx.actions.write( | 
|  | output = ctx.outputs.executable, | 
|  | content = script_content, | 
|  | ) | 
|  |  | 
|  | # The .elf file must be added to runfiles in order to be visible to the | 
|  | # test at runtime. In addition, we need to add all the runfiles from the | 
|  | # checker script itself (e.g. the Python runtime and dependencies). | 
|  | runfiles = ctx.runfiles(files = [elf]) | 
|  | runfiles = runfiles.merge(ctx.attr._checker[DefaultInfo].default_runfiles) | 
|  | return [DefaultInfo(runfiles = runfiles)] | 
|  |  | 
|  | def _otbn_insn_count_range(ctx): | 
|  | """This rule gets min/max possible instruction counts for an OTBN program. | 
|  | """ | 
|  |  | 
|  | # Extract the .elf file to check from the dependency list. | 
|  | elf = [f for t in ctx.attr.deps for f in t[OutputGroupInfo].elf.to_list()] | 
|  | if len(elf) != 1: | 
|  | fail("Expected only one .elf file in dependencies, got: " + str(elf)) | 
|  | elf = elf[0] | 
|  |  | 
|  | # Command to run the counter script and extract the min/max values. | 
|  | out = ctx.actions.declare_file(ctx.attr.name + ".txt") | 
|  | ctx.actions.run_shell( | 
|  | outputs = [out], | 
|  | inputs = [ctx.file._counter, elf], | 
|  | command = "{} {} > {}".format(ctx.file._counter.path, elf.path, out.path), | 
|  | ) | 
|  |  | 
|  | runfiles = ctx.runfiles(files = ([out])) | 
|  | return [DefaultInfo(files = depset([out]), runfiles = runfiles)] | 
|  |  | 
|  | otbn_library = rv_rule( | 
|  | implementation = _otbn_library, | 
|  | attrs = { | 
|  | "srcs": attr.label_list(allow_files = True), | 
|  | "_cc_toolchain": attr.label( | 
|  | default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), | 
|  | ), | 
|  | "_otbn_as": attr.label( | 
|  | default = "//hw/ip/otbn/util:otbn_as", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | }, | 
|  | fragments = ["cpp"], | 
|  | toolchains = ["@rules_cc//cc:toolchain_type"], | 
|  | incompatible_use_toolchain_transition = True, | 
|  | ) | 
|  |  | 
|  | otbn_binary = rv_rule( | 
|  | implementation = _otbn_binary, | 
|  | attrs = { | 
|  | "srcs": attr.label_list(allow_files = True), | 
|  | "deps": attr.label_list(providers = [DefaultInfo]), | 
|  | "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), | 
|  | "_otbn_as": attr.label( | 
|  | default = "//hw/ip/otbn/util:otbn_as", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | "_otbn_data": attr.label( | 
|  | default = "//hw/ip/otbn/data:all_files", | 
|  | allow_files = True, | 
|  | ), | 
|  | "_wrapper": attr.label( | 
|  | default = "//util:otbn_build", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | }, | 
|  | fragments = ["cpp"], | 
|  | toolchains = ["@rules_cc//cc:toolchain_type"], | 
|  | incompatible_use_toolchain_transition = True, | 
|  | ) | 
|  |  | 
|  | otbn_sim_test = rv_rule( | 
|  | implementation = _otbn_sim_test, | 
|  | test = True, | 
|  | attrs = { | 
|  | "srcs": attr.label_list(allow_files = True), | 
|  | "deps": attr.label_list(providers = [DefaultInfo]), | 
|  | "exp": attr.label(allow_single_file = True), | 
|  | "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), | 
|  | "_otbn_as": attr.label( | 
|  | default = "//hw/ip/otbn/util:otbn_as", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | "_otbn_data": attr.label( | 
|  | default = "//hw/ip/otbn/data:all_files", | 
|  | allow_files = True, | 
|  | ), | 
|  | "_simulator": attr.label( | 
|  | default = "//hw/ip/otbn/dv/otbnsim:standalone", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | "_sim_test_wrapper": attr.label( | 
|  | default = "//hw/ip/otbn/util:otbn_sim_test", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | "_wrapper": attr.label( | 
|  | default = "//util:otbn_build", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | }, | 
|  | fragments = ["cpp"], | 
|  | toolchains = ["@rules_cc//cc:toolchain_type"], | 
|  | incompatible_use_toolchain_transition = True, | 
|  | ) | 
|  |  | 
|  | otbn_consttime_test = rule( | 
|  | implementation = _otbn_consttime_test_impl, | 
|  | test = True, | 
|  | attrs = { | 
|  | "srcs": attr.label_list(allow_files = True), | 
|  | "deps": attr.label_list(providers = [OutputGroupInfo]), | 
|  | "subroutine": attr.string(), | 
|  | "secrets": attr.string_list(), | 
|  | "initial_constants": attr.string_list(), | 
|  | "_checker": attr.label( | 
|  | default = "//hw/ip/otbn/util:check_const_time", | 
|  | executable = True, | 
|  | cfg = "exec", | 
|  | ), | 
|  | }, | 
|  | ) | 
|  |  | 
|  | otbn_insn_count_range = rule( | 
|  | implementation = _otbn_insn_count_range, | 
|  | attrs = { | 
|  | "deps": attr.label_list(providers = [OutputGroupInfo]), | 
|  | "_counter": attr.label( | 
|  | default = "//hw/ip/otbn/util:get_instruction_count_range.py", | 
|  | allow_single_file = True, | 
|  | ), | 
|  | }, | 
|  | ) |