| # 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, |
| ) |