From 31cfac7df01270577ad0fbde89ecc279fb2d7526 Mon Sep 17 00:00:00 2001 From: Lucas Hix Date: Sun, 1 Nov 2026 00:39:18 -0300 Subject: [PATCH 1/2] Mirror the four-job CI of the TopLevel in the parent repository One workflow, run by a pull request (a new commit cancels the previous run) or a push to main, with four jobs of fixed names for a ruleset: vhdl-syntax, unit-tests, formal and python-static-analysis. The Makefile gets the matching targets: paths, check, unit-tests and formal run the target of each repository (the repositories sit side by side as submodules, which is how their Makefiles name each other), and riscv-tools comes from the CI image. The old sim.yml and vhdl_syntax_check.yml are replaced, so no test runs twice. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016kr8VNCHkZpPYMTWw5b2tY --- .github/workflows/ci.yml | 120 ++++++++++++++++++++++++ .github/workflows/sim.yml | 64 ------------- .github/workflows/vhdl_syntax_check.yml | 39 -------- Makefile | 81 +++++++--------- pyproject.toml | 85 +++++++++++++++++ 5 files changed, 239 insertions(+), 150 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/sim.yml delete mode 100644 .github/workflows/vhdl_syntax_check.yml create mode 100644 pyproject.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2d72530 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,120 @@ +name: ci + +# One run per commit: a pull request (a new commit cancels the run of the previous one) or a push to +# main. The four jobs have fixed names, which a ruleset of the organization can require as checks. +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + vhdl-syntax: + name: vhdl-syntax + runs-on: ubuntu-26.04 + timeout-minutes: 15 + # GHDL, Yosys, SymbiYosys, z3, uv and riscv-tools of insper-riscv/Infra. + container: + image: ghcr.io/insper-riscv/ci-vhdl:latest + + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + + - name: Every referenced path exists, in this repository and in each submodule + run: make paths + + - name: VHDL syntax check of every repository (GHDL, dependency order) + run: make check + + unit-tests: + name: unit-tests + runs-on: ubuntu-26.04 + timeout-minutes: 60 + container: + image: ghcr.io/insper-riscv/ci-vhdl:latest + + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + + - name: Per-entity cocotb tests of each repository and the memory map check + run: make unit-tests + + - name: Test programs of the Tests repository, in simulation + run: make sim + + - name: Upload waveforms + if: failure() + uses: actions/upload-artifact@v7 + with: + name: waveforms + path: "**/*.ghw" + if-no-files-found: ignore + + formal: + name: formal + runs-on: ubuntu-26.04 + timeout-minutes: 45 + container: + image: ghcr.io/insper-riscv/ci-vhdl:latest + + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + + - name: Prove every make target named formal* of each repository + # A repository without proofs passes with a message; the proofs join by adding a formal target to it. + run: make formal + + - name: Upload the logs + if: failure() + uses: actions/upload-artifact@v7 + with: + name: formal-logs + path: | + */build/formal/**/*.log + if-no-files-found: ignore + + python-static-analysis: + name: python-static-analysis + runs-on: ubuntu-26.04 + timeout-minutes: 15 + # The tools come pinned in the image, with the settings of [tool.ruff] and [tool.complexipy] of pyproject.toml. + container: + image: ghcr.io/insper-riscv/ci-vhdl:latest + + steps: + - uses: actions/checkout@v7 + + - name: ruff lint + run: ruff check . + + - name: ruff format + if: ${{ !cancelled() }} + run: ruff format . --check + + - name: pycodestyle + if: ${{ !cancelled() }} + run: pycodestyle --max-line-length=79 --max-doc-length=72 --ignore=E203,W503 --exclude=Common-Components,build,.venv,sim_build,__pycache__ . + + - name: pydocstyle + if: ${{ !cancelled() }} + run: pydocstyle --convention=pep257 --match-dir='^(?!(Common-Components|build|\.venv|sim_build|__pycache__)).*' . + + - name: complexipy + if: ${{ !cancelled() }} + run: complexipy . + + - name: semgrep + if: ${{ !cancelled() }} + run: semgrep scan --config p/python --error . diff --git a/.github/workflows/sim.yml b/.github/workflows/sim.yml deleted file mode 100644 index e2a3615..0000000 --- a/.github/workflows/sim.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: sim -# Integration CI of the parent: it pins Tests (and, through it, Tools), so it -# runs the same checks Tests runs on itself, against the VHDL of THIS commit. -on: - pull_request: - push: - branches: - - main - - test-pipeline -concurrency: - group: sim-${{ github.ref }} - cancel-in-progress: true -jobs: - cocotb-ghdl: - runs-on: ubuntu-26.04 - # insper-riscv/Infra's ELF image: GHDL, the RISC-V GCC with picolibc, - # Spike and uv already on PATH. - container: - image: ghcr.io/insper-riscv/ci-elf:latest - defaults: - run: - working-directory: Tests - steps: - - name: Checkout repository and submodules - uses: actions/checkout@v7 - with: - submodules: recursive - path: . - - name: Install Python dependencies - run: uv sync - - name: Run host-side UART harness tests - run: uv run python -m unittest discover -s tests/host -v - - name: Generate rv32_test.h - run: uv run riscv-tools --config tools/riscv_build/config.yaml generate-header - - name: Check the memory map against every copy - # Here Tests sits at RV32IM/Tests, so ../Core, ../Memory and ../TopLevel - # (the core's VHDL, the Quartus IPs, the platform) exist and every check runs. - run: uv run riscv-tools --root ../TopLevel check-memory-map --platform platforms/internal-mem/platform.yaml - - name: Check every referenced path exists - # paths.yaml lists each file the tree's configuration names (simulation - # sources, tests.json, the Quartus project). Also the safety net for - # moving files between repositories. - run: uv run riscv-tools --root .. check-paths --manifest paths.yaml - - name: Build sim test ROM images - run: uv run riscv-tools --config tools/riscv_build/config.yaml compile --emit hex - - name: Run sim tests - # The generic top exposes UART_RX/UART_TX, so the same invocation runs - # uart-tx, uart-echo and uart-errors beside every other program. - run: uv run riscv-tools --config tools/riscv_build/config.yaml sim - - name: Run per-entity VHDL unit tests - run: uv run python tests/python/runner.py - - name: Each repository's own checks and tests, at the pinned versions - # Core, Memory, Peripherals and TopLevel: paths, syntax, profiles, entity - # tests, memory map. They sit as siblings, which is what they expect. - working-directory: . - run: make subrepos - - - name: Upload waveforms - if: failure() - uses: actions/upload-artifact@v7 - with: - name: waveforms - path: "**/*.ghw" - if-no-files-found: ignore diff --git a/.github/workflows/vhdl_syntax_check.yml b/.github/workflows/vhdl_syntax_check.yml deleted file mode 100644 index 0a33b24..0000000 --- a/.github/workflows/vhdl_syntax_check.yml +++ /dev/null @@ -1,39 +0,0 @@ -# .github/workflows/vhdl_syntax_check.yml -name: VHDL Syntax Check - -on: - push: - paths: - - "Core" - - "Memory" - - "Peripherals" - - "TopLevel" - - "Makefile" - - ".github/workflows/vhdl_syntax_check.yml" - pull_request: - paths: - - "Core" - - "Memory" - - "Peripherals" - - "TopLevel" - - "Makefile" - -jobs: - ghdl-check: - name: GHDL syntax check - runs-on: ubuntu-26.04 - # insper-riscv/Infra's VHDL image: GHDL and uv on PATH. - container: - image: ghcr.io/insper-riscv/ci-vhdl:latest - - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - submodules: recursive - - - name: Install Python dependencies (riscv-tools, from Tests/tools/Tools) - run: uv sync --project Tests - - - name: VHDL syntax check (Makefile target) - run: make check diff --git a/Makefile b/Makefile index feafcd1..fdd481f 100644 --- a/Makefile +++ b/Makefile @@ -1,67 +1,54 @@ # The parent repository: it pins Core, Memory, Peripherals, TopLevel and Tests, and # this Makefile runs them together. Each of them has its own Makefile and uv project; -# `git submodule update --init --recursive` first. +# `git submodule update --init --recursive` first. The repositories sit side by side +# here, which is how their own Makefiles and configurations name each other (../Core). +# The riscv-tools command comes from the toolchain image of insper-riscv/Infra. SHELL := /bin/bash SUBREPOS := Core Memory Peripherals TopLevel -GHDL := ghdl -STD := --std=08 -WDIR := build/ghdl +# The Python of the image where riscv_tools is installed (the host tests of Tests import it). +PYTOOLS ?= /opt/riscv-tools/riscv-tools/bin/python -.PHONY: all sync check subrepos sim paths clean +.PHONY: all sync paths check unit-tests formal sim clean -all: paths check subrepos sim +all: paths check unit-tests formal sim sync: @set -e; for s in $(SUBREPOS) Tests; do (cd $$s && uv sync -q); done -# --------------------------------------------------------------- -# VHDL syntax check (GHDL), every source of the tree in ONE library: it also catches -# two repositories defining the same entity (as a stale ROM_simulation once did). -# Run with: make check -# --------------------------------------------------------------- -# 1) All .vhd/.vhdl of the repositories that hold VHDL. The Quartus IPs (Memory/ips) -# and the PLL need Intel libraries and are left out. -CHECK_SRCS := $(shell find TopLevel/platforms/internal-mem/rtl Memory/sim Peripherals/common Peripherals/GPIO Peripherals/TIMER Peripherals/UART Core/common Core/I Core/M Core/cores -type f \( -name '*.vhd' -o -name '*.vhdl' \) | sort) - -# 2) Dependency order (topological sort, riscv-tools vhdl-sort) -ORDERED_SRCS := $(shell uv run --project Tests riscv-tools vhdl-sort $(CHECK_SRCS)) - -.PHONY: print-check -print-check: - @echo "Files the check analyzes:"; echo - @printf ' %s\n' $(ORDERED_SRCS) +# Every file path that the configuration of each repository lists exists (the paths.yaml of each one). +paths: + @set -e; for s in $(SUBREPOS); do $(MAKE) -C $$s paths; done +# VHDL syntax check (GHDL) of every repository, in dependency order. The TopLevel analyzes the sources of +# Common-Components, Core, Memory and Peripherals together in one library, so it also catches two +# repositories defining the same entity; the Core also elaborates each of its profiles. check: - @echo "Checking VHDL syntax with GHDL..." - @mkdir -p $(WDIR) - @rm -rf $(WDIR)/* - @$(GHDL) -a $(STD) --work=work --workdir=$(WDIR) $(ORDERED_SRCS) - @echo "VHDL syntax check passed" + $(MAKE) -C Core profiles + $(MAKE) -C Memory check + $(MAKE) -C Peripherals check + $(MAKE) -C TopLevel check + +# The per-entity cocotb tests of each repository, and the memory map check of the TopLevel. +unit-tests: sync + $(MAKE) -C Core test + $(MAKE) -C Memory test + $(MAKE) -C Peripherals test + $(MAKE) -C TopLevel memory-map test + +# Every make target named formal* of each repository (SymbiYosys and z3); a repository without proofs is skipped. +formal: + @set -e; for s in $(SUBREPOS); do \ + for t in $$(sed -n 's/^\(formal[a-z-]*\):.*/\1/p' $$s/Makefile); do $(MAKE) -C $$s $$t; done; \ + done -# --------------------------------------------------------------- -# Each repository's own checks and tests, at the pinned versions. They find each -# other as siblings (../Core, ../Memory, ...), which is how the submodules sit. -# --------------------------------------------------------------- -subrepos: sync - $(MAKE) -C Core paths check profiles test - $(MAKE) -C Memory paths check test - $(MAKE) -C Peripherals paths check test - $(MAKE) -C TopLevel all - -# --------------------------------------------------------------- # The Tests project's generic simulation suite, including uart-tx, uart-echo # and uart-errors through the generic top's serial pins. -# --------------------------------------------------------------- sim: sync - cd Tests && uv run python -m unittest discover -s tests/host -v \ - && uv run riscv-tools --config tools/riscv_build/config.yaml generate-header \ - && uv run riscv-tools --config tools/riscv_build/config.yaml compile --emit hex \ - && uv run riscv-tools --config tools/riscv_build/config.yaml sim - -# Every file path the configuration lists exists (paths.yaml). -paths: sync - cd Tests && uv run riscv-tools --root .. check-paths --manifest paths.yaml + cd Tests && $(PYTOOLS) -m unittest discover -s tests/host -v \ + && riscv-tools --config tools/riscv_build/config.yaml generate-header \ + && riscv-tools --config tools/riscv_build/config.yaml compile --emit hex \ + && riscv-tools --config tools/riscv_build/config.yaml sim clean: rm -rf build diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..40a1f75 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +# The static analysis of the Python of this repository (the scripts), as run by the +# python-static-analysis job of the CI. The repository itself is not a Python project. +[tool.ruff] +target-version = "py314" +line-length = 79 +extend-exclude = ["Core", "Memory", "Peripherals", "TopLevel", "Tests", "Common-Components", "build"] + +[tool.ruff.lint] +select = [ + # Core + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + + # Security + "S", # flake8-bandit (Security Scanning/SAST) + "BLE", # flake8-blind-except + + # Import orders and formatting + "I", # isort + "Q", # flake8-quotes + "D", # pydocstyle + + # Standards and Moderization + "UP", # update-checker + "N", # pep8-naming, + "PTH", # flake8-pathlib + "TID", # flake8-tidy-imports (absolute imports only) + + # Quality and Simplification + "B", # flake8-bugbear + "G", # flake8-logging-format + "PERF", # perflint + "C4", # flake8-comprehensions + "SIM", # flake8-simplify + "RET", # flake8-return + "PL", # pylint + "RUF", # ruff linting rules + + # Cleanup and Unused Code + "ERA", # flake8-eradicate + "ARG", # flake8-arg-names (unused-arguments) +] + +# PEP 257 rules the numpy convention turns off. +extend-select = [ + "D107", # docstring in __init__ + "D402", # summary line must not repeat the signature +] + +ignore = [ + "S603", + "S607", +] + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" + +[tool.ruff.lint.pycodestyle] +max-line-length = 79 +max-doc-length = 72 + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ruff.lint.pylint] +max-args = 5 +max-statements = 50 +max-returns = 6 +max-branches = 12 +max-locals = 15 +max-public-methods = 10 + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["S101", "PLR2004"] +"tests/fixtures/**" = ["B011", "ARG001"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +docstring-code-line-length = 72 + +[tool.complexipy] +paths = ["scripts"] +max-complexity-allowed = 10 From 0f9370d314e783ef95229f8f2a41a42f0424dfd3 Mon Sep 17 00:00:00 2001 From: Lucas Hix Date: Sun, 1 Nov 2026 01:24:38 -0300 Subject: [PATCH 2/2] FIX pyproject.toml --- pyproject.toml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 40a1f75..ef9b0dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,11 +48,6 @@ extend-select = [ "D402", # summary line must not repeat the signature ] -ignore = [ - "S603", - "S607", -] - [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" @@ -71,10 +66,6 @@ max-branches = 12 max-locals = 15 max-public-methods = 10 -[tool.ruff.lint.per-file-ignores] -"tests/**" = ["S101", "PLR2004"] -"tests/fixtures/**" = ["B011", "ARG001"] - [tool.ruff.format] quote-style = "double" indent-style = "space"