Creating a new top

Once a top has been generated by topgen, some steps must be taken for this top to become visible to build system and integrate with the Top selection mechanism. This page documents those steps on an imaginary top named matcha.

Background references

This page assumes a minimal amount of familiarity with Bazel, but you should at the very least be familiar with the following concepts:

Preliminary steps

Before running topgen, you must ensure that every IP (including top-specific and generated IPs) contains a BUILD file and associated defs.bzl file describing the IP to the build system, in the root directory of the IP. This sections explains those steps in detail. These steps are only necessary if you have custom IPs, as all IPs in the OpenTitan repository are already setup.

Registering non-generated IPs

A BUILD file must be located in root directory of the IP and contain at the very least the following code:

# In <ip dir>/BUILD:
package(default_visibility = ["//visibility:public"])

# Most likely you will also need to create an `rtl_files` filegroup to declare RTL files if you want to
# build bitstreams or run verilator.

A defs.bzl file must be located in root directory of the IP and contain at the very least an opentitan_ip definition, as in the example below for AES.

# In <ip dir>/defs.bzl:
load("//rules/opentitan:hw.bzl", "opentitan_ip")

# The variable name must be the name of the IP in upper case.
AES = opentitan_ip(
    name = "aes",
    # This must be a **full** label (i.e. you must including the package name)
    # pointing to the IP's Hjson. There usually are two options:
    #
    # Option 1:
    # Create a BUILD file in the data/ subdirectory of the IP (here hw/ip/aes/data/BUILD)
    # so that it becomes a package. This option can be useful if your IP has more content
    # in the data/ directory that you want to make available to Bazel.
    hjson = "//hw/ip/aes/data:aes.hjson",
    #
    # Option 2:
    # Do NOT create a BUILD file in the data/ subdirectory of the IP and use a label
    # relative to the IP's root package:
    hjson = "//hw/ip/aes:data/aes.hjson",
)

For concrete examples, you can look at the AES module or the AST module.

Registering generated IPs

IPs generated by ipgen also need to contain BUILD and defs.bzl files as above but since the content is generated, it is usually recommended to make this file a template so that it is automatically generated by topgen. Furthermore, generated IPs usually should register their ipconfig file to the build system if want to use more advanced features of dtgen.

# In <ip template dir>/BUILD.tpl:
package(default_visibility = ["//visibility:public"])

# Most likely you will also need to create an `rtl_files` filegroup to declare RTL files if you want to
# build bitstreams or run verilator.

and

# In <ip template dir>/defs.bzl:
load("//rules/opentitan:hw.bzl", "opentitan_ip")

${module_instance_name.upper()} = opentitan_ip(
    name = "${module_instance_name}",
    # Example using Option 2, see the explanation above.
    hjson = "//hw/top_${topname}/ip_autogen/${module_instance_name}/data:${module_instance_name}.hjson",
    # NEW: optional but necessary if you want to use dtgen with IP template parameters.
    ipconfig = "//hw/top_${topname}/ip_autogen/${module_instance_name}:data/top_${topname}_${module_instance_name}.ipconfig.hjson",
)

For concrete examples, you can look at the clkmgr module.

Automatically generated files

After running topgen, the following files should have been created in hw/top_matcha:

  • A sw/autogen directory containing along others: including a BUILD file and some top library/ld code,
  • A data/autogen directory containing a BUILD file, a defs.bzl file and the generated complete top description top_matcha.gen.hjson

You MUST not edit those files.

Manual steps

The following steps require manual edits or creation of files and only need to be done once (though the content of some files may require future edits).

Adding your top to the build system

The first step is to make the build system aware of your top so you can use the top-selection mechanism. To do this, edit hw/top/defs.bzl and add the following lines marked as NEW:

# In hw/top/defs.bzl`:
load("//rules/opentitan:hw.bzl", "opentitan_top")
load("//hw/top_earlgrey/data/autogen:defs.bzl", "EARLGREY")
load("//hw/top_darjeeling/data/autogen:defs.bzl", "DARJEELING")
load("//hw/top_englishbreakfast/data/autogen:defs.bzl", "ENGLISHBREAKFAST")
# NEW: load your top's description
load("//hw/top_matcha/data/autogen:defs.bzl", "MATCHA")

ALL_TOPS = [
    EARLGREY,
    DARJEELING,
    ENGLISHBREAKFAST,
    # NEW: add your top to the build system
    MATCHA,
]

Once done, your top can be selected using --//hw/top=matcha when building targets.

Declaring execution environment

In order to build and run tests, the build system requires you to declare execution environments which describe the necessary steps required to build images. At minimum, you will want to declare a DV environment. To do so, create (or edit) a BUILD file in your top’s root directory and add the following:

# In hw/top_matcha/BUILD:
load(
    "//rules/opentitan:defs.bzl",
    "DEFAULT_TEST_FAILURE_MSG",
    "DEFAULT_TEST_SUCCESS_MSG",
    "sim_dv",
)

package(default_visibility = ["//visibility:public"])

###########################################################################
# Sim DV Environments
#
# The sim_dv_base target is only meant to be used for building ROMs and
# other items without `testonly=True`.
###########################################################################
sim_dv(
    name = "sim_dv_base",
    design = "matcha",
    exec_env = "sim_dv",
    extract_sw_logs = "//util/device_sw_utils:extract_sw_logs_db",
    flash_scramble_tool = "//util/design:gen-flash-img",
    libs = [
        "//sw/device/lib/arch:boot_stage_rom_ext",
        "//sw/device/lib/arch:sim_dv",
        "//hw/top_darjeeling/sw/dt:sim_dv",
    ],
    linker_script = "//sw/device/lib/testing/test_framework:ottf_ld_silicon_creator_slot_a",
    rom_scramble_config = "//hw/top_darjeeling/data/autogen:top_matcha.gen.hjson",
)

sim_dv(
    name = "sim_dv",
    testonly = True,
    base = ":sim_dv_base",
    exec_env = "sim_dv",
    rom = "//sw/device/lib/testing/test_rom:test_rom",
)

For concrete examples see Darjeeling (simple) or Earlgrey (very complex).

Important note: creating an execution environment does not automatically enroll tests, those must manually be enabled for your execution environment.

Build the test ROM for your top

At minimum, you will want to build the test ROM for your DV execution environment. To do so, edit sw/device/lib/testing/test_rom/BUILD and add the following:

# In sw/device/lib/testing/test_rom/BUILD
opentitan_binary(
    name = "test_rom",
    exec_env = [
        # ...
        "//hw/top_darjeeling:sim_dv_base",
        # NEW: note that this is using the sim_dv_base environment
        "//hw/top_matcha:sim_dv_base",
    ],
    # ...
)

Once done, you should be able to compile a test ROM for your top by running:

./bazelisk.sh build --//hw/top=matcha //sw/device/lib/testing/test_rom

NOTE: you might run into compilation errors due to top-specific constructs in the code of the test ROM. See below for more details.

Adding your execution environment to tests

This is usually done in two ways:

  • manually add a //hw/top_match:sim_dv to the exec_env attribute of a test, see example_test_from_rom for example:
opentitan_test(
    name = "example_test_from_rom",
    srcs = ["example_test_from_rom.c"],
    exec_env = {
        "//hw/top_earlgrey:sim_dv": None,
        "//hw/top_earlgrey:sim_verilator": None,
        "//hw/top_darjeeling:sim_dv": None,
        # NEW:
        "//hw/top_matcha:sim_dv": None,
    },
    # ...
)
# In rules/opentitan/defs.bzl

# NEW
# The default set of test environments for Matcha.
MATCHA_TEST_ENVS = {
    "//hw/top_matcha:sim_dv": None,
}

and add it to tests that you want to enroll, e.g. in sw/device/tests/BUILD

# In sw/device/tests/BUILD:
load(
    "//rules/opentitan:defs.bzl",
    # NEW
    "MATCHA_TEST_ENVS"
    # ...
)

# ...

opentitan_test(
    name = "uart_smoketest",
    srcs = ["uart_smoketest.c"],
    exec_env = dicts.add(
        EARLGREY_TEST_ENVS,
        EARLGREY_SILICON_OWNER_ROM_EXT_ENVS,
        EARLGREY_CW340_TEST_ENVS,
        DARJEELING_TEST_ENVS,
        # NEW
        MATCHA_TEST_ENVS,
        {
            "//hw/top_earlgrey:silicon_creator": None,
        },
    ),
)

Once done, you should be able to compile a DV UART smoke test for your top by running:

./bazelisk.sh build --//hw/top=matcha //sw/device/tests:uart_smoketest_sim_dv

NOTE: you might run into compilation errors due to top-specific constructs in the code of the test framework. See below for more details.

Porting the software to your top

TODO