Workflows

This section is currently under development.

  • More details on Yosys/Synthesis once finalised

  • Linters are still WIP on findings; the shared flow.mk path is the one to run

This document specifies the development methodology for this project.

1. Build with Bender

Bender is used for managing IP dependencies and managing builds. Makefiles are used for build automation.

Each subsystem ships repository-relative filelists under integration/filelists/: <system>.synth.f for synthesis, <system>.emul.f for emulation, and <system>.<tool>.sim.f for simulation. Bender selects the synthesis view with -t synth and the emulation view with -t synth -t emulation; each subsystem adds its own targets in its flow.mk. A bare bender script flist emits only the default target and omits the subsystems.

Regenerate every filelist from the repository root:

make update-integration-filelists

Add CHECK=1 to verify the committed filelists instead of rewriting them. The weekly update-integration-filelists workflow runs the same target.

1.1. Third-party (vendor) package imports with Bender

External IP is materialized under vendor/ via Bender’s vendor_package feature. If a vendored tree is also consumed as a Bender package, the root dependencies: entry points at the local package root under vendor/; direct third-party git: dependencies are not used.

1.2. Ground rules

  • Import only what OCAH uses. Every vendor_package entry carries a narrowly scoped include/mapping: list and uses exclude_from_upstream: to document intentionally omitted upstream paths. Whole-repository imports are prohibited.

  • Commit vendored sources. Materialized files under vendor/ are committed so every revision is self-contained and reproducible without network access.

  • Pin every upstream entry. rev: must be a full commit hash or an immutable tag; mutable refs such as branch names are prohibited.

  • Resolve vendored packages locally. Packages with hand-authored manifests under vendor/ are pinned via Bender.local so transitive users resolve the committed copy.

1.3. Filesystem layout

Vendored packages preserve upstream GitHub org/repo casing under vendor/<GitHubOrg>/<GitHubRepo>/. Bender materializes selected upstream files into upstream/ (wiped and refreshed by bender vendor init), then applies local patches. Patch files live in the sibling patches/ directory so they survive a refresh:

vendor/lowRISC/opentitan/
  patches/
    0001-some_change.patch     ← NNNN-<snake_case>.patch, applied in filename order
  upstream/                    ← target_dir (refreshed by bender vendor init)
    hw/ip/otbn/{data,rtl}/      ← selected register + RTL sources
    util/reggen/                ← selected reggen modules only

1.4. Bender.yml vendor_package template

vendor_package:
  - name: <descriptive-name>
    target_dir: vendor/<GitHubOrg>/<GitHubRepo>/upstream
    upstream: { git: "<upstream-url>", rev: "<full-commit-hash-or-tag>" }
    exclude_from_upstream:
      - "<unused/path/relative/to/upstream/root>"
    mapping:
      - { from: "<upstream/file/or/dir>", to: "<local/file/or/dir>", patch_dir: "<local/patch/scope>" }
    patch_dir: "vendor/<GitHubOrg>/<GitHubRepo>/patches"

1.5. Authoring a patch

  1. Materialize/refresh the vendor tree: bender vendor init

  2. Edit files under vendor/<GitHubOrg>/<GitHubRepo>/upstream/.

  3. Stage: git add vendor/<GitHubOrg>/<GitHubRepo>/

  4. Generate the patch (Bender prompts for a message): bender vendor patch

  5. Rename to the numbering convention: mv vendor/…​/patches/<auto-name>.patch vendor/…​/patches/<NNNN>-<description>.patch

  6. Commit both the patched source and the new .patch file.

bender vendor init re-fetches the pinned commit, copies the selected files, excludes exclude_from_upstream: paths, and re-applies all patches from patch_dir in lexicographic order.

CI runs bender vendor diff --err_on_diff on every hardware-CI push so a stale upstream/ tree or a patch that no longer applies fails the gate.

2. Register generation

Register collateral is generated from per-block SystemRDL under hw/{ip,sys}/<name>/regs/. HJSON-backed OpenTitan blocks are first exported to RDL via tools/regs/reggen_wrapper.py using the vendored OpenTitan reggen modules. Run make regen-regs to regenerate; committed generated output always corresponds to the RDL sources. The flow lives in hw/common/regs/ (discovery, classification, and rules).

3. DV Firmware

Per-subsystem DV firmware libraries (runtime drivers, not tests) are built via make dv-fw-libs [TARGET=sep|smc] (alias of ocah-dv-fw-libs). Each subsystem owns a fw.mk + toolchain.mk under hw/{ip,sys}/<name>/dv/fw/ and builds as an independent recursive sub-make so the target CPUs (VeeR EL2/SEP, Rocket/SMC) never share ISA/ABI/libc flag state.

The RISC-V toolchain is provided by the project container or selected via RISCV_TOOLCHAIN. The build flow lives in hw/common/dv/fw/.

3.1. SMC DV test firmware

C test images are built via the standard dispatcher:

# Prefix with `scripts/docker-run.sh run` to use the container RISC-V toolchain
make dv-fw-tests TARGET=smc
make dv-fw-tests TARGET=smc TEST=version_id   # one test

Each subsystem’s tests link against one of that subsystem’s link modes — memory targets discovered from link/modes/*.ld (e.g. SMC: sram, rom; SEP: tcm) — and output artifacts fold the mode into their name (<test>.<mode>.elf, e.g. version_id.sram.elf). A test uses its subsystem’s default mode unless it overrides FW_TEST_MODE_<test>; see hw/common/dv/fw/compile.mk for the full mechanism.

Most SMC tests default to sram mode. The OCCP master BFM tests (occp_sanity, occp_master) are declared rom-mode in hw/sys/smc/dv/fw/fw.mk because they exercise the I3C master path and must run from the ROM address space. They link against the open-source weak I3C_GetDriverInstance stub (returns NULL) so the build is self-contained.

3.2. SMC boot ROM

hw/sys/smc/bootrom/ holds two independent ROM firmware trees:

  • dummy/ — the lightweight DV stub used today. Its only job is to boot from the ROM address (0xc0040000), write TEST_ROM_PASS (0x77777777) to scratch_0, and spin in wfi so the testbench can load real firmware into SRAM. It builds as one rom-mode test named dummy (source rom.c) on top of the same generalized firmware engine as the SMC DV tests above, reusing `hw/sys/smc/dv/fw/’s drivers, toolchain settings, and link scripts.

  • prod/ — self-contained production boot ROM with its own includes and drivers (not shared with dv/fw/). It implements the OCCP target-side protocol and uses weak stubs so the open tree links cleanly. Adopters can supply strong-symbol replacements as explicit command-line overlay sources with OCAH_FW_OVERLAY_SOURCES_smc, and the include flags (-I<dir>) for their generated register headers with OCAH_FW_REG_OVERLAY_INCLUDE_DIRS_smc; those directories come ahead of the open generated headers. The Makefile ignores both variables when they are set only in the environment.

Build the dummy ROM with its standalone Makefile:

# From the repo root (OCAH_ROOT resolved automatically):
# Prefix with `scripts/docker-run.sh run` to use the container RISC-V toolchain
make -C hw/sys/smc/bootrom/dummy

Outputs land under hw/sys/smc/bootrom/dummy/build/tests/dummy/ (e.g. dummy.rom.elf, dummy.rom.bin, dummy.rom.hex, plus .preload.hex / .spi variants from the shared SMC post-process step).

Build the production ROM from the same container (self-contained Makefile, not the shared compile.mk engine). See hw/sys/smc/bootrom/prod/README.md and the SMC Boot ROM appendix of the Programmer’s Guide for the full target list, build variables, and architecture.

The build splits across two halves so each runs in the right environment:

  • toolchain-images — RISC-V/picolibc compilation; use a container when RISCV_TOOLCHAIN is unset.

  • pack-images — image conversion via uv; can run in the container or on the host.

# Compile and link (use the container toolchain when RISCV_TOOLCHAIN is unset):
scripts/docker-run.sh run-here make -C hw/sys/smc/bootrom/prod toolchain-images

# Convert outputs to ROM preload formats on the host:
make -C hw/sys/smc/bootrom/prod pack-images

# Or build both in one step when the host has a picolibc-enabled toolchain:
RISCV_TOOLCHAIN=/path/to/toolchain/bin make -C hw/sys/smc/bootrom/prod

Build products land under build/release/ (or build/debug/ with BUILD_TYPE=debug): bin/ holds the ELF and binary image, hex/ the ROM formats, and disasm/ the disassembly.

3.3. SEP boot ROM

hw/sys/sep/bootrom/prod/ is a self-contained production boot ROM for the SEP subsystem (VeeR EL2), with its own includes, drivers, and link script. It implements the full secure-boot flow — manifest loading, RSA-3072 signature verification on OTBN, optional AES payload decryption, PMP setup, and lifecycle enforcement — and uses weak stubs so the open tree links cleanly. A build that carries its own drivers overrides the stubs at link time: it passes the driver sources as OCAH_FW_OVERLAY_SOURCES_sep, and the include flags (-I<dir>) for their register headers as OCAH_FW_REG_OVERLAY_INCLUDE_DIRS_sep, on the make command line. The ROM takes the SMC registers it reaches through its SMC window from the generated SMC map; a build against another SMC map passes the include flags for that map as OCAH_FW_REG_OVERLAY_INCLUDE_DIRS_smc. The Makefile ignores these variables when they are set only in the environment, so a value exported in a shell cannot change an open-tree ROM. See hw/sys/sep/bootrom/prod/README.md and the SEP Boot ROM appendix of the Programmer’s Guide for the full target list, build variables, key provisioning, and boot flow.

The ROM supports two flash transports selected at build time by BOOT_SPI_CONTROLLER_OT; the unused driver is dropped by --gc-sections at link time. The default (BOOT_SPI_CONTROLLER_OT=1) uses the OpenTitan SPI host, and BOOT_OT_SPI_USE_PIO selects its RX-drain method (secure DMA by default, or CPU-programmed I/O). BOOT_SPI_CONTROLLER_OT=0 reads memory-mapped flash through the external XIP window; its controller driver is optional from the open tree’s perspective and is supplied by an integrator through the weak hooks in src/sep_spi.c.

The DV sep testbench has no SPI flash, so it boots instead from the SMC-SRAM manifest path: oca-images packs the non-secure boot-manifest image rebased to the SMC-SRAM base (build/oca_smc_mem.hex).

The build splits into two halves so each can run in the correct environment:

  • toolchain-images — RISC-V/picolibc compilation; use the container when RISCV_TOOLCHAIN is unset.

  • key-digests and oca-images — pure Python via uv, which the container carries; can run in the container or on the host. oca-images is transport-agnostic: the same manifest+BL1 payload works with every ROM build profile, so no extra flags are needed after toolchain-images.

The manifest-tool submodule must be initialized before running oca-images:

git submodule update --init hw/sys/sep/bootrom/prod/tools/tt-oca-manifest

DV test manifest configs live under configs/; the corresponding test signing keys are in tests/signing_keys/ (debug builds only — a release build reads public keys from release_signing_keys/ and refuses to produce images anchored on test keys).

# OpenTitan SPI-host build (outputs to build/):
# - Prefix with `scripts/docker-run.sh run-here` to run in the container
make -C hw/sys/sep/bootrom/prod toolchain-images

# Pack all OCA test images (signed, encrypted, per-ROM-key, and negative cases):
make -C hw/sys/sep/bootrom/prod oca-images

Outputs land under hw/sys/sep/bootrom/prod/build/:

  • boot_rom.vmem — 64-bit VMEM for the Boot ROM responder.

  • boot_rom.itcm.hex — ROM read-only sections as ICCM-relative byte hex.

  • boot_rom.dtcm.hex — DCCM .data/.bss initial image as DCCM-relative byte hex.

  • oca_*.bin / oca_*.spi_preload — OCA boot-manifest images, one per validator path (OCA_IMAGES in the Makefile).

  • oca_smc_mem.hex — the non-secure image rebased to the SMC-SRAM base, for the DV testbench.

The pio-toolchain-images target builds the CPU-PIO drain ROM into build_pio/; the packed images stay under build/.

3.4. KM application ROM

The Key Manager (KM) subsystem (PicoRV32) runs its firmware from ROM. That firmware is not only a boot loader: it is the KM runtime, serving the SEP mailbox, until the SEP loads alternative firmware into KM SRAM and hands over to it. It therefore lives in hw/ip/key_manager/approm/prod/ rather than a bootrom/ directory, and builds with a self-contained Makefile there. The Key Manager Application ROM appendix of the Programmer’s Guide describes the firmware.

The entry point is src/rom_main.c. The whole image — vectors, code, constants and the .data load image — links into the 16 KB ROM through link/km_production.ld.

# Build the application ROM; without RISCV_TOOLCHAIN it builds in the toolchain container
make -C hw/ip/key_manager/approm/prod

# Boot and unrecoverable-fault SRAM wipes disabled, in a separate build directory
make -C hw/ip/key_manager/approm/prod KM_BOOT_WIPE=0 KM_UNREC_WIPE=0 BUILD_DIR=build/nowipe

Outputs land under hw/ip/key_manager/approm/prod/build/ (or BUILD_DIR):

  • rom_main.elf — linked ELF, with rom_main.map and rom_main.dis beside it.

  • rom_main.rom.hex — address-zero-based Verilog hex for the ROM responder.

  • rom_main.rom.parhex — parity-annotated hex (used by the KM RTL’s ROM model).

  • rom_main.sym — address-sorted symbol table.

SEP DV builds its own wipe-disabled copy through hw/sys/sep/dv/cocotb/tests/km_fw (make rom_main), which run_dv.py drives for tests that declare mode = "km_rom_main".

4. Simulation and Testing

The OCA Harness uses open source tools as much as possible and hence focuses on using Verilator for unit tests. Verilator has comprehensive SystemVerilog (SV) support and is widely used in industry.

4.1. Testbench and Test Sequence

Most components use Cocotb Python tests. Some DUTs also have a nonfree UVM test workflow using VCS. The testbench is run and configured using a Python script. Run the following command to see all available options:

./tools/dv/run_dv.py --help
usage: run_dv.py --dut NAME [--framework NAME] [--items ITEM ...] [options]
       run_dv.py --list | --validate-configs | --doctor [options]

Native OSS DV/FV launcher.
...

If you want to manage python dependencies yourself, rather than through uv, the environment variable OCAH_DV_SKIP_UV=1 may be set, in which case the system Python installation will be used.

To check that all testsuites are configured properly, run the following:

./tools/dv/run_dv.py --validate-configs
Validating configs in hw/common/dv/configs

  simulators.toml  OK
  executors.toml   OK
  parsers.toml     OK
  ...
  sep              OK
  sep (uvm)        OK
  smc              OK
  smc (uvm)        OK
  smu              OK
  smu (uvm)        OK
  ...

Result: 22 DUT(s), 30 view(s): 30 OK, 0 FAILED

Use the --doctor flag to check the testbench environment for an individual configuration - note that while this will check for the presence of optional proprietary tools, these remain entirely optional.

./tools/dv/run_dv.py --doctor --dut sep
configs : OK
checking: DUT `sep` (would run with: verilator)

python/package:
  python                   OK       3.11.15
  shared dv package        OK       0.1.0
  cocotb                   OK       2.0.1
  pyuvm                    OK       4.0.1
  cocotbext-axi            OK       0.1.28
  cocotbext-jtag           OK       0.4.0
  cocotbext-i2c            OK       0.1.2
  namespace bridge         OK       <repo-root>/build/dv/python
  namespace PYTHONPATH     OK       <repo-root>/build/dv/python
  import ocah_axi_vip      OK       import OK
  import sep               OK       import OK
  import sep.cocotb.env    OK       import OK

  tool       binary       status                     licenses
  verilator  verilator    found: /nix/store/hiyac264333480jfax3374g1mp6nckv7-verilator-5.050/bin/verilator none needed
  xcelium    xrun         MISSING from PATH          0/2 env set  (optional)
  vcs        vcs          MISSING from PATH          0/2 env set  (optional)

Result: required tool `verilator` is available

Having selected a Device to Test with the --dut flag, use --list to print the available tests and testlists for the device.

./tools/dv/run_dv.py --dut sep --list
name       : sep
kind       : dv
framework  : cocotb
root       : hw/sys/sep/dv
tools      : verilator, xcelium, vcs
default    : verilator
stages     : ...
tests      : ...
groups     : all=... cpu=... cpu_stub=...

To run an individual set of tests, list them using the --items flag.

./tools/dv/run_dv.py --dut smc --items smc_octs_sanity_test smc_axil_idle_test
...

This may similarly be used to run preconfigured test groups.

Further options, including seeds and regressions are available, consult the --help flag to see usage.

4.2. Visual Debugging and Waveform Viewer

When debugging particularly difficult RTL issues, it is useful to be able to view the signals travelling between components. For this, a wave trace may be generated and inspected. To generate a trace, use the --waves argument.

./tools/dv/run_dv.py --dut smc --items smc_octs_sanity_test --waves

Each test writes its trace to a waves/ directory beside its results in the run directory, with a debug_context.json listing the trace files and a command to open them. We use Surfer, which the container includes, for viewing wave traces; open a trace with surfer <trace>.

The --waves-on-fail flag may be used instead of --waves; it reruns only the tests that did not pass, with waves enabled.

5. Virtual Platform

The OCA Harness Virtual Platform (VP) is a SystemC-based simulation environment for testing chiplets, provided by the tt-oca-harness-model submodule under virtual_platform/. It runs firmware (including production boot ROMs) faster than RTL simulation. The VP includes three executables:

  • sep-vp — the SEP subsystem (default).

  • smc-vp — the SMC subsystem (requires Whisper ISS).

  • smu-vp — the SMC and SEP subsystems in one process (for cross-subsystem testing).

5.1. Prerequisites

  • A C++20 compiler (e.g., g++-12).

  • SystemC, CCI, Boost and OpenSSL, built into virtual_platform/local/ when the host does not provide them.

  • The Whisper ISS, for smc-vp and smu-vp only; it is built on first use.

  • A RISC-V toolchain with picolibc for the SEP boot ROM and firmware tests; without one those builds fall back to the container.

  • uv for the Python environment.

With VP_CONTAINER=1 the container provides all of these.

5.2. Initialising the Virtual Platform

To use the container to build and run the VP, set VP_CONTAINER=1

# Initialize the model submodule
make ocah-vp-init

# Build dependencies (if not provided by host)
make ocah-vp-deps

5.3. Running Tests on the Virtual Platform

5.3.1. SEP Virtual Platform

The SEP Virtual Platform is integrated with multiple different test suites:

# Build
make -C virtual_platform vp

# Run the SEP test suite
make -C virtual_platform vp-test # pass extra arguments with PYTEST_ARGS='<args>'

# Run a software test
make -C virtual_platform smoke SW_TEST='<test>' # See virtual_platform/tt-oca-harness-model/sw/sep-vp-tests

# Run a fw test
make -C virtual_platform fw-run FW_TEST='<test>' # See hw/sys/sep/dv/fw

# Build and run the SEP BOOT ROM
make -C virtual_platform boot-build
make -C virtual_platform boot-run # Configure with BOOT_ARGS='<args>'

5.3.2. SMC and SMU Virtual Platforms

The SMC Virtual Platform provides a single test suite, while the SMU integrates the SMC and SEP, to provide a combined test environment and suite.

For the SMC:

# build smc-vp (requires Whisper ISS)
make -C virtual_platform smc-vp

# Run test suite
make -C virtual_platform smc-test

# Run single test, see virtual_platform/tt-oca-harness-model/sw/smc-vp-tests
make -C virtual_platform smc-test SMC_ARGS='<test-name>'

For the SMU:

# build smu-vp (requires Whisper ISS)
make -C virtual_platform smu-vp

# Run test suite
make -C virtual_platform smu-test

# Run single test, see virtual_platform/tt-oca-harness-model/sw/smu-vp-tests
make -C virtual_platform smu-test SMU_ARGS='<test-name>'

5.4. Further Reading

  • virtual_platform/Makefile for detailed build options (make -C virtual_platform help).

  • virtual_platform/README.md for the three executables, dependency resolution and the containerized flow.

  • virtual_platform/BUILDING_NATIVELY.md for building and running the VP on your own host.

  • virtual_platform/sepvp/README.md for the sepvp runner: boot straps, fuse maps and status channels.

  • virtual_platform/RUNNING_THE_SMC_BOOT_ROM.md for guidance on running the SMC Boot ROM on the Virtual Platform.

6. Synthesis

OCAH is designed around open-source workflows. For this reason, we have chosen to provide a synthesis flow using Yosys.

make ocah-synth-yosys-all requires yosys on PATH. If it is not installed, Make prints a hint to use the container instead:

# Native (yosys must be on PATH)
make ocah-synth-yosys-all

# Via the container
./scripts/docker-run.sh run-here make ocah-synth-yosys-all

The PDK (ihp-sg13g2 by default) is downloaded automatically by ciel on first run. To pre-download all supported PDKs:

make ocah-synth-pdks

This may be constrained with BLOCK to synthesise a single block only, and the PDK used for synthesis may be selected with TECH. BLOCK is a flow.mk top (dtp, sep, smc, smu, aou), not an IP name. Details on discovery and TECH= are in flows/synth/yosys/README.md.

The yosys flow is still WIP, as timing constraints are not yet integrated/pipe cleaned.

7. Emulation elaboration

OCAH also provides a Vivado flow that elaborates a block’s emulation view, the same Bender targets integrators receive as integration/filelists/<block>.emul.f. It is only a readiness check: it runs synth_design -rtl to confirm that Vivado reads and elaborates the sources, and does not synthesize, implement or time the design.

make ocah-emul-vivado-all requires a licensed vivado on PATH; the container does not provide it.

make ocah-emul-vivado-all BLOCK=smu
make ocah-emul-vivado-all BLOCK=smu OCAH_VIVADO_INPUT=script

By default Vivado reads the Bender file list; OCAH_VIVADO_INPUT=script reads Bender’s Vivado script instead. BLOCK is a flow.mk top that includes the flow (only smu today). Details are in flows/emul/vivado/README.md.

8. Linting

We use linters within the development of OCAH to perform static analysis on the codebase. The below rules will check code for errors and poor formatting. They also enforce a consistent code style.

Some linting workflows are still WIP, the code has not yet been cleaned up.

Slang and Verilator use the same flow.mk tops as synthesis. Verible walks hand-authored files under hw/, including leaf IP. To restrict slang or Verilator output to one IP directory, lint the parent system:

make lint-slang-all BLOCK=smu SLANG_LINT_PATH=hw/ip/gpio
make lint-verilator-all BLOCK=smu VERILATOR_LINT_PATH=hw/ip/gpio

8.1. Verilog

8.1.1. Style and Lint checking with Verible

To run the verible linter on all SystemVerilog files, use the following command:

make lint-sv-verible

To apply the verible formatter, use the following:

make format-sv

The need for the above may be checked with make format-sv-check.

To run the linter only on a single block, use the BLOCK variable. The paths to search for files may be further constrained with LINT_PATH and FORMAT_PATH respectively, for each rule.

8.1.2. Linting with Verilator

Verilator structurally lints each discovered hardware block as its own top:

make lint-verilator-all

To run the linter only on a single block, use the BLOCK environment variable. To further constrain the printed warnings, also use VERILATOR_LINT_PATH.

Verilator centrally loads hw/common/regs/lint/peakrdl.verilator.vlt for one generator-owned exception. It suppresses only MULTIDRIVEN reports naming the field_combo or field_storage aggregate in PeakRDL register modules and the copied SPI register module. PeakRDL emits separate processes for disjoint aggregate members, but Verilator reports the enclosing aggregate. The exception does not cover member names or width diagnostics.

Audit the exception against the integrated SMU before changing its paths or messages. Run without the common waiver and confirm that every generated match names a field_combo or field_storage aggregate, while hand-authored MULTIDRIVEN findings remain visible:

make lint-verilator-all BLOCK=smu \
  OCAH_LINT_VERILATOR_COMMON_WAIVERS= \
  OCAH_LINT_VERILATOR_EXTRA_FLAGS="-Wall -Wpedantic" \
  2>&1 | tee "$TMPDIR/verilator-smu-unwaived.log"

cocotb’s Verilator runner always passes --public-flat-rw, and some package parameters under it fail only in the generated C++, which lint never compiles. make lint-verilator-public-all BLOCK=smu builds the block’s packages under the flag; CI runs it for smu, whose filelist includes SEP and SMC.

8.1.3. Style and Lint checking with Slang

To run the slang linter on each flow.mk top, use the following command:

make lint-slang-all

To run the linter only on a single block, use the BLOCK environment variable. To further constrain the printed warnings, also use SLANG_LINT_PATH.

8.2. C/C++

8.2.1. Style and Format with Clang-Format

The C/C++ Firmware files included in each component are checked and formatted with clang-format. This may be managed by uv, or OCAH_CLANG_FORMAT_SKIP_UV=1 may be set to use a system installation - detected in the usual way from PATH.

make format-c

To check for errors without modifying files, use make format-c-check.

To run the linter only on a single block, use the BLOCK environment variable. The path for files to check and format may be alternatively constrained with FORMAT_C_PATH.

8.3. Python

8.3.1. Lint and Format with ruff

Ruff checks first-party Python under tools/, scripts/, hw/, and .github/. Generated register models, vendored sources, submodules, nonfree/, and build output are excluded.

make lint-python
make format-python-check

To apply safe lint fixes and formatting locally, run:

make lint-python-fix
make format-python

Pass PYTHON_PATH=<path> to restrict any command to part of the tree.

CI uses only the non-modifying lint-python and format-python-check commands.

8.3.2. Lint with mypy

MyPy also lints first-party Python while excluding generated register models, vendored sources, submodules, nonfree/, and build output. It starts from a lenient config (see pyproject.toml) with hundreds of pre-existing findings across the tree.

make lint-python-mypy

Pass MYPY_PATH=<path> to restrict to part of the tree.

8.4. Build System and Helper Scripts

8.4.1. Makefile Lint with checkmake

To lint all Makefile and .mk files used within OCAH, run

make lint-make

To constrain the output to a single file/subdirectory, use MAKEFILE_PATH.

Checkmake’s maxbodylength/phonydeclared/uniquetargets rules have false-positive modes against conventions this repo relies on deliberately (long multi-step recipe bodies, the deferred OCAH_PHONY += accumulator, and legitimate pattern-rule/ifeq target overloads) — see checkmake.ini and flows/lint/checkmake.mk. Treat both as read-and-judge, not fix-on-sight.

8.4.2. Shell Script Lint and Format with shellcheck

To lint all shell scripts used within OCAH, run

make lint-shell

To format files, run the below

make format-shell

# Or to check formatting without modification
make format-shell-check

In all cases, SHELL_PATH may be used to constrain the above.

8.4.3. Format Nix with nix fmt

Nix files (*.nix) are formatted with alejandra. To check formatting without modifying files:

make format-nix-check

To apply formatting in place:

make format-nix

Both commands invoke nix fmt under the hood via scripts/docker-run.sh nix-fmt[-check], which runs in the nix environment rather than the firmware toolchain container. On hosts without nix installed, a NixOS container provides it automatically.

CI runs format-nix-check on every pull request and push to main, reporting inline diff annotations through reviewdog.

8.5. Configuration Files

8.5.1. TOML file Lint with tomllint

TOML File syntax may be checked by running

make lint-toml

The variable TOML_PATH may be used to limit the files checked.

lint-toml (tomllint) checks TOML syntax only — it carries no style rules and no autofix, unlike yamllint/markdownlint. It mainly guards the DV testlists and sim configs under hw//dv//*.toml, catching a malformed file before the runner that reads it fails with a less legible error.

8.5.2. YAML file Lint with yamllint

YAML File syntax may be checked by running

make lint-yaml

The variable YAML_PATH may similarly be used to limit the files checked.

8.6. Documentation Files

lint-spelling (codespell) and lint-markdown (markdownlint) both offer autofix commands (-fix suffix); review the diff before committing, since either can rewrite a deliberate domain term (see [tool.codespell]’s `ignore-words-list in pyproject.toml) or mangle non-prose content such as math notation or bit-range syntax that only looks like Markdown.

8.6.1. Markdown Lint with markdownlint

To check the markdown files within the OCAH Directories, run

make lint-markdown

Fixes may automatically be applied by running

make lint-markdown-fix

In both cases, the directory linted may be constrained with MARKDOWN_PATH.

8.6.2. Spell-Checking with Codespell

To check the spelling of all documentation files, run the following

make lint-spelling

To apply fixes automatically, run

make lint-spelling-fix

In both cases, the path of files to be checked may be constrained with CODESPELL_PATH.

8.7. Synthesis Scripts

8.7.1. TCL Script Style, Lint and Format with Tclint

To lint the TCL scripts (used to automate the synthesis workflow) in the repository, we use the package tclint. This may be managed by uv - run uv sync to install, or to use a system installed version, detected in the usual way from PATH, set OCAH_TCLINT_SKIP_UV=1.

To lint, use the following command:

make lint-tcl

To then run the formatter and style checker, use the following, whose changes may first be checked with make format-tcl-check:

make format-tcl

The search path for script files may be overridden with TCL_PATH.

8.8. Optional Pre-Commit Checks

The repository provides optional, check-only hooks for staged Python, C/C++, Tcl, YAML, TOML, and shell files, plus a repo-wide spelling check. They run Ruff, clang-format, tclfmt, tclint, codespell, yamllint, tomllint, shellcheck, and shfmt from the locked uv environment, plus Git’s whitespace/conflict-marker check. The hooks do not modify or stage files. mypy, markdownlint, and checkmake are deliberately left out of this bundle — they are slow, need a separate Node.js toolchain, or ship as a standalone binary rather than a uv-managed package — and stay CI-only; see the CI job table above for their local commands.

make hooks-install    # explicit opt-in for this clone
make hooks-run        # check currently staged files without installing
make hooks-run-all    # check every eligible tracked file
make hooks-uninstall

Installation uses pre-commit’s standard local .git/hooks/pre-commit launcher. It does not change core.hooksPath, and no setup or checkout command installs it automatically. CI remains authoritative whether or not a contributor enables the hook.

8.9. Linting and CI

.github/workflows/lint.yml runs lint-sv-slang, lint-sv-verilator, lint-python, lint-python-mypy, format-c, lint-tcl, lint-spelling, lint-sv-comments, lint-sv-enums, lint-yaml, lint-toml, lint-markdown, lint-make, lint-shell, regen-regs, and vendor-diff on pull requests and pushes to main (lint-sv-verible is temporarily disabled). Setup jobs share bender, Verilator, and reviewdog artifacts; jobs report through .github/actions/reviewdog-report. A Checks API HTTP 5xx is retried. lint-sv-verilator and lint-python still pass or fail on the tool exit status when that post does not succeed.

Documentation-only diffs (every changed path is under doc/, an Antora playbook, or a .md / .adoc / image) skip lint, Verilator smoke, the native VP legs, and the nonfree GitLab child; lint-vale, which checks documentation prose, runs on every diff. The required verilator-smoke (dtp) / (sep), sep-vp (native), smc-vp + smu-vp (native) and GitLab checks still report success. scripts/ci/diff_class.py --self-test checks the classifier. Scheduled and manually dispatched pipelines always run in full.

CI job Reviewdog check(s) Local command

lint-sv-slang

lint-sv-slang/smu, lint-sv-slang/aou

make lint-slang-all BLOCK=smu / BLOCK=aou

lint-sv-verilator

lint-sv-verilator/<block> for aou, dtp, sep, smc, and smu

make lint-verilator-all [BLOCK=<block>], plus make lint-verilator-public-all BLOCK=smu on the smu row

lint-sv-verible (disabled in CI)

lint-sv-verible

make lint-sv-verible

lint-python

lint-python

make lint-python and make format-python-check

lint-python-mypy

lint-python-mypy (report-only)

make ocah-lint-python-mypy

format-c

format-c

make format-c-check

format-nix

format-nix

make format-nix-check

lint-tcl

lint-tcl

make lint-tcl and make format-tcl-check

lint-spelling

lint-spelling

make ocah-lint-spelling and make ocah-lint-spelling-fix

lint-sv-comments

lint-sv-comments

make ocah-lint-sv-comments

lint-sv-enums

lint-sv-enums

make ocah-lint-sv-enums

lint-yaml

lint-yaml

make ocah-lint-yaml

lint-toml

lint-toml

make ocah-lint-toml

lint-markdown

lint-markdown

make ocah-lint-markdown and make ocah-lint-markdown-fix

lint-make

lint-make (report-only)

make ocah-lint-make

lint-shell

lint-shell

make ocah-lint-shell, make ocah-format-shell, and make ocah-format-shell-check

regen-regs

— (job fails on a dirty tree)

make regen-regs regen-regs-adoc regen-regs-html

vendor-diff

— (job fails when committed upstream/ drifts)

bender vendor diff --err_on_diff

The regen-regs gate regenerates every register block’s collateral from the RDLs and fails if the working tree changes, so the committed **/regs/gen/ outputs (SystemVerilog, C headers, Python, RAL, IP-XACT, AsciiDoc, HTML) always match a fresh run. It is peakrdl-only (no bender or nonfree/), so a plain checkout reproduces it. To fix a failure, run the local command above and commit the result:

make regen-regs regen-regs-adoc regen-regs-html
git status --porcelain   # expect no output

The vendor-diff gate re-clones every vendor_package remote, applies patches/, and fails if the committed upstream/ tree does not match. To fix a failure, refresh the tree and commit:

bender vendor init
git status --porcelain   # expect only intended vendor/ changes