mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
69 files changed
+3200
-580
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
# Build and dependency controls require explicit maintainer review.
|
||||||
|
/Makefile @maksyuki
|
||||||
|
/config/ @maksyuki
|
||||||
|
/configs/ @maksyuki
|
||||||
|
/scripts/ @maksyuki
|
||||||
|
/.github/ @maksyuki
|
||||||
|
/quality/ @maksyuki
|
||||||
@@ -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[@]}"
|
||||||
@@ -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
|
||||||
@@ -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 }}
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
@@ -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*
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
extends: default
|
||||||
|
|
||||||
|
rules:
|
||||||
|
document-start: disable
|
||||||
|
line-length:
|
||||||
|
max: 160
|
||||||
|
truthy: disable
|
||||||
+31
-1
@@ -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`.
|
||||||
@@ -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
|
||||||
@@ -1,30 +1,38 @@
|
|||||||
# retroSoC
|
# retroSoC
|
||||||
|
|
||||||
[](https://github.com/retroSoC/retroSoC/actions/workflows/style.yml)
|
[](https://github.com/retroSoC/retroSoC/actions/workflows/quality.yml)
|
||||||
[](https://github.com/retroSoC/retroSoC/actions/workflows/lint.yml)
|
[](https://github.com/retroSoC/retroSoC/actions/workflows/regression.yml)
|
||||||
[](https://github.com/retroSoC/retroSoC/actions/workflows/test.yml)
|
[](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
@@ -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
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -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.
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
@@ -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())
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -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
@@ -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__":
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -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())
|
||||||
@@ -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())
|
||||||
@@ -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())
|
||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -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())
|
||||||
@@ -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}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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()
|
||||||
Reference in new issue
Block a user