blob: bf4886aa133ea17ba5941f89ca0fdf5a2484a4c1 [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""Rules to build Matcha for the RiscV target"""
load(
"@lowrisc_opentitan//rules:opentitan.bzl",
"bin_to_vmem",
"opentitan_binary",
"scramble_flash_vmem",
"sign_bin",
)
load(
"@lowrisc_opentitan//rules:rv.bzl",
"rv_rule",
_OPENTITAN_CPU = "OPENTITAN_CPU",
_OPENTITAN_PLATFORM = "OPENTITAN_PLATFORM",
_opentitan_transition = "opentitan_transition",
)
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
load("@rules_pkg//:pkg.bzl", "pkg_tar")
# Re-exports of names from transition.bzl; many files in the repo use opentitan.bzl
# to get to them.
OPENTITAN_CPU = _OPENTITAN_CPU
OPENTITAN_PLATFORM = _OPENTITAN_PLATFORM
opentitan_transition = _opentitan_transition
KELVIN_PLATFORM = "//platforms/riscv32:kelvin"
# Currently set to secure core's IMC extensions.
SMC_PLATFORM = OPENTITAN_PLATFORM
# List of supported riscv core targets.
VERILATOR_CORE_TARGETS = {
"secure_core": "@matcha//sw/device/lib/arch:sim_verilator",
"smc": "@matcha//sw/device/lib/arch:smc_sim_verilator",
}
DV_CORE_TARGETS = {
"secure_core": "@matcha//sw/device/lib/arch:sim_dv",
"smc": "@matcha//sw/device/lib/arch:smc_sim_dv",
}
NEXUS_CORE_TARGETS = {
"secure_core": "@matcha//sw/device/lib/arch:sc_fpga_nexus",
"smc": "@matcha//sw/device/lib/arch:smc_fpga_nexus",
}
ASIC_CORE_TARGETS = {
"secure_core": "@matcha//sw/device/lib/arch:sc_asic",
"smc": "@matcha//sw/device/lib/arch:smc_asic",
}
MATCHA_COPTS = [
"-Werror",
"-Wall",
"-Wno-unused-function",
]
# This helper function generates a dictionary of per-device dependencies which is used to
# generate slightly different binaries for each hardware target, including two
# simulation platforms (DV and Verilator), and one FPGA platform (Nexus),
# given a target core from VERILATOR_CORE_TARGETS.
def device_deps(core):
per_device_deps = {
"sim_verilator": [VERILATOR_CORE_TARGETS.get(core)],
"sim_dv": [DV_CORE_TARGETS.get(core)],
"fpga_nexus": [NEXUS_CORE_TARGETS.get(core)],
"asic": [ASIC_CORE_TARGETS.get(core)],
}
return per_device_deps
def _elf_to_scrambled_rom_impl(ctx):
outputs = []
for src in ctx.files.srcs:
if src.extension != "elf":
fail("only ROM images in the ELF format may be converted to the VMEM format and scrambled.")
scrambled = ctx.actions.declare_file(
"{}.39.scr.vmem".format(
# Remove ".elf" from file basename.
src.basename.replace("." + src.extension, ""),
),
)
outputs.append(scrambled)
ctx.actions.run(
outputs = [scrambled],
inputs = [
src,
ctx.executable._scramble_tool,
ctx.file._config,
],
arguments = [
ctx.file._config.path,
src.path,
scrambled.path,
],
executable = ctx.executable._scramble_tool,
)
return [DefaultInfo(
files = depset(outputs),
data_runfiles = ctx.runfiles(files = outputs),
)]
elf_to_scrambled_rom_vmem = rv_rule(
implementation = _elf_to_scrambled_rom_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
"_scramble_tool": attr.label(
default = "@lowrisc_opentitan//hw/ip/rom_ctrl/util:scramble_image",
executable = True,
cfg = "exec",
),
"_config": attr.label(
default = "@//hw/top_matcha:top_gen_rom_ctrl_hjson",
allow_single_file = True,
),
},
)
ArchiveInfo = provider(fields = ["archive_infos"])
def _pick_correct_archive_for_device(ctx):
cc_infos = []
for dep in ctx.attr.deps:
if CcInfo in dep:
cc_info = dep[CcInfo]
elif ArchiveInfo in dep:
cc_info = dep[ArchiveInfo].archive_infos[ctx.attr.device]
else:
fail("Expected either a CcInfo or an ArchiveInfo")
cc_infos.append(cc_info)
return [cc_common.merge_cc_infos(cc_infos = cc_infos)]
pick_correct_archive_for_device = rv_rule(
implementation = _pick_correct_archive_for_device,
attrs = {
"deps": attr.label_list(allow_files = True),
"device": attr.string(),
"platform": attr.string(),
},
fragments = ["cpp"],
toolchains = ["@rules_cc//cc:toolchain_type"],
)
def opentitan_rom_binary(
name,
per_device_deps = device_deps("secure_core"),
platform = OPENTITAN_PLATFORM,
testonly = False,
**kwargs):
"""A helper macro for generating OpenTitan binary artifacts for ROM.
This macro is mostly a wrapper around the `opentitan_binary` macro, but also
creates artifacts for each of the keys in `PER_DEVICE_DEPS`. The actual
artifacts created are outputs of the rules emitted by the `opentitan_binary`
macro and those listed below.
Args:
name: The name of this rule.
per_device_deps: List of devices and device deps to build the target for.
platform: The target platform for the artifacts.
testonly: The target is built for test only.
**kwargs: Arguments to forward to `opentitan_binary`.
Emits rules:
For each device in per_device_deps entry:
rules emitted by `opentitan_binary` named: see `opentitan_binary` macro
bin_to_rom_vmem named: <name>_<device>_vmem
elf_to_scrambled_rom_vmem named: <name>_<device>_scr_vmem
filegroup named: <name>_<device>
Containing all targets for a single device for the above generated rules.
filegroup named: <name>
Containing all targets across all devices for the above generated rules.
"""
deps = kwargs.pop("deps", [])
# Place the default copts first to allow per-target override.
copts = MATCHA_COPTS + kwargs.pop("copts", [])
kwargs.update({"copts": copts})
all_targets = []
for (device, dev_deps) in per_device_deps.items():
if device not in device_deps("secure_core"):
fail("invalid device; device must be in {}".format(device_deps("secure_core").keys()))
devname = "{}_{}".format(name, device)
dev_targets = []
# Generate ELF, Binary, Disassembly, and (maybe) sim_dv logs database
dev_targets.extend(opentitan_binary(
name = devname,
deps = deps + dev_deps,
extract_sw_logs_db = device == "sim_dv",
testonly = testonly,
**kwargs
))
# We need to generate VMEM files even for FPGA devices, because we use
# them for bitstream splicing.
elf_name = "{}.{}".format(devname, "elf")
bin_name = "{}_{}".format(devname, "bin")
# Generate Un-scrambled ROM VMEM
vmem_name = "{}_vmem".format(devname)
dev_targets.append(":" + vmem_name)
bin_to_vmem(
name = vmem_name,
bin = bin_name,
platform = platform,
testonly = testonly,
word_size = 32,
)
# Generate Scrambled ROM VMEM
scr_vmem_name = "{}_scr_vmem".format(devname)
dev_targets.append(":" + scr_vmem_name)
elf_to_scrambled_rom_vmem(
name = scr_vmem_name,
srcs = [elf_name],
platform = platform,
testonly = testonly,
)
# Create a filegroup with just the current device's targets.
native.filegroup(
name = devname,
srcs = dev_targets,
testonly = testonly,
)
all_targets.extend(dev_targets)
# Create a filegroup with just all targets from all devices.
native.filegroup(
name = name,
srcs = all_targets,
testonly = testonly,
)
def flash_binary(
name,
per_device_deps = device_deps("secure_core"),
platform = OPENTITAN_PLATFORM,
signing_keys = {
"fake_test_key_0": "@lowrisc_opentitan//sw/device/silicon_creator/rom/keys/fake:test_private_key_0",
},
signed = False,
sim_otp = None,
testonly = False,
manifest = None,
var_name = None,
word_size = 32,
**kwargs):
"""A helper macro for generating binary artifacts for a target core.
This macro is mostly a wrapper around the `opentitan_binary` macro, but also
creates artifacts for each of the keys in `PER_DEVICE_DEPS`, and if signing
is enabled, each of the keys in `signing_keys`. The actual artifacts created
artifacts created are outputs of the rules emitted by the `opentitan_binary`
macro and those listed below.
Args:
name: The name of this rule.
per_device_deps: List of devices and device deps to build the target for.
platform: The target platform for the artifacts.
signing_keys: The signing keys for to sign each BIN file with.
signed: Whether or not to emit signed binary/VMEM files.
sim_otp: OTP image that contains flash scrambling keys / enablement flag
(only relevant for VMEM files built for sim targets).
testonly: The target is built for test only.
manifest: Partially populated manifest to set boot stage/slot configs.
var_name: The variable name of the array in the c header file.
word_size: word_size for vmem. For SEC (flash) it can be 64, but
SMC can only take 32.
**kwargs: Arguments to forward to `opentitan_binary`.
Emits rules:
For each device in per_device_deps entry:
rules emitted by `opentitan_binary` named: see `opentitan_binary` macro
bin_to_vmem named: <name>_<device>_vmem64
scrambled_flash_vmem named: <name>_<device>_scr_vmem64
Optionally:
sign_bin named: <name>_<device>_bin_signed_<key_name>
bin_to_vmem named: <name>_<device>_vmem64_signed_<key_name>
scrambled_flash_vmem named: <name>_<device>_scr_vmem64_signed_<key_name>
filegroup named: <name>_<device>
Containing all targets for a single device for the above generated rules.
filegroup named: <name>
Containing all targets across all devices for the above generated rules.
"""
deps = kwargs.pop("deps", [])
# Place the default copts first to allow per-target override.
copts = MATCHA_COPTS + kwargs.pop("copts", [])
kwargs.update({"copts": copts})
all_targets = []
for (device, dev_deps) in per_device_deps.items():
if device not in device_deps("secure_core"):
fail("invalid device; device must be in {}".format(device_deps("secure_core").keys()))
devname = "{}_{}".format(name, device)
dev_targets = []
depname = "{}_deps".format(devname)
_platform = "@matcha//platforms/riscv32:sparrow" if device == "asic" else platform
pick_correct_archive_for_device(
name = depname,
platform = _platform,
deps = deps + dev_deps,
device = device,
testonly = testonly,
)
# Generate ELF, Binary, Disassembly, and (maybe) sim_dv logs database
dev_targets.extend(opentitan_binary(
name = devname,
deps = [depname],
extract_sw_logs_db = device == "sim_dv",
testonly = testonly,
**kwargs
))
bin_name = "{}_{}".format(devname, "bin")
# Sign BIN (if required) and generate scrambled VMEM images.
if signed:
if manifest == None:
fail("A 'manifest' must be provided in order to sign flash images.")
for (key_name, key) in signing_keys.items():
# Sign the Binary.
signed_bin_name = "{}_bin_signed_{}".format(devname, key_name)
dev_targets.append(":" + signed_bin_name)
sign_bin(
name = signed_bin_name,
bin = bin_name,
key = key,
key_name = key_name,
manifest = manifest,
testonly = testonly,
)
# We only need to generate VMEM files for sim devices.
if device in ["sim_dv", "sim_verilator"]:
# Generate a VMEM64 from the signed binary.
signed_vmem_name = "{}_vmem_signed_{}".format(
devname,
key_name,
)
dev_targets.append(":" + signed_vmem_name)
bin_to_vmem(
name = signed_vmem_name,
bin = signed_bin_name,
platform = platform,
testonly = testonly,
word_size = word_size,
)
# Scramble / compute ECC for signed VMEM64.
scr_signed_vmem_name = "{}_scr_vmem_signed_{}".format(
devname,
key_name,
)
dev_targets.append(":" + scr_signed_vmem_name)
scramble_flash_vmem(
name = scr_signed_vmem_name,
otp = sim_otp,
vmem = signed_vmem_name,
platform = platform,
testonly = testonly,
)
# We only need to generate VMEM files for sim devices.
if device in ["sim_dv", "sim_verilator"]:
# Generate a VMEM64 from the binary.
vmem_name = "{}_vmem".format(devname)
dev_targets.append(":" + vmem_name)
bin_to_vmem(
name = vmem_name,
bin = bin_name,
platform = platform,
testonly = testonly,
word_size = word_size,
)
# Scramble / compute ECC for VMEM.
scr_vmem_name = "{}_scr_vmem".format(devname)
dev_targets.append(":" + scr_vmem_name)
scramble_flash_vmem(
name = scr_vmem_name,
otp = sim_otp,
vmem = vmem_name,
platform = platform,
testonly = testonly,
)
if (device == "fpga_nexus" or device == "asic") and var_name:
# Generate c header based on the bin file
bin_c_header = "{}_bin_c".format(devname)
dev_targets.append(":" + bin_c_header)
bin_to_c_file(
name = bin_c_header,
srcs = [bin_name],
var_name = var_name,
)
# Create a filegroup with just the current device's targets.
native.filegroup(
name = devname,
srcs = dev_targets,
testonly = testonly,
)
all_targets.extend(dev_targets)
# Create a filegroup with just all targets from all devices.
native.filegroup(
name = name,
srcs = all_targets,
testonly = testonly,
)
def sec_flash_binary(
name,
per_device_deps = device_deps("secure_core"),
signing_keys = {
"fake_test_key_0": "@lowrisc_opentitan//sw/device/silicon_creator/rom/keys/fake:test_private_key_0",
},
signed = False,
**kwargs):
"""A helper macro for generating secure core binary artifacts, using the flash_binary wrapper.
Args:
name: The name of this rule.
signing_keys: The signing keys for to sign each BIN file with.
signed: Whether or not to emit signed binary/VMEM files.
per_device_deps: The deps for each of the hardware target.
**kwargs: Arguments to forward to `flash_binary`.
"""
flash_binary(
name = name,
platform = OPENTITAN_PLATFORM,
per_device_deps = per_device_deps,
signing_keys = signing_keys,
signed = signed,
word_size = 64, # Backdoor-load VMEM image uses 64-bit words
**kwargs
)
def smc_flash_binary(
name,
per_device_deps = device_deps("smc"),
signing_keys = {
"fake_test_key_0": "@lowrisc_opentitan//sw/device/silicon_creator/rom/keys/fake:test_private_key_0",
},
signed = False,
var_name = "smc_bin",
**kwargs):
"""A helper macro for generating SMC binary artifacts, using the flash_binary_wrapper.
Args:
name: The name of this rule.
signing_keys: The signing keys for to sign each BIN file with.
signed: Whether or not to emit signed binary/VMEM files.
per_device_deps: The deps for each of the hardware target.
var_name: The variable name of the array in the c header file.
**kwargs: Arguments to forward to `flash_binary`.
Emit rules:
{name}_bin_c_header: A header file generated by bin_to_c_file for FPGA
"""
flash_binary(
name = name,
platform = SMC_PLATFORM,
per_device_deps = per_device_deps,
signing_keys = signing_keys,
signed = signed,
var_name = var_name,
word_size = 32,
**kwargs
)
def matcha_extflash_tar(
name,
sc_binary,
smc_binary = None,
kelvin_binary = None,
data = None,
**kwargs):
"""A rule for creating a tarball containing program code and resources.
Args:
name: The name of the output tarball. The .tar extension is automatically added.
sc_binary: The binary for the secure core. This will be named matcha-tock-bundle.bin.
smc_binary: The binary for the system management core. This will be named smc.bin, and is optional.
kelvin_binary: The binary for the Kelvin core. This will be named kelvin.bin, and is optional.
data: A dictionary of extra files to add to the archive. The key is the input file, and the value is filename in the archive.
**kwargs: Arguments forwared to pkg_tar
"""
files = {
sc_binary: "matcha-tock-bundle.bin",
}
if smc_binary:
files[smc_binary] = "smc.bin"
if kelvin_binary:
files[kelvin_binary] = "kelvin.bin"
if data:
files.update(data)
pkg_tar(
name = name,
files = files,
**kwargs
)
def bin_to_c_file(
name,
srcs,
var_name = "data",
testonly = False,
**kwargs):
"""A rule to merge binary files into a C include file.
Args:
name: The name of the target.
srcs: The input binary source files.
var_name: The variable name of the array in output C include file.
testonly: The target is built for test only.
**kwargs: Extra arguments forwards to genrule.
"""
xxd_cmd = """
xxd -i $< > $@;
sed -i -e 's#\\w*_len#{}_len#g' $@;
sed -i -e "s#\\w*\\[\\]#{}\\[\\]#g" $@;
sed -i -e 's/unsigned/const unsigned/g' $@
""".format(var_name, var_name)
outs = ["{}.{}".format(name, "h")]
native.genrule(
name = name,
srcs = srcs,
outs = outs,
cmd = xxd_cmd,
testonly = testonly,
**kwargs
)
def _kelvin_transition_impl(settings, attr):
return {"//command_line_option:platforms": KELVIN_PLATFORM}
kelvin_transition = transition(
implementation = _kelvin_transition_impl,
inputs = [],
outputs = ["//command_line_option:platforms"],
)
def kelvin_rule(**kwargs):
"""
A wrapper over rule() for creating rules that trigger
the transition to the kelvin platform config.
"""
attrs = kwargs.pop("attrs", {})
if "platform" not in attrs:
attrs["platform"] = attr.string(default = KELVIN_PLATFORM)
attrs["_allowlist_function_transition"] = attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
)
return rule(
cfg = kelvin_transition,
attrs = attrs,
**kwargs
)
def _kelvin_binary_impl(ctx):
"""Implements compilation for kelvin executables.
This rule compiles and links provided input into an executable
suitable for use on the Kelvin core. Generates both an ELF
and a BIN.
Args:
srcs: Input source files.
deps: Target libraries that the binary depends upon.
hdrs: Header files that are local to the binary.
copts: Flags to pass along to the compiler.
defines: Preprocessor definitions.
linkopts: Flags to pass along to the linker.
Output:
OutputGroupsInfo to allow definition of filegroups
containing the output ELF and BIN.
"""
cc_toolchain = find_cc_toolchain(ctx).cc
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features,
unsupported_features = ctx.disabled_features,
)
compilation_contexts = []
linking_contexts = []
for dep in ctx.attr.deps:
if CcInfo in dep:
compilation_contexts.append(dep[CcInfo].compilation_context)
linking_contexts.append(dep[CcInfo].linking_context)
(_compilation_context, compilation_outputs) = cc_common.compile(
actions = ctx.actions,
cc_toolchain = cc_toolchain,
feature_configuration = feature_configuration,
name = ctx.label.name,
srcs = ctx.files.srcs,
compilation_contexts = compilation_contexts,
private_hdrs = ctx.files.hdrs,
user_compile_flags = ctx.attr.copts,
defines = ctx.attr.defines,
)
linking_outputs = cc_common.link(
name = "{}.elf".format(ctx.label.name),
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
compilation_outputs = compilation_outputs,
linking_contexts = linking_contexts,
user_link_flags = ctx.attr.linkopts + [
"-Wl,-T,{}".format(ctx.file.linker_script.path),
# binutils >= 2.39 checks the loadable segments RWX setting by default.
"-Wl,--no-warn-rwx-segments",
],
additional_inputs = depset([ctx.file.linker_script] + ctx.files.linker_script_includes),
output_type = "executable",
)
binary = ctx.actions.declare_file(
"{}.bin".format(
ctx.attr.name,
),
)
ctx.actions.run(
outputs = [binary],
inputs = [linking_outputs.executable] + cc_toolchain.all_files.to_list(),
arguments = [
"-g",
"-O",
"binary",
linking_outputs.executable.path,
binary.path,
],
executable = cc_toolchain.objcopy_executable,
)
return [
DefaultInfo(
files = depset([linking_outputs.executable, binary]),
),
OutputGroupInfo(
all_files = depset([linking_outputs.executable, binary]),
elf_file = depset([linking_outputs.executable]),
bin_file = depset([binary]),
),
]
kelvin_binary_impl = kelvin_rule(
implementation = _kelvin_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
"deps": attr.label_list(allow_empty = True, providers = [CcInfo]),
"hdrs": attr.label_list(allow_files = [".h"], allow_empty = True),
"copts": attr.string_list(),
"defines": attr.string_list(),
"linkopts": attr.string_list(),
"linker_script": attr.label(allow_single_file = True),
"linker_script_includes": attr.label_list(default = [], allow_files = True),
"_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
},
fragments = ["cpp"],
toolchains = ["@rules_cc//cc:toolchain_type"],
)
def kelvin_binary(name, srcs, **kwargs):
"""A helper macro for generating binary artifacts for the kelvin core.
This macro uses the kelvin toolchain, kelvin-specific starting asm,
and kelvin linker script to build kelvin binaries.
Args:
name: The name of this rule.
srcs: The c source files.
**kwargs: Additional arguments forward to cc_binary.
Emits rules:
bin_to_vmem named: <name>_vmem
Generating the 256-bitwidth vmem output for the target.
256_bitwidth_vmem named: <name>.256.vmem
Containing the 256-bitwidth vmem output.
filegroup named: <name>.bin
Containing the binary output for the target.
filegroup named: <name>.elf
Containing all elf output for the target.
filegroup named: <name>
Containing cc outputs and 256-bitwidth vmem target.
"""
srcs = srcs + [
"//sw/device/lib/testing/test_framework:kelvin_gloss.c",
"//sw/device/lib/testing/test_framework:kelvin_start.S",
"@lowrisc_opentitan//sw/device/lib/crt:crt.S",
]
kelvin_cc_name = "{}_cc".format(name)
kelvin_binary_impl(
name = kelvin_cc_name,
srcs = srcs,
linker_script = "//sw/device/lib/testing/test_framework:kelvin.ld",
linker_script_includes = [
"//hw/top_matcha/sw/autogen:top_matcha_memory.ld",
],
**kwargs
)
# Need to create the following filegroups to make the output discoverable.
bin_name = "{}.bin".format(name)
native.filegroup(
name = bin_name,
srcs = [kelvin_cc_name],
output_group = "bin_file",
)
elf_name = "{}.elf".format(name)
native.filegroup(
name = elf_name,
srcs = [kelvin_cc_name],
output_group = "elf_file",
)
# Generate vmem file for dv testbench
# Memory in ml_top has word size 256. srec_cat supports only up to 128.
# Build a flow to transform 32-bitwidth format into 256-bitwidth format.
vmem_32_name = "{}.32.vmem".format(name)
vmem_256_name = "{}.256.vmem".format(name)
bin_to_vmem(
name = vmem_32_name,
bin = bin_name,
word_size = 32,
)
cmd = """
$(location //util:gen_vmem_256) \
--input=$< \
--output=$@
"""
vmem_name = "{}_vmem".format(name)
native.genrule(
name = vmem_name,
srcs = [vmem_32_name],
outs = [vmem_256_name],
cmd = cmd,
tools = ["@matcha//util:gen_vmem_256"],
)
native.filegroup(
name = name,
srcs = [
bin_name,
elf_name,
vmem_name,
],
)