Workflows
|
This section is currently under development.
|
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_packageentry carries a narrowly scoped include/mapping:list and usesexclude_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 viaBender.localso 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
-
Materialize/refresh the vendor tree:
bender vendor init -
Edit files under
vendor/<GitHubOrg>/<GitHubRepo>/upstream/. -
Stage:
git add vendor/<GitHubOrg>/<GitHubRepo>/ -
Generate the patch (Bender prompts for a message):
bender vendor patch -
Rename to the numbering convention:
mv vendor/…/patches/<auto-name>.patch vendor/…/patches/<NNNN>-<description>.patch -
Commit both the patched source and the new
.patchfile.
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), writeTEST_ROM_PASS(0x77777777) toscratch_0, and spin inwfiso the testbench can load real firmware into SRAM. It builds as onerom-mode test nameddummy(sourcerom.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 withdv/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 withOCAH_FW_OVERLAY_SOURCES_smc, and the include flags (-I<dir>) for their generated register headers withOCAH_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 whenRISCV_TOOLCHAINis unset. -
pack-images— image conversion viauv; 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 whenRISCV_TOOLCHAINis unset. -
key-digestsandoca-images— pure Python viauv, which the container carries; can run in the container or on the host.oca-imagesis transport-agnostic: the same manifest+BL1 payload works with every ROM build profile, so no extra flags are needed aftertoolchain-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/.bssinitial image as DCCM-relative byte hex. -
oca_*.bin/oca_*.spi_preload— OCA boot-manifest images, one per validator path (OCA_IMAGESin 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, withrom_main.mapandrom_main.disbeside 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-vpandsmu-vponly; 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.
-
uvfor 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/Makefilefor detailed build options (make -C virtual_platform help). -
virtual_platform/README.mdfor the three executables, dependency resolution and the containerized flow. -
virtual_platform/BUILDING_NATIVELY.mdfor building and running the VP on your own host. -
virtual_platform/sepvp/README.mdfor thesepvprunner: boot straps, fuse maps and status channels. -
virtual_platform/RUNNING_THE_SMC_BOOT_ROM.mdfor 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 |
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.
|
|
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
|
|
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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
— (job fails on a dirty tree) |
|
|
— (job fails when committed |
|
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