blob: fef9621c1c209a2079e8ec58b090cbecef6e57b3 [file]
# 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("@rules_pkg//:pkg.bzl", "pkg_tar")
load(
"@lowrisc_opentitan//rules:rv.bzl",
"rv_rule",
_OPENTITAN_CPU = "OPENTITAN_CPU",
_OPENTITAN_PLATFORM = "OPENTITAN_PLATFORM",
_opentitan_transition = "opentitan_transition",
)
load(
"@lowrisc_opentitan//rules:opentitan.bzl",
"bin_to_vmem",
"opentitan_binary",
"scramble_flash_vmem",
"sign_bin",
)
# 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
# Currently set to secure core's IMC extensions.
SMC_PLATFORM = OPENTITAN_PLATFORM
# List of supported riscv core targets.
VERILATOR_CORE_TARGETS = {
"secure_core": "@lowrisc_opentitan//sw/device/lib/arch:sim_verilator",
"smc": "//sw/device/lib/arch:smc_sim_verilator",
}
NEXUS_CORE_TARGETS = {
"secure_core": "//sw/device/lib/arch:sc_fpga_nexus",
"smc": "//sw/device/lib/arch:smc_fpga_nexus",
}
# 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 (CW310),
# given a target core from VERILATOR_CORE_TARGETS.
def device_deps(core):
per_device_deps = {
"sim_verilator": [VERILATOR_CORE_TARGETS.get(core)],
"sim_dv": ["@lowrisc_opentitan//sw/device/lib/arch:sim_dv"],
"fpga_nexus": [NEXUS_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(
"{}.scr.39.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,
),
},
)
def opentitan_rom_binary(
name,
platform = OPENTITAN_PLATFORM,
per_device_deps = device_deps("secure_core"),
**kwargs):
"""A helper macro for generating OpenTitan binary artifacts for ROM.
This macro is mostly a wrapper around a opentitan_binary macro, which
itself is a wrapper around cc_binary, but also creates artifacts for each
of the keys in `per_device_deps`. The actual artifacts created are an ELF
file, a BIN file, the disassembly, the sim_dv logs database, the
unscrambled (ROM) VMEM file, and the scrambled (ROM) VMEM file. Each of
these output targets performs a bazel transition to the RV32I toolchain to
build the target under the correct compiler.
Args:
name: The name of this rule.
platform: The target platform for the artifacts.
per_device_deps: The deps for each of the hardware target.
**kwargs: Arguments to forward to `opentitan_binary`.
Emits rules:
For each device in per_device_deps entry:
cc_binary named: <name>_<device>
obj_transform named: <name>_<device>_elf
obj_transform named: <name>_<device>_bin
elf_to_dissassembly named: <name>_<device>_dis
bin_to_rom_vmem named: <name>_<device>_vmem
elf_to_scrambled_rom_vmem named: <name>_<device>_scr_vmem
For the sim_dv device:
gen_sim_dv_logs_db named: <name>_sim_dv_logs
filegroup named: <name>
with all the generated rules
"""
deps = kwargs.pop("deps", [])
targets = []
for (device, dev_deps) in per_device_deps.items():
devname = "{}_{}".format(name, device)
# Generate ELF, Binary, Disassembly, and (maybe) sim_dv logs database
targets.extend(opentitan_binary(
name = devname,
deps = deps + dev_deps,
extract_sw_logs_db = device in ["sim_dv", "sim_verilator"],
**kwargs
))
elf_name = "{}_{}".format(devname, "elf")
bin_name = "{}_{}".format(devname, "bin")
# Generate Un-scrambled ROM VMEM
vmem_name = "{}_vmem".format(devname)
targets.append(":" + vmem_name)
bin_to_vmem(
name = vmem_name,
bin = bin_name,
platform = platform,
word_size = 32,
)
# Generate Scrambled ROM VMEM
scr_vmem_name = "{}_scr_vmem".format(devname)
targets.append(":" + scr_vmem_name)
elf_to_scrambled_rom_vmem(
name = scr_vmem_name,
srcs = [elf_name],
platform = platform,
)
native.filegroup(
name = name,
srcs = targets,
)
def flash_binary(
name,
platform = OPENTITAN_PLATFORM,
signing_keys = {
"test_key_0": "@//sw/device/silicon_creator/mask_rom/keys:test_private_key_0",
},
per_device_deps = device_deps("secure_core"),
output_signed = False,
manifest = None,
**kwargs):
"""A helper macro for generating binary artifacts for a target core.
This macro is mostly a wrapper around a opentitan_binary macro, which itself
is a wrapper around cc_binary, 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 are an ELF file, a (signed and)
unsigned BIN file, the disassembly, the sim_dv logs database, a (signed and)
unsigned flash VMEM file, and a (signed and) unsigned scrambled flash VMEM
file. Some of these output targets perform a bazel transition to the RV32I
toolchain to build the target under the correct compiler.
Args:
name: The name of this rule.
platform: The target platform for the artifacts.
signing_keys: The signing keys for to sign each BIN file with.
per_device_deps: The deps for each of the hardware target.
output_signed: Whether or not to emit signed binary/VMEM files.
manifest: The manifest used to sign the binary
**kwargs: Arguments to forward to `opentitan_binary`.
Emits rules:
For each device in per_device_deps entry:
cc_binary named: <name>_<device>
obj_transform named: <name>_<device>_elf
obj_transform named: <name>_<device>_bin
elf_to_dissassembly named: <name>_<device>_dis
bin_to_vmem named: <name>_<device>_flash_vmem
scrambled_flash_vmem named: <name>_<device>_scr_flash_vmem
optionally:
sign_bin named: <name>_<device>_bin_signed_<key_name>
bin_to_vmem named: <name>_<device>_flash_vmem_signed_<key_name>
scrambled_flash_vmem named: <name>_<device>_scr_flash_vmem_signed_<key_name>
For the sim_dv device:
gen_sim_dv_logs_db named: <name>_sim_dv_logs
filegroup named: <name>
with all the generated rules
"""
deps = kwargs.pop("deps", [])
targets = []
for (device, dev_deps) in per_device_deps.items():
devname = "{}_{}".format(name, device)
# Generate ELF, Binary, Disassembly, and (maybe) sim_dv logs database
targets.extend(opentitan_binary(
name = devname,
deps = deps + dev_deps,
extract_sw_logs_db = device in ["sim_dv", "sim_verilator"],
**kwargs
))
bin_name = "{}_{}".format(devname, "bin")
# Sign BIN (if required) and generate scrambled VMEM images.
if output_signed:
for (key_name, key) in signing_keys.items():
# Sign the Binary.
signed_bin_name = "{}_bin_signed_{}".format(devname, key_name)
targets.append(":" + signed_bin_name)
sign_bin(
name = signed_bin_name,
bin = bin_name,
key = key,
key_name = key_name,
manifest = manifest,
)
# Generate a VMEM64 from the signed binary.
signed_vmem_name = "{}_vmem64_signed_{}".format(
devname,
key_name,
)
targets.append(":" + signed_vmem_name)
bin_to_vmem(
name = signed_vmem_name,
bin = signed_bin_name,
platform = platform,
word_size = 64, # Backdoor-load VMEM image uses 64-bit words
)
# Scramble signed VMEM64.
scr_signed_vmem_name = "{}_scr_vmem64_signed_{}".format(
devname,
key_name,
)
targets.append(":" + scr_signed_vmem_name)
scramble_flash_vmem(
name = scr_signed_vmem_name,
vmem = signed_vmem_name,
platform = platform,
)
else:
# Generate a VMEM64 from the binary.
vmem_name = "{}_vmem64".format(devname)
targets.append(":" + vmem_name)
bin_to_vmem(
name = vmem_name,
bin = bin_name,
platform = platform,
word_size = 64, # Backdoor-load VMEM image uses 64-bit words
)
# Scramble VMEM64.
scr_vmem_name = "{}_scr_vmem64".format(devname)
targets.append(":" + scr_vmem_name)
scramble_flash_vmem(
name = scr_vmem_name,
vmem = vmem_name,
platform = platform,
)
native.filegroup(
name = name,
srcs = targets,
)
def sec_flash_binary(
name,
signing_keys = {
"test_key_0": "@//sw/device/silicon_creator/mask_rom/keys:test_private_key_0",
},
output_signed = False,
per_device_deps = device_deps("secure_core"),
**kwargs):
"""A helper macro for generating secure core binary artifacts, using the flash_binary wrapper.
Args:
@param name: The name of this rule.
@param signing_keys: The signing keys for to sign each BIN file with.
@param output_signed: Whether or not to emit signed binary/VMEM files.
@param per_device_deps: The deps for each of the hardware target.
@param **kwargs: Arguments to forward to `flash_binary`.
"""
flash_binary(
name = name,
platform = OPENTITAN_PLATFORM,
per_device_deps = per_device_deps,
signing_keys = signing_keys,
output_signed = output_signed,
**kwargs
)
def smc_flash_binary(
name,
signing_keys = {
"test_key_0": "@//sw/device/silicon_creator/mask_rom/keys:test_private_key_0",
},
output_signed = False,
per_device_deps = device_deps("smc"),
**kwargs):
"""A helper macro for generating SMC binary artifacts, using the flash_binary_wrapper.
Args:
@param name: The name of this rule.
@param signing_keys: The signing keys for to sign each BIN file with.
@param output_signed: Whether or not to emit signed binary/VMEM files.
@param per_device_deps: The deps for each of the hardware target.
@param **kwargs: Arguments to forward to `flash_binary`.
"""
flash_binary(
name = name,
platform = SMC_PLATFORM,
per_device_deps = per_device_deps,
signing_keys = signing_keys,
output_signed = output_signed,
**kwargs
)
def matcha_extflash_tar(
name,
sc_binary,
smc_binary = None,
kelvin_binary = None,
data = None):
"""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.
"""
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,
)
def bin_to_c_file(
name,
srcs,
var_name = "data"):
"""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.
"""
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,
)