feat: refactor MPW preparation and add regression and flow scripts

- Updated  to utilize a new dependency locking mechanism and improved repository management.
- Introduced  for running regression tests with configurable command sets for different profiles.
- Added  to execute commands with structured logging and result reporting.
- Enhanced  to support fetching specific revisions from git repositories.
- Modified  to use the new dependency source structure.
- Updated OpenSTA makefile and TCL script to include metrics reporting.
- Improved synthesis scripts to utilize the new flow execution model.
- Added tests for dependency locking, flow execution, and warning analysis.
- Ensured that all scripts and tools are consistent with the new structure and functionality.
This commit is contained in:
maksyuki committed 2026-07-10 17:48:11 +08:00
1 parent 7d064ce782
commit 57c9d334ea
69 files changed
+3200 -580

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
# Build and dependency controls require explicit maintainer review.
/Makefile @maksyuki
/config/ @maksyuki
/configs/ @maksyuki
/scripts/ @maksyuki
/.github/ @maksyuki
/quality/ @maksyuki
+34
View File
@@ -0,0 +1,34 @@
name: Set up locked toolchains
description: Install checksum-verified retroSoC tool archives and their runtime libraries
inputs:
tools:
description: Space-separated tool names from config/dependencies.lock.json
required: true
runs:
using: composite
steps:
- name: Install runtime packages
shell: bash
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
ccache g++ libfl2 libgoogle-perftools4 libunwind8 make mold numactl zlib1g
- name: Install checksum-verified tools
shell: bash
env:
REQUESTED_TOOLS: ${{ inputs.tools }}
run: |
read -r -a tools <<< "${REQUESTED_TOOLS}"
args=()
for tool in "${tools[@]}"; do
args+=(--tool "${tool}")
done
python3 scripts/install_toolchain.py \
--lock config/dependencies.lock.json \
--platform ubuntu-22.04 \
--cache .cache/retrosoc \
--github-path "${GITHUB_PATH}" \
"${args[@]}"
+13
View File
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: pip
directory: /requirements
schedule:
interval: monthly
open-pull-requests-limit: 5
+103
View File
@@ -0,0 +1,103 @@
name: reusable regression
on:
workflow_call:
inputs:
suite:
description: Regression suite from scripts/regress.py
required: true
type: string
retention_days:
description: Report artifact retention
required: false
default: 30
type: number
permissions:
contents: read
jobs:
regression:
runs-on: ubuntu-22.04
timeout-minutes: 240
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Restore locked source dependencies
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
app/coremark/coremark-main
app/fatfs/ff16
pdk/IHP-Open-PDK
rtl/clusterip/archinfo
rtl/clusterip/clint
rtl/clusterip/common
rtl/clusterip/crc
rtl/clusterip/i2c
rtl/clusterip/i2s
rtl/clusterip/plic
rtl/clusterip/ps2
rtl/clusterip/pwm
rtl/clusterip/rng
rtl/clusterip/rtc
rtl/clusterip/spi
rtl/clusterip/timer
rtl/clusterip/uart
rtl/clusterip/wdg
rtl/ip/3rd-party
rtl/mini/mpw
.cache/retrosoc/stamps
key: sources-${{ runner.os }}-${{ hashFiles('config/dependencies.lock.json') }}
- name: Restore locked toolchains
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
.cache/retrosoc/downloads
.cache/retrosoc/toolchains
key: tools-${{ runner.os }}-${{ hashFiles('config/dependencies.lock.json') }}
- name: Restore Verilator compiler cache
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: .cache/retrosoc/ccache
key: ccache-${{ runner.os }}-${{ hashFiles('config/dependencies.lock.json') }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
ccache-${{ runner.os }}-${{ hashFiles('config/dependencies.lock.json') }}-${{ github.sha }}-
ccache-${{ runner.os }}-${{ hashFiles('config/dependencies.lock.json') }}-
- name: Install build Python dependency
run: python3 -m pip install --requirement requirements/build.txt
- name: Set up locked EDA and compiler tools
uses: ./.github/actions/locked-tools
with:
tools: verilator sv2v iverilog yosys opensta riscv_gnu
- name: Set up locked source dependencies
run: make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk setup
- name: Check environment
run: |
make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk \
SIMU=IVERILOG SYNTH=YOSYS STA=OPENSTA doctor
make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=VERILATOR doctor
- name: Run ${{ inputs.suite }} regression
run: python3 scripts/regress.py --root . --suite "${{ inputs.suite }}"
- name: Upload reports and structured results
if: always()
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: ${{ inputs.suite }}-regression-${{ github.run_id }}-${{ github.run_attempt }}
path: |
build/**/meta/*.json
build/**/*.log
build/**/*.rpt
if-no-files-found: warn
retention-days: ${{ inputs.retention_days }}
-42
View File
@@ -1,42 +0,0 @@
name: lint
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
jobs:
build:
name: Build and Test
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: echo "No setup needed for this minimal workflow"
- name: Run tests
run: echo "No tests defined yet"
- name: Build project
run: echo "Build placeholder"
- name: Success notification
run: echo "Workflow completed successfully"
# jobs:
# lint-tgt:
# name: SystemVerilog Lint
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - uses: chipsalliance/verible-linter-action@main
# with:
# paths: |
# ./rtl
# # extra_args: "--waiver_files lint/common_cells.style.waiver --rules=-interface-name-style --lint_fatal"
# # github_token: ${{ secrets.GITHUB_TOKEN }}
# # reviewdog_reporter: github-check
+20
View File
@@ -0,0 +1,20 @@
name: nightly
on:
schedule:
- cron: "17 18 * * *"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: nightly-regression
cancel-in-progress: false
jobs:
extended-open-source-flows:
uses: ./.github/workflows/_regression.yml
with:
suite: nightly
retention_days: 90
+59
View File
@@ -0,0 +1,59 @@
name: quality
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: quality-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
scripts:
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Restore pip cache
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements/ci.txt') }}
- name: Install pinned quality tools
run: python3 -m pip install --requirement requirements/ci.txt
- name: Validate dependency lock
run: python3 scripts/dependency_lock.py --lock config/dependencies.lock.json
- name: Lint Python
run: ruff check .
- name: Test scripts
run: python3 -m pytest -q
- name: Lint YAML
run: yamllint .github .yamllint.yml
- name: Install pinned actionlint
env:
GOBIN: ${{ runner.temp }}/actionlint-bin
run: |
go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
echo "${GOBIN}" >> "${GITHUB_PATH}"
- name: Lint GitHub Actions
run: actionlint
- name: Validate regression definitions
run: |
python3 scripts/regress.py --root . --suite pr --dry-run
python3 scripts/regress.py --root . --suite nightly --dry-run
+22
View File
@@ -0,0 +1,22 @@
name: regression
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: regression-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
verified-open-source-flows:
uses: ./.github/workflows/_regression.yml
with:
suite: pr
retention_days: 30
+71
View File
@@ -0,0 +1,71 @@
name: release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
source-package:
runs-on: ubuntu-22.04
timeout-minutes: 90
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Restore locked source dependencies
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
app/coremark/coremark-main
app/fatfs/ff16
pdk/IHP-Open-PDK
rtl/clusterip/archinfo
rtl/clusterip/clint
rtl/clusterip/common
rtl/clusterip/crc
rtl/clusterip/i2c
rtl/clusterip/i2s
rtl/clusterip/plic
rtl/clusterip/ps2
rtl/clusterip/pwm
rtl/clusterip/rng
rtl/clusterip/rtc
rtl/clusterip/spi
rtl/clusterip/timer
rtl/clusterip/uart
rtl/clusterip/wdg
rtl/ip/3rd-party
rtl/mini/mpw
.cache/retrosoc/stamps
key: sources-${{ runner.os }}-${{ hashFiles('config/dependencies.lock.json') }}
- name: Install build Python dependency
run: python3 -m pip install --requirement requirements/build.txt
- name: Set up locked source dependencies
run: make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk setup
- name: Build checksummed source package and SBOM
run: make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk package
- name: Upload workflow artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: retrosoc-${{ github.ref_name }}
path: dist/**
if-no-files-found: error
retention-days: 90
- name: Publish GitHub release assets
env:
GH_TOKEN: ${{ github.token }}
run: |
if ! gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then
gh release create "${GITHUB_REF_NAME}" --generate-notes --verify-tag
fi
mapfile -d '' files < <(find dist -type f -print0)
gh release upload "${GITHUB_REF_NAME}" "${files[@]}" --clobber
-42
View File
@@ -1,42 +0,0 @@
name: style
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
jobs:
build:
name: Build and Test
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: echo "No setup needed for this minimal workflow"
- name: Run tests
run: echo "No tests defined yet"
- name: Build project
run: echo "Build placeholder"
- name: Success notification
run: echo "Workflow completed successfully"
# jobs:
# lint-tgt:
# name: SystemVerilog Lint
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - uses: chipsalliance/verible-linter-action@main
# with:
# paths: |
# ./rtl
# # extra_args: "--waiver_files lint/common_cells.style.waiver --rules=-interface-name-style --lint_fatal"
# # github_token: ${{ secrets.GITHUB_TOKEN }}
# # reviewdog_reporter: github-check
-155
View File
@@ -1,155 +0,0 @@
name: test
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Test build scripts
run: |
python3 -m pip install pytest
python3 -m pytest -q tests
- name: Download and Extract Verilator Artifact
run: |
verilator_path="artifact-verilator"
mkdir $verilator_path
wget https://github.com/retroSoC/artifact/releases/download/verilator-v5.046/verilator-v5.046-ubuntu-22.04-gcc.tar.gz
tar -xvf verilator-v5.046-ubuntu-22.04-gcc.tar.gz -C $verilator_path
echo "$(pwd)/$verilator_path/bin" >> $GITHUB_PATH
sudo apt-get update
sudo apt-get install git help2man perl python3 make autoconf g++ flex bison ccache mold
sudo apt-get install libunwind-dev libgoogle-perftools-dev numactl perl-doc
sudo apt-get install libfl2
sudo apt-get install libfl-dev
sudo apt-get install zlib1g zlib1g-dev
sudo apt-get install tree
- name: Test Verilator Toolchain
run: |
verilator --version
- name: Download and Extract Sv2v Artifact
run: |
sv2v_path="artifact-sv2v"
mkdir $sv2v_path
wget https://github.com/retroSoC/artifact/releases/download/sv2v-v0.0.13-9-gd381209/sv2v-v0.0.13-9-gd381209-ubuntu-22.04-gcc.tar.gz
tar -xvf sv2v-v0.0.13-9-gd381209-ubuntu-22.04-gcc.tar.gz -C $sv2v_path
echo "$(pwd)/$sv2v_path" >> $GITHUB_PATH
- name: Test Sv2v Toolchain
run: |
sv2v --version
- name: Download and Extract Iverilog Artifact
run: |
iverilog_path="artifact-iverilog"
mkdir $iverilog_path
wget https://github.com/retroSoC/artifact/releases/download/iverilog-v13_0/iverilog-v13_0-ubuntu-22.04-gcc.tar.gz
tar -xvf iverilog-v13_0-ubuntu-22.04-gcc.tar.gz -C $iverilog_path
echo "$(pwd)/$iverilog_path/bin" >> $GITHUB_PATH
- name: Test Iverilog Toolchain
run: |
iverilog -V
- name: Download and Extract Yosys Artifact
run: |
yosys_path="artifact-yosys"
mkdir $yosys_path
wget https://github.com/retroSoC/artifact/releases/download/yosys-v0.58/yosys-v0.58-ubuntu-22.04-gcc.tar.gz
tar -xvf yosys-v0.58-ubuntu-22.04-gcc.tar.gz -C $yosys_path
echo "$(pwd)/$yosys_path/bin" >> $GITHUB_PATH
- name: Test Yosys Toolchain
run: |
yosys --version
- name: Download and Extract OpenSTA Artifact
run: |
opensta_path="artifact-opensta"
mkdir $opensta_path
wget https://github.com/retroSoC/artifact/releases/download/opensta-v2.2.0-1767-ga93b51ec/opensta-v2.2.0-1767-ga93b51ec-ubuntu-22.04-gcc.tar.gz
tar -xvf opensta-v2.2.0-1767-ga93b51ec-ubuntu-22.04-gcc.tar.gz -C $opensta_path
echo "$(pwd)/$opensta_path/bin" >> $GITHUB_PATH
- name: Test OpenSTA Toolchain
run: |
sta -version
- name: Download and Extract RISC-V GNU Toolchain Artifact
run: |
riscv_toolchain_path="artifact-riscv-toolchain"
mkdir $riscv_toolchain_path
wget https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.05.01/riscv32-elf-ubuntu-22.04-gcc-nightly-2025.05.01-nightly.tar.xz
tar -xvf riscv32-elf-ubuntu-22.04-gcc-nightly-2025.05.01-nightly.tar.xz -C $riscv_toolchain_path
echo "$(pwd)/$riscv_toolchain_path/riscv/bin" >> $GITHUB_PATH
- name: Test RISCV-V GNU Toolchain
run: |
riscv32-unknown-elf-gcc --version
- name: Setup MPW Repository
run: |
pip install tomli
python3 setup.py
- name: Generate modified core
run: python3 rtl/mini/core/setup.py
- name: Download clusterIP
run: python3 rtl/clusterip/setup.py
- name: Download 3rd-party IP and models
run: python3 rtl/ip/setup.py
- name: Download Open PDK Repository
run: python3 pdk/setup.py
- name: Config FatFs and Coremark Repository
run: python3 app/setup.py
- name: Compile Single Core Firmware
run: make CORE=HAZARD3 IP=NONE ISA=RV32IM HAVE_CSR=NO PROG_TYPE=BASE LINK_TYPE=ld2_psram firmware
- name: Run Verilator Behavioral Simulation(Single Core)
run: make CORE=HAZARD3 IP=NONE SIMU=VERILATOR PDK=IHP130 HAVE_SRAM_IF=NO HAVE_SRAM_MACRO=NO HAVE_PLL=NO sim
- name: Compile MDD Firmware
run: make CORE=MDD IP=NONE ISA=RV32IM HAVE_CSR=NO PROG_TYPE=BASE LINK_TYPE=ld2_psram firmware
- name: Run Verilator Behavioral Simulation(MDD)
run: make CORE=MDD IP=NONE SIMU=VERILATOR PDK=IHP130 HAVE_SRAM_IF=NO HAVE_SRAM_MACRO=NO HAVE_PLL=NO RTL_SIM_CORESEL=0 sim
- name: Compile Assembly Firmware
run: make ISA=RV32IM HAVE_CSR=NO PROG_TYPE=BASE LINK_TYPE=ld2_psram asm
- name: Run Iverilog Behavioral Simulation(Single Core)
run: make CORE=HAZARD3 IP=NONE SIMU=IVERILOG PDK=IHP130 HAVE_SRAM_IF=NO HAVE_SRAM_MACRO=NO HAVE_PLL=NO RTL_SIM_TIMEOUT=5200000 sim
- name: Run synthesis
run: make CORE=HAZARD3 IP=NONE SYNTH=YOSYS PDK=IHP130 HAVE_SRAM_IF=NO HAVE_SRAM_MACRO=NO HAVE_PLL=NO synth
- name: Run Iverilog Netlist Simulation(Single Core)
run: make CORE=HAZARD3 IP=NONE SIMU=IVERILOG PDK=IHP130 HAVE_SRAM_IF=NO HAVE_SRAM_MACRO=NO HAVE_PLL=NO RTL_SIM_TIMEOUT=5200000 netsim
# - name: Upload to Actions Artifacts(Temporary storage)
# uses: actions/upload-artifact@v4
# with:
# name: ${{ env.PACKAGE_NAME }}
# path: |
# verilator/${{ env.PACKAGE_NAME }}.tar.gz
# verilator/${{ env.PACKAGE_NAME }}.tar.gz.sha256
# retention-days: 90 # Retain for 90 days
- name: Success Notification
run: echo "RTL simulation completed successfully"
+5
View File
@@ -11,6 +11,8 @@
*.ddb *.ddb
*.fst *.fst
*.txt *.txt
!requirements/
!requirements/*.txt
*.gtkw *.gtkw
*.vmf *.vmf
*.tar.bz2 *.tar.bz2
@@ -39,6 +41,9 @@ audio_file.h
.metals .metals
.mypy_cache .mypy_cache
.pytest_cache .pytest_cache
/build/
/.cache/
/dist/
.vscode .vscode
retrosoc_sections.lds retrosoc_sections.lds
retrosoc_syn* retrosoc_syn*
+7
View File
@@ -0,0 +1,7 @@
extends: default
rules:
document-start: disable
line-length:
max: 160
truthy: disable
+31 -1
View File
@@ -1 +1,31 @@
## CONTRIBUTE # Contributing
Run the script quality gate before opening a pull request:
```sh
python3 -m pip install --requirement requirements/ci.txt
ruff check .
python3 -m pytest -q
yamllint .github .yamllint.yml
python3 scripts/regress.py --root . --suite pr --dry-run
```
For build changes, use a committed profile and keep generated files below `build/` or `.cache/`.
Do not add new downloads directly to workflow YAML or setup scripts. Add the source or archive to
`config/dependencies.lock.json`, use the shared dependency helpers, and include a full Git commit or
SHA-256 checksum.
The supported pull-request regression is:
```sh
make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk setup
make regress-pr
```
Warning baseline changes must be reviewed separately from implementation changes. Regenerate only
the affected profile/tool file, inspect every normalized signature, and explain additions in the
pull request. Metrics remain observational until ten successful `main` runs have been collected and
promoted according to `docs/engineering.md`.
Changes to the dependency lock, CI workflows, warning policy, metrics policy, or release packaging
require the owners listed in `.github/CODEOWNERS`.
+103 -10
View File
@@ -2,6 +2,22 @@ SHELL := /bin/bash
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
.DELETE_ON_ERROR: .DELETE_ON_ERROR:
ROOT_PATH ?= $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
CONFIG ?=
LOCK_FILE ?= $(ROOT_PATH)/config/dependencies.lock.json
ifneq ($(strip $(CONFIG)),)
CONFIG_PATH := $(if $(filter /%,$(CONFIG)),$(CONFIG),$(ROOT_PATH)/$(CONFIG))
ifeq ($(wildcard $(CONFIG_PATH)),)
$(error Configuration profile not found: $(CONFIG_PATH))
endif
include $(CONFIG_PATH)
PROFILE_NAME := $(basename $(notdir $(CONFIG_PATH)))
else
CONFIG_PATH :=
PROFILE_NAME := manual
endif
SOC ?= MINI SOC ?= MINI
SIMU ?= VCS SIMU ?= VCS
SYNTH ?= NONE SYNTH ?= NONE
@@ -19,8 +35,9 @@ RTL_SIM_PLLEN ?= NONE
RTL_SIM_PLLCFG ?= NONE RTL_SIM_PLLCFG ?= NONE
RTL_SIM_CORESEL ?= 0 RTL_SIM_CORESEL ?= 0
RTL_SIM_TIMEOUT ?= -1 RTL_SIM_TIMEOUT ?= -1
SIM_FIRMWARE_NAME ?= $(FIRMWARE_NAME)
SIM_SUCCESS_MARKER ?= retroSoC: A Customized ASIC for Retro Stuff
ROOT_PATH ?= $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
RTL_PATH := $(ROOT_PATH)/rtl/mini RTL_PATH := $(ROOT_PATH)/rtl/mini
CORE ?= HAZARD3 CORE ?= HAZARD3
@@ -34,6 +51,34 @@ FIRMWARE_NAME ?= retrosoc_fw
PROG_TYPE ?= FULL PROG_TYPE ?= FULL
LINK_TYPE ?= ld2_sram LINK_TYPE ?= ld2_sram
BUILD_ROOT ?= $(ROOT_PATH)/build
CACHE_ROOT ?= $(ROOT_PATH)/.cache/retrosoc
MAX_JOBS ?= 16
JOBS ?= $(shell count=$$(nproc 2>/dev/null || printf '1'); \
if [ "$$count" -gt "$(MAX_JOBS)" ]; then printf '%s' '$(MAX_JOBS)'; \
else printf '%s' "$$count"; fi)
CONFIG_KEY_VARS := SOC CORE IP PDK HAVE_PLL HAVE_SRAM_IF HAVE_SRAM_MACRO HAVE_SVA \
ISA HAVE_CSR PROG_TYPE LINK_TYPE RTL_TOP FIRMWARE_NAME
VARIANT_ID := $(strip $(shell python3 $(ROOT_PATH)/scripts/config_key.py \
--lock $(LOCK_FILE) --profile $(PROFILE_NAME) \
$(foreach var,$(CONFIG_KEY_VARS),--value $(var)=$($(var)))))
ifeq ($(VARIANT_ID),)
$(error Failed to calculate build variant ID)
endif
LOCK_DIGEST := $(strip $(shell python3 $(ROOT_PATH)/scripts/dependency_lock.py --lock $(LOCK_FILE) --digest))
VARIANT_ROOT := $(abspath $(BUILD_ROOT))/$(VARIANT_ID)
SW_BUILD_DIR := $(VARIANT_ROOT)/sw
SIM_TOOL_NAME := $(shell printf '%s' '$(SIMU)' | tr '[:upper:]' '[:lower:]')
SIM_BUILD_ROOT := $(VARIANT_ROOT)/sim/$(SIM_TOOL_NAME)
SYN_BUILD_ROOT := $(VARIANT_ROOT)/syn/yosys
STA_BUILD_ROOT := $(VARIANT_ROOT)/sta/opensta
META_DIR := $(VARIANT_ROOT)/meta
ifeq ($(SYNTH),YOSYS)
FLOW_FILELIST_DIR := $(SYN_BUILD_ROOT)/filelists
else
FLOW_FILELIST_DIR := $(SIM_BUILD_ROOT)/filelists
endif
VALID_SOC := MINI VALID_SOC := MINI
VALID_CORE := PICORV32 HAZARD3 MDD VALID_CORE := PICORV32 HAZARD3 MDD
VALID_IP := NONE MDD VALID_IP := NONE MDD
@@ -114,7 +159,9 @@ ifeq ($(STA), OPENSTA)
include sta/opensta/opensta.mk include sta/opensta/opensta.mk
endif endif
.PHONY: help config doctor setup setup-mpw setup-core setup-clusterip setup-ip setup-pdk setup-app clean-all .PHONY: help config doctor setup setup-mpw setup-core setup-clusterip setup-ip setup-pdk setup-app \
clean-all purge-cache manifest check-warnings metrics check-metrics package \
regress-pr regress-nightly sim-asm
.NOTPARALLEL: setup .NOTPARALLEL: setup
help: help:
@@ -122,19 +169,28 @@ help:
'retroSoC build targets:' \ 'retroSoC build targets:' \
' firmware | asm build firmware' \ ' firmware | asm build firmware' \
' comp | sim behavioral simulation' \ ' comp | sim behavioral simulation' \
' sim-asm build/run the assembly self-test' \
' netcomp | netsim synthesized-netlist simulation' \ ' netcomp | netsim synthesized-netlist simulation' \
' postcomp | postsim post-layout simulation' \ ' postcomp | postsim post-layout simulation' \
' synth | sta synthesis and timing analysis' \ ' synth | sta synthesis and timing analysis' \
' setup install pinned external dependencies' \ ' setup install pinned external dependencies' \
' doctor check tools, paths, and selected configuration' \ ' doctor check tools, paths, and selected configuration' \
' config print selected configuration' \ ' config | manifest print/write the effective configuration' \
' clean | clean-all clean current backend or all generated output' \ ' check-warnings | metrics analyze flow logs and reports' \
' check-metrics apply the committed metrics policy' \
' regress-pr | regress-nightly run supported regression suites' \
' package create checksummed source deliverables' \
' clean | clean-all clean current flow or all build output' \
' purge-cache remove dependency and compiler caches' \
'' \ '' \
'Key variables: SOC CORE IP SIMU SYNTH STA PDK ISA PROG_TYPE LINK_TYPE' 'Usage: make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=IVERILOG sim'
config: config:
@printf '%-18s %s\n' \ @printf '%-18s %s\n' \
ROOT_PATH '$(ROOT_PATH)' SOC '$(SOC)' CORE '$(CORE)' IP '$(IP)' \ ROOT_PATH '$(ROOT_PATH)' CONFIG '$(or $(CONFIG_PATH),<defaults>)' \
VARIANT_ID '$(VARIANT_ID)' VARIANT_ROOT '$(VARIANT_ROOT)' \
JOBS '$(JOBS)' \
SOC '$(SOC)' CORE '$(CORE)' IP '$(IP)' \
SIMU '$(SIMU)' SYNTH '$(SYNTH)' STA '$(STA)' PDK '$(PDK)' \ SIMU '$(SIMU)' SYNTH '$(SYNTH)' STA '$(STA)' PDK '$(PDK)' \
HAVE_PLL '$(HAVE_PLL)' HAVE_SRAM_IF '$(HAVE_SRAM_IF)' \ HAVE_PLL '$(HAVE_PLL)' HAVE_SRAM_IF '$(HAVE_SRAM_IF)' \
HAVE_SRAM_MACRO '$(HAVE_SRAM_MACRO)' HAVE_SVA '$(HAVE_SVA)' \ HAVE_SRAM_MACRO '$(HAVE_SRAM_MACRO)' HAVE_SVA '$(HAVE_SVA)' \
@@ -144,13 +200,14 @@ config:
doctor: doctor:
@python3 $(ROOT_PATH)/scripts/doctor.py \ @python3 $(ROOT_PATH)/scripts/doctor.py \
--root $(ROOT_PATH) --simu $(SIMU) --synth $(SYNTH) --sta $(STA) \ --root $(ROOT_PATH) --simu $(SIMU) --synth $(SYNTH) --sta $(STA) \
--pdk $(PDK) --core $(CORE) --ip $(IP) --pdk $(PDK) --core $(CORE) --ip $(IP) --lock $(LOCK_FILE)
setup: setup-mpw setup-core setup-clusterip setup-ip setup-pdk setup-app setup: setup-mpw setup-core setup-clusterip setup-ip setup-pdk setup-app
setup-mpw: setup-mpw:
python3 $(ROOT_PATH)/setup.py python3 $(ROOT_PATH)/setup.py
python3 $(ROOT_PATH)/scripts/prepare_mpw.py python3 $(ROOT_PATH)/scripts/prepare_mpw.py \
--lock-file $(CACHE_ROOT)/locks/mpw-prepare.lock
@mkdir -p $(dir $(MPW_STAMP)) @mkdir -p $(dir $(MPW_STAMP))
@touch $(MPW_STAMP) @touch $(MPW_STAMP)
@@ -164,10 +221,46 @@ setup-ip:
python3 $(ROOT_PATH)/rtl/ip/setup.py python3 $(ROOT_PATH)/rtl/ip/setup.py
setup-pdk: setup-pdk:
python3 $(ROOT_PATH)/pdk/setup.py python3 $(ROOT_PATH)/pdk/setup.py --pdk $(PDK)
setup-app: setup-app:
python3 $(ROOT_PATH)/app/setup.py python3 $(ROOT_PATH)/app/setup.py
clean-all: clean-all:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(abspath $(BUILD_ROOT))
purge-cache:
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(abspath $(CACHE_ROOT))
manifest:
python3 $(ROOT_PATH)/scripts/manifest.py create --root $(ROOT_PATH) \
--lock $(LOCK_FILE) --output $(META_DIR)/manifest.json --profile $(PROFILE_NAME) \
$(foreach var,$(CONFIG_KEY_VARS),--config $(var)=$($(var))) \
--config SIMU=$(SIMU) --config SYNTH=$(SYNTH) --config STA=$(STA)
check-warnings:
python3 $(ROOT_PATH)/scripts/analyze_warnings.py check --root $(ROOT_PATH) \
--profile $(PROFILE_NAME) --variant-root $(VARIANT_ROOT) --output $(META_DIR)/warnings.json
metrics:
python3 $(ROOT_PATH)/scripts/metrics.py collect --variant-root $(VARIANT_ROOT) \
--output $(META_DIR)/metrics.json
check-metrics: metrics
python3 $(ROOT_PATH)/scripts/metrics.py check --metrics $(META_DIR)/metrics.json \
--policy $(ROOT_PATH)/quality/metrics/policy.json \
--baseline $(ROOT_PATH)/quality/metrics/baseline.json
package: $(MPW_VARIANT_STAMP) $(FILELIST_STAMP) manifest
python3 $(ROOT_PATH)/scripts/package.py --root $(ROOT_PATH) --lock $(LOCK_FILE) \
--variant-root $(VARIANT_ROOT) --output-dir $(ROOT_PATH)/dist/$(VARIANT_ID)
regress-pr:
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite pr
regress-nightly:
python3 $(ROOT_PATH)/scripts/regress.py --root $(ROOT_PATH) --suite nightly
sim-asm: asm
$(MAKE) SIM_FIRMWARE_NAME=$(ASM_FIRMWARE_NAME) \
SIM_SUCCESS_MARKER='Mem wr/rd test success' sim
+30 -22
View File
@@ -1,30 +1,38 @@
# retroSoC # retroSoC
[![style](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml) [![quality](https://github.com/retroSoC/retroSoC/actions/workflows/quality.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/quality.yml)
[![lint](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml) [![regression](https://github.com/retroSoC/retroSoC/actions/workflows/regression.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/regression.yml)
[![test](https://github.com/retroSoC/retroSoC/actions/workflows/test.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/test.yml) [![nightly](https://github.com/retroSoC/retroSoC/actions/workflows/nightly.yml/badge.svg)](https://github.com/retroSoC/retroSoC/actions/workflows/nightly.yml)
## Build automation ## Build Automation
Run `make help` for the supported build targets and `make config` for the active The build uses committed configuration profiles and a single dependency lock. Start with the
configuration. `make doctor` checks the selected simulator, synthesis/STA tools, verified HAZARD3 profile:
RISC-V compiler, PDK, and external source trees before a long build starts.
External dependencies are pinned to known commits. Install them with `make setup`;
individual targets such as `setup-pdk`, `setup-ip`, and `setup-app` are also
available. Setup scripts are idempotent and accept `--update` when an existing
checkout must be moved to the pinned revision.
Typical open-source flows are:
```sh ```sh
make PROG_TYPE=BASE firmware python3 -m pip install --requirement requirements/build.txt
make SIMU=VERILATOR sim make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk setup
make SIMU=IVERILOG sim make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=IVERILOG doctor
make SYNTH=YOSYS synth make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk firmware
make STA=OPENSTA sta make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=IVERILOG RTL_SIM_TIMEOUT=5200000 sim-asm
``` ```
Generated outputs are isolated under ignored build directories. `make clean` Other open-source flows use the same profile:
cleans the selected simulator backend and `make clean-all` removes all generated
flow output while leaving dependency checkouts intact. ```sh
make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=VERILATOR sim
make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SYNTH=YOSYS synth
make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk STA=OPENSTA sta
```
`make help` lists targets, while `make config` prints the effective configuration and its variant
identifier. Outputs live below `build/<profile>-<config-hash>/`; firmware, simulator, synthesis,
STA, reports, and manifests are isolated inside that directory. `make clean` removes the selected
backend, `make clean-all` removes all build outputs, and `make purge-cache` explicitly removes
download and compiler caches.
Dependency sources and binary tool archives are pinned in
[`config/dependencies.lock.json`](config/dependencies.lock.json). Downloads are checksum verified,
and every flow writes a machine-readable manifest and result records. See
[`docs/engineering.md`](docs/engineering.md) for CI coverage, warning baselines, metrics promotion,
release packaging, and cluster-only flows.
+4 -2
View File
@@ -9,9 +9,11 @@ OBJCOPY = $(GCC)objcopy
AS_FLAGS = -fno-pic -march=rv32im -mabi=ilp32 AS_FLAGS = -fno-pic -march=rv32im -mabi=ilp32
LDFLAGS = -T flash.ld -melf32lriscv LDFLAGS = -T flash.ld -melf32lriscv
APP ?= hello APP ?= hello
APP_ALL = $(APP)-asm OUT_DIR ?= .
APP_ALL = $(OUT_DIR)/$(APP)-asm
all: $(APP).s all: $(APP).s
@mkdir -p $(OUT_DIR)
$(AS) $(AS_FLAGS) $(APP).s -o $(APP_ALL).o $(AS) $(AS_FLAGS) $(APP).s -o $(APP_ALL).o
$(LD) $(LDFLAGS) $(APP_ALL).o -o $(APP_ALL).elf $(LD) $(LDFLAGS) $(APP_ALL).o -o $(APP_ALL).elf
$(OBJDUMP) -d $(APP_ALL).elf > $(APP_ALL).txt $(OBJDUMP) -d $(APP_ALL).elf > $(APP_ALL).txt
@@ -19,4 +21,4 @@ all: $(APP).s
$(OBJCOPY) -S -I binary -O verilog $(APP_ALL).bin $(APP_ALL).flash $(OBJCOPY) -S -I binary -O verilog $(APP_ALL).bin $(APP_ALL).flash
clean: clean:
rm -rf $(APP_ALL).o $(APP_ALL).elf $(APP_ALL).txt $(APP_ALL).bin $(APP_ALL).flash rm -rf $(APP_ALL).o $(APP_ALL).elf $(APP_ALL).txt $(APP_ALL).bin $(APP_ALL).flash
+13 -20
View File
@@ -9,16 +9,13 @@ from zipfile import ZipFile
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import archive, source # noqa: E402
from scripts.setup_helpers import atomic_write, download_file, ensure_git_repo # noqa: E402 from scripts.setup_helpers import atomic_write, download_file, ensure_git_repo # noqa: E402
APP_DIR = Path(__file__).resolve().parent APP_DIR = Path(__file__).resolve().parent
FATFS_DIR = APP_DIR / "fatfs" FATFS_DIR = APP_DIR / "fatfs"
FATFS_ARCHIVE = FATFS_DIR / "ff16/ff16.zip"
FATFS_URL = "https://elm-chan.org/fsw/ff/arc/ff16.zip"
FATFS_SHA256 = "41d98115f72b090c2d0c269a001c5c0216efd78fbd84bb6427be808a76315a5a"
COREMARK_DIR = APP_DIR / "coremark/coremark-main" COREMARK_DIR = APP_DIR / "coremark/coremark-main"
COREMARK_REVISION = "1f483d5b8316753a742cbf5590caf5bd0a4e4777"
def patch_coremark() -> None: def patch_coremark() -> None:
@@ -47,26 +44,22 @@ def main() -> int:
parser.add_argument("--update", action="store_true") parser.add_argument("--update", action="store_true")
args = parser.parse_args() args = parser.parse_args()
download_file( fatfs = archive("fatfs")
FATFS_URL, fatfs_archive = ROOT / fatfs["destination"]
FATFS_ARCHIVE, download_file(fatfs["url"], fatfs_archive, fatfs["sha256"], update=args.update)
FATFS_SHA256, fatfs_source = fatfs_archive.parent / "source"
update=args.update,
)
fatfs_source = FATFS_ARCHIVE.parent / "source"
if not fatfs_source.is_dir(): if not fatfs_source.is_dir():
with ZipFile(FATFS_ARCHIVE) as archive: with ZipFile(fatfs_archive) as zip_archive:
archive.extractall(FATFS_ARCHIVE.parent) zip_archive.extractall(fatfs_archive.parent)
for name in ("ffconf.h", "diskio.c"): for name in ("ffconf.h", "diskio.c"):
source = FATFS_DIR / name config_source = FATFS_DIR / name
if not source.is_file(): if not config_source.is_file():
raise FileNotFoundError(f"FatFs configuration file missing: {source}") raise FileNotFoundError(f"FatFs configuration file missing: {config_source}")
shutil.copy2(source, fatfs_source / name) shutil.copy2(config_source, fatfs_source / name)
coremark = source("coremark")
ensure_git_repo( ensure_git_repo(
"https://github.com/eembc/coremark.git", coremark["url"], ROOT / coremark["destination"], coremark["revision"],
COREMARK_DIR,
COREMARK_REVISION,
update=args.update, update=args.update,
allow_dirty=True, allow_dirty=True,
) )
+124
View File
@@ -0,0 +1,124 @@
{
"schema_version": 1,
"sources": {
"mpw": {
"url": "https://github.com/retroSoC/mini-ver-mpw.git",
"revision": "30503eb8a7cf33a0c955913faba5014ebd00186f",
"destination": "rtl/mini/mpw",
"license": "NOASSERTION"
},
"hazard3": {
"url": "https://github.com/Wren6991/Hazard3.git",
"revision": "5b3a34f6955e50e03bdcb964201c91462f7078c3",
"destination": "rtl/mini/mpw/Hazard3",
"license": "NOASSERTION"
},
"ibex": {
"url": "https://github.com/lowRISC/ibex.git",
"revision": "0c233f54361d769f370889223acc456f2ac19d46",
"destination": "rtl/mini/mpw/ibex",
"license": "Apache-2.0"
},
"coremark": {
"url": "https://github.com/eembc/coremark.git",
"revision": "1f483d5b8316753a742cbf5590caf5bd0a4e4777",
"destination": "app/coremark/coremark-main",
"license": "Apache-2.0"
},
"third_party_ip": {
"url": "https://github.com/retroSoC/3rd-party.git",
"revision": "d991ec9f7bf696e7e5d840758487068b11bd4121",
"destination": "rtl/ip/3rd-party",
"license": "NOASSERTION"
},
"pdk_ihp130": {
"url": "https://github.com/IHP-GmbH/IHP-Open-PDK.git",
"revision": "68eebafcd9b2f5e92c69d37a8d3d90eb266550f5",
"destination": "pdk/IHP-Open-PDK",
"recursive": true,
"license": "Apache-2.0"
},
"pdk_ics55": {
"url": "https://github.com/openecos-projects/icsprout55-pdk.git",
"revision": "e696e093129ca2212487aa169af74d06ebd86eb6",
"destination": "pdk/icsprout55-pdk",
"recursive": true,
"license": "NOASSERTION"
},
"cluster_archinfo": {"url": "https://github.com/retroSoC/archinfo.git", "revision": "d65dd13c83a1176668bf5178004814a1551bf6ba", "destination": "rtl/clusterip/archinfo", "license": "NOASSERTION"},
"cluster_clint": {"url": "https://github.com/retroSoC/clint.git", "revision": "ce49f1a5501c0a9f18c0e3f358417648f3f2fb18", "destination": "rtl/clusterip/clint", "license": "NOASSERTION"},
"cluster_common": {"url": "https://github.com/retroSoC/common.git", "revision": "d9921eb05160be873aebc46bb2f820bee2c7eebc", "destination": "rtl/clusterip/common", "license": "NOASSERTION"},
"cluster_crc": {"url": "https://github.com/retroSoC/crc.git", "revision": "4654a63d06d487b5b9fe22af228c36c736b90bad", "destination": "rtl/clusterip/crc", "license": "NOASSERTION"},
"cluster_i2c": {"url": "https://github.com/retroSoC/i2c.git", "revision": "73b024547c5cd70fb8bf801cc7757db2536c604f", "destination": "rtl/clusterip/i2c", "license": "NOASSERTION"},
"cluster_i2s": {"url": "https://github.com/retroSoC/i2s.git", "revision": "bd0978e8b45e4cf4c1335a1fa7311385d2a4e805", "destination": "rtl/clusterip/i2s", "license": "NOASSERTION"},
"cluster_plic": {"url": "https://github.com/retroSoC/plic.git", "revision": "c851d93829d2e75b224003a058e718925855c0cc", "destination": "rtl/clusterip/plic", "license": "NOASSERTION"},
"cluster_ps2": {"url": "https://github.com/retroSoC/ps2.git", "revision": "60921e0f72d6113f8615d03ce54567388fc2410f", "destination": "rtl/clusterip/ps2", "license": "NOASSERTION"},
"cluster_pwm": {"url": "https://github.com/retroSoC/pwm.git", "revision": "0777ef085c661d2b103f2fa0f8519f97eea3c950", "destination": "rtl/clusterip/pwm", "license": "NOASSERTION"},
"cluster_rng": {"url": "https://github.com/retroSoC/rng.git", "revision": "7374e1e7f25bfd1ff9954ea99f19b77a583def29", "destination": "rtl/clusterip/rng", "license": "NOASSERTION"},
"cluster_rtc": {"url": "https://github.com/retroSoC/rtc.git", "revision": "69963b8424cb00e04c8be70157b8ca738464e342", "destination": "rtl/clusterip/rtc", "license": "NOASSERTION"},
"cluster_spi": {"url": "https://github.com/retroSoC/spi.git", "revision": "515cb408806584525605b4747db5e890dbdf1928", "destination": "rtl/clusterip/spi", "license": "NOASSERTION"},
"cluster_timer": {"url": "https://github.com/retroSoC/timer.git", "revision": "0c44b0d8b742c6a477fda15a812cf22962b54dd8", "destination": "rtl/clusterip/timer", "license": "NOASSERTION"},
"cluster_uart": {"url": "https://github.com/retroSoC/uart.git", "revision": "eca12213ba06f42e0a6dd0f27907fd4c447e2774", "destination": "rtl/clusterip/uart", "license": "NOASSERTION"},
"cluster_wdg": {"url": "https://github.com/retroSoC/wdg.git", "revision": "b2603c1faeba96603392f0fe9e692e645d7e061f", "destination": "rtl/clusterip/wdg", "license": "NOASSERTION"}
},
"archives": {
"fatfs": {
"url": "https://elm-chan.org/fsw/ff/arc/ff16.zip",
"sha256": "41d98115f72b090c2d0c269a001c5c0216efd78fbd84bb6427be808a76315a5a",
"destination": "app/fatfs/ff16/ff16.zip",
"license": "LicenseRef-FatFs"
},
"serv": {
"url": "https://github.com/olofk/serv/archive/refs/tags/1.4.0.tar.gz",
"sha256": "f81c37b9f9d548c658e23bb7eb90fbed8b54e2e9e51fa42b8cd2f34cecc472ab",
"destination": "rtl/mini/mpw/serv-1.4.0.tar.gz",
"license": "ISC"
}
},
"toolchains": {
"ubuntu-22.04": {
"verilator": {
"version": "5.046",
"url": "https://github.com/retroSoC/artifact/releases/download/verilator-v5.046/verilator-v5.046-ubuntu-22.04-gcc.tar.gz",
"sha256": "f91b8496a0aaf26bf72395e876e145ceb99c94ff724cc9a16bb81d243ce5d953",
"archive": "verilator-v5.046-ubuntu-22.04-gcc.tar.gz",
"path": "bin"
},
"sv2v": {
"version": "0.0.13-9-gd381209",
"url": "https://github.com/retroSoC/artifact/releases/download/sv2v-v0.0.13-9-gd381209/sv2v-v0.0.13-9-gd381209-ubuntu-22.04-gcc.tar.gz",
"sha256": "af0f5438fc293d1469d4d3e01be442663c55f5db6ff28bbed40722eaf429c690",
"archive": "sv2v-v0.0.13-9-gd381209-ubuntu-22.04-gcc.tar.gz",
"path": "."
},
"iverilog": {
"version": "13.0",
"url": "https://github.com/retroSoC/artifact/releases/download/iverilog-v13_0/iverilog-v13_0-ubuntu-22.04-gcc.tar.gz",
"sha256": "22b51e988a94abdd0cd94f31f804a9bd6183e98d9380799ad3441188abc12987",
"archive": "iverilog-v13_0-ubuntu-22.04-gcc.tar.gz",
"path": "bin"
},
"yosys": {
"version": "0.58",
"url": "https://github.com/retroSoC/artifact/releases/download/yosys-v0.58/yosys-v0.58-ubuntu-22.04-gcc.tar.gz",
"sha256": "589447b6ed9aa20a1cc90155851f809490815089e5cc181ed4d23a5389b65adb",
"archive": "yosys-v0.58-ubuntu-22.04-gcc.tar.gz",
"path": "bin"
},
"opensta": {
"version": "2.2.0-1767-ga93b51ec",
"url": "https://github.com/retroSoC/artifact/releases/download/opensta-v2.2.0-1767-ga93b51ec/opensta-v2.2.0-1767-ga93b51ec-ubuntu-22.04-gcc.tar.gz",
"sha256": "e8b17134cd0935d2f5d354e8233c27833834141f2aef44264a1ad5d2cca0623b",
"archive": "opensta-v2.2.0-1767-ga93b51ec-ubuntu-22.04-gcc.tar.gz",
"path": "bin"
},
"riscv_gnu": {
"version": "2025.05.01",
"url": "https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.05.01/riscv32-elf-ubuntu-22.04-gcc-nightly-2025.05.01-nightly.tar.xz",
"sha256": "39dc7e8554d8d152258d2588cdb14c9dff5d2e8563325b78a923faa868a07191",
"archive": "riscv32-elf-ubuntu-22.04-gcc-nightly-2025.05.01-nightly.tar.xz",
"path": "riscv/bin"
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
SOC := MINI
CORE := HAZARD3
IP := NONE
PDK := IHP130
HAVE_PLL := NO
HAVE_SRAM_IF := NO
HAVE_SRAM_MACRO := NO
HAVE_SVA := NO
ISA := RV32IM
HAVE_CSR := NO
PROG_TYPE := BASE
LINK_TYPE := ld2_psram
+12
View File
@@ -0,0 +1,12 @@
SOC := MINI
CORE := MDD
IP := NONE
PDK := IHP130
HAVE_PLL := NO
HAVE_SRAM_IF := NO
HAVE_SRAM_MACRO := NO
HAVE_SVA := NO
ISA := RV32IM
HAVE_CSR := NO
PROG_TYPE := BASE
LINK_TYPE := ld2_psram
+12
View File
@@ -0,0 +1,12 @@
SOC := MINI
CORE := HAZARD3
IP := NONE
PDK := ICS55
HAVE_PLL := NO
HAVE_SRAM_IF := NO
HAVE_SRAM_MACRO := NO
HAVE_SVA := NO
ISA := RV32IM
HAVE_CSR := NO
PROG_TYPE := BASE
LINK_TYPE := ld2_psram
+12
View File
@@ -0,0 +1,12 @@
SOC := MINI
CORE := PICORV32
IP := NONE
PDK := IHP130
HAVE_PLL := NO
HAVE_SRAM_IF := NO
HAVE_SRAM_MACRO := NO
HAVE_SVA := NO
ISA := RV32IM
HAVE_CSR := NO
PROG_TYPE := BASE
LINK_TYPE := ld2_psram
+102
View File
@@ -0,0 +1,102 @@
# Engineering Workflow
## Supported Profiles
| Tier | Profile | Automated coverage |
| --- | --- | --- |
| Pull request | `configs/ci/hazard3-rv32im-ihp130.mk` | firmware, Verilator, Icarus, Yosys, netlist Icarus, OpenSTA |
| Pull request | `configs/ci/mdd-rv32im-ihp130.mk` | firmware and Verilator |
| Nightly | `configs/nightly/picorv32-rv32im-ihp130.mk` | firmware, Verilator, and Icarus |
| Cluster | `configs/cluster/hazard3-ics55.mk` | configuration only; run with site PDK/tool access |
VCS and ICS55 flows remain cluster-only because public runners do not have the licensed simulator or
the validated site environment. They use the same variant layout, manifests, result JSON, warning
scanner, and cleanup rules.
## Reproducible Inputs
`config/dependencies.lock.json` is the source of truth for external Git repositories, downloaded
archives, and Ubuntu 22.04 toolchain bundles. Git checkouts use full 40-character revisions. Archive
downloads are accepted only after SHA-256 verification. CI actions are pinned to commit IDs.
To update a dependency:
1. Update its revision, version, URL, and checksum in the lock.
2. Run `python3 scripts/dependency_lock.py`.
3. Run the affected setup target with `--update` directly, then run `make doctor`.
4. Run `python3 -m pytest -q` and the affected regression profile.
5. Review the generated manifest, warning delta, metrics, and SBOM before merging.
Do not reuse an old checksum with a new URL or version. The lock digest is part of every variant ID,
so changing the lock creates a new output directory without contaminating prior results.
## Build Layout
The variant identifier is the profile name plus a hash over effective hardware/software build
settings and the dependency lock. Runtime-only settings such as wave capture and timeout do not
create a new variant.
```text
build/<variant>/
generated/mpw/<simulator>/
sw/
sim/<simulator>/
syn/yosys/
sta/opensta/
meta/manifest.json
meta/warnings.json
meta/metrics.json
```
Generated filelists and MPW output are flow-local. Make depfiles track expanded RTL sources and
included headers. The shared MPW generator is protected by a file lock. Default tool parallelism is
capped at 16 and can be set with `JOBS=<n>` or `MAX_JOBS=<n>`.
## Result Policy
Every EDA and simulation flow command is run through `scripts/run_flow.py`, which streams a log and writes a
result JSON containing the command, timestamps, duration, exit code, and status. A simulation passes
only when the command succeeds, its log contains the firmware startup marker, and no fatal/failure
marker is present. Icarus regressions use the assembly self-test as `retrosoc_asm`, while the normal
`retrosoc_fw` image remains available for firmware size tracking and Verilator regressions.
`make check-warnings` normalizes tool messages and compares them with
`quality/warnings/<profile>/<tool>.json`. New signatures and increased counts fail; resolved warnings
are reported but do not fail. To refresh one baseline after review:
```sh
python3 scripts/analyze_warnings.py baseline \
--root . --profile hazard3-rv32im-ihp130 --tool verilator \
--log build/<variant>/sim/verilator/verilating.log \
--output quality/warnings/hazard3-rv32im-ihp130/verilator.json
```
## Metrics Promotion
`make check-metrics` currently uses `quality/metrics/policy.json` in `observe` mode. Regression
artifacts retain the metrics from each successful run. After ten successful `main` runs for the same
profile and locked tools, create the median baseline:
```sh
python3 scripts/metrics.py baseline \
--input run-01/metrics.json --input run-02/metrics.json \
--input run-03/metrics.json --input run-04/metrics.json \
--input run-05/metrics.json --input run-06/metrics.json \
--input run-07/metrics.json --input run-08/metrics.json \
--input run-09/metrics.json --input run-10/metrics.json \
--output quality/metrics/baseline.json
```
Review the medians, commit the baseline, then change policy mode to `gate`. The committed future
limits are 5% firmware growth, 3% top area/cell growth, and 0.05 ns WNS regression. TNS is recorded
for diagnosis but is initially non-blocking.
## CI And Releases
`quality.yml` validates Python, YAML, GitHub Actions, the dependency lock, and script tests.
`regression.yml` runs verified configurations on pull requests and main branches. `nightly.yml` adds
PICORV32. Source dependencies, locked tool archives, and Verilator `ccache` use separate cache keys.
Tags matching `v*` run `release.yml`. The release contains a flattened SystemVerilog export, a source
tarball, build manifest, dependency lock, CycloneDX SBOM, and `SHA256SUMS`. `make package` creates the
same files locally below `dist/<variant>/`.
+10 -20
View File
@@ -7,34 +7,24 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import source # noqa: E402
from scripts.setup_helpers import ensure_git_repo # noqa: E402 from scripts.setup_helpers import ensure_git_repo # noqa: E402
DEPENDENCIES = (
(
"https://github.com/IHP-GmbH/IHP-Open-PDK.git",
"IHP-Open-PDK",
"68eebafcd9b2f5e92c69d37a8d3d90eb266550f5",
),
(
"https://github.com/openecos-projects/icsprout55-pdk.git",
"icsprout55-pdk",
"e696e093129ca2212487aa169af74d06ebd86eb6",
),
)
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned open PDKs") parser = argparse.ArgumentParser(description="Install pinned open PDKs")
parser.add_argument("--update", action="store_true") parser.add_argument("--update", action="store_true")
parser.add_argument("--pdk", choices=("IHP130", "ICS55"), action="append")
args = parser.parse_args() args = parser.parse_args()
pdk_dir = Path(__file__).resolve().parent selected = args.pdk or ("IHP130", "ICS55")
for url, name, revision in DEPENDENCIES: names = {"IHP130": "pdk_ihp130", "ICS55": "pdk_ics55"}
for name in (names[pdk] for pdk in selected):
dependency = source(name)
ensure_git_repo( ensure_git_repo(
url, dependency["url"],
pdk_dir / name, ROOT / dependency["destination"],
revision, dependency["revision"],
recursive=True, recursive=dependency.get("recursive", False),
update=args.update, update=args.update,
) )
return 0 return 0
+22
View File
@@ -0,0 +1,22 @@
[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
[tool.ruff]
line-length = 100
target-version = "py310"
extend-exclude = [
".cache",
"build",
"dist",
"app/coremark/coremark-main",
"app/fatfs/ff16",
"pdk/IHP-Open-PDK",
"pdk/icsprout55-pdk",
"rtl/clusterip",
"rtl/ip/3rd-party",
"rtl/mini/mpw",
]
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F"]
+5
View File
@@ -0,0 +1,5 @@
# Metrics Policy
The policy starts in `observe` mode. Exactly ten successful `main` run metric files are required to
create the median baseline. Promotion to `gate` is a reviewed commit containing both
`baseline.json` and the policy mode change. See `docs/engineering.md` for the command and thresholds.
+12
View File
@@ -0,0 +1,12 @@
{
"schema_version": 1,
"mode": "observe",
"required_baseline_samples": 10,
"maximum_growth": {
"firmware.retrosoc_fw.bin.bytes": 0.05,
"synthesis.top_area": 0.03,
"synthesis.top_cells": 0.03
},
"maximum_wns_regression": 0.05,
"tns_policy": "report_only"
}
+7
View File
@@ -0,0 +1,7 @@
# Warning Baselines
Baselines are scoped by committed profile and tool. Each entry stores a normalized warning signature,
its accepted count, and one example. Path roots and source line numbers are removed before hashing.
Do not hand-edit signatures. Regenerate only from a successful flow using
`scripts/analyze_warnings.py baseline`, review the full diff, and run `make check-warnings`.
@@ -0,0 +1,17 @@
{
"entries": [
{
"count": 2,
"example": "warning:Found both default and explicit timescale based delays. Use",
"signature": "aee22756a35889cc"
},
{
"count": 4,
"example": "sorry:ifnone with an edge-sensitive path is not supported.",
"signature": "c4297f454f443fed"
}
],
"profile": "hazard3-rv32im-ihp130",
"schema_version": 1,
"tool": "iverilog"
}
@@ -0,0 +1,6 @@
{
"entries": [],
"profile": "hazard3-rv32im-ihp130",
"schema_version": 1,
"tool": "opensta"
}
@@ -0,0 +1,42 @@
{
"entries": [
{
"count": 2,
"example": "CMPCONST:$ROOT/rtl/ip/native/spisd_write.sv:<line>: Comparison is constant due to limited range",
"signature": "11b9d285d4747e4f"
},
{
"count": 1,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_1024x64_c2_bm_bist.v:<line>: Ignoring unsupported: specify block construct",
"signature": "7a759d6e07651fd8"
},
{
"count": 68,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/verilog/sg13g2_stdcell.v:<line>: Ignoring unsupported: specify block construct",
"signature": "c54331448f63ac73"
},
{
"count": 3,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/spisd_write.sv:<line>: Bit extraction of var[7:0] requires 3 bit index, not 4 bits.",
"signature": "cdd5c3ca84a609c6"
},
{
"count": 1,
"example": "UNOPTFLAT:$ROOT/rtl/ip/native/xpi.sv:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_xpi_if.io_di_i'",
"signature": "ee29e23bc895ee17"
},
{
"count": 1,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/spisd_read.sv:<line>: Operator ASSIGNDLY expects 8 bits on the Assign RHS, but Assign RHS's REPLICATE generates 9 bits.",
"signature": "f8f8278c6ce4431d"
},
{
"count": 11,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/verilog/sg13g2_io.v:<line>: Ignoring unsupported: specify block construct",
"signature": "faa191e2dbf4a242"
}
],
"profile": "hazard3-rv32im-ihp130",
"schema_version": 1,
"tool": "verilator"
}
@@ -0,0 +1,42 @@
{
"entries": [
{
"count": 1,
"example": "warning:Wire hazard3_csr$retrosoc_asic.u_retrosoc.u_core_wrapper.u_hazard3_cpu_1port.core.csr_u.\\debug_mode is used but has no driver.",
"signature": "1097d6b92f19e707"
},
{
"count": 1,
"example": "warning:asynchronous load value missing for variable '\\update_irq_reg_arrays.i'",
"signature": "56c5f69398be8d7b"
},
{
"count": 1,
"example": "warning:Wire hazard3_csr$retrosoc_asic.u_retrosoc.u_core_wrapper.u_hazard3_cpu_1port.core.csr_u.\\pending_dbg_resume_prev is used but has no driver.",
"signature": "5bbc6025e51705ec"
},
{
"count": 9,
"example": "warning:The network is combinational (run \"fraig\" or \"fraig_sweep\").",
"signature": "62cc5ee554312205"
},
{
"count": 2,
"example": "warning:asynchronous load value missing for variable '\\update_nonconst.prev'",
"signature": "a30df20b98d49196"
},
{
"count": 2,
"example": "warning:asynchronous load value missing for variable '\\update_nonconst.nonconst'",
"signature": "d35463a1de40c7b2"
},
{
"count": 1,
"example": "warning:Wire hazard3_csr$retrosoc_asic.u_retrosoc.u_core_wrapper.u_hazard3_cpu_1port.core.csr_u.\\dbg_req_resume_prev is used but has no driver.",
"signature": "ef03dcca25b39dcb"
}
],
"profile": "hazard3-rv32im-ihp130",
"schema_version": 1,
"tool": "yosys"
}
@@ -0,0 +1,87 @@
{
"entries": [
{
"count": 2,
"example": "CMPCONST:$ROOT/rtl/ip/native/spisd_write.sv:<line>: Comparison is constant due to limited range",
"signature": "11b9d285d4747e4f"
},
{
"count": 1,
"example": "PINMISSING:$BUILD/generated/mpw/verilator/core/username4/./serv/serv_synth_wrapper_username4.v:<line>: Instance has missing pin: 'o_ren'",
"signature": "173218df65cf7bcd"
},
{
"count": 1,
"example": "UNOPTFLAT:$BUILD/generated/mpw/verilator/core/username1/./kianV/alu_username1.v:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_retrosoc.u_core_wrapper.u_user_core_wrapper.u_user_core_top.u_user_core_design_username1.u_kianv_harris_mc_edition.datapath_unit_I.alu_I.is_sub'",
"signature": "180cdbb151faa02d"
},
{
"count": 1,
"example": "UNOPTFLAT:$BUILD/generated/mpw/verilator/core/username1/./kianV/kianv_harris_mc_edition_username1.v:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_retrosoc.u_core_wrapper.u_user_core_wrapper.u_user_core_top.u_user_core_design_username1.u_kianv_harris_mc_edition.alu_valid'",
"signature": "25979c35fe4eb73f"
},
{
"count": 1,
"example": "WIDTHEXPAND:$BUILD/generated/mpw/verilator/core/username6/./darkriscv/darkriscv_username6.v:<line>: Operator COND expects 32 bits on the Conditional True, but Conditional True's LT generates 1 bits.",
"signature": "259baf70503bfe0d"
},
{
"count": 1,
"example": "WIDTHEXPAND:$BUILD/generated/mpw/verilator/core/username6/./darkriscv/darkriscv_username6.v:<line>: Operator COND expects 32 bits on the Conditional True, but Conditional True's LTS generates 1 bits.",
"signature": "323be25f98c25432"
},
{
"count": 14,
"example": "WIDTHTRUNC:$BUILD/generated/mpw/verilator/core/username6/./darkriscv/darkriscv_username6.v:<line>: Logical operator COND expects 1 bit on the Conditional Test, but Conditional Test's VARREF 'FLUSH' generates 2 bits.",
"signature": "4a2065ce464b898c"
},
{
"count": 1,
"example": "UNOPTFLAT:$BUILD/generated/mpw/verilator/core/username1/./kianV/kianv_harris_mc_edition_username1.v:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_retrosoc.u_core_wrapper.u_user_core_wrapper.u_user_core_top.u_user_core_design_username1.u_kianv_harris_mc_edition.ALUSrcA'",
"signature": "4edf683bf8ef29d3"
},
{
"count": 1,
"example": "UNOPTFLAT:$BUILD/generated/mpw/verilator/core/username1/./kianV/alu_decoder_username1.v:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_retrosoc.u_core_wrapper.u_user_core_wrapper.u_user_core_top.u_user_core_design_username1.u_kianv_harris_mc_edition.control_unit_I.alu_decoder_I.ALUControl'",
"signature": "5dc2cc2658c8e2c1"
},
{
"count": 1,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_1024x64_c2_bm_bist.v:<line>: Ignoring unsupported: specify block construct",
"signature": "7a759d6e07651fd8"
},
{
"count": 68,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/verilog/sg13g2_stdcell.v:<line>: Ignoring unsupported: specify block construct",
"signature": "c54331448f63ac73"
},
{
"count": 3,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/spisd_write.sv:<line>: Bit extraction of var[7:0] requires 3 bit index, not 4 bits.",
"signature": "cdd5c3ca84a609c6"
},
{
"count": 1,
"example": "UNOPTFLAT:$ROOT/rtl/ip/native/xpi.sv:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_xpi_if.io_di_i'",
"signature": "ee29e23bc895ee17"
},
{
"count": 1,
"example": "UNOPTFLAT:$BUILD/generated/mpw/verilator/core/username1/./kianV/kianv_harris_mc_edition_username1.v:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_retrosoc.u_core_wrapper.u_user_core_wrapper.u_user_core_top.u_user_core_design_username1.u_kianv_harris_mc_edition.ALUSrcB'",
"signature": "f1f9cfed209f63b9"
},
{
"count": 1,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/spisd_read.sv:<line>: Operator ASSIGNDLY expects 8 bits on the Assign RHS, but Assign RHS's REPLICATE generates 9 bits.",
"signature": "f8f8278c6ce4431d"
},
{
"count": 11,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/verilog/sg13g2_io.v:<line>: Ignoring unsupported: specify block construct",
"signature": "faa191e2dbf4a242"
}
],
"profile": "mdd-rv32im-ihp130",
"schema_version": 1,
"tool": "verilator"
}
@@ -0,0 +1,17 @@
{
"entries": [
{
"count": 1,
"example": "warning:Found both default and explicit timescale based delays. Use",
"signature": "aee22756a35889cc"
},
{
"count": 2,
"example": "sorry:ifnone with an edge-sensitive path is not supported.",
"signature": "c4297f454f443fed"
}
],
"profile": "picorv32-rv32im-ihp130",
"schema_version": 1,
"tool": "iverilog"
}
@@ -0,0 +1,42 @@
{
"entries": [
{
"count": 2,
"example": "CMPCONST:$ROOT/rtl/ip/native/spisd_write.sv:<line>: Comparison is constant due to limited range",
"signature": "11b9d285d4747e4f"
},
{
"count": 1,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog/RM_IHPSG13_1P_1024x64_c2_bm_bist.v:<line>: Ignoring unsupported: specify block construct",
"signature": "7a759d6e07651fd8"
},
{
"count": 68,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/verilog/sg13g2_stdcell.v:<line>: Ignoring unsupported: specify block construct",
"signature": "c54331448f63ac73"
},
{
"count": 3,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/spisd_write.sv:<line>: Bit extraction of var[7:0] requires 3 bit index, not 4 bits.",
"signature": "cdd5c3ca84a609c6"
},
{
"count": 1,
"example": "UNOPTFLAT:$ROOT/rtl/ip/native/xpi.sv:<line>: Signal unoptimizable: Circular combinational logic: 'retrosoc_top.u_retrosoc_asic.u_xpi_if.io_di_i'",
"signature": "ee29e23bc895ee17"
},
{
"count": 1,
"example": "WIDTHTRUNC:$ROOT/rtl/ip/native/spisd_read.sv:<line>: Operator ASSIGNDLY expects 8 bits on the Assign RHS, but Assign RHS's REPLICATE generates 9 bits.",
"signature": "f8f8278c6ce4431d"
},
{
"count": 11,
"example": "SPECIFYIGN:$ROOT/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/verilog/sg13g2_io.v:<line>: Ignoring unsupported: specify block construct",
"signature": "faa191e2dbf4a242"
}
],
"profile": "picorv32-rv32im-ihp130",
"schema_version": 1,
"tool": "verilator"
}
+5
View File
@@ -0,0 +1,5 @@
# Ubuntu 22.04/Python 3.10 build dependency lock.
--only-binary=:all:
--require-hashes
tomli==2.4.1 --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe
+15
View File
@@ -0,0 +1,15 @@
# Ubuntu 22.04/Python 3.10 CI dependency lock, including transitive dependencies.
--only-binary=:all:
--require-hashes
exceptiongroup==1.3.1 --hash=sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598
iniconfig==2.3.0 --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e
pathspec==1.1.1 --hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189
pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
pytest==8.3.5 --hash=sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820
PyYAML==6.0.3 --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b
ruff==0.11.0 --hash=sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db
tomli==2.4.1 --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe
typing_extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
yamllint==1.35.1 --hash=sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3
+8 -24
View File
@@ -7,38 +7,22 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import load_lock # noqa: E402
from scripts.setup_helpers import ensure_git_repo # noqa: E402 from scripts.setup_helpers import ensure_git_repo # noqa: E402
REVISIONS = {
"archinfo": "d65dd13c83a1176668bf5178004814a1551bf6ba",
"clint": "ce49f1a5501c0a9f18c0e3f358417648f3f2fb18",
"common": "d9921eb05160be873aebc46bb2f820bee2c7eebc",
"crc": "4654a63d06d487b5b9fe22af228c36c736b90bad",
"i2c": "73b024547c5cd70fb8bf801cc7757db2536c604f",
"i2s": "bd0978e8b45e4cf4c1335a1fa7311385d2a4e805",
"plic": "c851d93829d2e75b224003a058e718925855c0cc",
"ps2": "60921e0f72d6113f8615d03ce54567388fc2410f",
"pwm": "0777ef085c661d2b103f2fa0f8519f97eea3c950",
"rng": "7374e1e7f25bfd1ff9954ea99f19b77a583def29",
"rtc": "69963b8424cb00e04c8be70157b8ca738464e342",
"spi": "515cb408806584525605b4747db5e890dbdf1928",
"timer": "0c44b0d8b742c6a477fda15a812cf22962b54dd8",
"uart": "eca12213ba06f42e0a6dd0f27907fd4c447e2774",
"wdg": "b2603c1faeba96603392f0fe9e692e645d7e061f",
}
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned cluster IP repositories") parser = argparse.ArgumentParser(description="Install pinned cluster IP repositories")
parser.add_argument("--update", action="store_true") parser.add_argument("--update", action="store_true")
args = parser.parse_args() args = parser.parse_args()
destination = Path(__file__).resolve().parent dependencies = load_lock()["sources"]
for name, revision in REVISIONS.items(): for name in sorted(item for item in dependencies if item.startswith("cluster_")):
dependency = dependencies[name]
ensure_git_repo( ensure_git_repo(
f"https://github.com/retroSoC/{name}.git", dependency["url"],
destination / name, ROOT / dependency["destination"],
revision, dependency["revision"],
recursive=dependency.get("recursive", False),
update=args.update, update=args.update,
) )
return 0 return 0
+4 -7
View File
@@ -7,21 +7,18 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import source # noqa: E402
from scripts.setup_helpers import ensure_git_repo # noqa: E402 from scripts.setup_helpers import ensure_git_repo # noqa: E402
REVISION = "d991ec9f7bf696e7e5d840758487068b11bd4121"
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned third-party IP") parser = argparse.ArgumentParser(description="Install pinned third-party IP")
parser.add_argument("--update", action="store_true") parser.add_argument("--update", action="store_true")
args = parser.parse_args() args = parser.parse_args()
dependency = source("third_party_ip")
ensure_git_repo( ensure_git_repo(
"https://github.com/retroSoC/3rd-party.git", dependency["url"], ROOT / dependency["destination"], dependency["revision"],
Path(__file__).resolve().parent / "3rd-party", recursive=dependency.get("recursive", False), update=args.update,
REVISION,
update=args.update,
) )
return 0 return 0
+38 -35
View File
@@ -1,73 +1,76 @@
GENERATED_FL_DIR := $(RTL_PATH)/.generated_fl GENERATED_FL_DIR := $(FLOW_FILELIST_DIR)
MPW_STAMP := $(RTL_PATH)/mpw/.build/.prepared MPW_STAMP := $(CACHE_ROOT)/stamps/mpw-$(LOCK_DIGEST).prepared
MPW_OUTPUT_DIR := $(VARIANT_ROOT)/generated/mpw/$(SIM_TOOL_NAME)
MPW_VARIANT_STAMP := $(MPW_OUTPUT_DIR)/.stamp
FILELIST_STAMP := $(GENERATED_FL_DIR)/.stamp
FILELIST_TEMPLATES := $(sort $(wildcard $(ROOT_PATH)/rtl/filelist/pdk_*.fl) \
$(wildcard $(RTL_PATH)/filelist/*.fl) $(RTL_PATH)/lint.msg)
$(MPW_STAMP): $(MPW_STAMP):
@test -d $(RTL_PATH)/mpw/.git || { \ @test -d $(RTL_PATH)/mpw/.git || { \
echo "missing $(RTL_PATH)/mpw; run 'make setup-mpw' first" >&2; \ echo "missing $(RTL_PATH)/mpw; run 'make setup-mpw' first" >&2; \
exit 1; \ exit 1; \
} }
python3 $(ROOT_PATH)/scripts/prepare_mpw.py python3 $(ROOT_PATH)/scripts/prepare_mpw.py \
--lock-file $(CACHE_ROOT)/locks/mpw-prepare.lock
@mkdir -p $(@D) @mkdir -p $(@D)
@touch $@ @touch $@
gen_mpw_code: $(MPW_STAMP) $(MPW_VARIANT_STAMP): $(MPW_STAMP) $(ROOT_PATH)/scripts/generate_mpw.py
ifeq ($(IP), MDD) python3 $(ROOT_PATH)/scripts/generate_mpw.py --root $(ROOT_PATH) \
@echo "[mpw] generating user ip ..." --output $(MPW_OUTPUT_DIR) --simu $(SIMU) --core $(CORE) --ip $(IP) \
python3 $(RTL_PATH)/mpw/ip.py --lock-file $(CACHE_ROOT)/locks/mpw.lock
python3 $(RTL_PATH)/mpw/info.py IP @mkdir -p $(@D)
else @touch $@
@echo "[mpw] skipping user ip generation"
endif
ifeq ($(CORE), MDD)
@echo "[mpw] generating core ..."
python3 $(RTL_PATH)/mpw/core.py $(SIMU)
python3 $(RTL_PATH)/mpw/info.py CORE
else
@echo "[mpw] skipping core generation"
endif
generate_filelist: gen_mpw_code: $(MPW_VARIANT_STAMP)
$(FILELIST_STAMP): $(FILELIST_TEMPLATES) $(RTL_PATH)/script/generate_filelist.py \
$(RTL_PATH)/script/filelist.py
python3 $(RTL_PATH)/script/generate_filelist.py \ python3 $(RTL_PATH)/script/generate_filelist.py \
--output-dir $(GENERATED_FL_DIR) \ --output-dir $(GENERATED_FL_DIR) \
$(foreach define,$(DEF_LIST),--define $(define)) $(foreach define,$(DEF_LIST),--define $(define))
@touch $@
generate_filelist: $(FILELIST_STAMP)
# software # software
include $(RTL_PATH)/mk/software.mk include $(RTL_PATH)/mk/software.mk
#====================== filelist ====================== #====================== filelist ======================
ifeq ($(PDK), IHP130) ifeq ($(PDK), IHP130)
NET_FLIST := -f $(RTL_PATH)/.generated_fl/pdk_ihp130.fl NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_ihp130.fl
else ifeq ($(PDK), SKY130) else ifeq ($(PDK), SKY130)
NET_FLIST := -f $(RTL_PATH)/.generated_fl/pdk_sky130.fl NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_sky130.fl
else ifeq ($(PDK), ICS55) else ifeq ($(PDK), ICS55)
NET_FLIST := -f $(RTL_PATH)/.generated_fl/pdk_ics55.fl NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_ics55.fl
else ifeq ($(PDK), GF180) else ifeq ($(PDK), GF180)
NET_FLIST := -f $(RTL_PATH)/.generated_fl/pdk_gf180.fl NET_FLIST := -f $(GENERATED_FL_DIR)/pdk_gf180.fl
endif endif
NET_FLIST += -f $(RTL_PATH)/.generated_fl/def.fl NET_FLIST += -f $(GENERATED_FL_DIR)/def.fl
NET_FLIST += -f $(RTL_PATH)/.generated_fl/sys_def.fl NET_FLIST += -f $(GENERATED_FL_DIR)/sys_def.fl
NET_FLIST += -f $(RTL_PATH)/.generated_fl/commonip.fl NET_FLIST += -f $(GENERATED_FL_DIR)/commonip.fl
NET_FLIST += -f $(RTL_PATH)/.generated_fl/inc.fl NET_FLIST += -f $(GENERATED_FL_DIR)/inc.fl
RTL_FLIST := $(NET_FLIST) RTL_FLIST := $(NET_FLIST)
RTL_FLIST += -f $(RTL_PATH)/.generated_fl/clusterip.fl RTL_FLIST += -f $(GENERATED_FL_DIR)/clusterip.fl
RTL_FLIST += -f $(RTL_PATH)/.generated_fl/ip.fl RTL_FLIST += -f $(GENERATED_FL_DIR)/ip.fl
RTL_FLIST += -f $(RTL_PATH)/.generated_fl/tech.fl RTL_FLIST += -f $(GENERATED_FL_DIR)/tech.fl
ifeq ($(CORE), PICORV32) ifeq ($(CORE), PICORV32)
RTL_FLIST += -f $(RTL_PATH)/.generated_fl/core_picorv32.fl RTL_FLIST += -f $(GENERATED_FL_DIR)/core_picorv32.fl
else ifeq ($(CORE), HAZARD3) else ifeq ($(CORE), HAZARD3)
RTL_FLIST += -f $(RTL_PATH)/.generated_fl/core_hazard3.fl RTL_FLIST += -f $(GENERATED_FL_DIR)/core_hazard3.fl
else ifeq ($(CORE), MDD) else ifeq ($(CORE), MDD)
RTL_FLIST += -f $(RTL_PATH)/mpw/.build/core/core.fl RTL_FLIST += -f $(MPW_OUTPUT_DIR)/core/core.fl
endif endif
ifeq ($(IP), MDD) ifeq ($(IP), MDD)
RTL_FLIST += -f $(RTL_PATH)/mpw/.build/ip/ip.fl RTL_FLIST += -f $(MPW_OUTPUT_DIR)/ip/ip.fl
endif endif
RTL_FLIST += -f $(RTL_PATH)/.generated_fl/top.fl RTL_FLIST += -f $(GENERATED_FL_DIR)/top.fl
#====================== end ====================== #====================== end ======================
ifeq ($(SIMU), VCS) ifeq ($(SIMU), VCS)
+69 -31
View File
@@ -2,12 +2,12 @@ IVERILOG ?= iverilog
VVP ?= vvp VVP ?= vvp
GTKWAVE ?= gtkwave GTKWAVE ?= gtkwave
NETLIST_PATH ?= $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.v NETLIST_PATH ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v
POST_PATH ?= $(ROOT_PATH)/pd/sdf/retrosoc_asic.v POST_PATH ?= $(ROOT_PATH)/pd/sdf/retrosoc_asic.v
SDF_PATH ?= $(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz SDF_PATH ?= $(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz
SDF_SCOPE ?= $(RTL_TOP).u_retrosoc_asic SDF_SCOPE ?= $(RTL_TOP).u_retrosoc_asic
IVERILOG_ROOT := $(RTL_PATH)/.iverilog_build IVERILOG_ROOT := $(SIM_BUILD_ROOT)
IVERILOG_BEHV_DIR := $(IVERILOG_ROOT)/behv IVERILOG_BEHV_DIR := $(IVERILOG_ROOT)/behv
IVERILOG_NETL_DIR := $(IVERILOG_ROOT)/netl IVERILOG_NETL_DIR := $(IVERILOG_ROOT)/netl
IVERILOG_POST_DIR := $(IVERILOG_ROOT)/post IVERILOG_POST_DIR := $(IVERILOG_ROOT)/post
@@ -15,6 +15,15 @@ IVERILOG_BEHV_FLIST := $(IVERILOG_BEHV_DIR)/iverilog.fl
IVERILOG_NETL_FLIST := $(IVERILOG_NETL_DIR)/iverilog.fl IVERILOG_NETL_FLIST := $(IVERILOG_NETL_DIR)/iverilog.fl
IVERILOG_POST_FLIST := $(IVERILOG_POST_DIR)/iverilog.fl IVERILOG_POST_FLIST := $(IVERILOG_POST_DIR)/iverilog.fl
CONVERTED_SOC := $(IVERILOG_BEHV_DIR)/converted_soc.v CONVERTED_SOC := $(IVERILOG_BEHV_DIR)/converted_soc.v
CONVERTED_DEPFILE := $(IVERILOG_BEHV_DIR)/converted_soc.d
IVERILOG_BEHV_SIMV := $(IVERILOG_BEHV_DIR)/simv
IVERILOG_NETL_SIMV := $(IVERILOG_NETL_DIR)/simv
IVERILOG_POST_SIMV := $(IVERILOG_POST_DIR)/simv
IVERILOG_BEHV_DEPFILE := $(IVERILOG_BEHV_DIR)/simv.d
IVERILOG_NETL_DEPFILE := $(IVERILOG_NETL_DIR)/simv.d
IVERILOG_POST_DEPFILE := $(IVERILOG_POST_DIR)/simv.d
-include $(CONVERTED_DEPFILE) $(IVERILOG_BEHV_DEPFILE) $(IVERILOG_NETL_DEPFILE) $(IVERILOG_POST_DEPFILE)
IVERILOG_COMMON_OPTS := -g2012 IVERILOG_COMMON_OPTS := -g2012
IVERILOG_TIME_OPTS := -gno-specify IVERILOG_TIME_OPTS := -gno-specify
@@ -22,64 +31,93 @@ IVERILOG_POST_OPTS := -gspecify -Tmin
IVERILOG_SIM_OPTS := +$(RTL_SIM_PLLEN) +$(RTL_SIM_PLLCFG) \ IVERILOG_SIM_OPTS := +$(RTL_SIM_PLLEN) +$(RTL_SIM_PLLCFG) \
+core_sel=$(RTL_SIM_CORESEL) +sim_timeout=$(RTL_SIM_TIMEOUT) +wave_$(WAVE) +core_sel=$(RTL_SIM_CORESEL) +sim_timeout=$(RTL_SIM_TIMEOUT) +wave_$(WAVE)
$(CONVERTED_SOC): gen_mpw_code generate_filelist $(CONVERTED_SOC): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP)
@mkdir -p $(@D) @mkdir -p $(@D)
python3 $(RTL_PATH)/script/convt_sv2v.py $(RTL_FLIST) --output $@ python3 $(RTL_PATH)/script/convt_sv2v.py $(RTL_FLIST) --output $@
python3 $(RTL_PATH)/script/filelist_deps.py $(RTL_FLIST) --target $@ \
--output $(CONVERTED_DEPFILE)
$(IVERILOG_BEHV_FLIST): generate_filelist $(CONVERTED_SOC) $(IVERILOG_BEHV_FLIST): $(FILELIST_STAMP) $(CONVERTED_SOC)
@mkdir -p $(@D) @mkdir -p $(@D)
python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \ python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \
--mode behv --pdk $(PDK) --output $@ --converted $(CONVERTED_SOC) --mode behv --pdk $(PDK) --generated-dir $(GENERATED_FL_DIR) \
--output $@ --converted $(CONVERTED_SOC)
$(IVERILOG_NETL_FLIST): generate_filelist $(IVERILOG_NETL_FLIST): $(FILELIST_STAMP) $(NETLIST_PATH)
@mkdir -p $(@D) @mkdir -p $(@D)
python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \ python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \
--mode netl --pdk $(PDK) --output $@ --netlist $(NETLIST_PATH) --mode netl --pdk $(PDK) --generated-dir $(GENERATED_FL_DIR) \
--output $@ --netlist $(NETLIST_PATH)
$(IVERILOG_POST_FLIST): generate_filelist $(IVERILOG_POST_FLIST): $(FILELIST_STAMP) $(POST_PATH) $(SDF_PATH)
@mkdir -p $(@D) @mkdir -p $(@D)
python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \ python3 $(RTL_PATH)/script/gen_iverilog_filelist.py \
--mode post --pdk $(PDK) --output $@ --netlist $(POST_PATH) --sdf $(SDF_PATH) \ --mode post --pdk $(PDK) --generated-dir $(GENERATED_FL_DIR) \
--output $@ --netlist $(POST_PATH) --sdf $(SDF_PATH) \
--sdf-scope $(SDF_SCOPE) --sdf-scope $(SDF_SCOPE)
convt_sv2v: $(CONVERTED_SOC) convt_sv2v: $(CONVERTED_SOC)
gen_iverilog_filelist: $(IVERILOG_BEHV_FLIST) gen_iverilog_filelist: $(IVERILOG_BEHV_FLIST)
comp: $(IVERILOG_BEHV_FLIST) $(IVERILOG_BEHV_SIMV): $(IVERILOG_BEHV_FLIST)
@mkdir -p $(IVERILOG_BEHV_DIR) @mkdir -p $(IVERILOG_BEHV_DIR)
@cd $(IVERILOG_BEHV_DIR) && set -o pipefail && \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog \
$(IVERILOG) $(IVERILOG_COMMON_OPTS) $(IVERILOG_TIME_OPTS) -f $< \ --log $(IVERILOG_BEHV_DIR)/compile.log --result $(IVERILOG_BEHV_DIR)/result-compile.json \
-o simv -s $(RTL_TOP) 2>&1 | tee compile.log --cwd $(IVERILOG_BEHV_DIR) -- $(IVERILOG) $(IVERILOG_COMMON_OPTS) \
$(IVERILOG_TIME_OPTS) -f $< -o simv -s $(RTL_TOP)
python3 $(RTL_PATH)/script/filelist_deps.py -f $(IVERILOG_BEHV_FLIST) \
--target $@ --output $(IVERILOG_BEHV_DEPFILE)
netcomp: $(IVERILOG_NETL_FLIST) $(IVERILOG_NETL_SIMV): $(IVERILOG_NETL_FLIST)
@mkdir -p $(IVERILOG_NETL_DIR) @mkdir -p $(IVERILOG_NETL_DIR)
@cd $(IVERILOG_NETL_DIR) && set -o pipefail && \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog \
$(IVERILOG) $(IVERILOG_COMMON_OPTS) $(IVERILOG_TIME_OPTS) -f $< \ --log $(IVERILOG_NETL_DIR)/compile.log --result $(IVERILOG_NETL_DIR)/result-compile.json \
-o simv -s $(RTL_TOP) 2>&1 | tee compile.log --cwd $(IVERILOG_NETL_DIR) -- $(IVERILOG) $(IVERILOG_COMMON_OPTS) \
$(IVERILOG_TIME_OPTS) -f $< -o simv -s $(RTL_TOP)
python3 $(RTL_PATH)/script/filelist_deps.py -f $(IVERILOG_NETL_FLIST) \
--target $@ --output $(IVERILOG_NETL_DEPFILE)
postcomp: $(IVERILOG_POST_FLIST) $(IVERILOG_POST_SIMV): $(IVERILOG_POST_FLIST)
@mkdir -p $(IVERILOG_POST_DIR) @mkdir -p $(IVERILOG_POST_DIR)
@cd $(IVERILOG_POST_DIR) && set -o pipefail && \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog \
$(IVERILOG) $(IVERILOG_COMMON_OPTS) $(IVERILOG_POST_OPTS) -f $< \ --log $(IVERILOG_POST_DIR)/compile.log --result $(IVERILOG_POST_DIR)/result-compile.json \
-o simv -s $(RTL_TOP) -s retrosoc_sdf_annotator 2>&1 | tee compile.log --cwd $(IVERILOG_POST_DIR) -- $(IVERILOG) $(IVERILOG_COMMON_OPTS) \
$(IVERILOG_POST_OPTS) -f $< -o simv -s $(RTL_TOP) -s retrosoc_sdf_annotator
python3 $(RTL_PATH)/script/filelist_deps.py -f $(IVERILOG_POST_FLIST) \
--target $@ --output $(IVERILOG_POST_DEPFILE)
comp: $(IVERILOG_BEHV_SIMV)
netcomp: $(IVERILOG_NETL_SIMV)
postcomp: $(IVERILOG_POST_SIMV)
sim: comp sim: comp
python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_BEHV_DIR) \ python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_BEHV_DIR) \
--firmware $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex --firmware $(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).hex
@cd $(IVERILOG_BEHV_DIR) && set -o pipefail && \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog-sim \
stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS) 2>&1 | tee sim.log --log $(IVERILOG_BEHV_DIR)/sim.log --result $(IVERILOG_BEHV_DIR)/result-sim.json \
--cwd $(IVERILOG_BEHV_DIR) -- stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS)
python3 $(ROOT_PATH)/scripts/check_simulation.py --log $(IVERILOG_BEHV_DIR)/sim.log \
--result $(IVERILOG_BEHV_DIR)/result-sim-check.json --require '$(SIM_SUCCESS_MARKER)'
netsim: netcomp netsim: netcomp
python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_NETL_DIR) \ python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_NETL_DIR) \
--firmware $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex --firmware $(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).hex
@cd $(IVERILOG_NETL_DIR) && set -o pipefail && \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog-sim \
stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS) 2>&1 | tee sim.log --log $(IVERILOG_NETL_DIR)/sim.log --result $(IVERILOG_NETL_DIR)/result-sim.json \
--cwd $(IVERILOG_NETL_DIR) -- stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS)
python3 $(ROOT_PATH)/scripts/check_simulation.py --log $(IVERILOG_NETL_DIR)/sim.log \
--result $(IVERILOG_NETL_DIR)/result-sim-check.json --require '$(SIM_SUCCESS_MARKER)'
postsim: postcomp postsim: postcomp
python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_POST_DIR) \ python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(IVERILOG_POST_DIR) \
--firmware $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex --firmware $(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).hex
@cd $(IVERILOG_POST_DIR) && set -o pipefail && \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool iverilog-sim \
stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS) 2>&1 | tee sim.log --log $(IVERILOG_POST_DIR)/sim.log --result $(IVERILOG_POST_DIR)/result-sim.json \
--cwd $(IVERILOG_POST_DIR) -- stdbuf -oL -eL $(VVP) simv -fst $(IVERILOG_SIM_OPTS)
python3 $(ROOT_PATH)/scripts/check_simulation.py --log $(IVERILOG_POST_DIR)/sim.log \
--result $(IVERILOG_POST_DIR)/result-sim-check.json --require '$(SIM_SUCCESS_MARKER)'
comp netcomp postcomp sim netsim postsim: | manifest
wave: wave:
cd $(IVERILOG_BEHV_DIR) && $(GTKWAVE) $(RTL_TOP).fst & cd $(IVERILOG_BEHV_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
@@ -89,6 +127,6 @@ postwave:
cd $(IVERILOG_POST_DIR) && $(GTKWAVE) $(RTL_TOP).fst & cd $(IVERILOG_POST_DIR) && $(GTKWAVE) $(RTL_TOP).fst &
clean: clean:
rm -rf $(IVERILOG_ROOT) python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(IVERILOG_ROOT)
.PHONY: convt_sv2v gen_iverilog_filelist comp netcomp postcomp sim netsim postsim wave netwave postwave clean .PHONY: convt_sv2v gen_iverilog_filelist comp netcomp postcomp sim netsim postsim wave netwave postwave clean
+36 -18
View File
@@ -103,12 +103,13 @@ APP_PATH := $(ROOT_PATH)/app/base/src/at24cxx.c \
$(ROOT_PATH)/app/base/src/donut.c $(ROOT_PATH)/app/base/src/donut.c
endif endif
INC_PATH := -I$(ROOT_PATH)/crt/inc \ INC_PATH := -I$(SW_BUILD_DIR)/include \
-I$(ROOT_PATH)/crt/inc \
-I$(ROOT_PATH)/app/base/inc -I$(ROOT_PATH)/app/base/inc
ifneq ($(findstring MDD,$(CORE) $(IP)),) ifneq ($(findstring MDD,$(CORE) $(IP)),)
INC_PATH += -I$(ROOT_PATH)/rtl/mini/mpw/.build INC_PATH += -I$(MPW_OUTPUT_DIR)
endif endif
@@ -130,22 +131,39 @@ ifneq ($(filter RV32E RV32I,$(ISA)),)
endif endif
LDS_PATH := $(ROOT_PATH)/crt/lds/$(LINK_TYPE).lds LDS_PATH := $(ROOT_PATH)/crt/lds/$(LINK_TYPE).lds
VERSION_HEADER := $(SW_BUILD_DIR)/include/socver.h
FIRMWARE_ELF := $(SW_BUILD_DIR)/firmware
ASM_FIRMWARE_NAME ?= retrosoc_asm
SW_HEADERS := $(shell find $(ROOT_PATH)/crt/inc $(ROOT_PATH)/app -type f \
\( -name '*.h' -o -name '*.hpp' \) 2>/dev/null)
upd_ver_info: $(VERSION_HEADER): FORCE_VERSION $(ROOT_PATH)/crt/ver.py $(ROOT_PATH)/crt/ver.tmpl
python3 $(ROOT_PATH)/crt/ver.py --root $(ROOT_PATH) python3 $(ROOT_PATH)/crt/ver.py --root $(ROOT_PATH) \
--output $@
asm: gen_mpw_code FORCE_VERSION:
@mkdir -p $(ROOT_PATH)/.sw_build
$(MAKE) -C $(ROOT_PATH)/app/asm
cp $(ROOT_PATH)/app/asm/hello-asm.flash $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).hex
cp $(ROOT_PATH)/app/asm/hello-asm.bin $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).bin
cp $(ROOT_PATH)/app/asm/hello-asm.txt $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME)_all.txt
firmware: gen_mpw_code upd_ver_info upd_ver_info: $(VERSION_HEADER)
@mkdir -p $(ROOT_PATH)/.sw_build
cd $(ROOT_PATH)/.sw_build && $(CP) -P -o $(LINK_TYPE).lds $(LDS_PATH) asm: $(MPW_VARIANT_STAMP)
cd $(ROOT_PATH)/.sw_build && $(CC) $(CFLAGS) $(INC_PATH) -o $@ $(SRC_PATH) @mkdir -p $(SW_BUILD_DIR)/asm
cd $(ROOT_PATH)/.sw_build && $(OBJC) -O verilog $@ $(FIRMWARE_NAME).hex $(MAKE) -C $(ROOT_PATH)/app/asm OUT_DIR=$(SW_BUILD_DIR)/asm
cd $(ROOT_PATH)/.sw_build && $(OBJC) -O binary $@ $(FIRMWARE_NAME).bin cp $(SW_BUILD_DIR)/asm/hello-asm.flash $(SW_BUILD_DIR)/$(ASM_FIRMWARE_NAME).hex
cd $(ROOT_PATH)/.sw_build && $(DUMP) -d $@ > $(FIRMWARE_NAME).txt cp $(SW_BUILD_DIR)/asm/hello-asm.bin $(SW_BUILD_DIR)/$(ASM_FIRMWARE_NAME).bin
cd $(ROOT_PATH)/.sw_build && $(DUMP) -D $@ > $(FIRMWARE_NAME)_all.txt cp $(SW_BUILD_DIR)/asm/hello-asm.txt $(SW_BUILD_DIR)/$(ASM_FIRMWARE_NAME)_all.txt
$(FIRMWARE_ELF): $(MPW_VARIANT_STAMP) $(VERSION_HEADER) $(SRC_PATH) $(SW_HEADERS) $(LDS_PATH) \
$(ROOT_PATH)/rtl/mini/mk/software.mk
@mkdir -p $(SW_BUILD_DIR)
cd $(SW_BUILD_DIR) && $(CP) -P -o $(LINK_TYPE).lds $(LDS_PATH)
cd $(SW_BUILD_DIR) && $(CC) $(CFLAGS) $(INC_PATH) -o $(@F) $(SRC_PATH)
cd $(SW_BUILD_DIR) && $(OBJC) -O verilog $(@F) $(FIRMWARE_NAME).hex
cd $(SW_BUILD_DIR) && $(OBJC) -O binary $(@F) $(FIRMWARE_NAME).bin
cd $(SW_BUILD_DIR) && $(DUMP) -d $(@F) > $(FIRMWARE_NAME).txt
cd $(SW_BUILD_DIR) && $(DUMP) -D $(@F) > $(FIRMWARE_NAME)_all.txt
firmware: $(FIRMWARE_ELF)
firmware asm: | manifest
.PHONY: FORCE_VERSION
+60 -28
View File
@@ -10,11 +10,11 @@ VERDI_TOOL := $(VCS_RUNNER) $(VERDI)
COMP_LOG := -l compile.log COMP_LOG := -l compile.log
SIM_LOG := -l sim.log SIM_LOG := -l sim.log
# netlist file path # netlist file path
NETLIST_PATH := -v $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.v NETLIST_PATH := -v $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v
POST_PATH := -v $(ROOT_PATH)/pd/sdf/retrosoc_asic.v POST_PATH := -v $(ROOT_PATH)/pd/sdf/retrosoc_asic.v
SDF_PATH := "$(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz" SDF_PATH := "$(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz"
# testbench filelist # testbench filelist
TB_FLIST := -f $(RTL_PATH)/.generated_fl/tb.fl TB_FLIST := -f $(GENERATED_FL_DIR)/tb.fl
## vcs option ## vcs option
# -debug_region=cell+lib # -debug_region=cell+lib
@@ -27,7 +27,7 @@ COMMON_OPTS := -full64 +v2k -sverilog -timescale=1ns/10ps \
$(EXTRA) \ $(EXTRA) \
-kdb \ -kdb \
-debug_access+all \ -debug_access+all \
-msg_config=$(RTL_PATH)/.generated_fl/lint.msg \ -msg_config=$(GENERATED_FL_DIR)/lint.msg \
+error+500 \ +error+500 \
+vcs+flush+all \ +vcs+flush+all \
-xprop=$(RTL_PATH)/xprop_config \ -xprop=$(RTL_PATH)/xprop_config \
@@ -49,45 +49,77 @@ SIM_OPTS := +vcs+loopreport+1000 -suppress=ASLR_DETECTED_INFO \
+$(RTL_SIM_PLLEN) +$(RTL_SIM_PLLCFG) +core_sel=$(RTL_SIM_CORESEL) +sim_timeout=$(RTL_SIM_TIMEOUT) \ +$(RTL_SIM_PLLEN) +$(RTL_SIM_PLLCFG) +core_sel=$(RTL_SIM_CORESEL) +sim_timeout=$(RTL_SIM_TIMEOUT) \
+wave_$(WAVE) +wave_$(WAVE)
VCS_BEHV_DIR := $(SIM_BUILD_ROOT)/behv
VCS_NETL_DIR := $(SIM_BUILD_ROOT)/netl
VCS_POST_DIR := $(SIM_BUILD_ROOT)/post
VCS_BEHV_SIMV := $(VCS_BEHV_DIR)/simv
VCS_NETL_SIMV := $(VCS_NETL_DIR)/simv
VCS_POST_SIMV := $(VCS_POST_DIR)/simv
VCS_BEHV_DEPFILE := $(VCS_BEHV_DIR)/simv.d
VCS_NETL_DEPFILE := $(VCS_NETL_DIR)/simv.d
VCS_POST_DEPFILE := $(VCS_POST_DIR)/simv.d
comp: DIR := .vcs_build/behv -include $(VCS_BEHV_DEPFILE) $(VCS_NETL_DEPFILE) $(VCS_POST_DEPFILE)
comp: FLIST := $(RTL_FLIST) $(TB_FLIST)
comp: OPTS := $(TIME_OPTION)
netcomp: DIR := .vcs_build/netl $(VCS_BEHV_SIMV): DIR := $(VCS_BEHV_DIR)
netcomp: FLIST := $(NETLIST_PATH) $(NET_FLIST) $(TB_FLIST) $(VCS_BEHV_SIMV): FLIST := $(RTL_FLIST) $(TB_FLIST)
netcomp: OPTS := $(TIME_OPTION) $(VCS_BEHV_SIMV): OPTS := $(TIME_OPTION)
$(VCS_BEHV_SIMV): DEPFILE := $(VCS_BEHV_DEPFILE)
$(VCS_BEHV_SIMV): DEPS_ARGS := $(RTL_FLIST) -f $(GENERATED_FL_DIR)/tb.fl
postcomp: DIR := .vcs_build/post $(VCS_NETL_SIMV): DIR := $(VCS_NETL_DIR)
postcomp: FLIST := $(POST_PATH) $(NET_FLIST) $(TB_FLIST) $(VCS_NETL_SIMV): FLIST := $(NETLIST_PATH) $(NET_FLIST) $(TB_FLIST)
postcomp: OPTS := $(POST_OPTS) $(VCS_NETL_SIMV): OPTS := $(TIME_OPTION)
$(VCS_NETL_SIMV): DEPFILE := $(VCS_NETL_DEPFILE)
$(VCS_NETL_SIMV): DEPS_ARGS := $(NET_FLIST) -f $(GENERATED_FL_DIR)/tb.fl --extra $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v
sim: DIR := .vcs_build/behv $(VCS_POST_SIMV): DIR := $(VCS_POST_DIR)
netsim: DIR := .vcs_build/netl $(VCS_POST_SIMV): FLIST := $(POST_PATH) $(NET_FLIST) $(TB_FLIST)
postsim: DIR := .vcs_build/post $(VCS_POST_SIMV): OPTS := $(POST_OPTS)
$(VCS_POST_SIMV): DEPFILE := $(VCS_POST_DEPFILE)
$(VCS_POST_SIMV): DEPS_ARGS := $(NET_FLIST) -f $(GENERATED_FL_DIR)/tb.fl --extra $(ROOT_PATH)/pd/sdf/retrosoc_asic.v --extra $(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz
wave: DIR := .vcs_build/behv sim: DIR := $(VCS_BEHV_DIR)
netwave: DIR := .vcs_build/netl netsim: DIR := $(VCS_NETL_DIR)
postwave: DIR := .vcs_build/post postsim: DIR := $(VCS_POST_DIR)
wave: DIR := $(SIM_BUILD_ROOT)/behv
netwave: DIR := $(SIM_BUILD_ROOT)/netl
postwave: DIR := $(SIM_BUILD_ROOT)/post
sim: comp comp: $(VCS_BEHV_SIMV)
netsim: netcomp netcomp: $(VCS_NETL_SIMV)
postsim: postcomp postcomp: $(VCS_POST_SIMV)
sim: $(VCS_BEHV_SIMV)
netsim: $(VCS_NETL_SIMV)
postsim: $(VCS_POST_SIMV)
comp netcomp postcomp: gen_mpw_code generate_filelist $(VCS_BEHV_SIMV) $(VCS_NETL_SIMV) $(VCS_POST_SIMV): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP)
@mkdir -p $(DIR)
python3 $(ROOT_PATH)/scripts/run_flow.py --tool vcs --log $(DIR)/driver-compile.log \
--result $(DIR)/result-compile.json --cwd $(DIR) -- \
$(SIM_TOOL) $(COMMON_OPTS) $(OPTS) $(FLIST) -top $(RTL_TOP) $(COMP_LOG)
python3 $(RTL_PATH)/script/filelist_deps.py $(DEPS_ARGS) --target $@ --output $(DEPFILE)
comp netcomp postcomp: $(VCS_NETL_SIMV): $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v
@mkdir -p $(RTL_PATH)/$(DIR) $(VCS_POST_SIMV): $(ROOT_PATH)/pd/sdf/retrosoc_asic.v $(ROOT_PATH)/pd/sdf/retrosoc_asic_CTS_MIN.sdf.gz
cd $(RTL_PATH)/$(DIR) && $(SIM_TOOL) $(COMMON_OPTS) $(OPTS) $(FLIST) -top $(RTL_TOP) $(COMP_LOG)
sim netsim postsim: sim netsim postsim:
cd $(RTL_PATH)/$(DIR) && $(SIM_BINY) $(SIM_OPTS) $(if $(filter netsim postsim,$@),+bus_conflict_off) $(SIM_LOG) python3 $(RTL_PATH)/script/prepare_norflash.py --sim-dir $(DIR) \
--firmware $(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).hex
python3 $(ROOT_PATH)/scripts/run_flow.py --tool vcs-sim --log $(DIR)/driver-sim.log \
--result $(DIR)/result-sim.json --cwd $(DIR) -- \
$(SIM_BINY) $(SIM_OPTS) $(if $(filter netsim postsim,$@),+bus_conflict_off) $(SIM_LOG)
python3 $(ROOT_PATH)/scripts/check_simulation.py --log $(DIR)/driver-sim.log \
--result $(DIR)/result-sim-check.json --require '$(SIM_SUCCESS_MARKER)'
comp netcomp postcomp sim netsim postsim: | manifest
wave netwave postwave: wave netwave postwave:
cd $(RTL_PATH)/$(DIR) && ($(VERDI_TOOL) -ssf $(RTL_TOP).fsdb -nologo &) cd $(DIR) && ($(VERDI_TOOL) -ssf $(RTL_TOP).fsdb -nologo &)
clean: clean:
rm -rf $(RTL_PATH)/.vcs_build python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(SIM_BUILD_ROOT)
.PHONY: comp netcomp postcomp sim netsim postsim wave clean .PHONY: comp netcomp postcomp sim netsim postsim wave clean
+41 -12
View File
@@ -1,4 +1,4 @@
BUILD_DIR := $(RTL_PATH)/.verilator_build BUILD_DIR := $(SIM_BUILD_ROOT)
SOC_CSRC_HOME += $(RTL_PATH)/csrc SOC_CSRC_HOME += $(RTL_PATH)/csrc
SOC_CSRC_LIB_HOME += $(RTL_PATH)/csrc SOC_CSRC_LIB_HOME += $(RTL_PATH)/csrc
SOC_CXXFILES += $(sort $(wildcard $(SOC_CSRC_HOME)/*.cpp)) SOC_CXXFILES += $(sort $(wildcard $(SOC_CSRC_HOME)/*.cpp))
@@ -19,6 +19,7 @@ SOC_VXXFILES += $(RTL_PATH)/vsrc/retrosoc_top.sv
SOC_VSRC_INCLPATH += -I$(SOC_VSRC_HOME) SOC_VSRC_INCLPATH += -I$(SOC_VSRC_HOME)
VERILATOR ?= verilator VERILATOR ?= verilator
VERILATOR_JOBS ?= $(JOBS)
VERILATOR_CXXFLAGS += -std=c++17 -Wall $(SOC_CSRC_INCLPATH) -DDUMP_WAVE_FST VERILATOR_CXXFLAGS += -std=c++17 -Wall $(SOC_CSRC_INCLPATH) -DDUMP_WAVE_FST
VERILATOR_FLAGS += --cc --exe --no-timing --top-module $(SOC_VSRC_TOP) VERILATOR_FLAGS += --cc --exe --no-timing --top-module $(SOC_VSRC_TOP)
VERILATOR_FLAGS += --x-assign unique -O3 -CFLAGS "$(VERILATOR_CXXFLAGS)" VERILATOR_FLAGS += --x-assign unique -O3 -CFLAGS "$(VERILATOR_CXXFLAGS)"
@@ -29,32 +30,60 @@ VERILATOR_FLAGS += -Mdir $(SOC_COMPILE_HOME)
VERILATOR_FLAGS += $(SOC_VSRC_INCLPATH) $(SOC_CXXFILES) $(SOC_VXXFILES) VERILATOR_FLAGS += $(SOC_VSRC_INCLPATH) $(SOC_CXXFILES) $(SOC_VXXFILES)
SOC_SIM_TIME ?= 40 SOC_SIM_TIME ?= 40
VERILATOR_STAMP := $(BUILD_DIR)/verilate.stamp
VERILATOR_DEPFILE := $(BUILD_DIR)/verilate.d
VERILATOR_EMU := $(BUILD_DIR)/emu
VERILATOR_EXTRA_SOURCES := $(RTL_PATH)/../ip/tb/ESP_PSRAM64H.sv \
$(RTL_PATH)/vsrc/flash_read_binder.sv \
$(RTL_PATH)/vsrc/QSPIFlash.sv \
$(RTL_PATH)/vsrc/retrosoc_top.sv \
$(SOC_CXXFILES) $(wildcard $(SOC_CSRC_HOME)/*.h) \
$(wildcard $(SOC_CSRC_HOME)/*.hpp)
-include $(VERILATOR_DEPFILE)
CCACHE := $(shell command -v ccache 2>/dev/null) CCACHE := $(shell command -v ccache 2>/dev/null)
ifneq ($(CCACHE),) ifneq ($(CCACHE),)
export OBJCACHE = ccache export OBJCACHE = ccache
export CCACHE_DIR = $(BUILD_DIR)/ccache export CCACHE_DIR = $(CACHE_ROOT)/ccache/verilator
export CCACHE_TEMPDIR = $(BUILD_DIR)/ccache/tmp export CCACHE_TEMPDIR = $(CACHE_ROOT)/ccache/verilator/tmp
endif endif
lint: gen_mpw_code generate_filelist $(VERILATOR_STAMP): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP) $(VERILATOR_EXTRA_SOURCES)
@mkdir -p $(BUILD_DIR) @mkdir -p $(BUILD_DIR)
@mkdir -p $(BUILD_DIR)/ccache/tmp @mkdir -p $(CCACHE_TEMPDIR)
python3 $(ROOT_PATH)/scripts/run_flow.py --tool verilator \
--log $(BUILD_DIR)/verilating.log --result $(BUILD_DIR)/result-verilate.json \
-- $(VERILATOR) $(VERILATOR_FLAGS)
python3 $(RTL_PATH)/script/filelist_deps.py $(RTL_FLIST) \
$(foreach source,$(VERILATOR_EXTRA_SOURCES),--extra $(source)) \
--target $@ --output $(VERILATOR_DEPFILE)
@touch $@
comp: lint $(VERILATOR_EMU): $(VERILATOR_STAMP)
$(VERILATOR) $(VERILATOR_FLAGS) > $(BUILD_DIR)/verilating.log 2>&1 python3 $(ROOT_PATH)/scripts/run_flow.py --tool verilator-compile \
+$(MAKE) VM_PARALLEL_BUILDS=1 OPT_FAST="-O3" -C $(SOC_COMPILE_HOME) \ --log $(BUILD_DIR)/compile.log --result $(BUILD_DIR)/result-compile.json \
-f V$(SOC_VSRC_TOP).mk > $(BUILD_DIR)/compile.log 2>&1 -- make -j$(VERILATOR_JOBS) VM_PARALLEL_BUILDS=1 OPT_FAST=-O3 -C $(SOC_COMPILE_HOME) \
-f V$(SOC_VSRC_TOP).mk
lint: $(VERILATOR_STAMP)
comp: $(VERILATOR_EMU)
sim: comp sim: comp
@test -f $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).bin || { \ @test -f $(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).bin || { \
echo "firmware image missing; run 'make firmware' first" >&2; exit 1; } echo "firmware image missing; run 'make firmware' first" >&2; exit 1; }
$(BUILD_DIR)/emu -i $(ROOT_PATH)/.sw_build/$(FIRMWARE_NAME).bin \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool verilator-sim \
--log $(BUILD_DIR)/sim.log --result $(BUILD_DIR)/result-sim.json \
--cwd $(BUILD_DIR) -- $(BUILD_DIR)/emu -i $(SW_BUILD_DIR)/$(SIM_FIRMWARE_NAME).bin \
-s $(RTL_SIM_CORESEL) -t $(SOC_SIM_TIME) -s $(RTL_SIM_CORESEL) -t $(SOC_SIM_TIME)
python3 $(ROOT_PATH)/scripts/check_simulation.py --log $(BUILD_DIR)/sim.log \
--result $(BUILD_DIR)/result-sim-check.json --require '$(SIM_SUCCESS_MARKER)'
comp sim: | manifest
wave: wave:
clean: clean:
rm -rf $(BUILD_DIR) python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(BUILD_DIR)
.PHONY: comp sim clean .PHONY: comp sim clean
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
from pathlib import Path
from filelist import atomic_write, parse_filelists
HEADER_SUFFIXES = {".h", ".vh", ".svh"}
def make_escape(path: Path) -> str:
return (
str(path.resolve())
.replace("\\", "\\\\")
.replace("$", "$$")
.replace(" ", "\\ ")
.replace("#", "\\#")
)
def main() -> int:
parser = argparse.ArgumentParser(description="Generate Make dependencies from RTL filelists")
parser.add_argument("-f", "--filelist", type=Path, action="append", default=[])
parser.add_argument("--extra", type=Path, action="append", default=[])
parser.add_argument("--target", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
if not args.filelist:
parser.error("at least one -f/--filelist is required")
parsed = parse_filelists(args.filelist)
dependencies = {
*(path.resolve() for path in args.filelist),
*(path.resolve() for path in parsed.files),
*(path.resolve() for path in parsed.library_files),
*(path.resolve() for path in args.extra),
}
for directory in parsed.incdirs:
if directory.is_dir():
dependencies.update(
path.resolve()
for path in directory.rglob("*")
if path.is_file() and path.suffix.lower() in HEADER_SUFFIXES
)
content = f"{make_escape(args.target)}:"
if dependencies:
content += " \\\n " + " \\\n ".join(make_escape(path) for path in sorted(dependencies))
content += "\n"
atomic_write(args.output, content)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1 -1
View File
@@ -6,7 +6,7 @@ import argparse
import gzip import gzip
from pathlib import Path from pathlib import Path
from filelist import FileList, atomic_write, parse_filelists, write_filelist from filelist import atomic_write, parse_filelists, write_filelist
SCRIPT_DIR = Path(__file__).resolve().parent SCRIPT_DIR = Path(__file__).resolve().parent
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
TIMESTAMP_RE = re.compile(r"^\[[^]]+\]\s*")
LOCATION_RE = re.compile(
r"(?P<path>(?:[A-Za-z]:)?[^\s:]+?\."
r"(?:c|cc|cpp|cxx|h|hpp|py|sv|svh|tcl|v|vh)):\d+(?::\d+)?",
re.IGNORECASE,
)
def warning_line(tool: str, line: str) -> tuple[str, str] | None:
patterns = {
"verilator": re.compile(r"%Warning-([A-Z0-9_]+):\s*(.*)"),
"iverilog": re.compile(r"\b(warning|sorry):\s*(.*)", re.IGNORECASE),
"yosys": re.compile(r"\b(?:ABC:\s*)?Warning:\s*(.*)", re.IGNORECASE),
"opensta": re.compile(r"\bWarning\s+(\d+):\s*(.*)", re.IGNORECASE),
"vcs": re.compile(r"\bWarning(?:-\[([^]]+)\])?:\s*(.*)", re.IGNORECASE),
}
match = patterns[tool].search(line)
if not match:
return None
if tool in ("verilator", "opensta"):
return match.group(1), match.group(2)
if tool == "iverilog":
return match.group(1).lower(), match.group(2)
if tool == "vcs":
return match.group(1) or "warning", match.group(2)
return "warning", match.group(1)
def normalize(root: Path, identifier: str, message: str) -> str:
value = ANSI_RE.sub("", TIMESTAMP_RE.sub("", message.strip()))
resolved_root = str(root.resolve())
value = re.sub(
re.escape(resolved_root) + r"/build/[^/\s:]+",
"$BUILD",
value,
)
value = value.replace(resolved_root, "$ROOT")
value = LOCATION_RE.sub(lambda match: match.group("path") + ":<line>", value)
value = re.sub(r"\s+", " ", value)
return f"{identifier}:{value}"
def scan(tool: str, logs: list[Path], root: Path) -> dict[str, object]:
counts: Counter[str] = Counter()
examples: dict[str, str] = {}
for log in logs:
if not log.is_file():
continue
for line in log.read_text(encoding="utf-8", errors="replace").splitlines():
parsed = warning_line(tool, line)
if parsed is None:
continue
normalized = normalize(root, *parsed)
signature = hashlib.sha256(normalized.encode("utf-8")).hexdigest()[:16]
counts[signature] += 1
examples.setdefault(signature, normalized)
entries = [
{"signature": signature, "count": count, "example": examples[signature]}
for signature, count in sorted(counts.items())
]
return {"tool": tool, "logs": [str(path) for path in logs], "entries": entries}
def discover(variant_root: Path) -> dict[str, list[Path]]:
return {
"iverilog": sorted((variant_root / "sim/iverilog").glob("*/*.log")),
"verilator": sorted((variant_root / "sim/verilator").glob("*.log")),
"vcs": sorted((variant_root / "sim/vcs").glob("*/*.log")),
"yosys": sorted((variant_root / "syn/yosys").glob("*.log")),
"opensta": sorted((variant_root / "sta/opensta").glob("*.log")),
}
def write_baseline(args: argparse.Namespace) -> int:
result = scan(args.tool, [path.resolve() for path in args.log], args.root.resolve())
baseline = {
"schema_version": 1,
"profile": args.profile,
"tool": args.tool,
"entries": result["entries"],
}
atomic_write(args.output, json.dumps(baseline, indent=2, sort_keys=True) + "\n")
print(f"warning baseline: {args.output.resolve()}")
return 0
def check(args: argparse.Namespace) -> int:
root = args.root.resolve()
variant_root = args.variant_root.resolve()
tools = [args.tool] if args.tool else ["iverilog", "verilator", "vcs", "yosys", "opensta"]
logs = discover(variant_root)
reports: dict[str, object] = {}
failures: list[str] = []
for tool in tools:
if not logs[tool]:
reports[tool] = {"status": "not_run", "entries": []}
continue
current = scan(tool, logs[tool], root)
baseline_path = root / "quality/warnings" / args.profile / f"{tool}.json"
baseline_entries: dict[str, int] = {}
if baseline_path.is_file():
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
baseline_entries = {
entry["signature"]: entry["count"] for entry in baseline["entries"]
}
current_entries = {
entry["signature"]: entry["count"] for entry in current["entries"]
}
new = sorted(set(current_entries) - set(baseline_entries))
increased = sorted(
signature
for signature, count in current_entries.items()
if signature in baseline_entries and count > baseline_entries[signature]
)
if new or increased:
failures.append(tool)
reports[tool] = {
**current,
"status": "failed" if new or increased else "passed",
"baseline": str(baseline_path),
"new_signatures": new,
"increased_signatures": increased,
"resolved_signatures": sorted(set(baseline_entries) - set(current_entries)),
}
output = {
"schema_version": 1,
"profile": args.profile,
"status": "failed" if failures else "passed",
"failed_tools": failures,
"tools": reports,
}
atomic_write(args.output, json.dumps(output, indent=2, sort_keys=True) + "\n")
print(f"warning check: {output['status']} ({args.output.resolve()})")
return int(bool(failures))
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Manage normalized EDA warning baselines")
subparsers = parser.add_subparsers(dest="command", required=True)
baseline = subparsers.add_parser("baseline")
baseline.add_argument("--root", type=Path, default=ROOT)
baseline.add_argument("--profile", required=True)
baseline.add_argument("--tool", choices=("iverilog", "verilator", "vcs", "yosys", "opensta"), required=True)
baseline.add_argument("--log", type=Path, action="append", required=True)
baseline.add_argument("--output", type=Path, required=True)
check_parser = subparsers.add_parser("check")
check_parser.add_argument("--root", type=Path, default=ROOT)
check_parser.add_argument("--profile", required=True)
check_parser.add_argument("--variant-root", type=Path, required=True)
check_parser.add_argument("--tool", choices=("iverilog", "verilator", "vcs", "yosys", "opensta"))
check_parser.add_argument("--output", type=Path, required=True)
return parser.parse_args()
def main() -> int:
args = parse_args()
return write_baseline(args) if args.command == "baseline" else check(args)
if __name__ == "__main__":
raise SystemExit(main())
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
DEFAULT_SUCCESS = "retroSoC: A Customized ASIC for Retro Stuff"
DEFAULT_FAILURE = re.compile(
r"(?:\bFAILED?\b|\bFATAL\b|assertion failed|%Error)", re.IGNORECASE
)
def main() -> int:
parser = argparse.ArgumentParser(description="Validate a retroSoC simulation log")
parser.add_argument("--log", type=Path, required=True)
parser.add_argument("--result", type=Path, required=True)
parser.add_argument("--require", action="append", default=[])
parser.add_argument("--reject", action="append", default=[])
args = parser.parse_args()
if not args.log.is_file():
raise SystemExit(f"simulation log not found: {args.log}")
content = args.log.read_text(encoding="utf-8", errors="replace")
required = args.require or [DEFAULT_SUCCESS]
missing = [marker for marker in required if marker not in content]
rejected = [pattern for pattern in args.reject if re.search(pattern, content)]
default_failure = DEFAULT_FAILURE.search(content)
if default_failure:
rejected.append(default_failure.group(0))
passed = not missing and not rejected
report = {
"schema_version": 1,
"status": "passed" if passed else "failed",
"log": str(args.log.resolve()),
"required_markers": required,
"missing_markers": missing,
"rejected_matches": sorted(set(rejected)),
}
atomic_write(args.result, json.dumps(report, indent=2, sort_keys=True) + "\n")
print(f"simulation check: {report['status']} ({args.result.resolve()})")
return int(not passed)
if __name__ == "__main__":
raise SystemExit(main())
+18 -15
View File
@@ -10,12 +10,21 @@ from pathlib import Path
def parse_args() -> argparse.Namespace: def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Remove retroSoC generated output") parser = argparse.ArgumentParser(description="Remove retroSoC generated output")
parser.add_argument("--root", type=Path, required=True) parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--path", type=Path, action="append", required=True)
return parser.parse_args() return parser.parse_args()
def remove(root: Path, relative: str) -> None: def remove(root: Path, requested: Path) -> None:
target = (root / relative).resolve() requested = requested.absolute()
requested.parent.resolve().relative_to(root)
if requested.is_symlink():
requested.unlink()
print(f"cleaned {requested}")
return
target = requested.resolve()
target.relative_to(root) target.relative_to(root)
if target == root:
raise ValueError("refusing to remove the repository root")
if target.is_dir() and not target.is_symlink(): if target.is_dir() and not target.is_symlink():
shutil.rmtree(target) shutil.rmtree(target)
elif target.exists() or target.is_symlink(): elif target.exists() or target.is_symlink():
@@ -24,21 +33,15 @@ def remove(root: Path, relative: str) -> None:
def main() -> int: def main() -> int:
root = parse_args().root.resolve() args = parse_args()
root = args.root.resolve()
if not (root / "Makefile").is_file() or not (root / ".git").exists(): if not (root / "Makefile").is_file() or not (root / ".git").exists():
raise SystemExit(f"not a retroSoC repository root: {root}") raise SystemExit(f"not a retroSoC repository root: {root}")
generated = ( try:
".sw_build", for requested in args.path:
"rtl/mini/.generated_fl", remove(root, requested)
"rtl/mini/.iverilog_build", except ValueError as error:
"rtl/mini/.verilator_build", raise SystemExit(f"refusing unsafe clean path: {error}") from error
"rtl/mini/.vcs_build",
"syn/yosys/.synth_build",
"sta/opensta/retrosoc_sta.log",
"export/rtl",
)
for relative in generated:
remove(root, relative)
return 0 return 0
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import DEFAULT_LOCK, lock_digest, load_lock # noqa: E402
def slug(value: str) -> str:
normalized = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
return normalized or "manual"
def variant_id(profile: str, values: list[str], lock: Path) -> str:
parsed: dict[str, str] = {}
for item in values:
name, separator, value = item.partition("=")
if not separator or not name or name in parsed:
raise ValueError(f"invalid or duplicate configuration value: {item}")
parsed[name] = value
payload = {
"schema_version": 1,
"configuration": dict(sorted(parsed.items())),
"dependency_lock_sha256": lock_digest(lock),
}
digest = hashlib.sha256(
json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()[:12]
return f"{slug(profile)}-{digest}"
def main() -> int:
parser = argparse.ArgumentParser(description="Generate a deterministic build variant ID")
parser.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
parser.add_argument("--profile", default="manual")
parser.add_argument("--value", action="append", default=[])
args = parser.parse_args()
load_lock(args.lock)
try:
print(variant_id(args.profile, args.value, args.lock))
except ValueError as error:
parser.error(str(error))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import re
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_LOCK = ROOT / "config/dependencies.lock.json"
class LockError(ValueError):
pass
def load_lock(path: Path = DEFAULT_LOCK) -> dict[str, Any]:
path = path.resolve()
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise LockError(f"cannot read dependency lock {path}: {error}") from error
validate_lock(data)
return data
def validate_lock(data: dict[str, Any]) -> None:
if data.get("schema_version") != 1:
raise LockError("dependency lock schema_version must be 1")
for section in ("sources", "archives", "toolchains"):
if not isinstance(data.get(section), dict) or not data[section]:
raise LockError(f"dependency lock section is missing or empty: {section}")
for name, source in data["sources"].items():
_require_fields("source", name, source, ("url", "revision", "destination", "license"))
if not re.fullmatch(r"[0-9a-f]{40}", source["revision"]):
raise LockError(f"source {name} revision must be a full 40-character commit")
_validate_url(f"source {name}", source["url"])
_validate_relative_path(f"source {name} destination", source["destination"])
for name, archive in data["archives"].items():
_validate_archive("archive", name, archive)
for platform, tools in data["toolchains"].items():
if not isinstance(tools, dict) or not tools:
raise LockError(f"toolchain platform is empty: {platform}")
for name, tool in tools.items():
_require_fields("toolchain", name, tool, ("version", "url", "sha256", "archive", "path"))
_validate_sha(f"toolchain {platform}/{name}", tool["sha256"])
_validate_url(f"toolchain {platform}/{name}", tool["url"])
_validate_relative_path(f"toolchain {platform}/{name} archive", tool["archive"])
_validate_relative_path(f"toolchain {platform}/{name} path", tool["path"])
def _validate_archive(kind: str, name: str, value: dict[str, Any]) -> None:
_require_fields(kind, name, value, ("url", "sha256", "destination", "license"))
_validate_sha(f"{kind} {name}", value["sha256"])
_validate_url(f"{kind} {name}", value["url"])
_validate_relative_path(f"{kind} {name} destination", value["destination"])
def _require_fields(kind: str, name: str, value: Any, fields: tuple[str, ...]) -> None:
if not isinstance(value, dict):
raise LockError(f"{kind} {name} must be an object")
missing = [field for field in fields if not value.get(field)]
if missing:
raise LockError(f"{kind} {name} missing fields: {', '.join(missing)}")
def _validate_sha(label: str, value: str) -> None:
if len(value) != 64 or any(character not in "0123456789abcdef" for character in value):
raise LockError(f"{label} sha256 must be 64 lowercase hexadecimal characters")
def _validate_url(label: str, value: str) -> None:
if not value.startswith("https://"):
raise LockError(f"{label} URL must use HTTPS")
def _validate_relative_path(label: str, value: str) -> None:
path = Path(value)
if path.is_absolute() or ".." in path.parts:
raise LockError(f"{label} must be a repository-relative path")
def lock_digest(path: Path = DEFAULT_LOCK) -> str:
return hashlib.sha256(path.resolve().read_bytes()).hexdigest()
def source(name: str, path: Path = DEFAULT_LOCK) -> dict[str, Any]:
data = load_lock(path)
try:
return data["sources"][name]
except KeyError as error:
raise LockError(f"unknown source dependency: {name}") from error
def archive(name: str, path: Path = DEFAULT_LOCK) -> dict[str, Any]:
data = load_lock(path)
try:
return data["archives"][name]
except KeyError as error:
raise LockError(f"unknown archive dependency: {name}") from error
def main() -> int:
parser = argparse.ArgumentParser(description="Validate the retroSoC dependency lock")
parser.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
parser.add_argument("--digest", action="store_true")
args = parser.parse_args()
data = load_lock(args.lock)
if args.digest:
print(lock_digest(args.lock))
else:
print(
f"dependency lock valid: {len(data['sources'])} sources, "
f"{len(data['archives'])} archives"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+69 -30
View File
@@ -3,10 +3,19 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import json
import shutil import shutil
import subprocess
import sys
from pathlib import Path from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import DEFAULT_LOCK, load_lock, lock_digest # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402
def parse_args() -> argparse.Namespace: def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Check the retroSoC build environment") parser = argparse.ArgumentParser(description="Check the retroSoC build environment")
parser.add_argument("--root", type=Path, required=True) parser.add_argument("--root", type=Path, required=True)
@@ -16,12 +25,35 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--pdk", required=True) parser.add_argument("--pdk", required=True)
parser.add_argument("--core", required=True) parser.add_argument("--core", required=True)
parser.add_argument("--ip", required=True) parser.add_argument("--ip", required=True)
parser.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
parser.add_argument("--format", choices=("text", "json"), default="text")
parser.add_argument("--output", type=Path)
return parser.parse_args() return parser.parse_args()
def version(executable: str) -> str | None:
path = shutil.which(executable)
if path is None:
return None
options = {
"iverilog": "-V",
"vvp": "-V",
"sta": "-version",
}
result = subprocess.run(
[executable, options.get(executable, "--version")],
text=True,
capture_output=True,
check=False,
)
lines = (result.stdout or result.stderr).strip().splitlines()
return lines[0] if lines else str(path)
def main() -> int: def main() -> int:
args = parse_args() args = parse_args()
root = args.root.resolve() root = args.root.resolve()
lock = load_lock(args.lock)
tools = ["python3", "make", "git", "riscv32-unknown-elf-gcc"] tools = ["python3", "make", "git", "riscv32-unknown-elf-gcc"]
if args.simu == "IVERILOG": if args.simu == "IVERILOG":
tools.extend(("iverilog", "vvp", "sv2v")) tools.extend(("iverilog", "vvp", "sv2v"))
@@ -30,42 +62,49 @@ def main() -> int:
elif args.simu == "VCS": elif args.simu == "VCS":
tools.extend(("bsub", "vcs")) tools.extend(("bsub", "vcs"))
if args.synth == "YOSYS": if args.synth == "YOSYS":
tools.extend(("yosys", "gawk")) tools.append("yosys")
if args.sta == "OPENSTA": if args.sta == "OPENSTA":
tools.append("sta") tools.append("sta")
source_names = ["mpw", "cluster_common", "third_party_ip"]
pdk_names = {"IHP130": "pdk_ihp130", "ICS55": "pdk_ics55"}
if args.pdk in pdk_names:
source_names.append(pdk_names[args.pdk])
paths = { paths = {
"MPW generator": root / "rtl/mini/mpw/.git", name: root / lock["sources"][name]["destination"] for name in source_names
"cluster IP": root / "rtl/clusterip/common",
"third-party models": root / "rtl/ip/3rd-party",
} }
pdk_paths = { tool_results = {
"IHP130": root / "pdk/IHP-Open-PDK", tool: {"path": shutil.which(tool), "version": version(tool)}
"ICS55": root / "pdk/icsprout55-pdk", for tool in dict.fromkeys(tools)
"SKY130": root / "pdk/skywater-sdk",
"GF180": root / "pdk/gf180mcu-pdk",
} }
paths[f"PDK {args.pdk}"] = pdk_paths[args.pdk] path_results = {
name: {"path": str(path), "exists": path.exists()} for name, path in paths.items()
failures = 0 }
print("Tools:") failures = sum(value["path"] is None for value in tool_results.values())
for tool in dict.fromkeys(tools): failures += sum(not value["exists"] for value in path_results.values())
location = shutil.which(tool) report = {
status = location or "MISSING" "schema_version": 1,
print(f" {tool:<28} {status}") "ok": failures == 0,
failures += location is None "failures": failures,
"dependency_lock_sha256": lock_digest(args.lock),
print("Paths:") "tools": tool_results,
for name, path in paths.items(): "paths": path_results,
exists = path.exists() }
print(f" {name:<28} {path} {'[ok]' if exists else '[MISSING]'}") serialized = json.dumps(report, indent=2, sort_keys=True) + "\n"
failures += not exists if args.output:
atomic_write(args.output, serialized)
if failures: if args.format == "json":
print(f"doctor found {failures} missing requirement(s)") print(serialized, end="")
return 1 else:
print("doctor: all selected requirements are available") print("Tools:")
return 0 for name, value in tool_results.items():
print(f" {name:<28} {value['path'] or 'MISSING'}")
print("Paths:")
for name, value in path_results.items():
suffix = "[ok]" if value["exists"] else "[MISSING]"
print(f" {name:<28} {value['path']} {suffix}")
print("doctor: all selected requirements are available" if not failures else f"doctor found {failures} missing requirement(s)")
return int(failures != 0)
if __name__ == "__main__": if __name__ == "__main__":
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import fcntl
import hashlib
import os
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import TextIO
def tree_digest(root: Path) -> str:
digest = hashlib.sha256()
if not root.exists():
return ""
for path in sorted(
item for item in root.rglob("*") if item.is_file() and item.name != ".stamp"
):
digest.update(path.relative_to(root).as_posix().encode("utf-8"))
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def run(root: Path, log: TextIO, script: Path, *args: str) -> None:
command = ["python3", str(script), *args]
log.write("+ " + " ".join(command) + "\n")
log.flush()
subprocess.run(
command,
cwd=root,
check=True,
stdout=log,
stderr=subprocess.STDOUT,
text=True,
)
def rewrite_filelists(root: Path, old_prefix: Path, new_prefix: Path) -> None:
for filelist in root.rglob("*.fl"):
content = filelist.read_text(encoding="utf-8")
content = content.replace(str(old_prefix.resolve()), str(new_prefix.resolve()))
filelist.write_text(content, encoding="utf-8")
def generate(args: argparse.Namespace) -> None:
root = args.root.resolve()
mpw = root / "rtl/mini/mpw"
shared = mpw / ".build"
output = args.output.resolve()
needs_core = args.core == "MDD"
needs_ip = args.ip == "MDD"
log_path = output.parent / f"{output.name}-generation.log"
output.parent.mkdir(parents=True, exist_ok=True)
with log_path.open("w", encoding="utf-8") as log, tempfile.TemporaryDirectory(
prefix=f".{output.name}.", dir=output.parent
) as temp:
log.write(f"simulator={args.simu} core={args.core} ip={args.ip}\n")
candidate = Path(temp) / "output"
candidate.mkdir()
(shared / "user_design_info.h").unlink(missing_ok=True)
if needs_ip:
run(root, log, mpw / "ip.py")
run(root, log, mpw / "info.py", "IP")
shutil.copytree(shared / "ip", candidate / "ip")
if needs_core:
run(root, log, mpw / "core.py", args.simu)
run(root, log, mpw / "info.py", "CORE")
shutil.copytree(shared / "core", candidate / "core")
info = shared / "user_design_info.h"
if info.is_file():
shutil.copy2(info, candidate / info.name)
rewrite_filelists(candidate, shared, output)
if tree_digest(candidate) == tree_digest(output):
print(f"[mpw] generated output unchanged: {output} (log: {log_path})")
return
if output.exists():
shutil.rmtree(output)
os.replace(candidate, output)
print(f"[mpw] generated isolated output: {output} (log: {log_path})")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate isolated MPW build sources")
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--simu", choices=("VCS", "VERILATOR", "IVERILOG"), required=True)
parser.add_argument("--core", choices=("PICORV32", "HAZARD3", "MDD"), required=True)
parser.add_argument("--ip", choices=("NONE", "MDD"), required=True)
parser.add_argument("--lock-file", type=Path, required=True)
return parser.parse_args()
def main() -> int:
args = parse_args()
args.lock_file.parent.mkdir(parents=True, exist_ok=True)
with args.lock_file.open("a+", encoding="utf-8") as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
generate(args)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import os
import posixpath
import shutil
import sys
import tarfile
import tempfile
from pathlib import Path, PurePosixPath
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import DEFAULT_LOCK, load_lock # noqa: E402
from scripts.setup_helpers import atomic_write, download_file # noqa: E402
def safe_extract(archive: Path, destination: Path) -> None:
with tarfile.open(archive, "r:*") as bundle:
for member in bundle.getmembers():
member_path = PurePosixPath(member.name)
if member_path.is_absolute() or ".." in member_path.parts:
raise ValueError(f"unsafe archive member path: {member.name}")
if member.ischr() or member.isblk() or member.isfifo():
raise ValueError(f"unsupported archive member type: {member.name}")
if member.issym() or member.islnk():
link = PurePosixPath(member.linkname)
combined = member_path.parent / link if member.issym() else link
normalized = PurePosixPath(posixpath.normpath(str(combined)))
escapes_root = normalized.parts[:1] == ("..",)
if link.is_absolute() or normalized.is_absolute() or escapes_root:
raise ValueError(
f"unsafe archive link: {member.name} -> {member.linkname}"
)
bundle.extractall(destination)
def install(name: str, spec: dict[str, str], cache: Path, update: bool) -> Path:
downloads = cache / "downloads"
archive = downloads / spec["archive"]
download_file(spec["url"], archive, spec["sha256"], update=update, timeout=120)
destination = cache / "toolchains" / f"{name}-{spec['version']}"
marker = destination / ".complete"
if marker.is_file() and marker.read_text(encoding="utf-8").strip() == spec["sha256"]:
return destination / spec["path"]
destination.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix=f".{destination.name}.", dir=destination.parent) as temp:
extracted = Path(temp) / "content"
extracted.mkdir()
safe_extract(archive, extracted)
atomic_write(extracted / ".complete", spec["sha256"] + "\n")
if destination.exists():
shutil.rmtree(destination)
os.replace(extracted, destination)
return destination / spec["path"]
def main() -> int:
parser = argparse.ArgumentParser(description="Install pinned retroSoC toolchains")
parser.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
parser.add_argument("--platform", default="ubuntu-22.04")
parser.add_argument("--cache", type=Path, required=True)
parser.add_argument("--tool", action="append", required=True)
parser.add_argument("--github-path", type=Path)
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
lock = load_lock(args.lock)
try:
available = lock["toolchains"][args.platform]
except KeyError as error:
raise SystemExit(f"unsupported toolchain platform: {args.platform}") from error
paths: list[Path] = []
for name in args.tool:
if name not in available:
raise SystemExit(f"toolchain is not locked for {args.platform}: {name}")
path = install(name, available[name], args.cache.resolve(), args.update)
paths.append(path)
print(f"{name}: {path}")
if args.github_path:
args.github_path.parent.mkdir(parents=True, exist_ok=True)
with args.github_path.open("a", encoding="utf-8") as stream:
for path in paths:
stream.write(str(path.resolve()) + "\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import platform
import shutil
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import DEFAULT_LOCK, lock_digest, load_lock # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402
TOOLS = {
"python": ("python3", "--version"),
"make": ("make", "--version"),
"riscv_gcc": ("riscv32-unknown-elf-gcc", "--version"),
"iverilog": ("iverilog", "-V"),
"verilator": ("verilator", "--version"),
"yosys": ("yosys", "--version"),
"opensta": ("sta", "-version"),
}
def command_output(command: tuple[str, ...]) -> str | None:
if shutil.which(command[0]) is None:
return None
result = subprocess.run(command, text=True, capture_output=True, check=False)
output = (result.stdout or result.stderr).strip().splitlines()
return output[0] if output else None
def git_info(root: Path) -> dict[str, object]:
def git(*args: str) -> str:
return subprocess.check_output(
["git", "-C", str(root), *args], text=True, stderr=subprocess.DEVNULL
).strip()
try:
return {
"commit": git("rev-parse", "HEAD"),
"branch": git("rev-parse", "--abbrev-ref", "HEAD"),
"dirty": bool(git("status", "--porcelain")),
}
except (subprocess.CalledProcessError, FileNotFoundError):
return {"commit": "unknown", "branch": "unknown", "dirty": None}
def parse_pairs(values: list[str]) -> dict[str, str]:
parsed: dict[str, str] = {}
for item in values:
key, separator, value = item.partition("=")
if not separator or not key or key in parsed:
raise ValueError(f"invalid or duplicate key=value item: {item}")
parsed[key] = value
return dict(sorted(parsed.items()))
def file_hashes(root: Path) -> dict[str, str]:
hashes: dict[str, str] = {}
if not root.exists():
return hashes
for path in sorted(root.rglob("*.fl")):
hashes[str(path.relative_to(root))] = hashlib.sha256(path.read_bytes()).hexdigest()
return hashes
def create_manifest(args: argparse.Namespace) -> dict[str, object]:
root = args.root.resolve()
lock = args.lock.resolve()
load_lock(lock)
variant_root = args.output.resolve().parents[1]
return {
"schema_version": 1,
"generated_at": datetime.now(timezone.utc).isoformat(),
"repository": git_info(root),
"profile": args.profile,
"configuration": parse_pairs(args.config),
"dependency_lock": {
"path": str(lock.relative_to(root)),
"sha256": lock_digest(lock),
},
"host": {
"platform": platform.platform(),
"machine": platform.machine(),
},
"tools": {name: command_output(command) for name, command in TOOLS.items()},
"filelists": file_hashes(variant_root),
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Create a retroSoC build manifest")
subparsers = parser.add_subparsers(dest="command", required=True)
create = subparsers.add_parser("create")
create.add_argument("--root", type=Path, required=True)
create.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
create.add_argument("--output", type=Path, required=True)
create.add_argument("--profile", required=True)
create.add_argument("--config", action="append", default=[])
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
data = create_manifest(args)
except ValueError as error:
raise SystemExit(str(error)) from error
atomic_write(args.output, json.dumps(data, indent=2, sort_keys=True) + "\n")
print(f"manifest: {args.output.resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import re
import statistics
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
AREA_RE = re.compile(r"Chip area for top module[^:]*:\s*([0-9.eE+-]+)")
HIERARCHY_RE = re.compile(r"^\s*(\d+)\s+[0-9.eE+-]+\s+retrosoc_asic\s*$", re.MULTILINE)
def collect(args: argparse.Namespace) -> int:
root = args.variant_root.resolve()
metrics: dict[str, Any] = {
"schema_version": 1,
"policy": "observe",
"firmware": {},
"synthesis": {},
"timing": {},
"flows": {},
}
for binary in sorted((root / "sw").glob("*.bin")):
metrics["firmware"][binary.name] = {"bytes": binary.stat().st_size}
area_json = root / "syn/yosys/rpt/retrosoc_asic_area.json"
area_report = root / "syn/yosys/rpt/retrosoc_asic_area.rpt"
if area_json.is_file():
design = json.loads(area_json.read_text(encoding="utf-8"))["design"]
metrics["synthesis"]["top_area"] = float(design["area"])
metrics["synthesis"]["top_cells"] = int(design["num_cells"])
elif area_report.is_file():
text = area_report.read_text(encoding="utf-8", errors="replace")
areas = AREA_RE.findall(text)
hierarchy = HIERARCHY_RE.findall(text)
if areas:
metrics["synthesis"]["top_area"] = float(areas[-1])
if hierarchy:
metrics["synthesis"]["top_cells"] = int(hierarchy[-1])
timing_report = root / "sta/opensta/timing_metrics.rpt"
if timing_report.is_file():
labels = ("wns_min", "wns_max", "tns_min", "tns_max")
timing_text = timing_report.read_text(encoding="utf-8", errors="replace")
for line in timing_text.splitlines():
label, separator, value = line.partition("=")
numbers = re.findall(r"[-+]?(?:\d+\.\d+|\d+)", value)
if separator and label in labels and numbers:
metrics["timing"][label] = float(numbers[-1])
if not metrics["timing"]:
values = [
float(value)
for value in re.findall(r"[-+]?(?:\d+\.\d+|\d+)", timing_text)
]
metrics["timing"].update(dict(zip(labels, values[-4:])))
for result in sorted(root.glob("**/result*.json")):
try:
data = json.loads(result.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
continue
metrics["flows"][str(result.relative_to(root))] = {
"status": data.get("status"),
"duration_seconds": data.get("duration_seconds"),
}
atomic_write(args.output, json.dumps(metrics, indent=2, sort_keys=True) + "\n")
print(f"metrics: {args.output.resolve()}")
return 0
def numeric_values(value: Any, prefix: str = "") -> dict[str, float]:
output: dict[str, float] = {}
if isinstance(value, dict):
for key, child in value.items():
path = f"{prefix}.{key}" if prefix else key
output.update(numeric_values(child, path))
elif isinstance(value, (int, float)) and not isinstance(value, bool):
output[prefix] = float(value)
return output
def baseline(args: argparse.Namespace) -> int:
if len(args.input) != 10:
raise SystemExit("exactly 10 --input metrics files are required")
samples = [numeric_values(json.loads(path.read_text(encoding="utf-8"))) for path in args.input]
common = set.intersection(*(set(sample) for sample in samples))
medians = {key: statistics.median(sample[key] for sample in samples) for key in sorted(common)}
data = {"schema_version": 1, "sample_count": 10, "values": medians}
atomic_write(args.output, json.dumps(data, indent=2, sort_keys=True) + "\n")
print(f"metrics baseline: {args.output.resolve()}")
return 0
def check(args: argparse.Namespace) -> int:
policy = json.loads(args.policy.read_text(encoding="utf-8"))
current = numeric_values(json.loads(args.metrics.read_text(encoding="utf-8")))
mode = policy.get("mode")
if mode == "observe":
print("metrics policy: observe")
return 0
if mode != "gate":
raise SystemExit(f"unsupported metrics policy mode: {mode}")
if args.baseline is None or not args.baseline.is_file():
raise SystemExit("a metrics baseline is required when policy mode is gate")
baseline_data = json.loads(args.baseline.read_text(encoding="utf-8"))["values"]
failures: list[str] = []
for key, maximum_growth in policy["maximum_growth"].items():
if key in current and key in baseline_data and current[key] > baseline_data[key] * (1 + maximum_growth):
failures.append(key)
wns_key = "timing.wns_max"
if wns_key in current and wns_key in baseline_data:
if current[wns_key] < baseline_data[wns_key] - policy["maximum_wns_regression"]:
failures.append(wns_key)
print("metrics check: " + ("failed: " + ", ".join(failures) if failures else "passed"))
return int(bool(failures))
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Collect and compare retroSoC metrics")
subparsers = parser.add_subparsers(dest="command", required=True)
collect_parser = subparsers.add_parser("collect")
collect_parser.add_argument("--variant-root", type=Path, required=True)
collect_parser.add_argument("--output", type=Path, required=True)
baseline_parser = subparsers.add_parser("baseline")
baseline_parser.add_argument("--input", type=Path, action="append", required=True)
baseline_parser.add_argument("--output", type=Path, required=True)
check_parser = subparsers.add_parser("check")
check_parser.add_argument("--metrics", type=Path, required=True)
check_parser.add_argument("--baseline", type=Path)
check_parser.add_argument("--policy", type=Path, required=True)
return parser.parse_args()
def main() -> int:
args = parse_args()
return {"collect": collect, "baseline": baseline, "check": check}[args.command](args)
if __name__ == "__main__":
raise SystemExit(main())
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import DEFAULT_LOCK, load_lock # noqa: E402
def component(name: str, kind: str, spec: dict[str, object]) -> dict[str, object]:
license_name = str(spec.get("license", "NOASSERTION"))
license_value = (
{"name": license_name}
if license_name == "NOASSERTION" or license_name.startswith("LicenseRef-")
else {"id": license_name}
)
item: dict[str, object] = {
"type": "library" if kind != "toolchain" else "application",
"name": name,
"licenses": [{"license": license_value}],
"externalReferences": [{"type": "distribution", "url": spec["url"]}],
}
if "revision" in spec:
item["version"] = spec["revision"]
item["properties"] = [{"name": "vcs:revision", "value": spec["revision"]}]
else:
item["version"] = spec.get("version", spec.get("sha256", "unknown"))
item["hashes"] = [{"alg": "SHA-256", "content": spec["sha256"]}]
return item
def make_sbom(lock: dict[str, object]) -> dict[str, object]:
components = [component(name, "source", spec) for name, spec in lock["sources"].items()]
components.extend(component(name, "archive", spec) for name, spec in lock["archives"].items())
for platform, tools in lock["toolchains"].items():
components.extend(component(f"{platform}/{name}", "toolchain", spec) for name, spec in tools.items())
return {
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"components": components,
}
def main() -> int:
parser = argparse.ArgumentParser(description="Create retroSoC source deliverables")
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
parser.add_argument("--variant-root", type=Path, required=True)
parser.add_argument("--output-dir", type=Path, required=True)
args = parser.parse_args()
root = args.root.resolve()
manifest_path = args.variant_root.resolve() / "meta/manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
config = manifest["configuration"]
lock = load_lock(args.lock)
args.output_dir.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="retrosoc-package-", dir=args.output_dir.parent) as temp:
staging = Path(temp) / "package"
staging.mkdir()
command = [
"python3", str(root / "syn/tools/export_soc_sources.py"), "sv",
"--output-dir", str(staging), "--pdk", config["PDK"],
"--core", config["CORE"], "--ip", config["IP"], "--simu", config["SIMU"],
"--dynamic-core-filelist",
str(args.variant_root / "generated/mpw" / config["SIMU"].lower() / "core/core.fl"),
"--dynamic-ip-filelist",
str(args.variant_root / "generated/mpw" / config["SIMU"].lower() / "ip/ip.fl"),
]
for key, flag in (("HAVE_PLL", "--have-pll"), ("HAVE_SRAM_IF", "--have-sram-if"), ("HAVE_SRAM_MACRO", "--have-sram-macro"), ("HAVE_SVA", "--have-sva")):
if config.get(key) == "YES":
command.append(flag)
subprocess.run(command, cwd=root, check=True)
command[2] = "tar"
subprocess.run(command, cwd=root, check=True)
shutil.rmtree(staging / "rtl")
shutil.copy2(args.lock, staging / "dependencies.lock.json")
shutil.copy2(manifest_path, staging / "manifest.json")
(staging / "sbom.cdx.json").write_text(json.dumps(make_sbom(lock), indent=2, sort_keys=True) + "\n", encoding="utf-8")
checksums = []
for path in sorted(item for item in staging.iterdir() if item.is_file()):
checksums.append(f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}")
(staging / "SHA256SUMS").write_text("\n".join(checksums) + "\n", encoding="utf-8")
if args.output_dir.exists():
shutil.rmtree(args.output_dir)
shutil.copytree(staging, args.output_dir)
print(f"package: {args.output_dir.resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+26 -20
View File
@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import fcntl
import hashlib import hashlib
import shutil import shutil
import sys import sys
@@ -11,15 +12,12 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import archive, source # noqa: E402
from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402 from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402
MPW_DIR = ROOT / "rtl/mini/mpw" MPW_DIR = ROOT / "rtl/mini/mpw"
HAZARD3_REVISION = "5b3a34f6955e50e03bdcb964201c91462f7078c3"
IBEX_REVISION = "0c233f54361d769f370889223acc456f2ac19d46"
SERV_ARCHIVE = MPW_DIR / "serv-1.4.0.tar.gz" SERV_ARCHIVE = MPW_DIR / "serv-1.4.0.tar.gz"
SERV_URL = "https://github.com/olofk/serv/archive/refs/tags/1.4.0.tar.gz"
SERV_SHA256 = "f81c37b9f9d548c658e23bb7eb90fbed8b54e2e9e51fa42b8cd2f34cecc472ab"
def tree_digest(root: Path) -> str: def tree_digest(root: Path) -> str:
@@ -55,30 +53,24 @@ def extract_serv() -> Path:
return extracted return extracted
def main() -> int: def prepare(update: bool) -> None:
parser = argparse.ArgumentParser(description="Prepare pinned MPW core sources")
parser.add_argument("--update", action="store_true")
args = parser.parse_args()
if not (MPW_DIR / ".git").is_dir(): if not (MPW_DIR / ".git").is_dir():
raise SystemExit("MPW generator is missing; run 'python3 setup.py' first") raise SystemExit("MPW generator is missing; run 'python3 setup.py' first")
hazard3 = source("hazard3")
ensure_git_repo( ensure_git_repo(
"https://github.com/Wren6991/Hazard3.git", hazard3["url"], ROOT / hazard3["destination"], hazard3["revision"],
MPW_DIR / "Hazard3", update=update,
HAZARD3_REVISION,
update=args.update,
) )
ibex = source("ibex")
ensure_git_repo( ensure_git_repo(
"https://github.com/lowRISC/ibex.git", ibex["url"], ROOT / ibex["destination"], ibex["revision"],
MPW_DIR / "ibex", update=update,
IBEX_REVISION,
update=args.update,
) )
serv_archive = archive("serv")
download_file( download_file(
SERV_URL, serv_archive["url"], ROOT / serv_archive["destination"],
SERV_ARCHIVE, serv_archive["sha256"], update=update,
SERV_SHA256,
update=args.update,
) )
serv = extract_serv() serv = extract_serv()
@@ -87,6 +79,20 @@ def main() -> int:
sync_tree(MPW_DIR / "ibex/rtl", MPW_DIR / "core/username7/ibex") sync_tree(MPW_DIR / "ibex/rtl", MPW_DIR / "core/username7/ibex")
(MPW_DIR / ".build").mkdir(exist_ok=True) (MPW_DIR / ".build").mkdir(exist_ok=True)
print("pinned MPW core sources are ready") print("pinned MPW core sources are ready")
def main() -> int:
parser = argparse.ArgumentParser(description="Prepare pinned MPW core sources")
parser.add_argument("--update", action="store_true")
parser.add_argument("--lock-file", type=Path)
args = parser.parse_args()
if args.lock_file is None:
prepare(args.update)
return 0
args.lock_file.parent.mkdir(parents=True, exist_ok=True)
with args.lock_file.open("a+", encoding="utf-8") as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
prepare(args.update)
return 0 return 0
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import subprocess
from pathlib import Path
PR_COMMANDS = (
("configs/ci/hazard3-rv32im-ihp130.mk", ("firmware",)),
("configs/ci/mdd-rv32im-ihp130.mk", ("SIMU=VERILATOR", "firmware", "sim")),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=VERILATOR", "sim")),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm")),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SYNTH=YOSYS", "synth")),
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=IVERILOG", "SIM_FIRMWARE_NAME=retrosoc_asm", "SIM_SUCCESS_MARKER=Mem wr/rd test success", "RTL_SIM_TIMEOUT=5200000", "netsim")),
("configs/ci/hazard3-rv32im-ihp130.mk", ("STA=OPENSTA", "sta")),
)
NIGHTLY_COMMANDS = PR_COMMANDS + (
("configs/nightly/picorv32-rv32im-ihp130.mk", ("SIMU=VERILATOR", "firmware", "sim")),
("configs/nightly/picorv32-rv32im-ihp130.mk", ("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm")),
)
PR_PROFILES = (
"configs/ci/hazard3-rv32im-ihp130.mk",
"configs/ci/mdd-rv32im-ihp130.mk",
)
NIGHTLY_PROFILES = PR_PROFILES + ("configs/nightly/picorv32-rv32im-ihp130.mk",)
def main() -> int:
parser = argparse.ArgumentParser(description="Run a supported retroSoC regression suite")
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--suite", choices=("pr", "nightly"), required=True)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
commands = PR_COMMANDS if args.suite == "pr" else NIGHTLY_COMMANDS
profiles = PR_PROFILES if args.suite == "pr" else NIGHTLY_PROFILES
for profile, values in commands:
command = ["make", f"CONFIG={profile}", *values]
print("+ " + " ".join(command), flush=True)
if not args.dry_run:
subprocess.run(command, cwd=args.root, check=True)
for profile in profiles:
command = [
"make",
f"CONFIG={profile}",
"check-warnings",
"check-metrics",
]
print("+ " + " ".join(command), flush=True)
if not args.dry_run:
subprocess.run(command, cwd=args.root, check=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.setup_helpers import atomic_write # noqa: E402
def parse_env(values: list[str]) -> dict[str, str]:
environment = os.environ.copy()
for item in values:
name, separator, value = item.partition("=")
if not separator or not name:
raise ValueError(f"invalid environment assignment: {item}")
environment[name] = value
return environment
def should_display(tool: str, line: str) -> bool:
if tool != "yosys":
return True
keywords = ("Executing", "Warning", "Error", "Build succeeded", "End of script")
return any(keyword in line for keyword in keywords)
def jobserver_fds() -> tuple[int, ...]:
match = re.search(r"--jobserver-(?:auth|fds)=(\d+),(\d+)", os.environ.get("MAKEFLAGS", ""))
if not match:
return ()
descriptors = tuple(int(value) for value in match.groups())
try:
for descriptor in descriptors:
os.fstat(descriptor)
except OSError:
return ()
return descriptors
def main() -> int:
parser = argparse.ArgumentParser(description="Run a flow command with structured results")
parser.add_argument("--tool", required=True)
parser.add_argument("--log", type=Path, required=True)
parser.add_argument("--result", type=Path, required=True)
parser.add_argument("--cwd", type=Path)
parser.add_argument("--env", action="append", default=[])
parser.add_argument("command", nargs=argparse.REMAINDER)
args = parser.parse_args()
command = args.command[1:] if args.command[:1] == ["--"] else args.command
if not command:
parser.error("a command is required after --")
try:
environment = parse_env(args.env)
except ValueError as error:
parser.error(str(error))
args.log.parent.mkdir(parents=True, exist_ok=True)
args.result.parent.mkdir(parents=True, exist_ok=True)
started = datetime.now(timezone.utc)
start_clock = time.monotonic()
returncode = 127
error_message = None
process: subprocess.Popen[str] | None = None
try:
with args.log.open("w", encoding="utf-8", errors="replace") as log:
process = subprocess.Popen(
command,
cwd=args.cwd,
env=environment,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
errors="replace",
bufsize=1,
pass_fds=jobserver_fds(),
)
assert process.stdout is not None
for line in process.stdout:
log.write(line)
log.flush()
if should_display(args.tool, line):
sys.stdout.write(line)
sys.stdout.flush()
returncode = process.wait()
except KeyboardInterrupt:
error_message = "interrupted"
if process is not None and process.poll() is None:
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
returncode = 130
except (OSError, subprocess.SubprocessError) as error:
error_message = str(error)
finished = datetime.now(timezone.utc)
result = {
"schema_version": 1,
"tool": args.tool,
"command": command,
"cwd": str(args.cwd.resolve()) if args.cwd else os.getcwd(),
"started_at": started.isoformat(),
"finished_at": finished.isoformat(),
"duration_seconds": round(time.monotonic() - start_clock, 3),
"exit_code": returncode,
"status": "passed" if returncode == 0 else "failed",
"log": str(args.log.resolve()),
}
if error_message:
result["error"] = error_message
atomic_write(args.result, json.dumps(result, indent=2, sort_keys=True) + "\n")
return returncode
if __name__ == "__main__":
raise SystemExit(main())
+9 -7
View File
@@ -41,11 +41,10 @@ def ensure_git_repo(
created = False created = False
if not destination.exists(): if not destination.exists():
destination.parent.mkdir(parents=True, exist_ok=True) destination.parent.mkdir(parents=True, exist_ok=True)
command = ["git", "clone"] run(("git", "init", str(destination)))
if recursive: run(("git", "remote", "add", "origin", url), cwd=destination)
command.append("--recursive") run(("git", "fetch", "--depth", "1", "origin", revision), cwd=destination)
command.extend((url, str(destination))) run(("git", "checkout", "--detach", "FETCH_HEAD"), cwd=destination)
run(command)
created = True created = True
elif not (destination / ".git").exists(): elif not (destination / ".git").exists():
raise RuntimeError(f"dependency path is not a Git repository: {destination}") raise RuntimeError(f"dependency path is not a Git repository: {destination}")
@@ -62,13 +61,16 @@ def ensure_git_repo(
try: try:
git_output(destination, "cat-file", "-e", f"{revision}^{{commit}}") git_output(destination, "cat-file", "-e", f"{revision}^{{commit}}")
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
run(("git", "fetch", "origin", revision), cwd=destination) run(("git", "fetch", "--depth", "1", "origin", revision), cwd=destination)
run(("git", "checkout", "--detach", revision), cwd=destination) run(("git", "checkout", "--detach", revision), cwd=destination)
elif dirty and not allow_dirty: elif dirty and not allow_dirty:
raise RuntimeError(f"pinned dependency has local changes: {destination}") raise RuntimeError(f"pinned dependency has local changes: {destination}")
if recursive: if recursive:
run(("git", "submodule", "update", "--init", "--recursive"), cwd=destination) run(
("git", "submodule", "update", "--init", "--recursive", "--depth", "1"),
cwd=destination,
)
print(f"[dependency] {destination.name}: {revision}") print(f"[dependency] {destination.name}: {revision}")
+4 -5
View File
@@ -3,22 +3,21 @@
import argparse import argparse
from pathlib import Path from pathlib import Path
from scripts.dependency_lock import source
from scripts.setup_helpers import ensure_git_repo from scripts.setup_helpers import ensure_git_repo
ROOT = Path(__file__).resolve().parent ROOT = Path(__file__).resolve().parent
MPW_REVISION = "30503eb8a7cf33a0c955913faba5014ebd00186f"
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser(description="Install the pinned MPW generator") parser = argparse.ArgumentParser(description="Install the pinned MPW generator")
parser.add_argument("--update", action="store_true") parser.add_argument("--update", action="store_true")
args = parser.parse_args() args = parser.parse_args()
dependency = source("mpw")
ensure_git_repo( ensure_git_repo(
"https://github.com/retroSoC/mini-ver-mpw.git", dependency["url"], ROOT / dependency["destination"], dependency["revision"],
ROOT / "rtl/mini/mpw", recursive=dependency.get("recursive", False), update=args.update,
MPW_REVISION,
update=args.update,
) )
return 0 return 0
+16 -7
View File
@@ -1,14 +1,18 @@
OPENSTA ?= sta OPENSTA ?= sta
OPENSTA_NETLIST ?= $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.v OPENSTA_THREADS ?= $(JOBS)
OPENSTA_NETLIST ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.v
OPENSTA_LIBERTY ?= $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/lib/sg13g2_stdcell_typ_1p20V_25C.lib OPENSTA_LIBERTY ?= $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_stdcell/lib/sg13g2_stdcell_typ_1p20V_25C.lib
OPENSTA_IO_LIB ?= $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/lib/sg13g2_io_typ_1p2V_3p3V_25C.lib OPENSTA_IO_LIB ?= $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_io/lib/sg13g2_io_typ_1p2V_3p3V_25C.lib
OPENSTA_SRAM_LIBS ?= $(wildcard $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/lib/*_typ_1p20V_25C.lib) OPENSTA_SRAM_LIBS ?= $(wildcard $(ROOT_PATH)/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/lib/*_typ_1p20V_25C.lib)
OPENSTA_SDC ?= $(ROOT_PATH)/sta/opensta/gen2.sdc OPENSTA_SDC ?= $(ROOT_PATH)/sta/opensta/gen2.sdc
OPENSTA_REPORT ?= $(ROOT_PATH)/sta/opensta/retrosoc_sta.log OPENSTA_REPORT ?= $(STA_BUILD_ROOT)/retrosoc_sta.log
OPENSTA_CONFIG ?= $(ROOT_PATH)/syn/yosys/.synth_build/out/retrosoc_asic_yosys.config OPENSTA_LOG ?= $(STA_BUILD_ROOT)/opensta.log
OPENSTA_METRICS ?= $(STA_BUILD_ROOT)/timing_metrics.rpt
OPENSTA_CONFIG ?= $(SYN_BUILD_ROOT)/out/retrosoc_asic_yosys.config
sta: sta:
@mkdir -p $(STA_BUILD_ROOT)
@for input in $(OPENSTA_NETLIST) $(OPENSTA_LIBERTY) $(OPENSTA_IO_LIB) $(OPENSTA_SRAM_LIBS) $(OPENSTA_SDC) $(OPENSTA_CONFIG); do \ @for input in $(OPENSTA_NETLIST) $(OPENSTA_LIBERTY) $(OPENSTA_IO_LIB) $(OPENSTA_SRAM_LIBS) $(OPENSTA_SDC) $(OPENSTA_CONFIG); do \
test -f "$$input" || { echo "OpenSTA input missing: $$input" >&2; exit 1; }; \ test -f "$$input" || { echo "OpenSTA input missing: $$input" >&2; exit 1; }; \
done done
@@ -16,9 +20,14 @@ sta:
echo "OpenSTA netlist configuration does not match PDK=$(PDK); rerun synthesis" >&2; \ echo "OpenSTA netlist configuration does not match PDK=$(PDK); rerun synthesis" >&2; \
exit 1; \ exit 1; \
} }
OPENSTA_NETLIST=$(OPENSTA_NETLIST) OPENSTA_LIBERTY=$(OPENSTA_LIBERTY) \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool opensta --log $(OPENSTA_LOG) \
OPENSTA_IO_LIB=$(OPENSTA_IO_LIB) OPENSTA_SRAM_LIBS="$(OPENSTA_SRAM_LIBS)" OPENSTA_SDC=$(OPENSTA_SDC) \ --result $(STA_BUILD_ROOT)/result-sta.json \
OPENSTA_REPORT=$(OPENSTA_REPORT) \ --env OPENSTA_NETLIST=$(OPENSTA_NETLIST) --env OPENSTA_LIBERTY=$(OPENSTA_LIBERTY) \
$(OPENSTA) $(ROOT_PATH)/sta/opensta/opensta.tcl -threads max --env OPENSTA_IO_LIB=$(OPENSTA_IO_LIB) --env 'OPENSTA_SRAM_LIBS=$(OPENSTA_SRAM_LIBS)' \
--env OPENSTA_SDC=$(OPENSTA_SDC) --env OPENSTA_REPORT=$(OPENSTA_REPORT) \
--env OPENSTA_METRICS=$(OPENSTA_METRICS) -- \
$(OPENSTA) $(ROOT_PATH)/sta/opensta/opensta.tcl -threads $(OPENSTA_THREADS)
sta: | manifest
.PHONY: sta .PHONY: sta
+28 -1
View File
@@ -1,4 +1,4 @@
foreach variable {OPENSTA_NETLIST OPENSTA_LIBERTY OPENSTA_IO_LIB OPENSTA_SRAM_LIBS OPENSTA_SDC OPENSTA_REPORT} { foreach variable {OPENSTA_NETLIST OPENSTA_LIBERTY OPENSTA_IO_LIB OPENSTA_SRAM_LIBS OPENSTA_SDC OPENSTA_REPORT OPENSTA_METRICS} {
if {![info exists ::env($variable)] || $::env($variable) eq ""} { if {![info exists ::env($variable)] || $::env($variable) eq ""} {
error "required environment variable is missing: $variable" error "required environment variable is missing: $variable"
} }
@@ -15,6 +15,33 @@ read_sdc $::env(OPENSTA_SDC)
report_checks -path_delay min_max -path_group {clk_ext clk_aud } -sort_by_slack -slack_max 0.0 -group_path_count 1000 -endpoint_path_count 1000 > $::env(OPENSTA_REPORT) report_checks -path_delay min_max -path_group {clk_ext clk_aud } -sort_by_slack -slack_max 0.0 -group_path_count 1000 -endpoint_path_count 1000 > $::env(OPENSTA_REPORT)
set metrics_file $::env(OPENSTA_METRICS)
set metrics_tmp "${metrics_file}.tmp"
set metrics [open $metrics_file "w"]
report_wns -min > $metrics_tmp
set input [open $metrics_tmp "r"]
puts $metrics "wns_min=[string trim [read $input]]"
close $input
report_wns -max > $metrics_tmp
set input [open $metrics_tmp "r"]
puts $metrics "wns_max=[string trim [read $input]]"
close $input
report_tns -min > $metrics_tmp
set input [open $metrics_tmp "r"]
puts $metrics "tns_min=[string trim [read $input]]"
close $input
report_tns -max > $metrics_tmp
set input [open $metrics_tmp "r"]
puts $metrics "tns_max=[string trim [read $input]]"
close $input
close $metrics
file delete -force $metrics_tmp
# set_propagated_clock clk # set_propagated_clock clk
# read_spef gcd_sky130hd.spef # read_spef gcd_sky130hd.spef
# set_power_activity -input -activity .1 # set_power_activity -input -activity .1
+20 -4
View File
@@ -54,14 +54,20 @@ def configured_filelist(args: argparse.Namespace, generated_dir: Path) -> FileLi
if args.core in ("HAZARD3", "PICORV32"): if args.core in ("HAZARD3", "PICORV32"):
names.append(f"core_{args.core.lower()}.fl") names.append(f"core_{args.core.lower()}.fl")
else: else:
dynamic_core = REPO_ROOT / "rtl/mini/mpw/.build/core/core.fl" dynamic_core = args.dynamic_core_filelist
if not dynamic_core.is_file(): if not dynamic_core.is_file():
raise FileNotFoundError("MDD core is not generated; run 'make CORE=MDD gen_mpw_code'") raise FileNotFoundError(
f"MDD core filelist is not generated: {dynamic_core}; "
"run 'make CORE=MDD gen_mpw_code'"
)
names.append(str(dynamic_core)) names.append(str(dynamic_core))
if args.ip == "MDD": if args.ip == "MDD":
dynamic_ip = REPO_ROOT / "rtl/mini/mpw/.build/ip/ip.fl" dynamic_ip = args.dynamic_ip_filelist
if not dynamic_ip.is_file(): if not dynamic_ip.is_file():
raise FileNotFoundError("MDD IP is not generated; run 'make IP=MDD gen_mpw_code'") raise FileNotFoundError(
f"MDD IP filelist is not generated: {dynamic_ip}; "
"run 'make IP=MDD gen_mpw_code'"
)
names.append(str(dynamic_ip)) names.append(str(dynamic_ip))
names.append("top.fl") names.append("top.fl")
paths = [Path(name) if Path(name).is_absolute() else generated_dir / name for name in names] paths = [Path(name) if Path(name).is_absolute() else generated_dir / name for name in names]
@@ -181,6 +187,16 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--have-sram-if", action="store_true") parser.add_argument("--have-sram-if", action="store_true")
parser.add_argument("--have-sram-macro", action="store_true") parser.add_argument("--have-sram-macro", action="store_true")
parser.add_argument("--have-sva", action="store_true") parser.add_argument("--have-sva", action="store_true")
parser.add_argument(
"--dynamic-core-filelist",
type=Path,
default=REPO_ROOT / "rtl/mini/mpw/.build/core/core.fl",
)
parser.add_argument(
"--dynamic-ip-filelist",
type=Path,
default=REPO_ROOT / "rtl/mini/mpw/.build/ip/ip.fl",
)
parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "export") parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "export")
return parser.parse_args() return parser.parse_args()
+1
View File
@@ -171,6 +171,7 @@ yosys hilomap -singleton -hicell {*}$tech_cell_tiehi -locell {*}$tech_cell_tielo
# final reports # final reports
yosys tee -q -o "${report_dir}/${proj_name}_synth.rpt" check yosys tee -q -o "${report_dir}/${proj_name}_synth.rpt" check
yosys tee -q -o "${report_dir}/${proj_name}_area.rpt" stat -top $top_design {*}$liberty_args yosys tee -q -o "${report_dir}/${proj_name}_area.rpt" stat -top $top_design {*}$liberty_args
yosys tee -q -o "${report_dir}/${proj_name}_area.json" stat -json -top $top_design {*}$liberty_args
yosys tee -q -o "${report_dir}/${proj_name}_area_logic.rpt" stat -top $top_design {*}$tech_cells_args yosys tee -q -o "${report_dir}/${proj_name}_area_logic.rpt" stat -top $top_design {*}$tech_cells_args
# final netlist # final netlist
+22 -16
View File
@@ -20,7 +20,7 @@
# Directories # Directories
# directory of the path to the last called Makefile (this one) # directory of the path to the last called Makefile (this one)
YOSYS_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))) YOSYS_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST)))))
YOSYS_BUILD := $(YOSYS_DIR)/.synth_build YOSYS_BUILD := $(SYN_BUILD_ROOT)
YOSYS_OUT := $(YOSYS_BUILD)/out YOSYS_OUT := $(YOSYS_BUILD)/out
YOSYS_TMP := $(YOSYS_BUILD)/tmp YOSYS_TMP := $(YOSYS_BUILD)/tmp
YOSYS_RPT := $(YOSYS_BUILD)/rpt YOSYS_RPT := $(YOSYS_BUILD)/rpt
@@ -29,38 +29,44 @@ include $(YOSYS_DIR)/synth_config.mk
TOP_DESIGN ?= retrosoc_asic TOP_DESIGN ?= retrosoc_asic
RTL_NAME ?= retrosoc_asic RTL_NAME ?= retrosoc_asic
SV_FLIST := $(RTL_PATH)/.generated_fl/yosys.fl SV_FLIST := $(GENERATED_FL_DIR)/yosys.fl
NETLIST := $(YOSYS_OUT)/$(RTL_NAME)_yosys.v NETLIST := $(YOSYS_OUT)/$(RTL_NAME)_yosys.v
NETLIST_DEBUG := $(YOSYS_OUT)/$(RTL_NAME)_yosys_debug.v NETLIST_DEBUG := $(YOSYS_OUT)/$(RTL_NAME)_yosys_debug.v
NETLIST_CONFIG := $(YOSYS_OUT)/$(RTL_NAME)_yosys.config NETLIST_CONFIG := $(YOSYS_OUT)/$(RTL_NAME)_yosys.config
YOSYS_DEPFILE := $(YOSYS_BUILD)/yosys.d
YOSYS_SCRIPTS := $(wildcard $(YOSYS_DIR)/script/*) $(YOSYS_DIR)/synth_config.mk $(YOSYS_DIR)/yosys.mk
gen_synth_filelist: gen_mpw_code generate_filelist -include $(YOSYS_DEPFILE)
$(SV_FLIST): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP)
@python3 $(RTL_PATH)/script/comb.py $(RTL_FLIST) --output $(SV_FLIST) @python3 $(RTL_PATH)/script/comb.py $(RTL_FLIST) --output $(SV_FLIST)
@python3 $(RTL_PATH)/script/filelist_deps.py $(RTL_FLIST) --target $(NETLIST) \
--output $(YOSYS_DEPFILE)
gen_synth_filelist: $(SV_FLIST)
## Synthesize netlist using Yosys ## Synthesize netlist using Yosys
synth: $(NETLIST) synth: $(NETLIST)
$(NETLIST): gen_synth_filelist $(NETLIST): $(SV_FLIST) $(YOSYS_SCRIPTS)
@mkdir -p $(YOSYS_OUT) @mkdir -p $(YOSYS_OUT)
@mkdir -p $(YOSYS_TMP) @mkdir -p $(YOSYS_TMP)
@mkdir -p $(YOSYS_RPT) @mkdir -p $(YOSYS_RPT)
export PDK="$(PDK)" SOC="$(SOC)" CORE="$(CORE)" IP="$(IP)" \ python3 $(ROOT_PATH)/scripts/run_flow.py --tool yosys \
SV_FLIST="$(SV_FLIST)" TOP_DESIGN="$(TOP_DESIGN)" CONFIG="$(NETLIST_CONFIG)" \ --log $(YOSYS_BUILD)/$(RTL_NAME).log --result $(YOSYS_BUILD)/result-synth.json \
PROJ_NAME="$(RTL_NAME)" WORK="$(YOSYS_TMP)" BUILD="$(YOSYS_OUT)" \ --env PDK=$(PDK) --env SOC=$(SOC) --env CORE=$(CORE) --env IP=$(IP) \
REPORTS="$(YOSYS_RPT)" NETLIST="$(NETLIST)"; \ --env SV_FLIST=$(SV_FLIST) --env TOP_DESIGN=$(TOP_DESIGN) --env CONFIG=$(NETLIST_CONFIG) \
set -o pipefail; yosys -c $(YOSYS_DIR)/script/synth.tcl \ --env PROJ_NAME=$(RTL_NAME) --env WORK=$(YOSYS_TMP) --env BUILD=$(YOSYS_OUT) \
2>&1 | TZ=UTC-8 gawk '{ print strftime("[%Y-%m-%d %H:%M %Z]"), $$0 }' \ --env REPORTS=$(YOSYS_RPT) --env NETLIST=$(NETLIST) -- \
| tee "$(YOSYS_BUILD)/$(RTL_NAME).log" \ yosys -c $(YOSYS_DIR)/script/synth.tcl
| gawk -f $(YOSYS_DIR)/script/filter_output.awk
$(NETLIST_DEBUG): $(NETLIST) $(NETLIST_DEBUG): $(NETLIST)
@test -f $@ @test -f $@
synth_clean: synth_clean:
rm -rf $(YOSYS_OUT) python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(YOSYS_BUILD)
rm -rf $(YOSYS_TMP)
rm -rf $(YOSYS_RPT)
rm -f $(YOSYS_BUILD)/$(RTL_NAME).log
.PHONY: gen_synth_filelist synth_clean synth .PHONY: gen_synth_filelist synth_clean synth
synth: | manifest
+367 -5
View File
@@ -1,8 +1,14 @@
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
import io
import json
import re
import signal
import subprocess import subprocess
import sys import sys
import tarfile
import time
from pathlib import Path from pathlib import Path
@@ -11,8 +17,11 @@ FILELIST_SCRIPT_DIR = ROOT / "rtl/mini/script"
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(FILELIST_SCRIPT_DIR)) sys.path.insert(0, str(FILELIST_SCRIPT_DIR))
from filelist import FileList, atomic_write, parse_filelists, write_filelist # noqa: E402 from filelist import atomic_write, parse_filelists, write_filelist # noqa: E402
from generate_filelist import generate_all # noqa: E402 from generate_filelist import generate_all # noqa: E402
from scripts.analyze_warnings import normalize # noqa: E402
from scripts.dependency_lock import LockError, load_lock # noqa: E402
from scripts.install_toolchain import safe_extract # noqa: E402
from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402 from scripts.setup_helpers import download_file, ensure_git_repo # noqa: E402
@@ -145,12 +154,28 @@ def test_dependency_helpers_are_idempotent(tmp_path: Path) -> None:
def test_make_dry_run_and_validation_do_not_write_filelists() -> None: def test_make_dry_run_and_validation_do_not_write_filelists() -> None:
generated_def = ROOT / "rtl/mini/.generated_fl/def.fl" def build_state() -> dict[str, tuple[int, int]]:
before = generated_def.stat().st_mtime_ns if generated_def.exists() else None build = ROOT / "build"
if not build.exists():
return {}
return {
str(path.relative_to(build)): (path.stat().st_size, path.stat().st_mtime_ns)
for path in build.rglob("*")
if path.is_file()
}
before = build_state()
run("make", "-n", "help") run("make", "-n", "help")
run("make", "-n", "SIMU=IVERILOG", "comp") run("make", "-n", "SIMU=IVERILOG", "comp")
after = generated_def.stat().st_mtime_ns if generated_def.exists() else None run(
assert after == before "make",
"-n",
"CONFIG=configs/ci/hazard3-rv32im-ihp130.mk",
"SIMU=IVERILOG",
"RTL_SIM_TIMEOUT=5200000",
"sim-asm",
)
assert build_state() == before
invalid = subprocess.run( invalid = subprocess.run(
["make", "SIMU=UNKNOWN", "help"], ["make", "SIMU=UNKNOWN", "help"],
@@ -162,6 +187,312 @@ def test_make_dry_run_and_validation_do_not_write_filelists() -> None:
assert "Invalid SIMU='UNKNOWN'" in invalid.stderr assert "Invalid SIMU='UNKNOWN'" in invalid.stderr
def test_dependency_lock_and_config_key_are_deterministic(tmp_path: Path) -> None:
lock = load_lock(ROOT / "config/dependencies.lock.json")
assert lock["schema_version"] == 1
assert len(lock["sources"]["mpw"]["revision"]) == 40
command = (
sys.executable,
str(ROOT / "scripts/config_key.py"),
"--lock",
str(ROOT / "config/dependencies.lock.json"),
"--profile",
"unit",
"--value",
"CORE=HAZARD3",
"--value",
"PDK=IHP130",
)
first = run(*command).stdout.strip()
second = run(*command).stdout.strip()
assert first == second
assert first.startswith("unit-")
broken = tmp_path / "broken.json"
broken.write_text('{"schema_version": 1}\n', encoding="utf-8")
try:
load_lock(broken)
except LockError as error:
assert "missing or empty" in str(error)
else:
raise AssertionError("invalid dependency lock was accepted")
def test_run_flow_writes_structured_result(tmp_path: Path) -> None:
log = tmp_path / "flow.log"
result = tmp_path / "result.json"
run(
sys.executable,
str(ROOT / "scripts/run_flow.py"),
"--tool",
"unit",
"--log",
str(log),
"--result",
str(result),
"--",
sys.executable,
"-c",
"print('flow output')",
)
data = json.loads(result.read_text(encoding="utf-8"))
assert data["status"] == "passed"
assert data["exit_code"] == 0
assert data["duration_seconds"] >= 0
assert log.read_text(encoding="utf-8") == "flow output\n"
def test_run_flow_records_interruption(tmp_path: Path) -> None:
log = tmp_path / "interrupted.log"
result = tmp_path / "interrupted.json"
process = subprocess.Popen(
[
sys.executable,
str(ROOT / "scripts/run_flow.py"),
"--tool",
"unit",
"--log",
str(log),
"--result",
str(result),
"--",
sys.executable,
"-c",
"import time; time.sleep(30)",
],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
for _ in range(50):
if log.exists():
break
time.sleep(0.02)
process.send_signal(signal.SIGINT)
process.communicate(timeout=10)
assert process.returncode == 130
data = json.loads(result.read_text(encoding="utf-8"))
assert data["status"] == "failed"
assert data["exit_code"] == 130
assert data["error"] == "interrupted"
def test_simulation_success_marker_and_failure_detection(tmp_path: Path) -> None:
log = tmp_path / "sim.log"
result = tmp_path / "result.json"
log.write_text(
"retroSoC: A Customized ASIC for Retro Stuff\nSimulation complete\n",
encoding="utf-8",
)
run(
sys.executable,
str(ROOT / "scripts/check_simulation.py"),
"--log",
str(log),
"--result",
str(result),
)
assert json.loads(result.read_text(encoding="utf-8"))["status"] == "passed"
log.write_text(
"retroSoC: A Customized ASIC for Retro Stuff\nTEST FAILED\n",
encoding="utf-8",
)
failed = subprocess.run(
[
sys.executable,
str(ROOT / "scripts/check_simulation.py"),
"--log",
str(log),
"--result",
str(result),
],
text=True,
capture_output=True,
)
assert failed.returncode != 0
assert json.loads(result.read_text(encoding="utf-8"))["status"] == "failed"
def test_warning_baseline_rejects_new_signature(tmp_path: Path) -> None:
profile = "unit-profile"
log = tmp_path / "variant/sim/verilator/verilating.log"
log.parent.mkdir(parents=True)
log.write_text(
f"%Warning-WIDTH: {tmp_path}/rtl/top.sv:12: width mismatch\n",
encoding="utf-8",
)
baseline = tmp_path / f"quality/warnings/{profile}/verilator.json"
run(
sys.executable,
str(ROOT / "scripts/analyze_warnings.py"),
"baseline",
"--root",
str(tmp_path),
"--profile",
profile,
"--tool",
"verilator",
"--log",
str(log),
"--output",
str(baseline),
)
report = tmp_path / "warnings.json"
run(
sys.executable,
str(ROOT / "scripts/analyze_warnings.py"),
"check",
"--root",
str(tmp_path),
"--profile",
profile,
"--variant-root",
str(tmp_path / "variant"),
"--tool",
"verilator",
"--output",
str(report),
)
log.write_text(
log.read_text(encoding="utf-8")
+ f"%Warning-UNUSED: {tmp_path}/rtl/top.sv:20: unused signal\n",
encoding="utf-8",
)
failed = subprocess.run(
[
sys.executable,
str(ROOT / "scripts/analyze_warnings.py"),
"check",
"--root",
str(tmp_path),
"--profile",
profile,
"--variant-root",
str(tmp_path / "variant"),
"--tool",
"verilator",
"--output",
str(report),
],
text=True,
capture_output=True,
)
assert failed.returncode != 0
assert json.loads(report.read_text(encoding="utf-8"))["failed_tools"] == ["verilator"]
def test_warning_normalization_keeps_ranges_and_removes_variant_hash(tmp_path: Path) -> None:
message = (
f"{tmp_path}/build/profile-deadbeef/generated/core.sv:42: "
"Bit extraction of var[7:0] is too wide"
)
normalized = normalize(tmp_path, "WIDTH", message)
assert normalized == (
"WIDTH:$BUILD/generated/core.sv:<line>: "
"Bit extraction of var[7:0] is too wide"
)
def test_metrics_collection_and_observe_policy(tmp_path: Path) -> None:
variant = tmp_path / "variant"
(variant / "sw").mkdir(parents=True)
(variant / "sw/firmware.bin").write_bytes(b"1234")
report_dir = variant / "syn/yosys/rpt"
report_dir.mkdir(parents=True)
(report_dir / "retrosoc_asic_area.rpt").write_text(
" 42 1.23E+02 retrosoc_asic\n"
"Chip area for top module '\\retrosoc_asic': 123.0\n",
encoding="utf-8",
)
(report_dir / "retrosoc_asic_area.json").write_text(
json.dumps({"design": {"area": 124.0, "num_cells": 43}}),
encoding="utf-8",
)
timing_dir = variant / "sta/opensta"
timing_dir.mkdir(parents=True)
(timing_dir / "timing_metrics.rpt").write_text("-1.0\n-2.0\n-3.0\n-4.0\n", encoding="utf-8")
(variant / "result-unit.json").write_text(
json.dumps({"status": "passed", "duration_seconds": 1.25}),
encoding="utf-8",
)
metrics = tmp_path / "metrics.json"
run(
sys.executable,
str(ROOT / "scripts/metrics.py"),
"collect",
"--variant-root",
str(variant),
"--output",
str(metrics),
)
data = json.loads(metrics.read_text(encoding="utf-8"))
assert data["firmware"]["firmware.bin"]["bytes"] == 4
assert data["synthesis"] == {"top_area": 124.0, "top_cells": 43}
assert data["timing"]["wns_min"] == -1.0
policy = tmp_path / "policy.json"
policy.write_text('{"mode": "observe"}\n', encoding="utf-8")
run(
sys.executable,
str(ROOT / "scripts/metrics.py"),
"check",
"--metrics",
str(metrics),
"--policy",
str(policy),
)
def test_safe_extract_rejects_parent_traversal(tmp_path: Path) -> None:
archive = tmp_path / "unsafe.tar"
with tarfile.open(archive, "w") as bundle:
member = tarfile.TarInfo("../outside")
payload = b"unsafe"
member.size = len(payload)
bundle.addfile(member, io.BytesIO(payload))
try:
safe_extract(archive, tmp_path / "output")
except ValueError as error:
assert "unsafe archive member" in str(error)
else:
raise AssertionError("unsafe archive was extracted")
assert not (tmp_path / "outside").exists()
def test_ci_actions_are_pinned_to_commits() -> None:
action_files = [
*sorted((ROOT / ".github/workflows").glob("*.yml")),
*sorted((ROOT / ".github/actions").glob("*/action.yml")),
]
remote_uses = []
for path in action_files:
for line in path.read_text(encoding="utf-8").splitlines():
match = re.match(r"\s*-?\s*uses:\s*([^\s#]+)", line)
if match and not match.group(1).startswith("./"):
remote_uses.append((path, match.group(1)))
assert remote_uses
for path, value in remote_uses:
assert re.fullmatch(r"[^@]+@[0-9a-f]{40}", value), f"unpinned action in {path}: {value}"
def test_python_requirements_are_hash_locked() -> None:
for path in sorted((ROOT / "requirements").glob("*.txt")):
content = path.read_text(encoding="utf-8")
assert "--require-hashes" in content
assert "--only-binary=:all:" in content
requirements = [
line for line in content.splitlines() if line and not line.startswith(("#", "--"))
]
assert requirements
for requirement in requirements:
assert re.search(r"==[^ ]+ --hash=sha256:[0-9a-f]{64}$", requirement), (
f"unlocked requirement in {path}: {requirement}"
)
def test_clean_all_stays_within_repository(tmp_path: Path) -> None: def test_clean_all_stays_within_repository(tmp_path: Path) -> None:
fake_root = tmp_path / "repository" fake_root = tmp_path / "repository"
(fake_root / ".git").mkdir(parents=True) (fake_root / ".git").mkdir(parents=True)
@@ -178,6 +509,37 @@ def test_clean_all_stays_within_repository(tmp_path: Path) -> None:
str(ROOT / "scripts/clean.py"), str(ROOT / "scripts/clean.py"),
"--root", "--root",
str(fake_root), str(fake_root),
"--path",
str(generated),
) )
assert not generated.exists() assert not generated.exists()
assert (dependency / "model.v").is_file() assert (dependency / "model.v").is_file()
outside = subprocess.run(
[
sys.executable,
str(ROOT / "scripts/clean.py"),
"--root",
str(fake_root),
"--path",
str(tmp_path / "outside"),
],
text=True,
capture_output=True,
)
assert outside.returncode != 0
external = tmp_path / "external"
external.mkdir()
link = fake_root / "build-link"
link.symlink_to(external, target_is_directory=True)
run(
sys.executable,
str(ROOT / "scripts/clean.py"),
"--root",
str(fake_root),
"--path",
str(link),
)
assert not link.exists()
assert external.is_dir()