| # 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] |
| |
| # Create a simple script that runs the OTBN simulator on the .elf file and |
| # checks if the w0 register is 0. |
| simulator_cmd = "{} {} --dump-regs -".format(ctx.file._simulator.path, elf.short_path) |
| expected_string = "0x" + ("0" * 64) |
| script = ''' |
| echo "Running simulator: {simulator_cmd}" |
| result=$({simulator_cmd} | grep -m 1 -P "w0 = [0-9A-Fa-f]+") |
| echo "Got : $result" |
| echo "Expected: w0 = {expected_string}" |
| if [[ "$result" == *"{expected_string}" ]]; then |
| echo "PASS" |
| exit 0 |
| fi |
| echo "FAIL" |
| exit 1 |
| '''.format(simulator_cmd = simulator_cmd, expected_string = expected_string) |
| script_file = ctx.actions.declare_file("run_{}.sh".format(elf.basename)) |
| ctx.actions.write( |
| output = script_file, |
| content = script, |
| ) |
| |
| # The simulator and .elf file must be added to runfiles in order to be |
| # visible to the test at runtime. |
| runfiles = ctx.runfiles(files = (ctx.files.srcs + [elf, ctx.file._simulator])) |
| return [DefaultInfo(runfiles = runfiles, executable = script_file)] |
| |
| 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]), |
| "_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, |
| ), |
| # TODO: make the simulator target an executable and update this |
| # dependency to match the others. |
| "_simulator": attr.label(default = "//hw/ip/otbn/dv/otbnsim:standalone.py", allow_single_file = 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, |
| ) |