blob: e210c2eeaa7a21307065bf42a00a87d009738231 [file] [log] [blame]
# 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,
)