mirror of
https://github.com/YosysHQ/yosys
synced 2026-05-13 13:45:28 +00:00
Merge commit 'ab316c14d2' into abc-liberty-args
This commit is contained in:
commit
f8a50e7174
371 changed files with 15324 additions and 3587 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -6,7 +6,7 @@ body:
|
|||
attributes:
|
||||
value: >
|
||||
|
||||
Learn more [here](https://yosyshq.readthedocs.io/projects/yosys/en/latest/yosys_internals/extending_yosys/contributing.html#reporting-bugs) about how to report bugs. We fix well-reported bugs the fastest.
|
||||
Learn more in our [Reporting bugs](https://yosyshq.readthedocs.io/projects/yosys/en/latest/yosys_internals/extending_yosys/contributing.html#reporting-bugs) docs section. We fix well-reported bugs the fastest.
|
||||
|
||||
If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/).
|
||||
|
||||
|
|
|
|||
2
.github/actions/setup-build-env/action.yml
vendored
2
.github/actions/setup-build-env/action.yml
vendored
|
|
@ -42,7 +42,7 @@ runs:
|
|||
if: runner.os == 'Linux' && inputs.get-build-deps == 'true'
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libgtest-dev
|
||||
packages: bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libgtest-dev libgmock-dev
|
||||
version: ${{ inputs.runs-on }}-buildys
|
||||
|
||||
- name: Linux docs dependencies
|
||||
|
|
|
|||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
get-build-deps: true
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: cpp
|
||||
queries: security-extended,security-and-quality
|
||||
|
|
@ -32,4 +32,4 @@ jobs:
|
|||
run: make yosys -j6
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
|
|
|||
61
.github/workflows/extra-builds.yml
vendored
61
.github/workflows/extra-builds.yml
vendored
|
|
@ -1,23 +1,20 @@
|
|||
name: Test extra build flows
|
||||
|
||||
on:
|
||||
# always test main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
# test PRs
|
||||
pull_request:
|
||||
# allow triggering tests, ignores skip check
|
||||
merge_group:
|
||||
#push:
|
||||
# branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
should_skip: ${{ steps.set_output.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
# don't run on documentation changes
|
||||
|
|
@ -26,20 +23,28 @@ jobs:
|
|||
# but never cancel main
|
||||
cancel_others: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- id: set_output
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
vs-prep:
|
||||
name: Prepare Visual Studio build
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre_job]
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
- run: sudo apt-get install libfl-dev
|
||||
- name: Build
|
||||
run: make vcxsrc YOSYS_COMPILER="Visual Studio" VCX_DIR_NAME=yosys-win32-vcxsrc-latest
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: vcxsrc
|
||||
path: yosys-win32-vcxsrc-latest.zip
|
||||
|
|
@ -48,9 +53,9 @@ jobs:
|
|||
name: Visual Studio build
|
||||
runs-on: windows-latest
|
||||
needs: [vs-prep, pre_job]
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true'
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: vcxsrc
|
||||
path: .
|
||||
|
|
@ -65,17 +70,17 @@ jobs:
|
|||
wasi-build:
|
||||
name: WASI build
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
- name: Build
|
||||
run: |
|
||||
WASI_SDK=wasi-sdk-27.0-x86_64-linux
|
||||
WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-x86_64-linux.tar.gz
|
||||
WASI_SDK=wasi-sdk-33.0-x86_64-linux
|
||||
WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz
|
||||
if ! [ -d ${WASI_SDK} ]; then curl -L ${WASI_SDK_URL} | tar xzf -; fi
|
||||
|
||||
FLEX_VER=2.6.4
|
||||
|
|
@ -111,14 +116,14 @@ jobs:
|
|||
nix-build:
|
||||
name: "Build nix flake"
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
|
@ -126,3 +131,21 @@ jobs:
|
|||
with:
|
||||
install_url: https://releases.nixos.org/nix/nix-2.30.0/install
|
||||
- run: nix build .?submodules=1 -L
|
||||
|
||||
extra-builds-result:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- vs-build
|
||||
- wasi-build
|
||||
- nix-build
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results
|
||||
run: |
|
||||
echo "Needs results: ${{ join(needs.*.result, ',') }}"
|
||||
if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \
|
||||
[[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then
|
||||
echo "Some jobs failed or were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
- run: echo "All good"
|
||||
|
|
|
|||
28
.github/workflows/prepare-docs.yml
vendored
28
.github/workflows/prepare-docs.yml
vendored
|
|
@ -1,12 +1,18 @@
|
|||
name: Build docs artifact with Verific
|
||||
|
||||
on: [push, pull_request, merge_group]
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [ main, "docs-preview/**", "docs-preview*" ]
|
||||
tags: [ "*" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check_docs_rebuild:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skip_check: ${{ steps.skip_check.outputs.should_skip }}
|
||||
should_skip: ${{ steps.set_output.outputs.should_skip }}
|
||||
docs_export: ${{ steps.docs_var.outputs.docs_export }}
|
||||
env:
|
||||
docs_export: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/docs-preview') || startsWith(github.ref, 'refs/tags/') }}
|
||||
|
|
@ -17,16 +23,26 @@ jobs:
|
|||
paths_ignore: '["**/README.md"]'
|
||||
# don't cancel in case we're updating docs
|
||||
cancel_others: 'false'
|
||||
# only run on push *or* pull_request, not both
|
||||
concurrent_skipping: ${{ env.docs_export && 'never' || 'same_content_newer'}}
|
||||
# push filtering means we only want to skip duplicates for PRs
|
||||
do_not_skip: '["workflow_dispatch", "merge_group", "push"]'
|
||||
concurrent_skipping: 'same_content_newer'
|
||||
|
||||
- id: docs_var
|
||||
run: echo "docs_export=${docs_export}" >> $GITHUB_OUTPUT
|
||||
|
||||
- id: set_output
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
prepare-docs:
|
||||
# docs builds are needed for anything on main, any tagged versions, and any tag
|
||||
# or branch starting with docs-preview
|
||||
needs: check_docs_rebuild
|
||||
if: ${{ needs.check_docs_rebuild.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
|
||||
if: ${{ needs.check_docs_rebuild.outputs.should_skip != 'true' && github.repository_owner == 'YosysHQ' }}
|
||||
runs-on: [self-hosted, linux, x64, fast]
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
|
|
@ -75,7 +91,7 @@ jobs:
|
|||
make -C docs html -j$procs TARGETS= EXTRA_TARGETS=
|
||||
|
||||
- name: Trigger RTDs build
|
||||
if: ${{ needs.check_docs_rebuild.outputs.docs_export == 'true' }}
|
||||
if: ${{ needs.check_docs_rebuild.outputs.docs_export == 'true' && github.repository == 'YosysHQ/yosys' }}
|
||||
uses: dfm/rtds-action@v1.1.0
|
||||
with:
|
||||
webhook_url: ${{ secrets.RTDS_WEBHOOK_URL }}
|
||||
|
|
|
|||
11
.github/workflows/source-vendor.yml
vendored
11
.github/workflows/source-vendor.yml
vendored
|
|
@ -1,13 +1,18 @@
|
|||
name: Create source archive with vendored dependencies
|
||||
|
||||
on: [push, workflow_dispatch]
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
vendor-sources:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository with submodules
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
persist-credentials: false
|
||||
|
|
@ -27,7 +32,7 @@ jobs:
|
|||
gzip yosys-src-vendored.tar
|
||||
|
||||
- name: Store tarball artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: vendored-sources
|
||||
path: yosys-src-vendored.tar.gz
|
||||
|
|
|
|||
69
.github/workflows/test-build.yml
vendored
69
.github/workflows/test-build.yml
vendored
|
|
@ -1,23 +1,20 @@
|
|||
name: Build and run tests
|
||||
|
||||
on:
|
||||
# always test main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
# test PRs
|
||||
pull_request:
|
||||
# allow triggering tests, ignores skip check
|
||||
merge_group:
|
||||
#push:
|
||||
# branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
should_skip: ${{ steps.set_output.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
# don't run on documentation changes
|
||||
|
|
@ -26,12 +23,21 @@ jobs:
|
|||
# but never cancel main
|
||||
cancel_others: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- id: set_output
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
pre_docs_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
should_skip: ${{ steps.set_output.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
# don't run on readme changes
|
||||
|
|
@ -40,6 +46,14 @@ jobs:
|
|||
# but never cancel main
|
||||
cancel_others: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- id: set_output
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
build-yosys:
|
||||
name: Reusable build
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
|
@ -54,7 +68,7 @@ jobs:
|
|||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
|
@ -85,7 +99,7 @@ jobs:
|
|||
tar -cvf ../build.tar share/ yosys yosys-* libyosys.so
|
||||
|
||||
- name: Store build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-${{ matrix.os }}
|
||||
path: build.tar
|
||||
|
|
@ -104,7 +118,7 @@ jobs:
|
|||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
@ -116,7 +130,7 @@ jobs:
|
|||
get-iverilog: true
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: build-${{ matrix.os }}
|
||||
|
||||
|
|
@ -152,7 +166,7 @@ jobs:
|
|||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
@ -162,7 +176,7 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: build-${{ matrix.os }}
|
||||
|
||||
|
|
@ -192,7 +206,7 @@ jobs:
|
|||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
@ -204,7 +218,7 @@ jobs:
|
|||
get-docs-deps: true
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: build-${{ matrix.os }}
|
||||
|
||||
|
|
@ -226,7 +240,7 @@ jobs:
|
|||
name: Try build docs
|
||||
runs-on: [self-hosted, linux, x64, fast]
|
||||
needs: [pre_docs_job]
|
||||
if: ${{ needs.pre_docs_job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
|
||||
if: ${{ needs.pre_docs_job.outputs.should_skip != 'true' && github.repository_owner == 'YosysHQ' }}
|
||||
strategy:
|
||||
matrix:
|
||||
docs-target: [html, latexpdf]
|
||||
|
|
@ -265,3 +279,22 @@ jobs:
|
|||
name: docs-build-${{ matrix.docs-target }}
|
||||
path: docs/build/
|
||||
retention-days: 7
|
||||
|
||||
test-build-result:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-yosys
|
||||
- test-cells
|
||||
- test-docs
|
||||
- test-docs-build
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results
|
||||
run: |
|
||||
echo "Needs results: ${{ join(needs.*.result, ',') }}"
|
||||
if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \
|
||||
[[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then
|
||||
echo "Some jobs failed or were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
- run: echo "All good"
|
||||
|
|
|
|||
41
.github/workflows/test-compile.yml
vendored
41
.github/workflows/test-compile.yml
vendored
|
|
@ -1,23 +1,20 @@
|
|||
name: Compiler testing
|
||||
|
||||
on:
|
||||
# always test main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
# test PRs
|
||||
pull_request:
|
||||
# allow triggering tests, ignores skip check
|
||||
merge_group:
|
||||
#push:
|
||||
# branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
should_skip: ${{ steps.set_output.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
# don't run on documentation changes
|
||||
|
|
@ -26,10 +23,18 @@ jobs:
|
|||
# but never cancel main
|
||||
cancel_others: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- id: set_output
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
test-compile:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true'
|
||||
env:
|
||||
CXXFLAGS: ${{ startsWith(matrix.compiler, 'gcc') && '-Wp,-D_GLIBCXX_ASSERTIONS' || ''}}
|
||||
CC_SHORT: ${{ startsWith(matrix.compiler, 'gcc') && 'gcc' || 'clang' }}
|
||||
|
|
@ -54,7 +59,7 @@ jobs:
|
|||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
|
@ -90,3 +95,19 @@ jobs:
|
|||
run: |
|
||||
make config-$CC_SHORT
|
||||
make -j$procs CXXSTD=c++20 compile-only
|
||||
|
||||
test-compile-result:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-compile
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results
|
||||
run: |
|
||||
echo "Needs results: ${{ join(needs.*.result, ',') }}"
|
||||
if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \
|
||||
[[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then
|
||||
echo "Some jobs failed or were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
- run: echo "All good"
|
||||
|
|
|
|||
42
.github/workflows/test-sanitizers.yml
vendored
42
.github/workflows/test-sanitizers.yml
vendored
|
|
@ -1,32 +1,41 @@
|
|||
name: Check clang sanitizers
|
||||
|
||||
on:
|
||||
# always test main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
merge_group:
|
||||
# ignore PRs due to time needed
|
||||
# allow triggering tests, ignores skip check
|
||||
#push:
|
||||
# branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
should_skip: ${{ steps.set_output.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
# don't run on documentation changes
|
||||
paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]'
|
||||
# cancel previous builds if a new commit is pushed
|
||||
# but never cancel main
|
||||
cancel_others: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- id: set_output
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
run_san:
|
||||
name: Build and run tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true'
|
||||
env:
|
||||
CC: clang
|
||||
ASAN_OPTIONS: halt_on_error=1
|
||||
|
|
@ -38,7 +47,7 @@ jobs:
|
|||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
|
@ -73,3 +82,18 @@ jobs:
|
|||
run: |
|
||||
find tests/**/*.err -print -exec cat {} \;
|
||||
|
||||
test-sanitizers-result:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- run_san
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results
|
||||
run: |
|
||||
echo "Needs results: ${{ join(needs.*.result, ',') }}"
|
||||
if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \
|
||||
[[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then
|
||||
echo "Some jobs failed or were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
- run: echo "All good"
|
||||
52
.github/workflows/test-verific.yml
vendored
52
.github/workflows/test-verific.yml
vendored
|
|
@ -1,23 +1,20 @@
|
|||
name: Build and run tests with Verific (Linux)
|
||||
|
||||
on:
|
||||
# always test main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
# test PRs
|
||||
pull_request:
|
||||
# allow triggering tests, ignores skip check
|
||||
merge_group:
|
||||
#push:
|
||||
# branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre-job:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
should_skip: ${{ steps.set_output.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
if: ${{ github.event_name != 'merge_group' }}
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
# don't run on documentation changes
|
||||
|
|
@ -26,9 +23,17 @@ jobs:
|
|||
# but never cancel main
|
||||
cancel_others: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
- id: set_output
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "merge_group" ]; then
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
test-verific:
|
||||
needs: pre-job
|
||||
if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' && github.repository_owner == 'YosysHQ' }}
|
||||
runs-on: [self-hosted, linux, x64, fast]
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
|
|
@ -73,16 +78,16 @@ jobs:
|
|||
- name: Run Verific specific Yosys tests
|
||||
run: |
|
||||
make -C tests/sva
|
||||
cd tests/svtypes && bash run-test.sh
|
||||
make -C tests/svtypes
|
||||
|
||||
- name: Run SBY tests
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
if: ${{ github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' }}
|
||||
run: |
|
||||
make -C sby run_ci
|
||||
|
||||
test-pyosys:
|
||||
needs: pre-job
|
||||
if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' && github.repository_owner == 'YosysHQ' }}
|
||||
runs-on: [self-hosted, linux, x64, fast]
|
||||
steps:
|
||||
- name: Checkout Yosys
|
||||
|
|
@ -118,3 +123,20 @@ jobs:
|
|||
run: |
|
||||
export PYTHONPATH=${GITHUB_WORKSPACE}/.local/usr/lib/python3/site-packages:$PYTHONPATH
|
||||
python3 tests/pyosys/run_tests.py
|
||||
|
||||
test-verific-result:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-verific
|
||||
- test-pyosys
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results
|
||||
run: |
|
||||
echo "Needs results: ${{ join(needs.*.result, ',') }}"
|
||||
if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \
|
||||
[[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then
|
||||
echo "Some jobs failed or were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
- run: echo "All good"
|
||||
|
|
|
|||
8
.github/workflows/wheels.yml
vendored
8
.github/workflows/wheels.yml
vendored
|
|
@ -49,7 +49,7 @@ jobs:
|
|||
name: Build Wheels | ${{ matrix.os.name }} | ${{ matrix.os.archs }}
|
||||
runs-on: ${{ matrix.os.runner }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
|
@ -108,13 +108,13 @@ jobs:
|
|||
PATH="$PWD/bison/src:$PATH"
|
||||
CIBW_BEFORE_BUILD: bash ./.github/workflows/wheels/cibw_before_build.sh
|
||||
CIBW_TEST_COMMAND: python3 {project}/tests/pyosys/run_tests.py
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: python-wheels-${{ matrix.os.runner }}
|
||||
path: ./wheelhouse/*.whl
|
||||
upload_wheels:
|
||||
name: Upload Wheels
|
||||
if: (github.repository == 'YosysHQ/Yosys') && (github.event_name == 'workflow_dispatch')
|
||||
if: (github.repository == 'YosysHQ/yosys') && (github.event_name == 'workflow_dispatch')
|
||||
runs-on: ubuntu-latest
|
||||
# Specifying a GitHub environment is optional, but strongly encouraged
|
||||
environment: pypi
|
||||
|
|
@ -123,7 +123,7 @@ jobs:
|
|||
id-token: write
|
||||
needs: build_wheels
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: "."
|
||||
pattern: python-wheels-*
|
||||
|
|
|
|||
2
.github/workflows/wheels/cibw_before_all.sh
vendored
2
.github/workflows/wheels/cibw_before_all.sh
vendored
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e -x
|
||||
|
||||
# Build-time dependencies
|
||||
|
|
|
|||
2
Brewfile
2
Brewfile
|
|
@ -9,6 +9,6 @@ brew "python3"
|
|||
brew "uv"
|
||||
brew "xdot"
|
||||
brew "bash"
|
||||
brew "llvm@20"
|
||||
brew "llvm"
|
||||
brew "lld"
|
||||
brew "googletest"
|
||||
|
|
|
|||
34
CHANGELOG
34
CHANGELOG
|
|
@ -2,9 +2,41 @@
|
|||
List of major changes and improvements between releases
|
||||
=======================================================
|
||||
|
||||
Yosys 0.62 .. Yosys 0.63-dev
|
||||
Yosys 0.64 .. Yosys 0.65-dev
|
||||
--------------------------
|
||||
|
||||
Yosys 0.63 .. Yosys 0.64
|
||||
--------------------------
|
||||
* New commands and options
|
||||
- Added "synth_analogdevices" pass to support synthesis
|
||||
for Analog Devices FPGAs.
|
||||
|
||||
* Various
|
||||
- Removed rarely-used options from ABC/ABC9.
|
||||
- Removed "-S" option from "abc" pass.
|
||||
- Removed "-fast" option from "abc9" and "abc9_exe".
|
||||
- Calls to "abc -g AND -fast" to map logic to
|
||||
AND-Inverter Graph form should be replaced with
|
||||
"aigmap".
|
||||
- The above change was made to SBY, so we recommend
|
||||
updating it.
|
||||
- Added hardware latch support for Gowin FPGAs.
|
||||
|
||||
Yosys 0.62 .. Yosys 0.63
|
||||
--------------------------
|
||||
* Various
|
||||
- Added DSP inference for Gowin GW1N and GW2A.
|
||||
- Added support for subtract in preadder for Xilinx arch.
|
||||
- Added infrastructure to run a sat solver as a command.
|
||||
|
||||
* New commands and options
|
||||
- Added "-ignore-unknown-cells" option to "equiv_induct"
|
||||
and "equiv_simple" pass.
|
||||
- Added "-force-params" option to "memory_libmap" pass.
|
||||
- Added "-select-solver" option to "sat" pass.
|
||||
- Added "-default_params" option to "write_verilog" pass.
|
||||
- Added "-nodsp" option to "synth_gowin" pass.
|
||||
|
||||
Yosys 0.61 .. Yosys 0.62
|
||||
--------------------------
|
||||
* Various
|
||||
|
|
|
|||
|
|
@ -5,59 +5,11 @@ first time contributing to an open source project, please take a look at the
|
|||
following guide about the basics:
|
||||
https://opensource.guide/how-to-contribute/#orienting-yourself-to-a-new-project.
|
||||
|
||||
## Asking questions
|
||||
Check out our [Contributing guidelines](https://yosys.readthedocs.io/en/latest/yosys_internals/extending_yosys/contributing.html) to learn the best ways to
|
||||
|
||||
If you have a question about how to use Yosys, please ask on our [Discourse forum](https://yosyshq.discourse.group/).
|
||||
The Discourse is also a great place to ask questions about developing or
|
||||
contributing to Yosys.
|
||||
+ get help
|
||||
+ report bugs
|
||||
+ contribute code
|
||||
+ review code
|
||||
|
||||
We have open [dev 'jour fixe' (JF) meetings](https://docs.google.com/document/d/1SapA6QAsJcsgwsdKJDgnGR2mr97pJjV4eeXg_TVJhRU/edit?usp=sharing) where developers from YosysHQ and the
|
||||
community come together to discuss open issues and PRs. This is also a good
|
||||
place to talk to us about how to implement larger PRs.
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The [issue tracker](https://github.com/YosysHQ/yosys/issues) is used for
|
||||
tracking bugs or other problems with Yosys or its documentation. It is also the
|
||||
place to go for requesting new features.
|
||||
|
||||
### Bug reports
|
||||
|
||||
Learn more [here](https://yosyshq.readthedocs.io/projects/yosys/en/latest/yosys_internals/extending_yosys/contributing.html#reporting-bugs) about how to report bugs. We fix well-reported bugs the fastest.
|
||||
|
||||
## Contributing code
|
||||
|
||||
If you're adding complex functionality, or modifying core parts of Yosys,
|
||||
we highly recommend discussing your motivation and approach
|
||||
ahead of time on the [Discourse forum](https://yosyshq.discourse.group/).
|
||||
|
||||
### Using pull requests
|
||||
|
||||
If you are working on something to add to Yosys, or fix something that isn't
|
||||
working quite right,
|
||||
make a [pull request (PR)](https://github.com/YosysHQ/yosys/pulls).
|
||||
|
||||
An open PR, even as a draft, tells everyone that you're working on it and they
|
||||
don't have to. It can also be a useful way to solicit feedback on in-progress
|
||||
changes. See above to find the best way to [ask us questions](#asking-questions).
|
||||
|
||||
### Continuous integration
|
||||
|
||||
[Continuous Integration (CI)](https://github.com/YosysHQ/yosys/actions) tools
|
||||
automatically compile Yosys and run it with the full suite of tests.
|
||||
If you're a first time contributor, a maintainer has to trigger a run for you.
|
||||
We test on various platforms, compilers. Sanitizer builds are only tested
|
||||
on the main branch.
|
||||
|
||||
### Labels
|
||||
|
||||
We use [labels](https://github.com/YosysHQ/yosys/labels) to help categorise
|
||||
issues and PRs. If a label seems relevant to your work, please do add it; this
|
||||
also includes the labels beginning with 'status-'. The 'merge-' labels are used
|
||||
by maintainers for tracking and communicating which PRs are ready and pending
|
||||
merge; please do not use these labels if you are not a maintainer.
|
||||
|
||||
|
||||
### Coding style
|
||||
|
||||
Learn more [here](https://yosys.readthedocs.io/en/latest/yosys_internals/extending_yosys/contributing.html).
|
||||
If you're reading this file offline and don't have internet access, you can [read the contributing.rst file locally](docs/source/yosys_internals/extending_yosys/contributing.rst).
|
||||
|
|
|
|||
140
Makefile
140
Makefile
|
|
@ -161,7 +161,7 @@ ifeq ($(OS), Haiku)
|
|||
CXXFLAGS += -D_DEFAULT_SOURCE
|
||||
endif
|
||||
|
||||
YOSYS_VER := 0.62
|
||||
YOSYS_VER := 0.64
|
||||
|
||||
ifneq (, $(shell command -v git 2>/dev/null))
|
||||
ifneq (, $(shell git rev-parse --git-dir 2>/dev/null))
|
||||
|
|
@ -291,18 +291,19 @@ ifeq ($(WASI_SDK),)
|
|||
CXX = clang++
|
||||
AR = llvm-ar
|
||||
RANLIB = llvm-ranlib
|
||||
WASIFLAGS := -target wasm32-wasi $(WASIFLAGS)
|
||||
WASIFLAGS := -target wasm32-wasip1 $(WASIFLAGS)
|
||||
else
|
||||
CXX = $(WASI_SDK)/bin/clang++
|
||||
AR = $(WASI_SDK)/bin/ar
|
||||
RANLIB = $(WASI_SDK)/bin/ranlib
|
||||
endif
|
||||
CXXFLAGS := $(WASIFLAGS) -std=$(CXXSTD) $(OPT_LEVEL) -D_WASI_EMULATED_PROCESS_CLOCKS $(filter-out -fPIC,$(CXXFLAGS))
|
||||
LINKFLAGS := $(WASIFLAGS) -Wl,-z,stack-size=1048576 $(filter-out -rdynamic,$(LINKFLAGS))
|
||||
CXXFLAGS := $(WASIFLAGS) -std=$(CXXSTD) $(OPT_LEVEL) -D_WASI_EMULATED_PROCESS_CLOCKS -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false $(filter-out -fPIC,$(CXXFLAGS))
|
||||
LINKFLAGS := $(WASIFLAGS) -Wl,-z,stack-size=1048576 $(filter-out -rdynamic,$(LINKFLAGS)) -fwasm-exceptions -lunwind
|
||||
LIBS := -lwasi-emulated-process-clocks $(filter-out -lrt,$(LIBS))
|
||||
ABCMKARGS += AR="$(AR)" RANLIB="$(RANLIB)"
|
||||
ABCMKARGS += ARCHFLAGS="$(WASIFLAGS) -D_WASI_EMULATED_PROCESS_CLOCKS -DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING -DABC_NO_RLIMIT"
|
||||
ABCMKARGS += OPTFLAGS="-Os"
|
||||
LTOFLAGS =
|
||||
EXE = .wasm
|
||||
|
||||
DISABLE_SPAWN := 1
|
||||
|
|
@ -613,6 +614,7 @@ $(eval $(call add_include_file,kernel/bitpattern.h))
|
|||
$(eval $(call add_include_file,kernel/cellaigs.h))
|
||||
$(eval $(call add_include_file,kernel/celledges.h))
|
||||
$(eval $(call add_include_file,kernel/celltypes.h))
|
||||
$(eval $(call add_include_file,kernel/newcelltypes.h))
|
||||
$(eval $(call add_include_file,kernel/consteval.h))
|
||||
$(eval $(call add_include_file,kernel/constids.inc))
|
||||
$(eval $(call add_include_file,kernel/cost.h))
|
||||
|
|
@ -917,109 +919,15 @@ else
|
|||
ABCOPT=""
|
||||
endif
|
||||
|
||||
# Tests that generate .mk with tests/gen-tests-makefile.sh
|
||||
MK_TEST_DIRS =
|
||||
MK_TEST_DIRS += tests/arch/anlogic
|
||||
MK_TEST_DIRS += tests/arch/ecp5
|
||||
MK_TEST_DIRS += tests/arch/efinix
|
||||
MK_TEST_DIRS += tests/arch/gatemate
|
||||
MK_TEST_DIRS += tests/arch/gowin
|
||||
MK_TEST_DIRS += tests/arch/ice40
|
||||
MK_TEST_DIRS += tests/arch/intel_alm
|
||||
MK_TEST_DIRS += tests/arch/machxo2
|
||||
MK_TEST_DIRS += tests/arch/microchip
|
||||
MK_TEST_DIRS += tests/arch/nanoxplore
|
||||
MK_TEST_DIRS += tests/arch/nexus
|
||||
MK_TEST_DIRS += tests/arch/quicklogic/pp3
|
||||
MK_TEST_DIRS += tests/arch/quicklogic/qlf_k6n10f
|
||||
MK_TEST_DIRS += tests/arch/xilinx
|
||||
MK_TEST_DIRS += tests/bugpoint
|
||||
MK_TEST_DIRS += tests/opt
|
||||
MK_TEST_DIRS += tests/sat
|
||||
MK_TEST_DIRS += tests/sdc
|
||||
MK_TEST_DIRS += tests/sim
|
||||
MK_TEST_DIRS += tests/svtypes
|
||||
MK_TEST_DIRS += tests/techmap
|
||||
MK_TEST_DIRS += tests/various
|
||||
MK_TEST_DIRS += tests/rtlil
|
||||
ifeq ($(ENABLE_VERIFIC),1)
|
||||
ifneq ($(YOSYS_NOVERIFIC),1)
|
||||
MK_TEST_DIRS += tests/verific
|
||||
endif
|
||||
endif
|
||||
MK_TEST_DIRS += tests/verilog
|
||||
|
||||
# Tests that don't generate .mk
|
||||
SH_TEST_DIRS =
|
||||
SH_TEST_DIRS += tests/simple
|
||||
SH_TEST_DIRS += tests/simple_abc9
|
||||
SH_TEST_DIRS += tests/hana
|
||||
SH_TEST_DIRS += tests/asicworld
|
||||
# SH_TEST_DIRS += tests/realmath
|
||||
SH_TEST_DIRS += tests/share
|
||||
SH_TEST_DIRS += tests/opt_share
|
||||
SH_TEST_DIRS += tests/fsm
|
||||
SH_TEST_DIRS += tests/memlib
|
||||
SH_TEST_DIRS += tests/bram
|
||||
SH_TEST_DIRS += tests/svinterfaces
|
||||
SH_TEST_DIRS += tests/xprop
|
||||
SH_TEST_DIRS += tests/select
|
||||
SH_TEST_DIRS += tests/peepopt
|
||||
SH_TEST_DIRS += tests/proc
|
||||
SH_TEST_DIRS += tests/blif
|
||||
SH_TEST_DIRS += tests/arch
|
||||
SH_TEST_DIRS += tests/rpc
|
||||
SH_TEST_DIRS += tests/memfile
|
||||
SH_TEST_DIRS += tests/fmt
|
||||
SH_TEST_DIRS += tests/cxxrtl
|
||||
SH_TEST_DIRS += tests/liberty
|
||||
ifeq ($(ENABLE_FUNCTIONAL_TESTS),1)
|
||||
SH_TEST_DIRS += tests/functional
|
||||
endif
|
||||
|
||||
# Tests that don't generate .mk and need special args
|
||||
SH_ABC_TEST_DIRS =
|
||||
SH_ABC_TEST_DIRS += tests/memories
|
||||
SH_ABC_TEST_DIRS += tests/aiger
|
||||
SH_ABC_TEST_DIRS += tests/alumacc
|
||||
|
||||
# seed-tests/ is a dummy string, not a directory
|
||||
.PHONY: seed-tests
|
||||
seed-tests: $(SH_TEST_DIRS:%=seed-tests/%)
|
||||
.PHONY: seed-tests/%
|
||||
seed-tests/%: %/run-test.sh $(TARGETS) $(EXTRA_TARGETS)
|
||||
+cd $* && bash run-test.sh $(SEEDOPT)
|
||||
+@echo "...passed tests in $*"
|
||||
|
||||
# abcopt-tests/ is a dummy string, not a directory
|
||||
.PHONY: abcopt-tests
|
||||
abcopt-tests: $(SH_ABC_TEST_DIRS:%=abcopt-tests/%)
|
||||
abcopt-tests/%: %/run-test.sh $(TARGETS) $(EXTRA_TARGETS)
|
||||
+cd $* && bash run-test.sh $(ABCOPT) $(SEEDOPT)
|
||||
+@echo "...passed tests in $*"
|
||||
|
||||
# makefile-tests/ is a dummy string, not a directory
|
||||
.PHONY: makefile-tests
|
||||
makefile-tests: $(MK_TEST_DIRS:%=makefile-tests/%)
|
||||
# this target actually emits .mk files
|
||||
%.mk:
|
||||
+cd $(dir $*) && bash run-test.sh
|
||||
# this one spawns submake on each
|
||||
makefile-tests/%: %/run-test.mk $(TARGETS) $(EXTRA_TARGETS)
|
||||
$(MAKE) -C $* -f run-test.mk
|
||||
+@echo "...passed tests in $*"
|
||||
|
||||
test: vanilla-test unit-test
|
||||
|
||||
vanilla-test: makefile-tests abcopt-tests seed-tests
|
||||
@echo ""
|
||||
@echo " Passed \"make vanilla-test\"."
|
||||
ifeq ($(ENABLE_VERIFIC),1)
|
||||
ifeq ($(YOSYS_NOVERIFIC),1)
|
||||
@echo " Ran tests without verific support due to YOSYS_NOVERIFIC=1."
|
||||
endif
|
||||
endif
|
||||
@echo ""
|
||||
.PHONY: vanilla-test
|
||||
|
||||
vanilla-test: $(TARGETS) $(EXTRA_TARGETS)
|
||||
@$(MAKE) -C tests vanilla-test \
|
||||
$(if $(ENABLE_VERIFIC),ENABLE_VERIFIC=$(ENABLE_VERIFIC)) \
|
||||
$(if $(YOSYS_NOVERIFIC),YOSYS_NOVERIFIC=$(YOSYS_NOVERIFIC)) \
|
||||
SEEDOPT=$(SEEDOPT) ABCOPT=$(ABCOPT)
|
||||
|
||||
VALGRIND ?= valgrind --error-exitcode=1 --leak-check=full --show-reachable=yes --errors-for-leak-kinds=all
|
||||
|
||||
|
|
@ -1060,14 +968,14 @@ install-dev: $(PROGRAM_PREFIX)yosys-config share
|
|||
install: $(TARGETS) $(EXTRA_TARGETS)
|
||||
$(INSTALL_SUDO) mkdir -p $(DESTDIR)$(BINDIR)
|
||||
$(INSTALL_SUDO) cp $(filter-out libyosys.so libyosys.a,$(TARGETS)) $(DESTDIR)$(BINDIR)
|
||||
ifneq ($(filter $(PROGRAM_PREFIX)yosys,$(TARGETS)),)
|
||||
if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys; fi
|
||||
ifneq ($(filter $(PROGRAM_PREFIX)yosys$(EXE),$(TARGETS)),)
|
||||
if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys$(EXE); fi
|
||||
endif
|
||||
ifneq ($(filter $(PROGRAM_PREFIX)yosys-abc,$(TARGETS)),)
|
||||
if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-abc; fi
|
||||
ifneq ($(filter $(PROGRAM_PREFIX)yosys-abc$(EXE),$(TARGETS)),)
|
||||
if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-abc$(EXE); fi
|
||||
endif
|
||||
ifneq ($(filter $(PROGRAM_PREFIX)yosys-filterlib,$(TARGETS)),)
|
||||
if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-filterlib; fi
|
||||
ifneq ($(filter $(PROGRAM_PREFIX)yosys-filterlib$(EXE),$(TARGETS)),)
|
||||
if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-filterlib$(EXE); fi
|
||||
endif
|
||||
$(INSTALL_SUDO) mkdir -p $(DESTDIR)$(DATDIR)
|
||||
$(INSTALL_SUDO) cp -r share/. $(DESTDIR)$(DATDIR)/.
|
||||
|
|
@ -1185,16 +1093,8 @@ clean: clean-py clean-unit-test
|
|||
rm -f $(OBJS) $(GENFILES) $(TARGETS) $(EXTRA_TARGETS) $(EXTRA_OBJS)
|
||||
rm -f kernel/version_*.o kernel/version_*.cc
|
||||
rm -f libs/*/*.d frontends/*/*.d passes/*/*.d backends/*/*.d kernel/*.d techlibs/*/*.d
|
||||
rm -rf tests/asicworld/*.out tests/asicworld/*.log
|
||||
rm -rf tests/hana/*.out tests/hana/*.log
|
||||
rm -rf tests/simple/*.out tests/simple/*.log
|
||||
rm -rf tests/memories/*.out tests/memories/*.log tests/memories/*.dmp
|
||||
rm -rf tests/sat/*.log tests/techmap/*.log tests/various/*.log
|
||||
rm -rf tests/bram/temp tests/fsm/temp tests/realmath/temp tests/share/temp tests/smv/temp tests/various/temp
|
||||
rm -rf vloghtb/Makefile vloghtb/refdat vloghtb/rtl vloghtb/scripts vloghtb/spec vloghtb/check_yosys vloghtb/vloghammer_tb.tar.bz2 vloghtb/temp vloghtb/log_test_*
|
||||
rm -f tests/svinterfaces/*.log_stdout tests/svinterfaces/*.log_stderr tests/svinterfaces/dut_result.txt tests/svinterfaces/reference_result.txt tests/svinterfaces/a.out tests/svinterfaces/*_syn.v tests/svinterfaces/*.diff
|
||||
rm -f tests/tools/cmp_tbdata
|
||||
rm -f $(addsuffix /run-test.mk,$(MK_TEST_DIRS))
|
||||
-$(MAKE) -C $(YOSYS_SRC)/tests clean
|
||||
-$(MAKE) -C $(YOSYS_SRC)/docs clean
|
||||
rm -rf docs/util/__pycache__
|
||||
rm -f libyosys.so
|
||||
|
|
|
|||
2
abc
2
abc
|
|
@ -1 +1 @@
|
|||
Subproject commit 8e401543d3ecf65e3a3631c7a271793a4d356cb0
|
||||
Subproject commit d217b351925ffc5e3036073776fef1810d258499
|
||||
|
|
@ -21,9 +21,12 @@
|
|||
// - gracefully handling inout ports (an error message probably)
|
||||
// - undriven wires
|
||||
// - zero-width operands
|
||||
// - decide how to unify this with cellaigs
|
||||
// - break up Index into something smaller
|
||||
// - (C++20) remove snprintf-into-std::ostream weirdness
|
||||
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/rtlil.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
|
|
@ -45,8 +48,22 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
// TODO
|
||||
//#define ARITH_OPS ID($add), ID($sub), ID($neg)
|
||||
|
||||
#define KNOWN_OPS BITWISE_OPS, REDUCE_OPS, LOGIC_OPS, GATE_OPS, ID($pos), CMP_OPS, \
|
||||
ID($pmux), ID($bmux) /*, ARITH_OPS*/
|
||||
static constexpr auto known_ops = []() constexpr {
|
||||
StaticCellTypes::Categories::Category c{};
|
||||
for (auto id : {BITWISE_OPS})
|
||||
c.set_id(id);
|
||||
for (auto id : {REDUCE_OPS})
|
||||
c.set_id(id);
|
||||
for (auto id : {LOGIC_OPS})
|
||||
c.set_id(id);
|
||||
for (auto id : {GATE_OPS})
|
||||
c.set_id(id);
|
||||
for (auto id : {CMP_OPS})
|
||||
c.set_id(id);
|
||||
for (auto id : {ID($pos), ID($pmux), ID($bmux)})
|
||||
c.set_id(id);
|
||||
return c;
|
||||
}();
|
||||
|
||||
template<typename Writer, typename Lit, Lit CFALSE, Lit CTRUE>
|
||||
struct Index {
|
||||
|
|
@ -92,7 +109,7 @@ struct Index {
|
|||
int pos = index_wires(info, m);
|
||||
|
||||
for (auto cell : m->cells()) {
|
||||
if (cell->type.in(KNOWN_OPS) || cell->type.in(ID($scopeinfo), ID($specify2), ID($specify3), ID($input_port)))
|
||||
if (known_ops(cell->type) || cell->type.in(ID($scopeinfo), ID($specify2), ID($specify3), ID($input_port)))
|
||||
continue;
|
||||
|
||||
Module *submodule = m->design->module(cell->type);
|
||||
|
|
@ -104,7 +121,7 @@ struct Index {
|
|||
pos += index_module(submodule);
|
||||
} else {
|
||||
if (allow_blackboxes) {
|
||||
info.found_blackboxes.insert(cell);
|
||||
info.found_blackboxes.insert(cell);
|
||||
} else {
|
||||
// Even if we don't allow blackboxes these might still be
|
||||
// present outside of any traversed input cones, so we
|
||||
|
|
@ -163,6 +180,11 @@ struct Index {
|
|||
if (!strashing) {
|
||||
return (static_cast<Writer*>(this))->emit_gate(a, b);
|
||||
} else {
|
||||
// AigMaker::node2index
|
||||
|
||||
// In XAIGER, the ordering of inputs is used to distinguish between AND
|
||||
// and XOR gates. AND gates have their first input literal be larger
|
||||
// than their second, and vice-versa for XORs.
|
||||
if (a < b) std::swap(a, b);
|
||||
auto pair = std::make_pair(a, b);
|
||||
|
||||
|
|
@ -183,7 +205,9 @@ struct Index {
|
|||
|
||||
Lit OR(Lit a, Lit b)
|
||||
{
|
||||
return NOT(AND(NOT(a), NOT(b)));
|
||||
Lit not_a = NOT(a);
|
||||
Lit not_b = NOT(b);
|
||||
return NOT(AND(not_a, not_b));
|
||||
}
|
||||
|
||||
Lit MUX(Lit a, Lit b, Lit s)
|
||||
|
|
@ -197,17 +221,24 @@ struct Index {
|
|||
return b;
|
||||
}
|
||||
|
||||
return OR(AND(a, NOT(s)), AND(b, s));
|
||||
Lit not_s = NOT(s);
|
||||
Lit a_active = AND(a, not_s);
|
||||
Lit b_active = AND(b, s);
|
||||
return OR(a_active, b_active);
|
||||
}
|
||||
|
||||
Lit XOR(Lit a, Lit b)
|
||||
{
|
||||
return OR(AND(a, NOT(b)), AND(NOT(a), b));
|
||||
Lit not_a = NOT(a);
|
||||
Lit not_b = NOT(b);
|
||||
Lit a_and_not_b = AND(a, not_b);
|
||||
Lit not_a_and_b = AND(not_a, b);
|
||||
return OR(a_and_not_b, not_a_and_b);
|
||||
}
|
||||
|
||||
Lit XNOR(Lit a, Lit b)
|
||||
{
|
||||
return NOT(OR(AND(a, NOT(b)), AND(NOT(a), b)));
|
||||
return NOT(XOR(a, b));
|
||||
}
|
||||
|
||||
Lit CARRY(Lit a, Lit b, Lit c)
|
||||
|
|
@ -219,7 +250,10 @@ struct Index {
|
|||
return AND(a, b);
|
||||
}
|
||||
}
|
||||
return OR(AND(a, b), AND(c, OR(a, b)));
|
||||
Lit a_or_b = OR(a, b);
|
||||
Lit a_or_b_and_c = AND(c, a_or_b);
|
||||
Lit a_and_b = AND(a, b);
|
||||
return OR(a_and_b, a_or_b_and_c);
|
||||
}
|
||||
|
||||
Lit REDUCE(std::vector<Lit> lits, bool op_xor=false)
|
||||
|
|
@ -269,7 +303,7 @@ struct Index {
|
|||
} else if (cell->type.in(ID($lt), ID($le), ID($gt), ID($ge))) {
|
||||
if (cell->type.in(ID($gt), ID($ge)))
|
||||
std::swap(aport, bport);
|
||||
int carry = cell->type.in(ID($le), ID($ge)) ? CFALSE : CTRUE;
|
||||
int carry = cell->type.in(ID($le), ID($ge)) ? CFALSE : CTRUE;
|
||||
Lit a = Writer::EMPTY_LIT;
|
||||
Lit b = Writer::EMPTY_LIT;
|
||||
// TODO: this might not be the most economic structure; revisit at a later date
|
||||
|
|
@ -367,7 +401,7 @@ struct Index {
|
|||
} else if (cell->type.in(ID($xor), ID($_XOR_))) {
|
||||
return XOR(a, b);
|
||||
} else if (cell->type.in(ID($xnor), ID($_XNOR_))) {
|
||||
return NOT(XOR(a, b));
|
||||
return XNOR(a, b);
|
||||
} else if (cell->type.in(ID($_ANDNOT_))) {
|
||||
return AND(a, NOT(b));
|
||||
} else if (cell->type.in(ID($_ORNOT_))) {
|
||||
|
|
@ -387,7 +421,9 @@ struct Index {
|
|||
if (oport == ID::Y) {
|
||||
return XOR(ab, c);
|
||||
} else /* oport == ID::X */ {
|
||||
return OR(AND(a, b), AND(c, ab));
|
||||
Lit a_and_b = AND(a, b);
|
||||
Lit c_and_ab = AND(c, ab);
|
||||
return OR(a_and_b, c_and_ab);
|
||||
}
|
||||
} else if (cell->type.in(ID($_AOI3_), ID($_OAI3_), ID($_AOI4_), ID($_OAI4_))) {
|
||||
Lit c, d;
|
||||
|
|
@ -398,10 +434,15 @@ struct Index {
|
|||
else
|
||||
d = cell->type == ID($_AOI3_) ? CTRUE : CFALSE;
|
||||
|
||||
if (/* aoi */ cell->type.in(ID($_AOI3_), ID($_AOI4_)))
|
||||
return NOT(OR(AND(a, b), AND(c, d)));
|
||||
else
|
||||
return NOT(AND(OR(a, b), OR(c, d)));
|
||||
if (/* aoi */ cell->type.in(ID($_AOI3_), ID($_AOI4_))) {
|
||||
Lit a_and_b = AND(a, b);
|
||||
Lit c_and_d = AND(c, d);
|
||||
return NOT(OR(a_and_b, c_and_d));
|
||||
} else {
|
||||
Lit a_or_b = OR(a, b);
|
||||
Lit c_or_d = OR(c, d);
|
||||
return NOT(AND(a_or_b, c_or_d));
|
||||
}
|
||||
} else {
|
||||
log_abort();
|
||||
}
|
||||
|
|
@ -422,7 +463,11 @@ struct Index {
|
|||
sels.push_back(NOT(s));
|
||||
}
|
||||
|
||||
return OR(AND(REDUCE(sels), a), NOT(REDUCE(bar)));
|
||||
Lit reduce_sels = REDUCE(sels);
|
||||
Lit reduce_sels_and_a = AND(reduce_sels, a);
|
||||
Lit reduce_bar = NOT(REDUCE(bar));
|
||||
|
||||
return OR(reduce_sels_and_a, reduce_bar);
|
||||
} else if (cell->type == ID($bmux)) {
|
||||
SigSpec aport = cell->getPort(ID::A);
|
||||
SigSpec sport = cell->getPort(ID::S);
|
||||
|
|
@ -492,7 +537,7 @@ struct Index {
|
|||
Design *design = index.design;
|
||||
auto &minfo = leaf_minfo(index);
|
||||
if (!minfo.suboffsets.count(cell))
|
||||
log_error("Reached unsupport cell %s (%s in %s)\n", log_id(cell->type), log_id(cell), log_id(cell->module));
|
||||
log_error("Reached unsupported cell %s (%s in %s)\n", log_id(cell->type), log_id(cell), log_id(cell->module));
|
||||
Module *def = design->module(cell->type);
|
||||
log_assert(def);
|
||||
levels.push_back(Level(index.modules.at(def), cell));
|
||||
|
|
@ -511,13 +556,13 @@ struct Index {
|
|||
{
|
||||
std::string ret;
|
||||
bool first = true;
|
||||
for (auto pair : levels) {
|
||||
for (auto [minfo, cell] : levels) {
|
||||
if (!first)
|
||||
ret += ".";
|
||||
if (!pair.second)
|
||||
ret += RTLIL::unescape_id(pair.first.module->name);
|
||||
if (!cell)
|
||||
ret += RTLIL::unescape_id(minfo.module->name);
|
||||
else
|
||||
ret += RTLIL::unescape_id(pair.second->name);
|
||||
ret += RTLIL::unescape_id(cell->name);
|
||||
first = false;
|
||||
}
|
||||
return ret;
|
||||
|
|
@ -526,8 +571,8 @@ struct Index {
|
|||
int hash() const
|
||||
{
|
||||
int hash = 0;
|
||||
for (auto pair : levels)
|
||||
hash += (uintptr_t) pair.second;
|
||||
for (auto [_, cell] : levels)
|
||||
hash += (uintptr_t) cell;
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
|
@ -536,9 +581,12 @@ struct Index {
|
|||
if (levels.size() != other.levels.size())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < levels.size(); i++)
|
||||
if (levels[i].second != other.levels[i].second)
|
||||
for (int i = 0; i < levels.size(); i++) {
|
||||
auto* cell = levels[i].second;
|
||||
auto* other_cell = other.levels[i].second;
|
||||
if (cell != other_cell)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -579,7 +627,7 @@ struct Index {
|
|||
// an output of a cell
|
||||
Cell *driver = bit.wire->driverCell();
|
||||
|
||||
if (driver->type.in(KNOWN_OPS)) {
|
||||
if (known_ops(driver->type)) {
|
||||
ret = impl_op(cursor, driver, bit.wire->driverPort(), bit.offset);
|
||||
} else {
|
||||
Module *def = cursor.enter(*this, driver);
|
||||
|
|
@ -701,6 +749,9 @@ struct AigerWriter : Index<AigerWriter, unsigned int, 0, 1> {
|
|||
nands++;
|
||||
lit_counter += 2;
|
||||
|
||||
// In XAIGER, the ordering of inputs is used to distinguish between AND
|
||||
// and XOR gates. AND gates have their first input literal be larger
|
||||
// than their second, and vice-versa for XORs.
|
||||
if (a < b) std::swap(a, b);
|
||||
encode(out - a);
|
||||
encode(a - b);
|
||||
|
|
@ -717,7 +768,7 @@ struct AigerWriter : Index<AigerWriter, unsigned int, 0, 1> {
|
|||
log_assert(lit_counter == (Lit) (ninputs + nlatches + nands) * 2 + 2);
|
||||
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf) - 1, "aig %08d %08d %08d %08d %08d\n",
|
||||
snprintf(buf, sizeof(buf), "aig %08d %08d %08d %08d %08d\n",
|
||||
ninputs + nlatches + nands, ninputs, nlatches, noutputs, nands);
|
||||
f->write(buf, strlen(buf));
|
||||
}
|
||||
|
|
@ -730,15 +781,16 @@ struct AigerWriter : Index<AigerWriter, unsigned int, 0, 1> {
|
|||
// populate inputs
|
||||
std::vector<SigBit> inputs;
|
||||
for (auto id : top->ports) {
|
||||
Wire *w = top->wire(id);
|
||||
log_assert(w);
|
||||
if (w->port_input && !w->port_output)
|
||||
for (int i = 0; i < w->width; i++) {
|
||||
pi_literal(SigBit(w, i)) = lit_counter;
|
||||
inputs.push_back(SigBit(w, i));
|
||||
lit_counter += 2;
|
||||
ninputs++;
|
||||
}
|
||||
Wire *w = top->wire(id);
|
||||
log_assert(w);
|
||||
if (w->port_input && !w->port_output)
|
||||
for (int i = 0; i < w->width; i++) {
|
||||
auto bit = SigBit(w, i);
|
||||
pi_literal(bit) = lit_counter;
|
||||
inputs.push_back(bit);
|
||||
lit_counter += 2;
|
||||
ninputs++;
|
||||
}
|
||||
}
|
||||
|
||||
this->f = f;
|
||||
|
|
@ -746,35 +798,38 @@ struct AigerWriter : Index<AigerWriter, unsigned int, 0, 1> {
|
|||
write_header();
|
||||
// insert padding where output literals will go (once known)
|
||||
for (auto id : top->ports) {
|
||||
Wire *w = top->wire(id);
|
||||
log_assert(w);
|
||||
if (w->port_output) {
|
||||
for (auto bit : SigSpec(w)) {
|
||||
(void) bit;
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf) - 1, "%08d\n", 0);
|
||||
f->write(buf, strlen(buf));
|
||||
noutputs++;
|
||||
Wire *w = top->wire(id);
|
||||
log_assert(w);
|
||||
if (w->port_output) {
|
||||
for (auto bit : SigSpec(w)) {
|
||||
(void) bit;
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%08d\n", 0);
|
||||
f->write(buf, strlen(buf));
|
||||
noutputs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto data_start = f->tellp();
|
||||
|
||||
// now the guts
|
||||
std::vector<std::pair<SigBit, int>> outputs;
|
||||
for (auto w : top->wires())
|
||||
if (w->port_output) {
|
||||
for (auto bit : SigSpec(w))
|
||||
outputs.push_back({bit, eval_po(bit)});
|
||||
}
|
||||
if (w->port_output) {
|
||||
for (auto bit : SigSpec(w))
|
||||
// Each call to eval_po eventually reaches emit_gate and
|
||||
// encode which writes to f.
|
||||
outputs.push_back({bit, eval_po(bit)});
|
||||
}
|
||||
|
||||
auto data_end = f->tellp();
|
||||
|
||||
// revisit header and the list of outputs
|
||||
f->seekp(file_start);
|
||||
write_header();
|
||||
for (auto pair : outputs) {
|
||||
for (auto [_, po] : outputs) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf) - 1, "%08d\n", pair.second);
|
||||
snprintf(buf, sizeof(buf), "%08d\n", po);
|
||||
f->write(buf, strlen(buf));
|
||||
}
|
||||
// double check we arrived at the same offset for the
|
||||
|
|
@ -783,12 +838,13 @@ struct AigerWriter : Index<AigerWriter, unsigned int, 0, 1> {
|
|||
|
||||
f->seekp(data_end);
|
||||
int i = 0;
|
||||
for (auto pair : outputs) {
|
||||
if (SigSpec(pair.first).is_wire()) {
|
||||
for (auto [bit, _] : outputs) {
|
||||
if (SigSpec(bit).is_wire()) {
|
||||
// primary output symbol
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf) - 1, "o%d ", i);
|
||||
snprintf(buf, sizeof(buf), "o%d ", i);
|
||||
f->write(buf, strlen(buf));
|
||||
std::string name = RTLIL::unescape_id(pair.first.wire->name);
|
||||
std::string name = RTLIL::unescape_id(bit.wire->name);
|
||||
f->write(name.data(), name.size());
|
||||
f->put('\n');
|
||||
}
|
||||
|
|
@ -797,8 +853,9 @@ struct AigerWriter : Index<AigerWriter, unsigned int, 0, 1> {
|
|||
i = 0;
|
||||
for (auto bit : inputs) {
|
||||
if (SigSpec(bit).is_wire()) {
|
||||
// primary input symbol
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf) - 1, "i%d ", i);
|
||||
snprintf(buf, sizeof(buf), "i%d ", i);
|
||||
f->write(buf, strlen(buf));
|
||||
std::string name = RTLIL::unescape_id(bit.wire->name);
|
||||
f->write(name.data(), name.size());
|
||||
|
|
@ -871,33 +928,34 @@ struct XAigerAnalysis : Index<XAigerAnalysis, int, 0, 0> {
|
|||
Wire *w = top->wire(id);
|
||||
log_assert(w);
|
||||
if (w->port_input && !w->port_output)
|
||||
for (int i = 0; i < w->width; i++)
|
||||
pi_literal(SigBit(w, i)) = 0;
|
||||
for (int i = 0; i < w->width; i++)
|
||||
pi_literal(SigBit(w, i)) = 0;
|
||||
}
|
||||
|
||||
HierCursor cursor;
|
||||
for (auto box : top_minfo->found_blackboxes) {
|
||||
Module *def = design->module(box->type);
|
||||
if (!(def && def->has_attribute(ID::abc9_box_id)))
|
||||
for (auto &conn : box->connections_)
|
||||
if (box->port_dir(conn.first) != RTLIL::PD_INPUT)
|
||||
for (auto bit : conn.second)
|
||||
pi_literal(bit, &cursor) = 0;
|
||||
for (auto &conn : box->connections_)
|
||||
if (box->port_dir(conn.first) != RTLIL::PD_INPUT)
|
||||
for (auto bit : conn.second)
|
||||
pi_literal(bit, &cursor) = 0;
|
||||
}
|
||||
|
||||
for (auto w : top->wires())
|
||||
if (w->port_output) {
|
||||
for (auto bit : SigSpec(w))
|
||||
(void) eval_po(bit);
|
||||
for (auto w : top->wires()) {
|
||||
if (w->port_output) {
|
||||
for (auto bit : SigSpec(w))
|
||||
(void) eval_po(bit);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto box : top_minfo->found_blackboxes) {
|
||||
Module *def = design->module(box->type);
|
||||
if (!(def && def->has_attribute(ID::abc9_box_id)))
|
||||
for (auto &conn : box->connections_)
|
||||
if (box->port_dir(conn.first) == RTLIL::PD_INPUT)
|
||||
for (auto bit : conn.second)
|
||||
(void) eval_po(bit);
|
||||
for (auto &conn : box->connections_)
|
||||
if (box->port_dir(conn.first) == RTLIL::PD_INPUT)
|
||||
for (auto bit : conn.second)
|
||||
(void) eval_po(bit);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -916,15 +974,15 @@ struct XAigerWriter : AigerWriter {
|
|||
std::vector<HierBit> pos;
|
||||
std::vector<HierBit> pis;
|
||||
|
||||
// * The aiger output port sequence is COs (inputs to modeled boxes),
|
||||
// inputs to opaque boxes, then module outputs. COs going first is
|
||||
// required by abc.
|
||||
// * proper_pos_counter counts ports which follow after COs
|
||||
// * The mapping file `pseudopo` and `po` statements use indexing relative
|
||||
// to the first port following COs.
|
||||
// * If a module output is directly driven by an opaque box, the emission
|
||||
// of the po statement in the mapping file is skipped. This is done to
|
||||
// aid re-integration of the mapped result.
|
||||
// * The aiger output port sequence is COs (inputs to modeled boxes),
|
||||
// inputs to opaque boxes, then module outputs. COs going first is
|
||||
// required by abc.
|
||||
// * proper_pos_counter counts ports which follow after COs
|
||||
// * The mapping file `pseudopo` and `po` statements use indexing relative
|
||||
// to the first port following COs.
|
||||
// * If a module output is directly driven by an opaque box, the emission
|
||||
// of the po statement in the mapping file is skipped. This is done to
|
||||
// aid re-integration of the mapped result.
|
||||
int proper_pos_counter = 0;
|
||||
|
||||
pool<SigBit> driven_by_opaque_box;
|
||||
|
|
@ -1202,29 +1260,29 @@ struct XAigerWriter : AigerWriter {
|
|||
reset_counters();
|
||||
|
||||
for (auto w : top->wires())
|
||||
if (w->port_input && !w->port_output)
|
||||
for (int i = 0; i < w->width; i++)
|
||||
ensure_pi(SigBit(w, i));
|
||||
if (w->port_input && !w->port_output)
|
||||
for (int i = 0; i < w->width; i++)
|
||||
ensure_pi(SigBit(w, i));
|
||||
|
||||
int proper_po_num = 0;
|
||||
for (auto w : top->wires())
|
||||
if (w->port_output)
|
||||
proper_po_num += w->width;
|
||||
if (w->port_output)
|
||||
proper_po_num += w->width;
|
||||
|
||||
prep_boxes(proper_po_num);
|
||||
for (auto w : top->wires())
|
||||
if (w->port_output)
|
||||
for (int i = 0; i < w->width; i++) {
|
||||
// When a module output is directly driven by an opaque box, we
|
||||
// don't emit it to the mapping file to aid re-integration, but we
|
||||
// do emit a proper PO.
|
||||
if (map_file.is_open() && !driven_by_opaque_box.count(SigBit(w, i))) {
|
||||
map_file << "po " << proper_pos_counter << " " << i
|
||||
<< " " << w->name.c_str() << "\n";
|
||||
}
|
||||
proper_pos_counter++;
|
||||
pos.push_back(std::make_pair(SigBit(w, i), HierCursor{}));
|
||||
}
|
||||
if (w->port_output)
|
||||
for (int i = 0; i < w->width; i++) {
|
||||
// When a module output is directly driven by an opaque box, we
|
||||
// don't emit it to the mapping file to aid re-integration, but we
|
||||
// do emit a proper PO.
|
||||
if (map_file.is_open() && !driven_by_opaque_box.count(SigBit(w, i))) {
|
||||
map_file << "po " << proper_pos_counter << " " << i
|
||||
<< " " << w->name.c_str() << "\n";
|
||||
}
|
||||
proper_pos_counter++;
|
||||
pos.push_back(std::make_pair(SigBit(w, i), HierCursor{}));
|
||||
}
|
||||
|
||||
this->f = f;
|
||||
// start with the header
|
||||
|
|
@ -1234,7 +1292,7 @@ struct XAigerWriter : AigerWriter {
|
|||
// insert padding where output literals will go (once known)
|
||||
for (auto _ : pos) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf) - 1, "%08d\n", 0);
|
||||
snprintf(buf, sizeof(buf), "%08d\n", 0);
|
||||
f->write(buf, strlen(buf));
|
||||
}
|
||||
auto data_start = f->tellp();
|
||||
|
|
@ -1251,35 +1309,36 @@ struct XAigerWriter : AigerWriter {
|
|||
write_header();
|
||||
for (auto lit : outlits) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf) - 1, "%08d\n", lit);
|
||||
snprintf(buf, sizeof(buf), "%08d\n", lit);
|
||||
f->write(buf, strlen(buf));
|
||||
}
|
||||
// double check we arrived at the same offset for the
|
||||
// main data section
|
||||
log_assert(data_start == f->tellp());
|
||||
|
||||
// extensions
|
||||
// XAIGER extensions
|
||||
f->seekp(0, std::ios::end);
|
||||
|
||||
f->put('c');
|
||||
f->put('c'); // 'c': comment (marks beginning of extensions)
|
||||
|
||||
// insert empty 'r' and 's' sections (abc crashes if we provide 'a' without those)
|
||||
f->put('r');
|
||||
write_be32(*f, 4);
|
||||
write_be32(*f, 0);
|
||||
f->put('s');
|
||||
write_be32(*f, 4);
|
||||
write_be32(*f, 0);
|
||||
f->put('r'); // 'r': register classes
|
||||
write_be32(*f, 4); // length in bytes
|
||||
write_be32(*f, 0); // no register classes
|
||||
|
||||
f->put('h');
|
||||
f->put('s'); // 's': register initial values
|
||||
write_be32(*f, 4); // length in bytes
|
||||
write_be32(*f, 0); // no register initial values
|
||||
|
||||
f->put('h'); // 'h': hierarchy information
|
||||
// TODO: get rid of std::string copy
|
||||
std::string h_buffer_str = h_buffer.str();
|
||||
write_be32(*f, h_buffer_str.size());
|
||||
f->write(h_buffer_str.data(), h_buffer_str.size());
|
||||
write_be32(*f, h_buffer_str.size()); // length in bytes
|
||||
f->write(h_buffer_str.data(), h_buffer_str.size()); // data
|
||||
|
||||
#if 1
|
||||
f->put('a');
|
||||
write_be32(*f, 0); // size to be filled later
|
||||
f->put('a'); // 'a': additional AIG (used for holes)
|
||||
write_be32(*f, 0); // length in bytes (to be filled later)
|
||||
auto holes_aiger_start = f->tellp();
|
||||
{
|
||||
AigerWriter holes_writer;
|
||||
|
|
@ -1291,7 +1350,7 @@ struct XAigerWriter : AigerWriter {
|
|||
auto holes_aiger_size = f->tellp() - holes_aiger_start;
|
||||
f->seekp(holes_aiger_start, std::ios::beg);
|
||||
f->seekp(-4, std::ios::cur);
|
||||
write_be32(*f, holes_aiger_size);
|
||||
write_be32(*f, holes_aiger_size); // length in bytes
|
||||
#endif
|
||||
f->seekp(0, std::ios::end);
|
||||
|
||||
|
|
@ -1331,41 +1390,50 @@ struct Aiger2Backend : Backend {
|
|||
log(" perform structural hashing while writing\n");
|
||||
log("\n");
|
||||
log(" -flatten\n");
|
||||
log(" allow descending into submodules and write a flattened view of the design\n");
|
||||
log(" hierarchy starting at the selected top\n");
|
||||
log("\n");
|
||||
log(" allow descending into submodules and write a flattened view of the design\n");
|
||||
log(" hierarchy starting at the selected top\n");
|
||||
log("\n");
|
||||
log("This command is able to ingest all combinational cells except for:\n");
|
||||
log("\n");
|
||||
pool<IdString> supported = {KNOWN_OPS};
|
||||
CellTypes ct;
|
||||
ct.setup_internals_eval();
|
||||
log(" ");
|
||||
int col = 0;
|
||||
for (auto pair : ct.cell_types)
|
||||
if (!supported.count(pair.first)) {
|
||||
if (col + pair.first.size() + 2 > 72) {
|
||||
for (size_t i = 0; i < StaticCellTypes::builder.count; i++) {
|
||||
auto &cell = StaticCellTypes::builder.cells[i];
|
||||
if (!cell.features.is_evaluable)
|
||||
continue;
|
||||
if (cell.features.is_stdcell)
|
||||
continue;
|
||||
if (known_ops(cell.type))
|
||||
continue;
|
||||
std::string name = log_id(cell.type);
|
||||
if (col + name.size() + 2 > 72) {
|
||||
log("\n ");
|
||||
col = 0;
|
||||
}
|
||||
col += pair.first.size() + 2;
|
||||
log("%s, ", log_id(pair.first));
|
||||
col += name.size() + 2;
|
||||
log("%s, ", name.c_str());
|
||||
}
|
||||
log("\n");
|
||||
log("\n");
|
||||
log("And all combinational gates except for:\n");
|
||||
log("\n");
|
||||
CellTypes ct2;
|
||||
ct2.setup_stdcells();
|
||||
log(" ");
|
||||
col = 0;
|
||||
for (auto pair : ct2.cell_types)
|
||||
if (!supported.count(pair.first)) {
|
||||
if (col + pair.first.size() + 2 > 72) {
|
||||
for (size_t i = 0; i < StaticCellTypes::builder.count; i++) {
|
||||
auto &cell = StaticCellTypes::builder.cells[i];
|
||||
if (!cell.features.is_evaluable)
|
||||
continue;
|
||||
if (!cell.features.is_stdcell)
|
||||
continue;
|
||||
if (known_ops(cell.type))
|
||||
continue;
|
||||
std::string name = log_id(cell.type);
|
||||
if (col + name.size() + 2 > 72) {
|
||||
log("\n ");
|
||||
col = 0;
|
||||
}
|
||||
col += pair.first.size() + 2;
|
||||
log("%s, ", log_id(pair.first));
|
||||
col += name.size() + 2;
|
||||
log("%s, ", name.c_str());
|
||||
}
|
||||
log("\n");
|
||||
}
|
||||
|
|
@ -1423,20 +1491,20 @@ struct XAiger2Backend : Backend {
|
|||
log(" perform structural hashing while writing\n");
|
||||
log("\n");
|
||||
log(" -flatten\n");
|
||||
log(" allow descending into submodules and write a flattened view of the design\n");
|
||||
log(" hierarchy starting at the selected top\n");
|
||||
log("\n");
|
||||
log(" -mapping_prep\n");
|
||||
log(" after the file is written, prepare the module for reintegration of\n");
|
||||
log(" a mapping in a subsequent command. all cells which are not blackboxed nor\n");
|
||||
log(" whiteboxed are removed from the design as well as all wires which only\n");
|
||||
log(" connect to removed cells\n");
|
||||
log(" (conflicts with -flatten)\n");
|
||||
log("\n");
|
||||
log(" -map2 <file>\n");
|
||||
log(" write a map2 file which 'read_xaiger2 -sc_mapping' can read to\n");
|
||||
log(" reintegrate a mapping\n");
|
||||
log(" (conflicts with -flatten)\n");
|
||||
log(" allow descending into submodules and write a flattened view of the design\n");
|
||||
log(" hierarchy starting at the selected top\n");
|
||||
log("\n");
|
||||
log(" -mapping_prep\n");
|
||||
log(" after the file is written, prepare the module for reintegration of\n");
|
||||
log(" a mapping in a subsequent command. all cells which are not blackboxed nor\n");
|
||||
log(" whiteboxed are removed from the design as well as all wires which only\n");
|
||||
log(" connect to removed cells\n");
|
||||
log(" (conflicts with -flatten)\n");
|
||||
log("\n");
|
||||
log(" -map2 <file>\n");
|
||||
log(" write a map2 file which 'read_xaiger2 -sc_mapping' can read to\n");
|
||||
log(" reintegrate a mapping\n");
|
||||
log(" (conflicts with -flatten)\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
#include "kernel/rtlil.h"
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/log.h"
|
||||
#include <string>
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ struct BlifDumper
|
|||
RTLIL::Module *module;
|
||||
RTLIL::Design *design;
|
||||
BlifDumperConfig *config;
|
||||
CellTypes ct;
|
||||
NewCellTypes ct;
|
||||
|
||||
SigMap sigmap;
|
||||
dict<SigBit, int> init_bits;
|
||||
|
|
|
|||
|
|
@ -2776,7 +2776,8 @@ struct CxxrtlWorker {
|
|||
{
|
||||
RTLIL::Module *top_module = nullptr;
|
||||
std::vector<RTLIL::Module*> modules;
|
||||
TopoSort<RTLIL::Module*> topo_design;
|
||||
using Order = IdString::compare_ptr_by_name<RTLIL::NamedObject>;
|
||||
TopoSort<RTLIL::Module*, Order> topo_design;
|
||||
for (auto module : design->modules()) {
|
||||
if (!design->selected_module(module))
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -614,7 +614,7 @@ struct value : public expr_base<value<Bits>> {
|
|||
int64_t divisor_shift = divisor.ctlz() - dividend.ctlz();
|
||||
assert(divisor_shift >= 0);
|
||||
divisor = divisor.shl(value<Bits>{(chunk::type) divisor_shift});
|
||||
for (size_t step = 0; step <= divisor_shift; step++) {
|
||||
for (size_t step = 0; step <= (uint64_t) divisor_shift; step++) {
|
||||
quotient = quotient.shl(value<Bits>{1u});
|
||||
if (!dividend.ucmp(divisor)) {
|
||||
dividend = dividend.sub(divisor);
|
||||
|
|
@ -1119,7 +1119,7 @@ struct fmt_part {
|
|||
|
||||
case STRING: {
|
||||
buf.reserve(Bits/8);
|
||||
for (int i = 0; i < Bits; i += 8) {
|
||||
for (size_t i = 0; i < Bits; i += 8) {
|
||||
char ch = 0;
|
||||
for (int j = 0; j < 8 && i + j < int(Bits); j++)
|
||||
if (val.bit(i + j))
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#include "kernel/rtlil.h"
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/log.h"
|
||||
#include <string>
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ struct EdifBackend : public Backend {
|
|||
bool lsbidx = false;
|
||||
std::map<RTLIL::IdString, std::map<RTLIL::IdString, int>> lib_cell_ports;
|
||||
bool nogndvcc = false, gndvccy = false, keepmode = false;
|
||||
CellTypes ct(design);
|
||||
NewCellTypes ct(design);
|
||||
EdifNames edif_names;
|
||||
|
||||
size_t argidx;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "kernel/rtlil.h"
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/log.h"
|
||||
#include <string>
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ struct IntersynthBackend : public Backend {
|
|||
|
||||
std::set<std::string> conntypes_code, celltypes_code;
|
||||
std::string netlists_code;
|
||||
CellTypes ct(design);
|
||||
NewCellTypes ct(design);
|
||||
|
||||
for (auto lib : libs)
|
||||
ct.setup_design(lib);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "kernel/rtlil.h"
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/mem.h"
|
||||
#include "libs/json11/json11.hpp"
|
||||
|
|
@ -32,7 +32,7 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
|
||||
struct Smt2Worker
|
||||
{
|
||||
CellTypes ct;
|
||||
NewCellTypes ct;
|
||||
SigMap sigmap;
|
||||
RTLIL::Module *module;
|
||||
bool bvmode, memmode, wiresmode, verbose, statebv, statedt, forallmode;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "kernel/rtlil.h"
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/log.h"
|
||||
#include <string>
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
|
||||
struct SmvWorker
|
||||
{
|
||||
CellTypes ct;
|
||||
NewCellTypes ct;
|
||||
SigMap sigmap;
|
||||
RTLIL::Module *module;
|
||||
std::ostream &f;
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ FORCE:
|
|||
TEX_FILES := $(shell find . -name *.tex)
|
||||
all_tex: $(TEX_FILES:.tex=.pdf) $(TEX_FILES:.tex=.svg)
|
||||
|
||||
# limit output size to US letter (same as latexpdf output) to avoid oversize error
|
||||
%.pdf: %.dot
|
||||
$(FAKETIME) dot -Tpdf -o $@ $<
|
||||
$(FAKETIME) dot -Tpdf -Gsize="8.5,11" -o $@ $<
|
||||
|
||||
%.pdf: %.tex
|
||||
cd $(@D) && $(FAKETIME) pdflatex $(<F) --interaction=nonstopmode
|
||||
|
|
|
|||
|
|
@ -154,6 +154,10 @@ to the following Verilog code template, where ``RST_EDGE`` is ``posedge`` if
|
|||
``RST_LVL`` if ``1``, ``negedge`` otherwise, and ``SET_EDGE`` is ``posedge`` if
|
||||
``SET_LVL`` if ``1``, ``negedge`` otherwise.
|
||||
|
||||
When both set and reset are active, the state and output is undefined. The Verilog
|
||||
code model does not correspond to this due to limitations
|
||||
of synthesizable Verilog.
|
||||
|
||||
.. code-block:: verilog
|
||||
:force:
|
||||
|
||||
|
|
@ -187,6 +191,10 @@ types relate to the following Verilog code template, where ``RST_EDGE`` is
|
|||
``posedge`` if ``RST_LVL`` if ``1``, ``negedge`` otherwise, and ``SET_EDGE`` is
|
||||
``posedge`` if ``SET_LVL`` if ``1``, ``negedge`` otherwise.
|
||||
|
||||
When both set and reset are active, the state and output is undefined. The Verilog
|
||||
code model does not correspond to this due to limitations
|
||||
of synthesizable Verilog.
|
||||
|
||||
.. code-block:: verilog
|
||||
:force:
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ D-type flip-flops with asynchronous set and reset are represented by `$dffsr`
|
|||
cells. As the `$dff` cells they have ``CLK``, ``D`` and ``Q`` ports. In addition
|
||||
they also have multi-bit ``SET`` and ``CLR`` input ports and the corresponding
|
||||
polarity parameters, like `$sr` cells.
|
||||
When both set and reset are active, the state and output is undefined.
|
||||
|
||||
D-type flip-flops with enable are represented by `$dffe`, `$adffe`, `$aldffe`,
|
||||
`$dffsre`, `$sdffe`, and `$sdffce` cells, which are enhanced variants of `$dff`,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ begin:
|
|||
proc
|
||||
|
||||
flatten:
|
||||
check
|
||||
flatten
|
||||
tribuf -logic
|
||||
deminout
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import os
|
|||
project = 'YosysHQ Yosys'
|
||||
author = 'YosysHQ GmbH'
|
||||
copyright ='2026 YosysHQ GmbH'
|
||||
yosys_ver = "0.62"
|
||||
yosys_ver = "0.64"
|
||||
|
||||
# select HTML theme
|
||||
html_theme = 'furo-ys'
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ is still valid.
|
|||
:caption: Example test.sh for C-Reduce
|
||||
:name: egtest
|
||||
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
verilator --lint-only test.v &&/
|
||||
yosys -p 'logger -expect error "unsupported" 1; read_verilog test.v'
|
||||
|
||||
|
|
@ -333,7 +333,7 @@ an input argument: ``sv-bugpoint outDir/ test.sh test.v``
|
|||
.. code-block:: bash
|
||||
:caption: Example test.sh for sv-bugpoint
|
||||
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
verilator --lint-only $1 &&/
|
||||
yosys -p "logger -expect error \"unsupported\" 1; read_verilog $1"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ Contributing to Yosys
|
|||
Reporting bugs
|
||||
--------------
|
||||
|
||||
A good bug report includes the following information:
|
||||
We fix well-reported bugs the fastest. A good bug report is an issue on the `issue tracker`_
|
||||
and includes the following information:
|
||||
|
||||
.. _`issue tracker`: https://github.com/YosysHQ/yosys/issues
|
||||
|
||||
Title
|
||||
~~~~~
|
||||
|
|
@ -27,8 +29,10 @@ The reproduction steps should be a minimal, complete and verifiable
|
|||
example `MVCE`_.
|
||||
Providing an MVCE with your bug report drastically increases the likelihood that
|
||||
someone will be able to help resolve your issue.
|
||||
One way to minimize a design is to use the `bugpoint_` command.
|
||||
You can learn more in the `how-to guide for bugpoint_`.
|
||||
Make sure that your report input is free of any problems as reported by the
|
||||
`check` command.
|
||||
One way to minimize a design is to use the `bugpoint` command.
|
||||
You can learn more in the :doc:`how-to guide for bugpoint </using_yosys/bugpoint>`.
|
||||
|
||||
The reproduction steps are ideally a code-block (starting and ending with
|
||||
triple backquotes) containing
|
||||
|
|
@ -85,7 +89,6 @@ Don't forget to mention:
|
|||
reproduction steps to just the Yosys part.
|
||||
|
||||
.. _MVCE: https://stackoverflow.com/help/minimal-reproducible-example
|
||||
.. _bugpoint: https://yosys.readthedocs.io/en/latest/cmd/bugpoint.html
|
||||
.. _how-to guide for bugpoint: https://yosys.readthedocs.io/en/latest/using_yosys/bugpoint.html
|
||||
|
||||
Expected Behaviour
|
||||
|
|
@ -176,6 +179,12 @@ based on their descriptions first, code second.
|
|||
|
||||
Before you build or fix something, also search for existing `issues`_.
|
||||
|
||||
We have open `developer 'jour fixe' (Dev JF) meetings`_
|
||||
where developers from YosysHQ and the
|
||||
community come together to discuss open issues and PRs. This is also a good
|
||||
place to talk to us about how to implement larger PRs.
|
||||
|
||||
.. _`developer 'jour fixe' (Dev JF) meetings`: https://docs.google.com/document/d/1SapA6QAsJcsgwsdKJDgnGR2mr97pJjV4eeXg_TVJhRU/edit?usp=sharing
|
||||
.. _`Discourse forum`: https://yosyshq.discourse.group/
|
||||
.. _`issues`: https://github.com/YosysHQ/yosys/issues
|
||||
|
||||
|
|
@ -297,6 +306,26 @@ Otherwise stick to the `Linux Kernel Coding Style`_.
|
|||
|
||||
.. _Linux Kernel Coding Style: https://www.kernel.org/doc/Documentation/process/coding-style.rst
|
||||
|
||||
Pull requests (PRs)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are working on something to add to Yosys, or fix something that isn't
|
||||
working quite right,
|
||||
make a `pull request (PR)`_.
|
||||
|
||||
An open PR, even as a draft, tells everyone that you're working on it and they
|
||||
don't have to. It can also be a useful way to solicit feedback on in-progress
|
||||
changes.
|
||||
|
||||
We use `labels`_ to help categorise
|
||||
issues and PRs. If a label seems relevant to your work, please do add it; this
|
||||
also includes the labels beginning with 'status-'. The 'merge-' labels are used
|
||||
by maintainers for tracking and communicating which PRs are ready and pending
|
||||
merge; please do not use these labels if you are not a maintainer.
|
||||
|
||||
.. _`pull request (PR)`: https://github.com/YosysHQ/yosys/pulls
|
||||
.. _`labels`: https://github.com/YosysHQ/yosys/labels
|
||||
|
||||
Git style
|
||||
~~~~~~~~~
|
||||
|
||||
|
|
@ -317,10 +346,12 @@ Reviewing PRs is a totally valid form of external contributing to the project!
|
|||
Who's the reviewer?
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Yosys HQ is a company with the inherited mandate to make decisions on behalf
|
||||
of the open source project. As such, we at HQ are collectively the maintainers.
|
||||
Within HQ, we allocate reviews based on expertise with the topic at hand
|
||||
as well as member time constraints.
|
||||
YosysHQ GmbH is a company with a mandate to make decisions for the good
|
||||
of YosysHQ open source software. It was co-founded by Claire Xenia Wolf,
|
||||
the original author of Yosys.
|
||||
Within it, we allocate reviews based on expertise with the topic at hand
|
||||
as well as member time constraints. However, decisions including reviews
|
||||
are also contributed by people external to the company.
|
||||
|
||||
If you're intimately acquainted with a part of the codebase, we will be happy
|
||||
to defer to your experience and have you review PRs. The official way we like
|
||||
|
|
@ -339,7 +370,8 @@ and stop being responsive, in the future, we might decide to remove such code
|
|||
if convenient and costly to maintain. It's simply more respectful of the users'
|
||||
time to explicitly cut something out than let it "bitrot". Larger projects like
|
||||
LLVM or linux could not survive without such things, but Yosys is far smaller,
|
||||
and there are expectations
|
||||
and there are implicit expectations of stability we aim to
|
||||
respect within reason.
|
||||
|
||||
.. TODO this deserves its own section elsewhere I think? But it would be distracting elsewhere
|
||||
|
||||
|
|
@ -375,3 +407,5 @@ they just are good enough to merge as-is.
|
|||
The CI is required to go green for merging. New contributors need a CI
|
||||
run to be triggered by a maintainer before their PRs take up computing
|
||||
resources. It's a single click from the github web interface.
|
||||
We test on various platforms and compilers. Sanitizer builds are only
|
||||
tested on the main branch.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ in the PATH. E.g. extract the release to /usr/local/libexec/super_prove
|
|||
and then create a /usr/local/bin/super_prove file with the following
|
||||
contents (and "chmod +x" that file):
|
||||
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
exec /usr/local/libexec/super_prove/bin/super_prove.sh "$@"
|
||||
|
||||
The "demo.sh" script also expects the "z3" SMT2 solver in the PATH for
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
yosys -p '
|
||||
read_verilog -formal demo.v
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
yosys demo.ys
|
||||
$TD_HOME/bin/td build.tcl
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
yosys run_yosys.ys
|
||||
vivado -nolog -nojournal -mode batch -source run_vivado.tcl
|
||||
vivado -nolog -nojournal -mode batch -source run_prog.tcl
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
yosys -p "synth_gowin -top demo -vout demo_syn.v" demo.v
|
||||
$GOWIN_HOME/bin/gowin -d demo_syn.v -cst demo.cst -sdc demo.sdc -p GW1NR-9-QFN88-6 -pn GW1NR-LV9QN88C6/I5 -cfg device.cfg -bit -tr -ph -timing -gpa -rpt -warning_all
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
yosys -p 'synth_sf2 -top example -edif netlist.edn -vlog netlist.vm' example.v
|
||||
export LM_LICENSE_FILE=${LM_LICENSE_FILE:-1702@localhost}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export REV="de2i"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
iverilog -D POST_IMPL -o verif_post -s tb_top tb_top.v top.vqm $(yosys-config --datdir/altera_intel/max10/cells_comb_max10.v)
|
||||
vvp -N verif_post
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
yosys -p "synth_intel -family cycloneiv -top lfsr_updown -vqm top.vqm" lfsr_updown.v
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
yosys -p "synth_intel -family max10 -top lfsr_updown -vqm top.vqm" lfsr_updown.v
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
iverilog -D POST_IMPL -o verif_post -s tb lfsr_updown_tb.v top.vqm $(yosys-config --datdir/altera_intel/max10/cells_comb_max10.v)
|
||||
vvp -N verif_post
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
iverilog -o presynth lfsr_updown_tb.v lfsr_updown.v &&\
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
yosys run_yosys.ys
|
||||
edif2ngd example.edif
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "aigerparse.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
|
||||
uint32_t read_be32(std::istream &f) {
|
||||
return ((uint32_t) f.get() << 24) |
|
||||
((uint32_t) f.get() << 16) |
|
||||
((uint32_t) f.get() << 16) |
|
||||
((uint32_t) f.get() << 8) | (uint32_t) f.get();
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +80,9 @@ struct Xaiger2Frontend : public Frontend {
|
|||
extra_args(f, filename, args, argidx, true);
|
||||
|
||||
if (map_filename.empty())
|
||||
log_error("A '-map2' argument required\n");
|
||||
log_error("A '-map2' argument is required\n");
|
||||
if (module_name.empty())
|
||||
log_error("A '-module_name' argument required\n");
|
||||
log_error("A '-module_name' argument is required\n");
|
||||
|
||||
Module *module = design->module(module_name);
|
||||
if (!module)
|
||||
|
|
@ -128,10 +128,10 @@ struct Xaiger2Frontend : public Frontend {
|
|||
int woffset;
|
||||
std::string name;
|
||||
if (!(map_file >> pi_idx >> woffset >> name))
|
||||
log_error("Bad map file (1)\n");
|
||||
log_error("Bad map file: couldn't read 'pi' line\n");
|
||||
int lit = (2 * pi_idx) + 2;
|
||||
if (lit < 0 || lit >= (int) bits.size())
|
||||
log_error("Bad map file (2)\n");
|
||||
log_error("Bad map file: primary input literal out of range\n");
|
||||
Wire *w = module->wire(name);
|
||||
if (!w || woffset < 0 || woffset >= w->width)
|
||||
log_error("Map file references non-existent signal bit %s[%d]\n",
|
||||
|
|
@ -141,9 +141,9 @@ struct Xaiger2Frontend : public Frontend {
|
|||
int box_seq;
|
||||
std::string name;
|
||||
if (!(map_file >> box_seq >> name))
|
||||
log_error("Bad map file (20)\n");
|
||||
log_error("Bad map file: couldn't read 'box' line\n");
|
||||
if (box_seq < 0)
|
||||
log_error("Bad map file (21)\n");
|
||||
log_error("Bad map file: box out of range\n");
|
||||
|
||||
Cell *box = module->cell(RTLIL::escape_id(name));
|
||||
if (!box)
|
||||
|
|
@ -158,7 +158,7 @@ struct Xaiger2Frontend : public Frontend {
|
|||
}
|
||||
|
||||
if (!def)
|
||||
log_error("Bad map file (22)\n");
|
||||
log_error("Bad map file: no module found for box type '%s'\n", log_id(box->type));
|
||||
|
||||
if (box_seq >= (int) boxes.size()) {
|
||||
boxes.resize(box_seq + 1);
|
||||
|
|
@ -403,15 +403,15 @@ struct Xaiger2Frontend : public Frontend {
|
|||
int woffset;
|
||||
std::string name;
|
||||
if (!(map_file >> po_idx >> woffset >> name))
|
||||
log_error("Bad map file (3)\n");
|
||||
log_error("Bad map file: couldn't read 'po' line\n");
|
||||
po_idx += co_counter;
|
||||
if (po_idx < 0 || po_idx >= (int) outputs.size())
|
||||
log_error("Bad map file (4)\n");
|
||||
log_error("Bad map file: primary output index out of range\n");
|
||||
int lit = outputs[po_idx];
|
||||
if (lit < 0 || lit >= (int) bits.size())
|
||||
log_error("Bad map file (5)\n");
|
||||
log_error("Bad map file: primary output literal out of range\n");
|
||||
if (bits[lit] == RTLIL::Sm)
|
||||
log_error("Bad map file (6)\n");
|
||||
log_error("Bad map file: primary output literal is a marker\n");
|
||||
Wire *w = module->wire(name);
|
||||
if (!w || woffset < 0 || woffset >= w->width)
|
||||
log_error("Map file references non-existent signal bit %s[%d]\n",
|
||||
|
|
@ -423,15 +423,15 @@ struct Xaiger2Frontend : public Frontend {
|
|||
std::string box_name;
|
||||
std::string box_port;
|
||||
if (!(map_file >> po_idx >> poffset >> box_name >> box_port))
|
||||
log_error("Bad map file (7)\n");
|
||||
log_error("Bad map file: couldn't read 'pseudopo' line\n");
|
||||
po_idx += co_counter;
|
||||
if (po_idx < 0 || po_idx >= (int) outputs.size())
|
||||
log_error("Bad map file (8)\n");
|
||||
log_error("Bad map file: pseudo primary output index out of range\n");
|
||||
int lit = outputs[po_idx];
|
||||
if (lit < 0 || lit >= (int) bits.size())
|
||||
log_error("Bad map file (9)\n");
|
||||
log_error("Bad map file: pseudo primary output literal out of range\n");
|
||||
if (bits[lit] == RTLIL::Sm)
|
||||
log_error("Bad map file (10)\n");
|
||||
log_error("Bad map file: pseudo primary output literal is a marker\n");
|
||||
Cell *cell = module->cell(box_name);
|
||||
if (!cell || !cell->hasPort(box_port))
|
||||
log_error("Map file references non-existent box port %s/%s\n",
|
||||
|
|
|
|||
|
|
@ -342,6 +342,9 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
// The most recently assigned $print or $check cell \PRIORITY.
|
||||
int last_effect_priority;
|
||||
|
||||
// Track which signals have been assigned in current_case to avoid unnecessary removeSignalFromCaseTree calls
|
||||
pool<RTLIL::SigBit> current_case_assigned_bits;
|
||||
|
||||
ProcessGenerator(std::unique_ptr<AstNode> a, RTLIL::SigSpec initSyncSignalsArg = RTLIL::SigSpec()) : always(std::move(a)), initSyncSignals(initSyncSignalsArg), last_effect_priority(0)
|
||||
{
|
||||
// rewrite lookahead references
|
||||
|
|
@ -403,6 +406,18 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
if (GetSize(syncrule->signal) != 1)
|
||||
always->input_error("Found posedge/negedge event on a signal that is not 1 bit wide!\n");
|
||||
addChunkActions(syncrule->actions, subst_lvalue_from, subst_lvalue_to, true);
|
||||
// Automatic (nosync) variables must not become flip-flops: remove
|
||||
// them from clocked sync rules so that proc_dff does not infer
|
||||
// an unnecessary register for a purely combinational temporary.
|
||||
syncrule->actions.erase(
|
||||
std::remove_if(syncrule->actions.begin(), syncrule->actions.end(),
|
||||
[](const RTLIL::SigSig &ss) {
|
||||
for (auto &chunk : ss.first.chunks())
|
||||
if (chunk.wire && chunk.wire->get_bool_attribute(ID::nosync))
|
||||
return true;
|
||||
return false;
|
||||
}),
|
||||
syncrule->actions.end());
|
||||
proc->syncs.push_back(syncrule);
|
||||
}
|
||||
if (proc->syncs.empty()) {
|
||||
|
|
@ -418,6 +433,10 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
subst_rvalue_map = subst_lvalue_from.to_sigbit_dict(RTLIL::SigSpec(RTLIL::State::Sx, GetSize(subst_lvalue_from)));
|
||||
} else {
|
||||
addChunkActions(current_case->actions, subst_lvalue_to, subst_lvalue_from);
|
||||
// Track initial assignments
|
||||
for (auto &bit : subst_lvalue_to)
|
||||
if (bit.wire != NULL)
|
||||
current_case_assigned_bits.insert(bit);
|
||||
}
|
||||
|
||||
// process the AST
|
||||
|
|
@ -545,14 +564,42 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
// e.g. when the last statement in the code "a = 23; if (b) a = 42; a = 0;" is processed this
|
||||
// function is called to clean up the first two assignments as they are overwritten by
|
||||
// the third assignment.
|
||||
void removeSignalFromCaseTree(const RTLIL::SigSpec &pattern, RTLIL::CaseRule *cs)
|
||||
void removeSignalFromCaseTree(const pool<RTLIL::SigBit> &pattern_bits, RTLIL::CaseRule *cs)
|
||||
{
|
||||
for (auto it = cs->actions.begin(); it != cs->actions.end(); it++)
|
||||
it->first.remove2(pattern, &it->second);
|
||||
// Optimization: check actions in reverse order and stop early if we've found all pattern bits
|
||||
pool<RTLIL::SigBit> remaining_bits = pattern_bits;
|
||||
|
||||
for (auto it = cs->actions.rbegin(); it != cs->actions.rend(); ++it) {
|
||||
bool has_pattern = false;
|
||||
for (auto &bit : it->first) {
|
||||
if (bit.wire != NULL && remaining_bits.count(bit)) {
|
||||
has_pattern = true;
|
||||
remaining_bits.erase(bit);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_pattern) {
|
||||
it->first.remove2(pattern_bits, &it->second);
|
||||
}
|
||||
|
||||
// Early exit if we've processed all bits in pattern
|
||||
if (remaining_bits.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto it = cs->switches.begin(); it != cs->switches.end(); it++)
|
||||
for (auto it2 = (*it)->cases.begin(); it2 != (*it)->cases.end(); it2++)
|
||||
removeSignalFromCaseTree(pattern, *it2);
|
||||
removeSignalFromCaseTree(pattern_bits, *it2);
|
||||
}
|
||||
|
||||
void removeSignalFromCaseTree(const RTLIL::SigSpec &pattern, RTLIL::CaseRule *cs)
|
||||
{
|
||||
pool<RTLIL::SigBit> pattern_bits;
|
||||
pattern_bits.reserve(pattern.size());
|
||||
for (auto &bit : pattern)
|
||||
if (bit.wire != NULL)
|
||||
pattern_bits.insert(bit);
|
||||
removeSignalFromCaseTree(pattern_bits, cs);
|
||||
}
|
||||
|
||||
// add an assignment (aka "action") but split it up in chunks. this way huge assignments
|
||||
|
|
@ -611,7 +658,23 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
subst_rvalue_map.set(unmapped_lvalue[i], rvalue[i]);
|
||||
}
|
||||
|
||||
removeSignalFromCaseTree(lvalue, current_case);
|
||||
// Check if any bits in lvalue have been assigned before in current_case
|
||||
bool has_overlap = false;
|
||||
for (auto &bit : lvalue) {
|
||||
if (bit.wire != NULL && current_case_assigned_bits.count(bit)) {
|
||||
has_overlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_overlap)
|
||||
removeSignalFromCaseTree(lvalue, current_case);
|
||||
|
||||
// Track newly assigned bits
|
||||
for (auto &bit : lvalue)
|
||||
if (bit.wire != NULL)
|
||||
current_case_assigned_bits.insert(bit);
|
||||
|
||||
remove_unwanted_lvalue_bits(lvalue, rvalue);
|
||||
current_case->actions.push_back(RTLIL::SigSig(lvalue, rvalue));
|
||||
}
|
||||
|
|
@ -658,9 +721,15 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
|
||||
RTLIL::CaseRule *backup_case = current_case;
|
||||
current_case = new RTLIL::CaseRule;
|
||||
pool<RTLIL::SigBit> backup_assigned_bits = std::move(current_case_assigned_bits);
|
||||
current_case_assigned_bits.clear();
|
||||
set_src_attr(current_case, child.get());
|
||||
last_generated_case = current_case;
|
||||
addChunkActions(current_case->actions, this_case_eq_ltemp, this_case_eq_rvalue);
|
||||
// Track temp assignments
|
||||
for (auto &bit : this_case_eq_ltemp)
|
||||
if (bit.wire != NULL)
|
||||
current_case_assigned_bits.insert(bit);
|
||||
for (auto& node : child->children) {
|
||||
if (node->type == AST_DEFAULT)
|
||||
default_case = current_case;
|
||||
|
|
@ -674,6 +743,7 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
else
|
||||
log_assert(current_case->compare.size() == 0);
|
||||
current_case = backup_case;
|
||||
current_case_assigned_bits = std::move(backup_assigned_bits);
|
||||
|
||||
subst_lvalue_map.restore();
|
||||
subst_rvalue_map.restore();
|
||||
|
|
@ -702,8 +772,24 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
subst_rvalue_map.set(this_case_eq_lvalue[i], this_case_eq_ltemp[i]);
|
||||
|
||||
this_case_eq_lvalue.replace(subst_lvalue_map.stdmap());
|
||||
removeSignalFromCaseTree(this_case_eq_lvalue, current_case);
|
||||
|
||||
// Check if any bits in lvalue have been assigned before in current_case
|
||||
bool has_overlap = false;
|
||||
for (auto &bit : this_case_eq_lvalue) {
|
||||
if (bit.wire != NULL && current_case_assigned_bits.count(bit)) {
|
||||
has_overlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_overlap)
|
||||
removeSignalFromCaseTree(this_case_eq_lvalue, current_case);
|
||||
|
||||
addChunkActions(current_case->actions, this_case_eq_lvalue, this_case_eq_ltemp);
|
||||
// Track newly assigned bits
|
||||
for (auto &bit : this_case_eq_lvalue)
|
||||
if (bit.wire != NULL)
|
||||
current_case_assigned_bits.insert(bit);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -712,7 +798,7 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
break;
|
||||
|
||||
case AST_ASSIGN:
|
||||
ast->input_error("Found continous assignment in always/initial block!\n");
|
||||
ast->input_error("Found continuous assignment in always/initial block!\n");
|
||||
break;
|
||||
|
||||
case AST_PARAMETER:
|
||||
|
|
|
|||
|
|
@ -269,6 +269,83 @@ static int add_dimension(AstNode *node, AstNode *rnode)
|
|||
node->input_error("Unpacked array in packed struct/union member %s\n", node->str);
|
||||
}
|
||||
|
||||
// Check if node is an unexpanded array reference (AST_IDENTIFIER -> AST_MEMORY without indexing)
|
||||
static bool is_unexpanded_array_ref(AstNode *node)
|
||||
{
|
||||
if (node->type != AST_IDENTIFIER)
|
||||
return false;
|
||||
if (node->id2ast == nullptr || node->id2ast->type != AST_MEMORY)
|
||||
return false;
|
||||
// No indexing children = whole array reference
|
||||
return node->children.empty();
|
||||
}
|
||||
|
||||
// Check if two memories have compatible unpacked dimensions for array assignment
|
||||
static bool arrays_have_compatible_dims(AstNode *mem_a, AstNode *mem_b)
|
||||
{
|
||||
if (mem_a->unpacked_dimensions != mem_b->unpacked_dimensions)
|
||||
return false;
|
||||
for (int i = 0; i < mem_a->unpacked_dimensions; i++) {
|
||||
if (mem_a->dimensions[i].range_width != mem_b->dimensions[i].range_width)
|
||||
return false;
|
||||
}
|
||||
// Also check packed dimensions (element width)
|
||||
int a_width, a_size, a_bits;
|
||||
int b_width, b_size, b_bits;
|
||||
mem_a->meminfo(a_width, a_size, a_bits);
|
||||
mem_b->meminfo(b_width, b_size, b_bits);
|
||||
return a_width == b_width;
|
||||
}
|
||||
|
||||
// Convert per-dimension element positions to declared index values.
|
||||
// Position 0 is the first declared element for each unpacked dimension.
|
||||
static std::vector<int> array_indices_from_position(AstNode *mem, const std::vector<int> &position)
|
||||
{
|
||||
int num_dims = mem->unpacked_dimensions;
|
||||
log_assert(GetSize(position) == num_dims);
|
||||
|
||||
std::vector<int> indices(num_dims);
|
||||
for (int d = 0; d < num_dims; d++) {
|
||||
int low = mem->dimensions[d].range_right;
|
||||
int high = low + mem->dimensions[d].range_width - 1;
|
||||
indices[d] = mem->dimensions[d].range_swapped ? (low + position[d]) : (high - position[d]);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
// Generate all element positions for a multi-dimensional unpacked array and
|
||||
// call callback once for each combination.
|
||||
static void foreach_array_position(AstNode *mem, std::function<void(const std::vector<int>&)> callback)
|
||||
{
|
||||
int num_dims = mem->unpacked_dimensions;
|
||||
if (num_dims == 0) {
|
||||
callback({});
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> position(num_dims, 0);
|
||||
std::vector<int> sizes(num_dims);
|
||||
|
||||
for (int d = 0; d < num_dims; d++)
|
||||
sizes[d] = mem->dimensions[d].range_width;
|
||||
|
||||
// Iterate through all position combinations (rightmost dimension fastest).
|
||||
while (true) {
|
||||
callback(position);
|
||||
|
||||
int d = num_dims - 1;
|
||||
while (d >= 0) {
|
||||
position[d]++;
|
||||
if (position[d] < sizes[d])
|
||||
break;
|
||||
position[d] = 0;
|
||||
d--;
|
||||
}
|
||||
if (d < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int size_packed_struct(AstNode *snode, int base_offset)
|
||||
{
|
||||
// Struct members will be laid out in the structure contiguously from left to right.
|
||||
|
|
@ -3200,6 +3277,123 @@ skip_dynamic_range_lvalue_expansion:;
|
|||
}
|
||||
}
|
||||
|
||||
// Expand array assignment: arr_out = arr_in OR arr_out = cond ? arr_a : arr_b
|
||||
// Supports multi-dimensional unpacked arrays
|
||||
if ((type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE || type == AST_ASSIGN) &&
|
||||
is_unexpanded_array_ref(children[0].get()))
|
||||
{
|
||||
AstNode *lhs = children[0].get();
|
||||
AstNode *rhs = children[1].get();
|
||||
AstNode *lhs_mem = lhs->id2ast;
|
||||
|
||||
// Case 1: Direct array assignment (b = a)
|
||||
bool is_direct_assign = is_unexpanded_array_ref(rhs);
|
||||
|
||||
// Case 2: Ternary array assignment (out = sel ? a : b)
|
||||
bool is_ternary_assign = (rhs->type == AST_TERNARY &&
|
||||
is_unexpanded_array_ref(rhs->children[1].get()) &&
|
||||
is_unexpanded_array_ref(rhs->children[2].get()));
|
||||
|
||||
if (is_direct_assign || is_ternary_assign)
|
||||
{
|
||||
AstNode *direct_rhs_mem = nullptr;
|
||||
AstNode *true_mem = nullptr;
|
||||
AstNode *false_mem = nullptr;
|
||||
|
||||
// Validate array compatibility
|
||||
if (is_direct_assign) {
|
||||
direct_rhs_mem = rhs->id2ast;
|
||||
if (!arrays_have_compatible_dims(lhs_mem, direct_rhs_mem))
|
||||
input_error("Array dimension mismatch in assignment\n");
|
||||
} else {
|
||||
true_mem = rhs->children[1]->id2ast;
|
||||
false_mem = rhs->children[2]->id2ast;
|
||||
if (!arrays_have_compatible_dims(lhs_mem, true_mem) ||
|
||||
!arrays_have_compatible_dims(lhs_mem, false_mem))
|
||||
input_error("Array dimension mismatch in ternary expression\n");
|
||||
}
|
||||
|
||||
int num_dims = lhs_mem->unpacked_dimensions;
|
||||
|
||||
// Helper to add index to an identifier clone
|
||||
auto add_indices_to_id = [&](std::unique_ptr<AstNode> id, const std::vector<int>& indices) {
|
||||
if (num_dims == 1) {
|
||||
// Single dimension: use AST_RANGE
|
||||
id->children.push_back(std::make_unique<AstNode>(location, AST_RANGE,
|
||||
mkconst_int(location, indices[0], true)));
|
||||
} else {
|
||||
// Multiple dimensions: use AST_MULTIRANGE
|
||||
auto multirange = std::make_unique<AstNode>(location, AST_MULTIRANGE);
|
||||
for (int idx : indices) {
|
||||
multirange->children.push_back(std::make_unique<AstNode>(location, AST_RANGE,
|
||||
mkconst_int(location, idx, true)));
|
||||
}
|
||||
id->children.push_back(std::move(multirange));
|
||||
}
|
||||
id->integer = num_dims;
|
||||
// Reset basic_prep so multirange gets resolved during subsequent simplify passes
|
||||
id->basic_prep = false;
|
||||
return id;
|
||||
};
|
||||
|
||||
// Calculate total number of elements and warn if large
|
||||
int total_elements = 1;
|
||||
for (int d = 0; d < num_dims; d++)
|
||||
total_elements *= lhs_mem->dimensions[d].range_width;
|
||||
if (total_elements > 10000)
|
||||
log_warning("Expanding array assignment with %d elements at %s, this may be slow.\n",
|
||||
total_elements, location.to_string().c_str());
|
||||
|
||||
// Collect all assignments
|
||||
std::vector<std::unique_ptr<AstNode>> assignments;
|
||||
|
||||
foreach_array_position(lhs_mem, [&](const std::vector<int>& position) {
|
||||
auto lhs_indices = array_indices_from_position(lhs_mem, position);
|
||||
auto lhs_idx = add_indices_to_id(lhs->clone(), lhs_indices);
|
||||
|
||||
std::unique_ptr<AstNode> rhs_expr;
|
||||
if (is_direct_assign) {
|
||||
auto rhs_indices = array_indices_from_position(direct_rhs_mem, position);
|
||||
rhs_expr = add_indices_to_id(rhs->clone(), rhs_indices);
|
||||
} else {
|
||||
// Ternary case
|
||||
AstNode *cond = rhs->children[0].get();
|
||||
AstNode *true_val = rhs->children[1].get();
|
||||
AstNode *false_val = rhs->children[2].get();
|
||||
|
||||
auto true_indices = array_indices_from_position(true_mem, position);
|
||||
auto false_indices = array_indices_from_position(false_mem, position);
|
||||
auto true_idx = add_indices_to_id(true_val->clone(), true_indices);
|
||||
auto false_idx = add_indices_to_id(false_val->clone(), false_indices);
|
||||
|
||||
rhs_expr = std::make_unique<AstNode>(location, AST_TERNARY,
|
||||
cond->clone(), std::move(true_idx), std::move(false_idx));
|
||||
}
|
||||
|
||||
auto assign = std::make_unique<AstNode>(location, type,
|
||||
std::move(lhs_idx), std::move(rhs_expr));
|
||||
assign->was_checked = true;
|
||||
assignments.push_back(std::move(assign));
|
||||
});
|
||||
|
||||
// For continuous assignments, add to module; for procedural, use block
|
||||
if (type == AST_ASSIGN) {
|
||||
// Add all but last to module
|
||||
for (size_t i = 0; i + 1 < assignments.size(); i++)
|
||||
current_ast_mod->children.push_back(std::move(assignments[i]));
|
||||
// Last one replaces current node
|
||||
newNode = std::move(assignments.back());
|
||||
} else {
|
||||
// Wrap in AST_BLOCK for procedural
|
||||
newNode = std::make_unique<AstNode>(location, AST_BLOCK);
|
||||
for (auto& assign : assignments)
|
||||
newNode->children.push_back(std::move(assign));
|
||||
}
|
||||
|
||||
goto apply_newNode;
|
||||
}
|
||||
}
|
||||
|
||||
// assignment with memory in left-hand side expression -> replace with memory write port
|
||||
if (stage > 1 && (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) && children[0]->type == AST_IDENTIFIER &&
|
||||
children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY && children[0]->id2ast->children.size() >= 2 &&
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "passes/techmap/libparse.h"
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/log.h"
|
||||
#include <array>
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
|
@ -210,7 +211,10 @@ static void create_ff(RTLIL::Module *module, const LibertyAst *node)
|
|||
auto [iq_sig, iqn_sig] = find_latch_ff_wires(module, node);
|
||||
RTLIL::SigSpec clk_sig, data_sig, clear_sig, preset_sig;
|
||||
bool clk_polarity = true, clear_polarity = true, preset_polarity = true;
|
||||
const std::string name = RTLIL::unescape_id(module->name);
|
||||
|
||||
std::optional<char> clear_preset_var1;
|
||||
std::optional<char> clear_preset_var2;
|
||||
for (auto child : node->children) {
|
||||
if (child->id == "clocked_on")
|
||||
clk_sig = parse_func_expr(module, child->value.c_str());
|
||||
|
|
@ -220,10 +224,18 @@ static void create_ff(RTLIL::Module *module, const LibertyAst *node)
|
|||
clear_sig = parse_func_expr(module, child->value.c_str());
|
||||
if (child->id == "preset")
|
||||
preset_sig = parse_func_expr(module, child->value.c_str());
|
||||
|
||||
for (auto& [id, var] : {pair{"clear_preset_var1", &clear_preset_var1}, {"clear_preset_var2", &clear_preset_var2}})
|
||||
if (child->id == id) {
|
||||
if (child->value.size() != 1)
|
||||
log_error("Unexpected length of clear_preset_var* value %s in FF cell %s\n", child->value, name);
|
||||
*var = child->value[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (clk_sig.size() == 0 || data_sig.size() == 0)
|
||||
log_error("FF cell %s has no next_state and/or clocked_on attribute.\n", RTLIL::unescape_id(module->name));
|
||||
log_error("FF cell %s has no next_state and/or clocked_on attribute.\n", name);
|
||||
|
||||
for (bool rerun_invert_rollback = true; rerun_invert_rollback;)
|
||||
{
|
||||
|
|
@ -248,36 +260,64 @@ static void create_ff(RTLIL::Module *module, const LibertyAst *node)
|
|||
}
|
||||
}
|
||||
|
||||
RTLIL::Cell *cell = module->addCell(NEW_ID, ID($_NOT_));
|
||||
cell->setPort(ID::A, iq_sig);
|
||||
cell->setPort(ID::Y, iqn_sig);
|
||||
for (auto& [out_sig, cp_var, neg] : {tuple{iq_sig, clear_preset_var1, false}, {iqn_sig, clear_preset_var2, true}}) {
|
||||
SigSpec q_sig = out_sig;
|
||||
if (neg) {
|
||||
q_sig = module->addWire(NEW_ID, out_sig.as_wire());
|
||||
module->addNotGate(NEW_ID, q_sig, out_sig);
|
||||
}
|
||||
|
||||
cell = module->addCell(NEW_ID, "");
|
||||
cell->setPort(ID::D, data_sig);
|
||||
cell->setPort(ID::Q, iq_sig);
|
||||
cell->setPort(ID::C, clk_sig);
|
||||
RTLIL::Cell* cell = module->addCell(NEW_ID, "");
|
||||
cell->setPort(ID::D, data_sig);
|
||||
cell->setPort(ID::Q, q_sig);
|
||||
cell->setPort(ID::C, clk_sig);
|
||||
|
||||
if (clear_sig.size() == 0 && preset_sig.size() == 0) {
|
||||
cell->type = stringf("$_DFF_%c_", clk_polarity ? 'P' : 'N');
|
||||
if (clear_sig.size() == 0 && preset_sig.size() == 0) {
|
||||
cell->type = stringf("$_DFF_%c_", clk_polarity ? 'P' : 'N');
|
||||
}
|
||||
|
||||
if (clear_sig.size() == 1 && preset_sig.size() == 0) {
|
||||
cell->type = stringf("$_DFF_%c%c0_", clk_polarity ? 'P' : 'N', clear_polarity ? 'P' : 'N');
|
||||
cell->setPort(ID::R, clear_sig);
|
||||
}
|
||||
|
||||
if (clear_sig.size() == 0 && preset_sig.size() == 1) {
|
||||
cell->type = stringf("$_DFF_%c%c1_", clk_polarity ? 'P' : 'N', preset_polarity ? 'P' : 'N');
|
||||
cell->setPort(ID::R, preset_sig);
|
||||
}
|
||||
|
||||
if (clear_sig.size() == 1 && preset_sig.size() == 1) {
|
||||
cell->type = stringf("$_DFFSR_%c%c%c_", clk_polarity ? 'P' : 'N', preset_polarity ? 'P' : 'N', clear_polarity ? 'P' : 'N');
|
||||
|
||||
SigBit s_sig = preset_sig;
|
||||
SigBit r_sig = clear_sig;
|
||||
if (cp_var && *cp_var != 'X') {
|
||||
// Either set or reset dominates
|
||||
bool set_dominates;
|
||||
if (*cp_var == 'L') {
|
||||
set_dominates = neg;
|
||||
} else if (*cp_var == 'H') {
|
||||
set_dominates = !neg;
|
||||
} else {
|
||||
log_error("FF cell %s has unsupported clear&preset behavior \'%c\'.\n", name, *cp_var);
|
||||
}
|
||||
log_debug("cell %s variable %d cp_var %c set dominates? %d\n", name, (int)neg + 1, *cp_var, set_dominates);
|
||||
// S&R priority is well-defined now
|
||||
if (set_dominates) {
|
||||
r_sig = module->AndnotGate(NEW_ID, r_sig, s_sig);
|
||||
} else {
|
||||
s_sig = module->AndnotGate(NEW_ID, s_sig, r_sig);
|
||||
}
|
||||
} else {
|
||||
log_debug("cell %s variable %d undef c&p behavior\n", name, (int)neg + 1);
|
||||
}
|
||||
|
||||
cell->setPort(ID::S, s_sig);
|
||||
cell->setPort(ID::R, r_sig);
|
||||
}
|
||||
|
||||
log_assert(!cell->type.empty());
|
||||
}
|
||||
|
||||
if (clear_sig.size() == 1 && preset_sig.size() == 0) {
|
||||
cell->type = stringf("$_DFF_%c%c0_", clk_polarity ? 'P' : 'N', clear_polarity ? 'P' : 'N');
|
||||
cell->setPort(ID::R, clear_sig);
|
||||
}
|
||||
|
||||
if (clear_sig.size() == 0 && preset_sig.size() == 1) {
|
||||
cell->type = stringf("$_DFF_%c%c1_", clk_polarity ? 'P' : 'N', preset_polarity ? 'P' : 'N');
|
||||
cell->setPort(ID::R, preset_sig);
|
||||
}
|
||||
|
||||
if (clear_sig.size() == 1 && preset_sig.size() == 1) {
|
||||
cell->type = stringf("$_DFFSR_%c%c%c_", clk_polarity ? 'P' : 'N', preset_polarity ? 'P' : 'N', clear_polarity ? 'P' : 'N');
|
||||
cell->setPort(ID::S, preset_sig);
|
||||
cell->setPort(ID::R, clear_sig);
|
||||
}
|
||||
|
||||
log_assert(!cell->type.empty());
|
||||
}
|
||||
|
||||
static bool create_latch(RTLIL::Module *module, const LibertyAst *node, bool flag_ignore_miss_data_latch)
|
||||
|
|
@ -797,3 +837,4 @@ skip_cell:;
|
|||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ struct RTLILFrontendWorker {
|
|||
if (width > MAX_CONST_WIDTH)
|
||||
error("Constant width %lld out of range before `%s`.", width, error_token());
|
||||
bits.reserve(width);
|
||||
int start_idx = idx;
|
||||
while (true) {
|
||||
RTLIL::State bit;
|
||||
switch (line[idx]) {
|
||||
|
|
@ -300,8 +301,9 @@ struct RTLILFrontendWorker {
|
|||
bits.push_back(bit);
|
||||
++idx;
|
||||
}
|
||||
done:
|
||||
std::reverse(bits.begin(), bits.end());
|
||||
done:
|
||||
if (start_idx < idx)
|
||||
std::reverse(bits.begin(), bits.end());
|
||||
|
||||
if (GetSize(bits) > width)
|
||||
bits.resize(width);
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
int current_function_or_task_port_id;
|
||||
std::vector<char> case_type_stack;
|
||||
bool do_not_require_port_stubs;
|
||||
bool current_wire_rand, current_wire_const;
|
||||
bool current_wire_rand, current_wire_const, current_wire_automatic;
|
||||
bool current_modport_input, current_modport_output;
|
||||
bool default_nettype_wire = true;
|
||||
std::istream* lexin;
|
||||
|
|
@ -958,14 +958,18 @@ delay:
|
|||
non_opt_delay | %empty;
|
||||
|
||||
io_wire_type:
|
||||
{ extra->astbuf3 = std::make_unique<AstNode>(@$, AST_WIRE); extra->current_wire_rand = false; extra->current_wire_const = false; }
|
||||
{ extra->astbuf3 = std::make_unique<AstNode>(@$, AST_WIRE); extra->current_wire_rand = false; extra->current_wire_const = false; extra->current_wire_automatic = false; }
|
||||
wire_type_token_io wire_type_const_rand opt_wire_type_token wire_type_signedness
|
||||
{ $$ = std::move(extra->astbuf3); SET_RULE_LOC(@$, @2, @$); };
|
||||
|
||||
non_io_wire_type:
|
||||
{ extra->astbuf3 = std::make_unique<AstNode>(@$, AST_WIRE); extra->current_wire_rand = false; extra->current_wire_const = false; }
|
||||
wire_type_const_rand wire_type_token wire_type_signedness
|
||||
{ $$ = std::move(extra->astbuf3); SET_RULE_LOC(@$, @2, @$); };
|
||||
{ extra->astbuf3 = std::make_unique<AstNode>(@$, AST_WIRE); extra->current_wire_rand = false; extra->current_wire_const = false; extra->current_wire_automatic = false; }
|
||||
opt_lifetime wire_type_const_rand wire_type_token wire_type_signedness
|
||||
{
|
||||
if (extra->current_wire_automatic)
|
||||
extra->astbuf3->set_attribute(ID::nosync, AstNode::mkconst_int(extra->astbuf3->location, 1, false));
|
||||
$$ = std::move(extra->astbuf3); SET_RULE_LOC(@$, @2, @$);
|
||||
};
|
||||
|
||||
wire_type:
|
||||
io_wire_type { $$ = std::move($1); } |
|
||||
|
|
@ -1253,6 +1257,10 @@ opt_automatic:
|
|||
TOK_AUTOMATIC |
|
||||
%empty;
|
||||
|
||||
opt_lifetime:
|
||||
TOK_AUTOMATIC { extra->current_wire_automatic = true; } |
|
||||
%empty;
|
||||
|
||||
task_func_args_opt:
|
||||
TOK_LPAREN TOK_RPAREN | %empty | TOK_LPAREN {
|
||||
extra->albuf = nullptr;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Getting Started
|
|||
Outline of a Yosys command
|
||||
--------------------------
|
||||
|
||||
Here is a the C++ code for a "hello_world" Yosys command (hello.cc):
|
||||
Here is the C++ code for a "hello_world" Yosys command (hello.cc):
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ the declarations for the following types in kernel/rtlil.h:
|
|||
The module is a container with connected cells and wires
|
||||
in it. The design is a container with modules in it.
|
||||
|
||||
All this types are also available without the RTLIL:: prefix in the Yosys
|
||||
All these types are also available without the RTLIL:: prefix in the Yosys
|
||||
namespace.
|
||||
|
||||
4. SigMap and other Helper Classes
|
||||
|
|
@ -204,4 +204,4 @@ Notes on the existing codebase
|
|||
|
||||
For historical reasons not all parts of Yosys adhere to the current coding
|
||||
style. When adding code to existing parts of the system, adhere to this guide
|
||||
for the new code instead of trying to mimic the style of the surrounding code.
|
||||
for the new code instead of trying to mimic the style of the surrounding code.
|
||||
|
|
|
|||
|
|
@ -66,14 +66,7 @@ struct AigMaker
|
|||
Cell *cell;
|
||||
idict<AigNode> aig_indices;
|
||||
|
||||
int the_true_node;
|
||||
int the_false_node;
|
||||
|
||||
AigMaker(Aig *aig, Cell *cell) : aig(aig), cell(cell)
|
||||
{
|
||||
the_true_node = -1;
|
||||
the_false_node = -1;
|
||||
}
|
||||
AigMaker(Aig *aig, Cell *cell) : aig(aig), cell(cell) {}
|
||||
|
||||
int node2index(const AigNode &node)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#define CELLTYPES_H
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
|
@ -87,22 +88,22 @@ struct CellTypes
|
|||
{
|
||||
setup_internals_eval();
|
||||
|
||||
setup_type(ID($tribuf), {ID::A, ID::EN}, {ID::Y}, true);
|
||||
setup_type(ID($tribuf), {ID::A, ID::EN}, {ID::Y});
|
||||
|
||||
setup_type(ID($assert), {ID::A, ID::EN}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($assume), {ID::A, ID::EN}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($live), {ID::A, ID::EN}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($fair), {ID::A, ID::EN}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($cover), {ID::A, ID::EN}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($initstate), pool<RTLIL::IdString>(), {ID::Y}, true);
|
||||
setup_type(ID($anyconst), pool<RTLIL::IdString>(), {ID::Y}, true);
|
||||
setup_type(ID($anyseq), pool<RTLIL::IdString>(), {ID::Y}, true);
|
||||
setup_type(ID($allconst), pool<RTLIL::IdString>(), {ID::Y}, true);
|
||||
setup_type(ID($allseq), pool<RTLIL::IdString>(), {ID::Y}, true);
|
||||
setup_type(ID($equiv), {ID::A, ID::B}, {ID::Y}, true);
|
||||
setup_type(ID($specify2), {ID::EN, ID::SRC, ID::DST}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($specify3), {ID::EN, ID::SRC, ID::DST, ID::DAT}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($specrule), {ID::EN_SRC, ID::EN_DST, ID::SRC, ID::DST}, pool<RTLIL::IdString>(), true);
|
||||
setup_type(ID($assert), {ID::A, ID::EN}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($assume), {ID::A, ID::EN}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($live), {ID::A, ID::EN}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($fair), {ID::A, ID::EN}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($cover), {ID::A, ID::EN}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($initstate), pool<RTLIL::IdString>(), {ID::Y});
|
||||
setup_type(ID($anyconst), pool<RTLIL::IdString>(), {ID::Y});
|
||||
setup_type(ID($anyseq), pool<RTLIL::IdString>(), {ID::Y});
|
||||
setup_type(ID($allconst), pool<RTLIL::IdString>(), {ID::Y});
|
||||
setup_type(ID($allseq), pool<RTLIL::IdString>(), {ID::Y});
|
||||
setup_type(ID($equiv), {ID::A, ID::B}, {ID::Y});
|
||||
setup_type(ID($specify2), {ID::EN, ID::SRC, ID::DST}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($specify3), {ID::EN, ID::SRC, ID::DST, ID::DAT}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($specrule), {ID::SRC_EN, ID::DST_EN, ID::SRC, ID::DST}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($print), {ID::EN, ID::ARGS, ID::TRG}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($check), {ID::A, ID::EN, ID::ARGS, ID::TRG}, pool<RTLIL::IdString>());
|
||||
setup_type(ID($set_tag), {ID::A, ID::SET, ID::CLR}, {ID::Y});
|
||||
|
|
@ -195,7 +196,7 @@ struct CellTypes
|
|||
{
|
||||
setup_stdcells_eval();
|
||||
|
||||
setup_type(ID($_TBUF_), {ID::A, ID::E}, {ID::Y}, true);
|
||||
setup_type(ID($_TBUF_), {ID::A, ID::E}, {ID::Y});
|
||||
}
|
||||
|
||||
void setup_stdcells_eval()
|
||||
|
|
@ -548,9 +549,6 @@ struct CellTypes
|
|||
}
|
||||
};
|
||||
|
||||
// initialized by yosys_setup()
|
||||
extern CellTypes yosys_celltypes;
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/macc.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
|
@ -44,9 +45,8 @@ struct ConstEval
|
|||
|
||||
ConstEval(RTLIL::Module *module, RTLIL::State defaultval = RTLIL::State::Sm) : module(module), assign_map(module), defaultval(defaultval)
|
||||
{
|
||||
CellTypes ct;
|
||||
ct.setup_internals();
|
||||
ct.setup_stdcells();
|
||||
auto ct = NewCellTypes();
|
||||
ct.static_cell_types = StaticCellTypes::Compat::nomem_noff;
|
||||
|
||||
for (auto &it : module->cells_) {
|
||||
if (!ct.cell_known(it.second->type))
|
||||
|
|
|
|||
|
|
@ -459,9 +459,7 @@ X(EDGE_POL)
|
|||
X(EFX_ADD)
|
||||
X(EN)
|
||||
X(ENPOL)
|
||||
X(EN_DST)
|
||||
X(EN_POLARITY)
|
||||
X(EN_SRC)
|
||||
X(EQN)
|
||||
X(F)
|
||||
X(FDCE)
|
||||
|
|
@ -835,6 +833,7 @@ X(abcgroup)
|
|||
X(acc_fir)
|
||||
X(acc_fir_i)
|
||||
X(add_carry)
|
||||
X(aiger2_zbuf)
|
||||
X(allconst)
|
||||
X(allseq)
|
||||
X(always_comb)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#define CXXOPTS_VECTOR_DELIMITER '\0'
|
||||
#include "libs/cxxopts/include/cxxopts.hpp"
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
#ifdef YOSYS_ENABLE_READLINE
|
||||
# include <readline/readline.h>
|
||||
|
|
@ -143,19 +144,6 @@ int yosys_history_offset = 0;
|
|||
std::string yosys_history_file;
|
||||
#endif
|
||||
|
||||
#if defined(__wasm)
|
||||
extern "C" {
|
||||
// FIXME: WASI does not currently support exceptions.
|
||||
void* __cxa_allocate_exception(size_t thrown_size) throw() {
|
||||
return malloc(thrown_size);
|
||||
}
|
||||
bool __cxa_uncaught_exception() throw();
|
||||
void __cxa_throw(void* thrown_exception, struct std::type_info * tinfo, void (*dest)(void*)) {
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void yosys_atexit()
|
||||
{
|
||||
#if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE)
|
||||
|
|
@ -195,6 +183,7 @@ namespace Yosys {
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
auto wall_clock_start = std::chrono::steady_clock::now();
|
||||
std::string frontend_command = "auto";
|
||||
std::string backend_command = "auto";
|
||||
std::vector<std::string> vlog_defines;
|
||||
|
|
@ -700,8 +689,11 @@ int main(int argc, char **argv)
|
|||
meminfo = stringf(", MEM: %.2f MB peak",
|
||||
ru_buffer.ru_maxrss / (1024.0 * 1024.0));
|
||||
#endif
|
||||
log("End of script. Logfile hash: %s%sCPU: user %.2fs system %.2fs%s\n", hash,
|
||||
stats_divider.c_str(), ru_buffer.ru_utime.tv_sec + 1e-6 * ru_buffer.ru_utime.tv_usec,
|
||||
double wall_seconds = std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now() - wall_clock_start).count();
|
||||
|
||||
log("End of script. Logfile hash: %s%stime: %.2fs, user: %.2fs, system: %.2fs%s\n", hash,
|
||||
stats_divider.c_str(), wall_seconds, ru_buffer.ru_utime.tv_sec + 1e-6 * ru_buffer.ru_utime.tv_usec,
|
||||
ru_buffer.ru_stime.tv_sec + 1e-6 * ru_buffer.ru_stime.tv_usec, meminfo.c_str());
|
||||
#endif
|
||||
log("%s\n", yosys_maybe_version());
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
#include "kernel/rtlil.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
|
@ -1093,10 +1093,10 @@ private:
|
|||
|
||||
struct DriverMap
|
||||
{
|
||||
CellTypes celltypes;
|
||||
NewCellTypes celltypes;
|
||||
|
||||
DriverMap() { celltypes.setup(); }
|
||||
DriverMap(Design *design) { celltypes.setup(); celltypes.setup_design(design); }
|
||||
DriverMap(Design *design) { celltypes.setup(design); }
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/threading.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
|
@ -35,34 +36,55 @@ struct FfInitVals
|
|||
sigmap = sigmap_;
|
||||
initbits.clear();
|
||||
for (auto wire : module->wires())
|
||||
if (wire->attributes.count(ID::init))
|
||||
process_wire(wire);
|
||||
}
|
||||
|
||||
void process_wire(RTLIL::Wire *wire)
|
||||
{
|
||||
SigSpec wirebits = (*sigmap)(wire);
|
||||
Const initval = wire->attributes.at(ID::init);
|
||||
|
||||
for (int i = 0; i < GetSize(wirebits) && i < GetSize(initval); i++)
|
||||
{
|
||||
if (wire->attributes.count(ID::init) == 0)
|
||||
SigBit bit = wirebits[i];
|
||||
State val = initval[i];
|
||||
|
||||
if (val != State::S0 && val != State::S1 && bit.wire != nullptr)
|
||||
continue;
|
||||
|
||||
SigSpec wirebits = (*sigmap)(wire);
|
||||
Const initval = wire->attributes.at(ID::init);
|
||||
|
||||
for (int i = 0; i < GetSize(wirebits) && i < GetSize(initval); i++)
|
||||
{
|
||||
SigBit bit = wirebits[i];
|
||||
State val = initval[i];
|
||||
|
||||
if (val != State::S0 && val != State::S1 && bit.wire != nullptr)
|
||||
continue;
|
||||
|
||||
if (initbits.count(bit)) {
|
||||
if (initbits.at(bit).first != val)
|
||||
log_error("Conflicting init values for signal %s (%s = %s != %s).\n",
|
||||
log_signal(bit), log_signal(SigBit(wire, i)),
|
||||
log_signal(val), log_signal(initbits.at(bit).first));
|
||||
continue;
|
||||
}
|
||||
|
||||
initbits[bit] = std::make_pair(val,SigBit(wire,i));
|
||||
if (initbits.count(bit)) {
|
||||
if (initbits.at(bit).first != val)
|
||||
log_error("Conflicting init values for signal %s (%s = %s != %s).\n",
|
||||
log_signal(bit), log_signal(SigBit(wire, i)),
|
||||
log_signal(val), log_signal(initbits.at(bit).first));
|
||||
continue;
|
||||
}
|
||||
|
||||
initbits[bit] = std::make_pair(val,SigBit(wire,i));
|
||||
}
|
||||
}
|
||||
|
||||
void set_parallel(const SigMapView *sigmap_, ParallelDispatchThreadPool &thread_pool, RTLIL::Module *module)
|
||||
{
|
||||
sigmap = sigmap_;
|
||||
initbits.clear();
|
||||
|
||||
const RTLIL::Module *const_module = module;
|
||||
ParallelDispatchThreadPool::Subpool subpool(thread_pool, ThreadPool::work_pool_size(0, module->wires_size(), 1000));
|
||||
ShardedVector<RTLIL::Wire*> init_wires(subpool);
|
||||
subpool.run([const_module, &init_wires](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(const_module->wires_size())) {
|
||||
RTLIL::Wire *wire = const_module->wire_at(i);
|
||||
if (wire->attributes.count(ID::init))
|
||||
init_wires.insert(ctx, wire);
|
||||
}
|
||||
});
|
||||
|
||||
for (RTLIL::Wire *wire : init_wires)
|
||||
process_wire(wire);
|
||||
}
|
||||
|
||||
RTLIL::State operator()(RTLIL::SigBit bit) const
|
||||
{
|
||||
auto it = initbits.find((*sigmap)(bit));
|
||||
|
|
|
|||
|
|
@ -45,8 +45,7 @@ FstData::FstData(std::string filename) : ctx(nullptr)
|
|||
ctx = (fstReaderContext *)fstReaderOpen(filename.c_str());
|
||||
if (!ctx)
|
||||
log_error("Error opening '%s' as FST file\n", filename);
|
||||
int scale = (int)fstReaderGetTimescale(ctx);
|
||||
timescale = pow(10.0, scale);
|
||||
scale = (int)fstReaderGetTimescale(ctx);
|
||||
timescale_str = "";
|
||||
int unit = 0;
|
||||
int zeros = 0;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class FstData
|
|||
std::string valueOf(fstHandle signal);
|
||||
fstHandle getHandle(std::string name);
|
||||
dict<int,fstHandle> getMemoryHandles(std::string name);
|
||||
double getTimescale() { return timescale; }
|
||||
int getScale() { return scale; }
|
||||
const char *getTimescaleString() { return timescale_str.c_str(); }
|
||||
private:
|
||||
void extractVarNames();
|
||||
|
|
@ -69,7 +69,7 @@ private:
|
|||
uint64_t last_time;
|
||||
std::map<fstHandle, std::string> past_data;
|
||||
uint64_t past_time;
|
||||
double timescale;
|
||||
int scale; // exponent of 10, e.g. -6 = us, -9 = ns
|
||||
std::string timescale_str;
|
||||
uint64_t start_time;
|
||||
uint64_t end_time;
|
||||
|
|
|
|||
|
|
@ -324,6 +324,14 @@ void log_formatted_file_info(std::string_view filename, int lineno, std::string
|
|||
log("%s:%d: Info: %s", filename, lineno, str);
|
||||
}
|
||||
|
||||
void log_suppressed() {
|
||||
if (log_debug_suppressed && !log_make_debug) {
|
||||
constexpr const char* format = "<suppressed ~%d debug messages>\n";
|
||||
logv_string(format, stringf(format, log_debug_suppressed));
|
||||
log_debug_suppressed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
static void log_error_with_prefix(std::string_view prefix, std::string str)
|
||||
{
|
||||
|
|
@ -345,7 +353,9 @@ static void log_error_with_prefix(std::string_view prefix, std::string str)
|
|||
}
|
||||
|
||||
log_last_error = std::move(str);
|
||||
log("%s%s", prefix, log_last_error);
|
||||
std::string message(prefix);
|
||||
message += log_last_error;
|
||||
logv_string("%s%s", message);
|
||||
log_flush();
|
||||
|
||||
log_make_debug = bak_log_make_debug;
|
||||
|
|
@ -355,7 +365,7 @@ static void log_error_with_prefix(std::string_view prefix, std::string str)
|
|||
item.current_count++;
|
||||
|
||||
for (auto &[_, item] : log_expect_prefix_error)
|
||||
if (std::regex_search(string(prefix) + string(log_last_error), item.pattern))
|
||||
if (std::regex_search(message, item.pattern))
|
||||
item.current_count++;
|
||||
|
||||
log_check_expected();
|
||||
|
|
|
|||
|
|
@ -206,12 +206,7 @@ template <typename... Args>
|
|||
log_formatted_cmd_error(fmt.format(args...));
|
||||
}
|
||||
|
||||
static inline void log_suppressed() {
|
||||
if (log_debug_suppressed && !log_make_debug) {
|
||||
log("<suppressed ~%d debug messages>\n", log_debug_suppressed);
|
||||
log_debug_suppressed = 0;
|
||||
}
|
||||
}
|
||||
void log_suppressed();
|
||||
|
||||
struct LogMakeDebugHdl {
|
||||
bool status = false;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "kernel/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
|
@ -177,8 +178,8 @@ struct ModIndex : public RTLIL::Monitor
|
|||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void check()
|
||||
|
|
@ -357,7 +358,7 @@ struct ModWalker
|
|||
RTLIL::Design *design;
|
||||
RTLIL::Module *module;
|
||||
|
||||
CellTypes ct;
|
||||
NewCellTypes ct;
|
||||
SigMap sigmap;
|
||||
|
||||
dict<RTLIL::SigBit, pool<PortBit>> signal_drivers;
|
||||
|
|
|
|||
651
kernel/newcelltypes.h
Normal file
651
kernel/newcelltypes.h
Normal file
|
|
@ -0,0 +1,651 @@
|
|||
#ifndef NEWCELLTYPES_H
|
||||
#define NEWCELLTYPES_H
|
||||
|
||||
#include "kernel/rtlil.h"
|
||||
#include "kernel/yosys.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This API is unstable.
|
||||
* It may change or be removed in future versions and break dependent code.
|
||||
*/
|
||||
|
||||
namespace StaticCellTypes {
|
||||
|
||||
// Given by last internal cell type IdString constids.inc, compilation error if too low
|
||||
constexpr int MAX_CELLS = 300;
|
||||
// Currently given by _MUX16_, compilation error if too low
|
||||
constexpr int MAX_PORTS = 20;
|
||||
struct CellTableBuilder {
|
||||
struct PortList {
|
||||
std::array<RTLIL::IdString, MAX_PORTS> ports{};
|
||||
size_t count = 0;
|
||||
constexpr PortList() = default;
|
||||
constexpr PortList(std::initializer_list<RTLIL::IdString> init) {
|
||||
for (auto p : init) {
|
||||
ports[count++] = p;
|
||||
}
|
||||
}
|
||||
constexpr auto begin() const { return ports.begin(); }
|
||||
constexpr auto end() const { return ports.begin() + count; }
|
||||
constexpr bool contains(RTLIL::IdString port) const {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (port == ports[i])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
constexpr size_t size() const { return count; }
|
||||
};
|
||||
struct Features {
|
||||
bool is_evaluable = false;
|
||||
bool is_combinatorial = false;
|
||||
bool is_synthesizable = false;
|
||||
bool is_stdcell = false;
|
||||
bool is_ff = false;
|
||||
bool is_mem_noff = false;
|
||||
bool is_anyinit = false;
|
||||
bool is_tristate = false;
|
||||
};
|
||||
struct CellInfo {
|
||||
RTLIL::IdString type;
|
||||
PortList inputs, outputs;
|
||||
Features features;
|
||||
};
|
||||
std::array<CellInfo, MAX_CELLS> cells{};
|
||||
size_t count = 0;
|
||||
|
||||
constexpr void setup_type(RTLIL::IdString type, std::initializer_list<RTLIL::IdString> inputs, std::initializer_list<RTLIL::IdString> outputs, const Features& features) {
|
||||
cells[count++] = {type, PortList(inputs), PortList(outputs), features};
|
||||
}
|
||||
constexpr void setup_internals_other()
|
||||
{
|
||||
Features features {};
|
||||
features.is_tristate = true;
|
||||
setup_type(ID($tribuf), {ID::A, ID::EN}, {ID::Y}, features);
|
||||
|
||||
features = {};
|
||||
setup_type(ID($assert), {ID::A, ID::EN}, {}, features);
|
||||
setup_type(ID($assume), {ID::A, ID::EN}, {}, features);
|
||||
setup_type(ID($live), {ID::A, ID::EN}, {}, features);
|
||||
setup_type(ID($fair), {ID::A, ID::EN}, {}, features);
|
||||
setup_type(ID($cover), {ID::A, ID::EN}, {}, features);
|
||||
setup_type(ID($initstate), {}, {ID::Y}, features);
|
||||
setup_type(ID($anyconst), {}, {ID::Y}, features);
|
||||
setup_type(ID($anyseq), {}, {ID::Y}, features);
|
||||
setup_type(ID($allconst), {}, {ID::Y}, features);
|
||||
setup_type(ID($allseq), {}, {ID::Y}, features);
|
||||
setup_type(ID($equiv), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($specify2), {ID::EN, ID::SRC, ID::DST}, {}, features);
|
||||
setup_type(ID($specify3), {ID::EN, ID::SRC, ID::DST, ID::DAT}, {}, features);
|
||||
setup_type(ID($specrule), {ID::SRC_EN, ID::DST_EN, ID::SRC, ID::DST}, {}, features);
|
||||
setup_type(ID($print), {ID::EN, ID::ARGS, ID::TRG}, {}, features);
|
||||
setup_type(ID($check), {ID::A, ID::EN, ID::ARGS, ID::TRG}, {}, features);
|
||||
setup_type(ID($set_tag), {ID::A, ID::SET, ID::CLR}, {ID::Y}, features);
|
||||
setup_type(ID($get_tag), {ID::A}, {ID::Y}, features);
|
||||
setup_type(ID($overwrite_tag), {ID::A, ID::SET, ID::CLR}, {}, features);
|
||||
setup_type(ID($original_tag), {ID::A}, {ID::Y}, features);
|
||||
setup_type(ID($future_ff), {ID::A}, {ID::Y}, features);
|
||||
setup_type(ID($scopeinfo), {}, {}, features);
|
||||
setup_type(ID($input_port), {}, {ID::Y}, features);
|
||||
setup_type(ID($connect), {ID::A, ID::B}, {}, features);
|
||||
}
|
||||
constexpr void setup_internals_eval()
|
||||
{
|
||||
Features features {};
|
||||
features.is_evaluable = true;
|
||||
std::initializer_list<RTLIL::IdString> unary_ops = {
|
||||
ID($not), ID($pos), ID($buf), ID($neg),
|
||||
ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool),
|
||||
ID($logic_not), ID($slice), ID($lut), ID($sop)
|
||||
};
|
||||
|
||||
std::initializer_list<RTLIL::IdString> binary_ops = {
|
||||
ID($and), ID($or), ID($xor), ID($xnor),
|
||||
ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx),
|
||||
ID($lt), ID($le), ID($eq), ID($ne), ID($eqx), ID($nex), ID($ge), ID($gt),
|
||||
ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($divfloor), ID($modfloor), ID($pow),
|
||||
ID($logic_and), ID($logic_or), ID($concat), ID($macc),
|
||||
ID($bweqx)
|
||||
};
|
||||
|
||||
for (auto type : unary_ops)
|
||||
setup_type(type, {ID::A}, {ID::Y}, features);
|
||||
|
||||
for (auto type : binary_ops)
|
||||
setup_type(type, {ID::A, ID::B}, {ID::Y}, features);
|
||||
|
||||
for (auto type : {ID($mux), ID($pmux), ID($bwmux)})
|
||||
setup_type(type, {ID::A, ID::B, ID::S}, {ID::Y}, features);
|
||||
|
||||
for (auto type : {ID($bmux), ID($demux)})
|
||||
setup_type(type, {ID::A, ID::S}, {ID::Y}, features);
|
||||
|
||||
setup_type(ID($lcu), {ID::P, ID::G, ID::CI}, {ID::CO}, features);
|
||||
setup_type(ID($alu), {ID::A, ID::B, ID::CI, ID::BI}, {ID::X, ID::Y, ID::CO}, features);
|
||||
setup_type(ID($macc_v2), {ID::A, ID::B, ID::C}, {ID::Y}, features);
|
||||
setup_type(ID($fa), {ID::A, ID::B, ID::C}, {ID::X, ID::Y}, features);
|
||||
}
|
||||
constexpr void setup_internals_ff()
|
||||
{
|
||||
Features features {};
|
||||
features.is_ff = true;
|
||||
setup_type(ID($sr), {ID::SET, ID::CLR}, {ID::Q}, features);
|
||||
setup_type(ID($ff), {ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($dff), {ID::CLK, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($dffe), {ID::CLK, ID::EN, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($dffsr), {ID::CLK, ID::SET, ID::CLR, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($dffsre), {ID::CLK, ID::SET, ID::CLR, ID::D, ID::EN}, {ID::Q}, features);
|
||||
setup_type(ID($adff), {ID::CLK, ID::ARST, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($adffe), {ID::CLK, ID::ARST, ID::D, ID::EN}, {ID::Q}, features);
|
||||
setup_type(ID($aldff), {ID::CLK, ID::ALOAD, ID::AD, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($aldffe), {ID::CLK, ID::ALOAD, ID::AD, ID::D, ID::EN}, {ID::Q}, features);
|
||||
setup_type(ID($sdff), {ID::CLK, ID::SRST, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($sdffe), {ID::CLK, ID::SRST, ID::D, ID::EN}, {ID::Q}, features);
|
||||
setup_type(ID($sdffce), {ID::CLK, ID::SRST, ID::D, ID::EN}, {ID::Q}, features);
|
||||
setup_type(ID($dlatch), {ID::EN, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($adlatch), {ID::EN, ID::D, ID::ARST}, {ID::Q}, features);
|
||||
setup_type(ID($dlatchsr), {ID::EN, ID::SET, ID::CLR, ID::D}, {ID::Q}, features);
|
||||
}
|
||||
constexpr void setup_internals_anyinit()
|
||||
{
|
||||
Features features {};
|
||||
features.is_anyinit = true;
|
||||
setup_type(ID($anyinit), {ID::D}, {ID::Q}, features);
|
||||
}
|
||||
constexpr void setup_internals_mem_noff()
|
||||
{
|
||||
Features features {};
|
||||
features.is_mem_noff = true;
|
||||
// NOT setup_internals_ff()
|
||||
|
||||
setup_type(ID($memrd), {ID::CLK, ID::EN, ID::ADDR}, {ID::DATA}, features);
|
||||
setup_type(ID($memrd_v2), {ID::CLK, ID::EN, ID::ARST, ID::SRST, ID::ADDR}, {ID::DATA}, features);
|
||||
setup_type(ID($memwr), {ID::CLK, ID::EN, ID::ADDR, ID::DATA}, {}, features);
|
||||
setup_type(ID($memwr_v2), {ID::CLK, ID::EN, ID::ADDR, ID::DATA}, {}, features);
|
||||
setup_type(ID($meminit), {ID::ADDR, ID::DATA}, {}, features);
|
||||
setup_type(ID($meminit_v2), {ID::ADDR, ID::DATA, ID::EN}, {}, features);
|
||||
setup_type(ID($mem), {ID::RD_CLK, ID::RD_EN, ID::RD_ADDR, ID::WR_CLK, ID::WR_EN, ID::WR_ADDR, ID::WR_DATA}, {ID::RD_DATA}, features);
|
||||
setup_type(ID($mem_v2), {ID::RD_CLK, ID::RD_EN, ID::RD_ARST, ID::RD_SRST, ID::RD_ADDR, ID::WR_CLK, ID::WR_EN, ID::WR_ADDR, ID::WR_DATA}, {ID::RD_DATA}, features);
|
||||
|
||||
// What?
|
||||
setup_type(ID($fsm), {ID::CLK, ID::ARST, ID::CTRL_IN}, {ID::CTRL_OUT}, features);
|
||||
}
|
||||
constexpr void setup_stdcells_tristate()
|
||||
{
|
||||
Features features {};
|
||||
features.is_stdcell = true;
|
||||
features.is_tristate = true;
|
||||
setup_type(ID($_TBUF_), {ID::A, ID::E}, {ID::Y}, features);
|
||||
}
|
||||
|
||||
constexpr void setup_stdcells_eval()
|
||||
{
|
||||
Features features {};
|
||||
features.is_stdcell = true;
|
||||
features.is_evaluable = true;
|
||||
setup_type(ID($_BUF_), {ID::A}, {ID::Y}, features);
|
||||
setup_type(ID($_NOT_), {ID::A}, {ID::Y}, features);
|
||||
setup_type(ID($_AND_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_NAND_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_OR_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_NOR_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_XOR_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_XNOR_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_ANDNOT_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_ORNOT_), {ID::A, ID::B}, {ID::Y}, features);
|
||||
setup_type(ID($_MUX_), {ID::A, ID::B, ID::S}, {ID::Y}, features);
|
||||
setup_type(ID($_NMUX_), {ID::A, ID::B, ID::S}, {ID::Y}, features);
|
||||
setup_type(ID($_MUX4_), {ID::A, ID::B, ID::C, ID::D, ID::S, ID::T}, {ID::Y}, features);
|
||||
setup_type(ID($_MUX8_), {ID::A, ID::B, ID::C, ID::D, ID::E, ID::F, ID::G, ID::H, ID::S, ID::T, ID::U}, {ID::Y}, features);
|
||||
setup_type(ID($_MUX16_), {ID::A, ID::B, ID::C, ID::D, ID::E, ID::F, ID::G, ID::H, ID::I, ID::J, ID::K, ID::L, ID::M, ID::N, ID::O, ID::P, ID::S, ID::T, ID::U, ID::V}, {ID::Y}, features);
|
||||
setup_type(ID($_AOI3_), {ID::A, ID::B, ID::C}, {ID::Y}, features);
|
||||
setup_type(ID($_OAI3_), {ID::A, ID::B, ID::C}, {ID::Y}, features);
|
||||
setup_type(ID($_AOI4_), {ID::A, ID::B, ID::C, ID::D}, {ID::Y}, features);
|
||||
setup_type(ID($_OAI4_), {ID::A, ID::B, ID::C, ID::D}, {ID::Y}, features);
|
||||
}
|
||||
|
||||
constexpr void setup_stdcells_ff() {
|
||||
Features features {};
|
||||
features.is_stdcell = true;
|
||||
features.is_ff = true;
|
||||
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// setup_type(std::string("$_SR_") + c1 + c2 + "_", {ID::S, ID::R}, {ID::Q}, features);
|
||||
setup_type(ID($_SR_NN_), {ID::S, ID::R}, {ID::Q}, features);
|
||||
setup_type(ID($_SR_NP_), {ID::S, ID::R}, {ID::Q}, features);
|
||||
setup_type(ID($_SR_PN_), {ID::S, ID::R}, {ID::Q}, features);
|
||||
setup_type(ID($_SR_PP_), {ID::S, ID::R}, {ID::Q}, features);
|
||||
|
||||
setup_type(ID($_FF_), {ID::D}, {ID::Q}, features);
|
||||
|
||||
// for (auto c1 : list_np)
|
||||
// setup_type(std::string("$_DFF_") + c1 + "_", {ID::C, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID::$_DFF_N_, {ID::C, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID::$_DFF_P_, {ID::C, ID::D}, {ID::Q}, features);
|
||||
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// setup_type(std::string("$_DFFE_") + c1 + c2 + "_", {ID::C, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID::$_DFFE_NN_, {ID::C, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID::$_DFFE_NP_, {ID::C, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID::$_DFFE_PN_, {ID::C, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID::$_DFFE_PP_, {ID::C, ID::D, ID::E}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_01)
|
||||
// setup_type(std::string("$_DFF_") + c1 + c2 + c3 + "_", {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_NN0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_NN1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_NP0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_NP1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_PN0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_PN1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_PP0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFF_PP1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_01)
|
||||
// for (auto c4 : list_np)
|
||||
// setup_type(std::string("$_DFFE_") + c1 + c2 + c3 + c4 + "_", {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NN0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NN0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NN1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NN1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NP0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NP0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NP1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_NP1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PN0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PN0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PN1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PN1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PP0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PP0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PP1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFE_PP1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// setup_type(std::string("$_ALDFF_") + c1 + c2 + "_", {ID::C, ID::L, ID::AD, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFF_NN_), {ID::C, ID::L, ID::AD, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFF_NP_), {ID::C, ID::L, ID::AD, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFF_PN_), {ID::C, ID::L, ID::AD, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFF_PP_), {ID::C, ID::L, ID::AD, ID::D}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_np)
|
||||
// setup_type(std::string("$_ALDFFE_") + c1 + c2 + c3 + "_", {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_NNN_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_NNP_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_NPN_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_NPP_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_PNN_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_PNP_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_PPN_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_ALDFFE_PPP_), {ID::C, ID::L, ID::AD, ID::D, ID::E}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_np)
|
||||
// setup_type(std::string("$_DFFSR_") + c1 + c2 + c3 + "_", {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_NNN_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_NNP_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_NPN_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_NPP_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_PNN_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_PNP_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_PPN_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSR_PPP_), {ID::C, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_np)
|
||||
// for (auto c4 : list_np)
|
||||
// setup_type(std::string("$_DFFSRE_") + c1 + c2 + c3 + c4 + "_", {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NNNN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NNNP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NNPN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NNPP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NPNN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NPNP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NPPN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_NPPP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PNNN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PNNP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PNPN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PNPP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PPNN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PPNP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PPPN_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_DFFSRE_PPPP_), {ID::C, ID::S, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_01)
|
||||
// setup_type(std::string("$_SDFF_") + c1 + c2 + c3 + "_", {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_NN0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_NN1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_NP0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_NP1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_PN0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_PN1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_PP0_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFF_PP1_), {ID::C, ID::R, ID::D}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_01)
|
||||
// for (auto c4 : list_np)
|
||||
// setup_type(std::string("$_SDFFE_") + c1 + c2 + c3 + c4 + "_", {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NN0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NN0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NN1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NN1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NP0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NP0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NP1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_NP1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PN0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PN0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PN1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PN1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PP0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PP0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PP1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFE_PP1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_01)
|
||||
// for (auto c4 : list_np)
|
||||
// setup_type(std::string("$_SDFFCE_") + c1 + c2 + c3 + c4 + "_", {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NN0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NN0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NN1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NN1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NP0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NP0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NP1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_NP1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PN0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PN0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PN1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PN1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PP0N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PP0P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PP1N_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
setup_type(ID($_SDFFCE_PP1P_), {ID::C, ID::R, ID::D, ID::E}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// setup_type(std::string("$_DLATCH_") + c1 + "_", {ID::E, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_N_), {ID::E, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_P_), {ID::E, ID::D}, {ID::Q}, features);
|
||||
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_01)
|
||||
// setup_type(std::string("$_DLATCH_") + c1 + c2 + c3 + "_", {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_NN0_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_NN1_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_NP0_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_NP1_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_PN0_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_PN1_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_PP0_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCH_PP1_), {ID::E, ID::R, ID::D}, {ID::Q}, features);
|
||||
// for (auto c1 : list_np)
|
||||
// for (auto c2 : list_np)
|
||||
// for (auto c3 : list_np)
|
||||
// setup_type(std::string("$_DLATCHSR_") + c1 + c2 + c3 + "_", {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_NNN_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_NNP_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_NPN_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_NPP_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_PNN_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_PNP_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_PPN_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
setup_type(ID($_DLATCHSR_PPP_), {ID::E, ID::S, ID::R, ID::D}, {ID::Q}, features);
|
||||
}
|
||||
constexpr CellTableBuilder() {
|
||||
setup_internals_other();
|
||||
setup_internals_eval();
|
||||
setup_internals_ff();
|
||||
setup_internals_anyinit();
|
||||
setup_internals_mem_noff();
|
||||
setup_stdcells_tristate();
|
||||
setup_stdcells_eval();
|
||||
setup_stdcells_ff();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
constexpr CellTableBuilder builder{};
|
||||
|
||||
struct PortInfo {
|
||||
struct PortLists {
|
||||
std::array<CellTableBuilder::PortList, MAX_CELLS> data{};
|
||||
constexpr CellTableBuilder::PortList operator()(IdString type) const {
|
||||
return data[type.index_];
|
||||
}
|
||||
constexpr CellTableBuilder::PortList& operator[](size_t idx) {
|
||||
return data[idx];
|
||||
}
|
||||
constexpr size_t size() const { return data.size(); }
|
||||
};
|
||||
PortLists inputs {};
|
||||
PortLists outputs {};
|
||||
constexpr PortInfo() {
|
||||
for (size_t i = 0; i < builder.count; ++i) {
|
||||
auto& cell = builder.cells[i];
|
||||
size_t idx = cell.type.index_;
|
||||
inputs[idx] = cell.inputs;
|
||||
outputs[idx] = cell.outputs;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Categories {
|
||||
struct Category {
|
||||
std::array<bool, MAX_CELLS> data{};
|
||||
constexpr bool operator()(IdString type) const {
|
||||
size_t idx = type.index_;
|
||||
if (idx >= MAX_CELLS)
|
||||
return false;
|
||||
return data[idx];
|
||||
}
|
||||
constexpr bool operator[](size_t idx) {
|
||||
return data[idx];
|
||||
}
|
||||
constexpr void set_id(IdString type, bool val = true) {
|
||||
size_t idx = type.index_;
|
||||
if (idx >= MAX_CELLS)
|
||||
return; // TODO should be an assert but then it's not constexpr
|
||||
data[idx] = val;
|
||||
}
|
||||
constexpr void set(size_t idx, bool val = true) {
|
||||
data[idx] = val;
|
||||
}
|
||||
constexpr size_t size() const { return data.size(); }
|
||||
};
|
||||
Category empty {};
|
||||
Category is_known {};
|
||||
Category is_evaluable {};
|
||||
Category is_combinatorial {};
|
||||
Category is_synthesizable {};
|
||||
Category is_stdcell {};
|
||||
Category is_ff {};
|
||||
Category is_mem_noff {};
|
||||
Category is_anyinit {};
|
||||
Category is_tristate {};
|
||||
constexpr Categories() {
|
||||
for (size_t i = 0; i < builder.count; ++i) {
|
||||
auto& cell = builder.cells[i];
|
||||
size_t idx = cell.type.index_;
|
||||
is_known.set(idx);
|
||||
is_evaluable.set(idx, cell.features.is_evaluable);
|
||||
is_combinatorial.set(idx, cell.features.is_combinatorial);
|
||||
is_synthesizable.set(idx, cell.features.is_synthesizable);
|
||||
is_stdcell.set(idx, cell.features.is_stdcell);
|
||||
is_ff.set(idx, cell.features.is_ff);
|
||||
is_mem_noff.set(idx, cell.features.is_mem_noff);
|
||||
is_anyinit.set(idx, cell.features.is_anyinit);
|
||||
is_tristate.set(idx, cell.features.is_tristate);
|
||||
}
|
||||
}
|
||||
constexpr static Category join(Category left, Category right) {
|
||||
Category c {};
|
||||
for (size_t i = 0; i < MAX_CELLS; ++i) {
|
||||
c.set(i, left[i] || right[i]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
constexpr static Category meet(Category left, Category right) {
|
||||
Category c {};
|
||||
for (size_t i = 0; i < MAX_CELLS; ++i) {
|
||||
c.set(i, left[i] && right[i]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
// Sketchy! Make sure to always meet with only the known universe.
|
||||
// In other words, no modus tollens allowed
|
||||
constexpr static Category complement(Category arg) {
|
||||
Category c {};
|
||||
for (size_t i = 0; i < MAX_CELLS; ++i) {
|
||||
c.set(i, !arg[i]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
// Pure
|
||||
static constexpr PortInfo port_info;
|
||||
static constexpr Categories categories;
|
||||
|
||||
// Legacy
|
||||
namespace Compat {
|
||||
static constexpr auto internals_all = Categories::meet(categories.is_known, Categories::complement(categories.is_stdcell));
|
||||
static constexpr auto mem_ff = Categories::join(categories.is_ff, categories.is_mem_noff);
|
||||
// old setup_internals + setup_stdcells
|
||||
static constexpr auto nomem_noff = Categories::meet(categories.is_known, Categories::complement(mem_ff));
|
||||
static constexpr auto internals_mem_ff = Categories::meet(internals_all, mem_ff);
|
||||
// old setup_internals
|
||||
static constexpr auto internals_nomem_noff = Categories::meet(internals_all, nomem_noff);
|
||||
// old setup_stdcells
|
||||
static constexpr auto stdcells_nomem_noff = Categories::meet(categories.is_stdcell, nomem_noff);
|
||||
static constexpr auto stdcells_mem = Categories::meet(categories.is_stdcell, categories.is_mem_noff);
|
||||
// old setup_internals_eval
|
||||
// static constexpr auto internals_eval = Categories::meet(internals_all, categories.is_evaluable);
|
||||
};
|
||||
|
||||
namespace {
|
||||
static_assert(categories.is_evaluable(ID($and)));
|
||||
static_assert(!categories.is_ff(ID($and)));
|
||||
static_assert(Categories::join(categories.is_evaluable, categories.is_ff)(ID($and)));
|
||||
static_assert(Categories::join(categories.is_evaluable, categories.is_ff)(ID($dffsr)));
|
||||
static_assert(!Categories::join(categories.is_evaluable, categories.is_ff)(ID($anyinit)));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct NewCellType {
|
||||
RTLIL::IdString type;
|
||||
pool<RTLIL::IdString> inputs, outputs;
|
||||
bool is_evaluable;
|
||||
bool is_combinatorial;
|
||||
bool is_synthesizable;
|
||||
};
|
||||
|
||||
struct NewCellTypes {
|
||||
struct IdStringHash {
|
||||
std::size_t operator()(const IdString id) const {
|
||||
return static_cast<size_t>(id.hash_top().yield());
|
||||
}
|
||||
};
|
||||
StaticCellTypes::Categories::Category static_cell_types = StaticCellTypes::categories.empty;
|
||||
std::unordered_map<RTLIL::IdString, NewCellType, IdStringHash> custom_cell_types {};
|
||||
|
||||
NewCellTypes() {
|
||||
static_cell_types = StaticCellTypes::categories.empty;
|
||||
}
|
||||
|
||||
NewCellTypes(RTLIL::Design *design) {
|
||||
static_cell_types = StaticCellTypes::categories.empty;
|
||||
setup(design);
|
||||
}
|
||||
void setup(RTLIL::Design *design = NULL) {
|
||||
if (design)
|
||||
setup_design(design);
|
||||
static_cell_types = StaticCellTypes::categories.is_known;
|
||||
}
|
||||
void setup_design(RTLIL::Design *design) {
|
||||
for (auto module : design->modules())
|
||||
setup_module(module);
|
||||
}
|
||||
|
||||
void setup_module(RTLIL::Module *module) {
|
||||
pool<RTLIL::IdString> inputs, outputs;
|
||||
for (RTLIL::IdString wire_name : module->ports) {
|
||||
RTLIL::Wire *wire = module->wire(wire_name);
|
||||
if (wire->port_input)
|
||||
inputs.insert(wire->name);
|
||||
if (wire->port_output)
|
||||
outputs.insert(wire->name);
|
||||
}
|
||||
setup_type(module->name, inputs, outputs);
|
||||
}
|
||||
|
||||
void setup_type(RTLIL::IdString type, const pool<RTLIL::IdString> &inputs, const pool<RTLIL::IdString> &outputs, bool is_evaluable = false, bool is_combinatorial = false, bool is_synthesizable = false) {
|
||||
NewCellType ct = {type, inputs, outputs, is_evaluable, is_combinatorial, is_synthesizable};
|
||||
custom_cell_types[ct.type] = ct;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
custom_cell_types.clear();
|
||||
static_cell_types = StaticCellTypes::categories.empty;
|
||||
}
|
||||
|
||||
bool cell_known(const RTLIL::IdString &type) const {
|
||||
return static_cell_types(type) || custom_cell_types.count(type) != 0;
|
||||
}
|
||||
|
||||
bool cell_output(const RTLIL::IdString &type, const RTLIL::IdString &port) const
|
||||
{
|
||||
if (static_cell_types(type) && StaticCellTypes::port_info.outputs(type).contains(port)) {
|
||||
return true;
|
||||
}
|
||||
auto it = custom_cell_types.find(type);
|
||||
return it != custom_cell_types.end() && it->second.outputs.count(port) != 0;
|
||||
}
|
||||
|
||||
bool cell_input(const RTLIL::IdString &type, const RTLIL::IdString &port) const
|
||||
{
|
||||
if (static_cell_types(type) && StaticCellTypes::port_info.inputs(type).contains(port)) {
|
||||
return true;
|
||||
}
|
||||
auto it = custom_cell_types.find(type);
|
||||
return it != custom_cell_types.end() && it->second.inputs.count(port) != 0;
|
||||
}
|
||||
|
||||
RTLIL::PortDir cell_port_dir(RTLIL::IdString type, RTLIL::IdString port) const
|
||||
{
|
||||
bool is_input, is_output;
|
||||
if (static_cell_types(type)) {
|
||||
is_input = StaticCellTypes::port_info.inputs(type).contains(port);
|
||||
is_output = StaticCellTypes::port_info.outputs(type).contains(port);
|
||||
} else {
|
||||
auto it = custom_cell_types.find(type);
|
||||
if (it == custom_cell_types.end())
|
||||
return RTLIL::PD_UNKNOWN;
|
||||
is_input = it->second.inputs.count(port);
|
||||
is_output = it->second.outputs.count(port);
|
||||
}
|
||||
return RTLIL::PortDir(is_input + is_output * 2);
|
||||
}
|
||||
bool cell_evaluable(const RTLIL::IdString &type) const
|
||||
{
|
||||
return static_cell_types(type) && StaticCellTypes::categories.is_evaluable(type);
|
||||
}
|
||||
};
|
||||
|
||||
extern NewCellTypes yosys_celltypes;
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
#include "kernel/json.h"
|
||||
#include "kernel/gzip.h"
|
||||
#include "kernel/log_help.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -975,16 +976,18 @@ struct HelpPass : public Pass {
|
|||
json.entry("generator", yosys_maybe_version());
|
||||
|
||||
dict<string, vector<string>> groups;
|
||||
dict<string, pair<SimHelper, CellType>> cells;
|
||||
dict<string, pair<SimHelper, StaticCellTypes::CellTableBuilder::CellInfo>> cells;
|
||||
|
||||
// iterate over cells
|
||||
bool raise_error = false;
|
||||
for (auto &it : yosys_celltypes.cell_types) {
|
||||
auto name = it.first.str();
|
||||
for (auto it : StaticCellTypes::builder.cells) {
|
||||
if (!StaticCellTypes::categories.is_known(it.type))
|
||||
continue;
|
||||
auto name = it.type.str();
|
||||
if (cell_help_messages.contains(name)) {
|
||||
auto cell_help = cell_help_messages.get(name);
|
||||
groups[cell_help.group].emplace_back(name);
|
||||
auto cell_pair = pair<SimHelper, CellType>(cell_help, it.second);
|
||||
auto cell_pair = pair<SimHelper, StaticCellTypes::CellTableBuilder::CellInfo>(cell_help, it);
|
||||
cells.emplace(name, cell_pair);
|
||||
} else {
|
||||
log("ERROR: Missing cell help for cell '%s'.\n", name);
|
||||
|
|
@ -1025,9 +1028,9 @@ struct HelpPass : public Pass {
|
|||
json.name("outputs"); json.value(outputs);
|
||||
vector<string> properties;
|
||||
// CellType properties
|
||||
if (ct.is_evaluable) properties.push_back("is_evaluable");
|
||||
if (ct.is_combinatorial) properties.push_back("is_combinatorial");
|
||||
if (ct.is_synthesizable) properties.push_back("is_synthesizable");
|
||||
if (ct.features.is_evaluable) properties.push_back("is_evaluable");
|
||||
if (ct.features.is_combinatorial) properties.push_back("is_combinatorial");
|
||||
if (ct.features.is_synthesizable) properties.push_back("is_synthesizable");
|
||||
// SimHelper properties
|
||||
size_t last = 0; size_t next = 0;
|
||||
while ((next = ch.tags.find(", ", last)) != string::npos) {
|
||||
|
|
|
|||
568
kernel/rtlil.cc
568
kernel/rtlil.cc
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/macc.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/binding.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/threading.h"
|
||||
#include "frontends/verilog/verilog_frontend.h"
|
||||
#include "frontends/verilog/preproc.h"
|
||||
#include "backends/rtlil/rtlil_backend.h"
|
||||
|
|
@ -142,9 +143,17 @@ static constexpr bool check_well_known_id_order()
|
|||
// and in sorted ascii order, as required by the ID macro.
|
||||
static_assert(check_well_known_id_order());
|
||||
|
||||
constexpr int STATIC_ID_END = static_cast<int>(RTLIL::StaticId::STATIC_ID_END);
|
||||
|
||||
struct IdStringCollector {
|
||||
IdStringCollector(std::vector<MonotonicFlag> &live_ids)
|
||||
: live_ids(live_ids) {}
|
||||
|
||||
void trace(IdString id) {
|
||||
live.insert(id.index_);
|
||||
if (id.index_ >= STATIC_ID_END)
|
||||
live_ids[id.index_ - STATIC_ID_END].set();
|
||||
else if (id.index_ < 0)
|
||||
live_autoidx_ids.push_back(id.index_);
|
||||
}
|
||||
template <typename T> void trace(const T* v) {
|
||||
trace(*v);
|
||||
|
|
@ -178,10 +187,6 @@ struct IdStringCollector {
|
|||
trace(element);
|
||||
}
|
||||
|
||||
void trace(const RTLIL::Design &design) {
|
||||
trace_values(design.modules_);
|
||||
trace(design.selection_vars);
|
||||
}
|
||||
void trace(const RTLIL::Selection &selection_var) {
|
||||
trace(selection_var.selected_modules);
|
||||
trace(selection_var.selected_members);
|
||||
|
|
@ -190,15 +195,6 @@ struct IdStringCollector {
|
|||
trace_keys(named.attributes);
|
||||
trace(named.name);
|
||||
}
|
||||
void trace(const RTLIL::Module &module) {
|
||||
trace_named(module);
|
||||
trace_values(module.wires_);
|
||||
trace_values(module.cells_);
|
||||
trace(module.avail_parameters);
|
||||
trace_keys(module.parameter_default_values);
|
||||
trace_values(module.memories);
|
||||
trace_values(module.processes);
|
||||
}
|
||||
void trace(const RTLIL::Wire &wire) {
|
||||
trace_named(wire);
|
||||
if (wire.known_driver())
|
||||
|
|
@ -234,7 +230,8 @@ struct IdStringCollector {
|
|||
trace(action.memid);
|
||||
}
|
||||
|
||||
std::unordered_set<int> live;
|
||||
std::vector<MonotonicFlag> &live_ids;
|
||||
std::vector<int> live_autoidx_ids;
|
||||
};
|
||||
|
||||
int64_t RTLIL::OwningIdString::gc_ns;
|
||||
|
|
@ -243,20 +240,55 @@ int RTLIL::OwningIdString::gc_count;
|
|||
void RTLIL::OwningIdString::collect_garbage()
|
||||
{
|
||||
int64_t start = PerformanceTimer::query();
|
||||
IdStringCollector collector;
|
||||
for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) {
|
||||
collector.trace(*design);
|
||||
}
|
||||
int size = GetSize(global_id_storage_);
|
||||
for (int i = static_cast<int>(StaticId::STATIC_ID_END); i < size; ++i) {
|
||||
RTLIL::IdString::Storage &storage = global_id_storage_.at(i);
|
||||
if (storage.buf == nullptr)
|
||||
continue;
|
||||
if (collector.live.find(i) != collector.live.end())
|
||||
continue;
|
||||
if (global_refcount_storage_.find(i) != global_refcount_storage_.end())
|
||||
continue;
|
||||
|
||||
int pool_size = 0;
|
||||
for (auto &[idx, design] : *RTLIL::Design::get_all_designs())
|
||||
for (RTLIL::Module *module : design->modules())
|
||||
pool_size = std::max(pool_size, ThreadPool::work_pool_size(0, module->cells_size(), 1000));
|
||||
ParallelDispatchThreadPool thread_pool(pool_size);
|
||||
|
||||
int size = GetSize(global_id_storage_);
|
||||
std::vector<MonotonicFlag> live_ids(size - STATIC_ID_END);
|
||||
std::vector<IdStringCollector> collectors;
|
||||
int num_threads = thread_pool.num_threads();
|
||||
collectors.reserve(num_threads);
|
||||
for (int i = 0; i < num_threads; ++i)
|
||||
collectors.emplace_back(live_ids);
|
||||
|
||||
for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) {
|
||||
for (RTLIL::Module *module : design->modules()) {
|
||||
collectors[0].trace_named(*module);
|
||||
ParallelDispatchThreadPool::Subpool subpool(thread_pool, ThreadPool::work_pool_size(0, module->cells_size(), 1000));
|
||||
subpool.run([&collectors, module](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(module->cells_size()))
|
||||
collectors[ctx.thread_num].trace(module->cell_at(i));
|
||||
for (int i : ctx.item_range(module->wires_size()))
|
||||
collectors[ctx.thread_num].trace(module->wire_at(i));
|
||||
});
|
||||
collectors[0].trace(module->avail_parameters);
|
||||
collectors[0].trace_keys(module->parameter_default_values);
|
||||
collectors[0].trace_values(module->memories);
|
||||
collectors[0].trace_values(module->processes);
|
||||
}
|
||||
collectors[0].trace(design->selection_vars);
|
||||
}
|
||||
|
||||
ShardedVector<int> free_ids(thread_pool);
|
||||
thread_pool.run([&live_ids, size, &free_ids](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(size - STATIC_ID_END)) {
|
||||
int index = i + STATIC_ID_END;
|
||||
RTLIL::IdString::Storage &storage = global_id_storage_.at(index);
|
||||
if (storage.buf == nullptr)
|
||||
continue;
|
||||
if (live_ids[i].load())
|
||||
continue;
|
||||
if (global_refcount_storage_.find(index) != global_refcount_storage_.end())
|
||||
continue;
|
||||
free_ids.insert(ctx, index);
|
||||
}
|
||||
});
|
||||
for (int i : free_ids) {
|
||||
RTLIL::IdString::Storage &storage = global_id_storage_.at(i);
|
||||
if (yosys_xtrace) {
|
||||
log("#X# Removed IdString '%s' with index %d.\n", storage.buf, i);
|
||||
log_backtrace("-X- ", yosys_xtrace-1);
|
||||
|
|
@ -268,8 +300,13 @@ void RTLIL::OwningIdString::collect_garbage()
|
|||
global_free_idx_list_.push_back(i);
|
||||
}
|
||||
|
||||
std::unordered_set<int> live_autoidx_ids;
|
||||
for (IdStringCollector &collector : collectors)
|
||||
for (int id : collector.live_autoidx_ids)
|
||||
live_autoidx_ids.insert(id);
|
||||
|
||||
for (auto it = global_autoidx_id_storage_.begin(); it != global_autoidx_id_storage_.end();) {
|
||||
if (collector.live.find(it->first) != collector.live.end()) {
|
||||
if (live_autoidx_ids.find(it->first) != live_autoidx_ids.end()) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -288,159 +325,17 @@ void RTLIL::OwningIdString::collect_garbage()
|
|||
|
||||
dict<std::string, std::string> RTLIL::constpad;
|
||||
|
||||
static const pool<IdString> &builtin_ff_cell_types_internal() {
|
||||
static const pool<IdString> res = {
|
||||
ID($sr),
|
||||
ID($ff),
|
||||
ID($dff),
|
||||
ID($dffe),
|
||||
ID($dffsr),
|
||||
ID($dffsre),
|
||||
ID($adff),
|
||||
ID($adffe),
|
||||
ID($aldff),
|
||||
ID($aldffe),
|
||||
ID($sdff),
|
||||
ID($sdffe),
|
||||
ID($sdffce),
|
||||
ID($dlatch),
|
||||
ID($adlatch),
|
||||
ID($dlatchsr),
|
||||
ID($_DFFE_NN_),
|
||||
ID($_DFFE_NP_),
|
||||
ID($_DFFE_PN_),
|
||||
ID($_DFFE_PP_),
|
||||
ID($_DFFSR_NNN_),
|
||||
ID($_DFFSR_NNP_),
|
||||
ID($_DFFSR_NPN_),
|
||||
ID($_DFFSR_NPP_),
|
||||
ID($_DFFSR_PNN_),
|
||||
ID($_DFFSR_PNP_),
|
||||
ID($_DFFSR_PPN_),
|
||||
ID($_DFFSR_PPP_),
|
||||
ID($_DFFSRE_NNNN_),
|
||||
ID($_DFFSRE_NNNP_),
|
||||
ID($_DFFSRE_NNPN_),
|
||||
ID($_DFFSRE_NNPP_),
|
||||
ID($_DFFSRE_NPNN_),
|
||||
ID($_DFFSRE_NPNP_),
|
||||
ID($_DFFSRE_NPPN_),
|
||||
ID($_DFFSRE_NPPP_),
|
||||
ID($_DFFSRE_PNNN_),
|
||||
ID($_DFFSRE_PNNP_),
|
||||
ID($_DFFSRE_PNPN_),
|
||||
ID($_DFFSRE_PNPP_),
|
||||
ID($_DFFSRE_PPNN_),
|
||||
ID($_DFFSRE_PPNP_),
|
||||
ID($_DFFSRE_PPPN_),
|
||||
ID($_DFFSRE_PPPP_),
|
||||
ID($_DFF_N_),
|
||||
ID($_DFF_P_),
|
||||
ID($_DFF_NN0_),
|
||||
ID($_DFF_NN1_),
|
||||
ID($_DFF_NP0_),
|
||||
ID($_DFF_NP1_),
|
||||
ID($_DFF_PN0_),
|
||||
ID($_DFF_PN1_),
|
||||
ID($_DFF_PP0_),
|
||||
ID($_DFF_PP1_),
|
||||
ID($_DFFE_NN0N_),
|
||||
ID($_DFFE_NN0P_),
|
||||
ID($_DFFE_NN1N_),
|
||||
ID($_DFFE_NN1P_),
|
||||
ID($_DFFE_NP0N_),
|
||||
ID($_DFFE_NP0P_),
|
||||
ID($_DFFE_NP1N_),
|
||||
ID($_DFFE_NP1P_),
|
||||
ID($_DFFE_PN0N_),
|
||||
ID($_DFFE_PN0P_),
|
||||
ID($_DFFE_PN1N_),
|
||||
ID($_DFFE_PN1P_),
|
||||
ID($_DFFE_PP0N_),
|
||||
ID($_DFFE_PP0P_),
|
||||
ID($_DFFE_PP1N_),
|
||||
ID($_DFFE_PP1P_),
|
||||
ID($_ALDFF_NN_),
|
||||
ID($_ALDFF_NP_),
|
||||
ID($_ALDFF_PN_),
|
||||
ID($_ALDFF_PP_),
|
||||
ID($_ALDFFE_NNN_),
|
||||
ID($_ALDFFE_NNP_),
|
||||
ID($_ALDFFE_NPN_),
|
||||
ID($_ALDFFE_NPP_),
|
||||
ID($_ALDFFE_PNN_),
|
||||
ID($_ALDFFE_PNP_),
|
||||
ID($_ALDFFE_PPN_),
|
||||
ID($_ALDFFE_PPP_),
|
||||
ID($_SDFF_NN0_),
|
||||
ID($_SDFF_NN1_),
|
||||
ID($_SDFF_NP0_),
|
||||
ID($_SDFF_NP1_),
|
||||
ID($_SDFF_PN0_),
|
||||
ID($_SDFF_PN1_),
|
||||
ID($_SDFF_PP0_),
|
||||
ID($_SDFF_PP1_),
|
||||
ID($_SDFFE_NN0N_),
|
||||
ID($_SDFFE_NN0P_),
|
||||
ID($_SDFFE_NN1N_),
|
||||
ID($_SDFFE_NN1P_),
|
||||
ID($_SDFFE_NP0N_),
|
||||
ID($_SDFFE_NP0P_),
|
||||
ID($_SDFFE_NP1N_),
|
||||
ID($_SDFFE_NP1P_),
|
||||
ID($_SDFFE_PN0N_),
|
||||
ID($_SDFFE_PN0P_),
|
||||
ID($_SDFFE_PN1N_),
|
||||
ID($_SDFFE_PN1P_),
|
||||
ID($_SDFFE_PP0N_),
|
||||
ID($_SDFFE_PP0P_),
|
||||
ID($_SDFFE_PP1N_),
|
||||
ID($_SDFFE_PP1P_),
|
||||
ID($_SDFFCE_NN0N_),
|
||||
ID($_SDFFCE_NN0P_),
|
||||
ID($_SDFFCE_NN1N_),
|
||||
ID($_SDFFCE_NN1P_),
|
||||
ID($_SDFFCE_NP0N_),
|
||||
ID($_SDFFCE_NP0P_),
|
||||
ID($_SDFFCE_NP1N_),
|
||||
ID($_SDFFCE_NP1P_),
|
||||
ID($_SDFFCE_PN0N_),
|
||||
ID($_SDFFCE_PN0P_),
|
||||
ID($_SDFFCE_PN1N_),
|
||||
ID($_SDFFCE_PN1P_),
|
||||
ID($_SDFFCE_PP0N_),
|
||||
ID($_SDFFCE_PP0P_),
|
||||
ID($_SDFFCE_PP1N_),
|
||||
ID($_SDFFCE_PP1P_),
|
||||
ID($_SR_NN_),
|
||||
ID($_SR_NP_),
|
||||
ID($_SR_PN_),
|
||||
ID($_SR_PP_),
|
||||
ID($_DLATCH_N_),
|
||||
ID($_DLATCH_P_),
|
||||
ID($_DLATCH_NN0_),
|
||||
ID($_DLATCH_NN1_),
|
||||
ID($_DLATCH_NP0_),
|
||||
ID($_DLATCH_NP1_),
|
||||
ID($_DLATCH_PN0_),
|
||||
ID($_DLATCH_PN1_),
|
||||
ID($_DLATCH_PP0_),
|
||||
ID($_DLATCH_PP1_),
|
||||
ID($_DLATCHSR_NNN_),
|
||||
ID($_DLATCHSR_NNP_),
|
||||
ID($_DLATCHSR_NPN_),
|
||||
ID($_DLATCHSR_NPP_),
|
||||
ID($_DLATCHSR_PNN_),
|
||||
ID($_DLATCHSR_PNP_),
|
||||
ID($_DLATCHSR_PPN_),
|
||||
ID($_DLATCHSR_PPP_),
|
||||
ID($_FF_),
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
const pool<IdString> &RTLIL::builtin_ff_cell_types() {
|
||||
return builtin_ff_cell_types_internal();
|
||||
static const pool<IdString> res = []() {
|
||||
pool<IdString> r;
|
||||
for (size_t i = 0; i < StaticCellTypes::builder.count; i++) {
|
||||
auto &cell = StaticCellTypes::builder.cells[i];
|
||||
if (cell.features.is_ff)
|
||||
r.insert(cell.type);
|
||||
}
|
||||
return r;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
#define check(condition) log_assert(condition && "malformed Const union")
|
||||
|
|
@ -1466,15 +1361,21 @@ void RTLIL::Design::sort_modules()
|
|||
modules_.sort(sort_by_id_str());
|
||||
}
|
||||
|
||||
void check_module(RTLIL::Module *module, ParallelDispatchThreadPool &thread_pool);
|
||||
|
||||
void RTLIL::Design::check()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
log_assert(!selection_stack.empty());
|
||||
int pool_size = 0;
|
||||
for (auto &it : modules_)
|
||||
pool_size = std::max(pool_size, ThreadPool::work_pool_size(0, it.second->cells_size(), 1000));
|
||||
ParallelDispatchThreadPool thread_pool(pool_size);
|
||||
for (auto &it : modules_) {
|
||||
log_assert(this == it.second->design);
|
||||
log_assert(it.first == it.second->name);
|
||||
log_assert(!it.first.empty());
|
||||
it.second->check();
|
||||
check_module(it.second, thread_pool);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1710,11 +1611,11 @@ size_t RTLIL::Module::count_id(RTLIL::IdString id)
|
|||
namespace {
|
||||
struct InternalCellChecker
|
||||
{
|
||||
RTLIL::Module *module;
|
||||
const RTLIL::Module *module;
|
||||
RTLIL::Cell *cell;
|
||||
pool<RTLIL::IdString> expected_params, expected_ports;
|
||||
|
||||
InternalCellChecker(RTLIL::Module *module, RTLIL::Cell *cell) : module(module), cell(cell) { }
|
||||
InternalCellChecker(const RTLIL::Module *module, RTLIL::Cell *cell) : module(module), cell(cell) { }
|
||||
|
||||
void error(int linenr)
|
||||
{
|
||||
|
|
@ -2690,88 +2591,96 @@ void RTLIL::Module::sort()
|
|||
it.second->attributes.sort(sort_by_id_str());
|
||||
}
|
||||
|
||||
void RTLIL::Module::check()
|
||||
void check_module(RTLIL::Module *module, ParallelDispatchThreadPool &thread_pool)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
std::vector<bool> ports_declared;
|
||||
for (auto &it : wires_) {
|
||||
log_assert(this == it.second->module);
|
||||
log_assert(it.first == it.second->name);
|
||||
log_assert(!it.first.empty());
|
||||
log_assert(it.second->width >= 0);
|
||||
log_assert(it.second->port_id >= 0);
|
||||
for (auto &it2 : it.second->attributes)
|
||||
log_assert(!it2.first.empty());
|
||||
if (it.second->port_id) {
|
||||
log_assert(GetSize(ports) >= it.second->port_id);
|
||||
log_assert(ports.at(it.second->port_id-1) == it.first);
|
||||
log_assert(it.second->port_input || it.second->port_output);
|
||||
if (GetSize(ports_declared) < it.second->port_id)
|
||||
ports_declared.resize(it.second->port_id);
|
||||
log_assert(ports_declared[it.second->port_id-1] == false);
|
||||
ports_declared[it.second->port_id-1] = true;
|
||||
} else
|
||||
log_assert(!it.second->port_input && !it.second->port_output);
|
||||
}
|
||||
for (auto port_declared : ports_declared)
|
||||
log_assert(port_declared == true);
|
||||
log_assert(GetSize(ports) == GetSize(ports_declared));
|
||||
ParallelDispatchThreadPool::Subpool subpool(thread_pool, ThreadPool::work_pool_size(0, module->cells_size(), 1000));
|
||||
const RTLIL::Module *const_module = module;
|
||||
|
||||
for (auto &it : memories) {
|
||||
pool<std::string> memory_strings;
|
||||
for (auto &it : module->memories) {
|
||||
log_assert(it.first == it.second->name);
|
||||
log_assert(!it.first.empty());
|
||||
log_assert(it.second->width >= 0);
|
||||
log_assert(it.second->size >= 0);
|
||||
for (auto &it2 : it.second->attributes)
|
||||
log_assert(!it2.first.empty());
|
||||
memory_strings.insert(it.second->name.str());
|
||||
}
|
||||
|
||||
pool<IdString> packed_memids;
|
||||
std::vector<MonotonicFlag> ports_declared(GetSize(module->ports));
|
||||
ShardedVector<std::string> memids(subpool);
|
||||
subpool.run([const_module, &ports_declared, &memory_strings, &memids](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(const_module->cells_size())) {
|
||||
auto it = *const_module->cells_.element(i);
|
||||
log_assert(const_module == it.second->module);
|
||||
log_assert(it.first == it.second->name);
|
||||
log_assert(!it.first.empty());
|
||||
log_assert(!it.second->type.empty());
|
||||
for (auto &it2 : it.second->connections()) {
|
||||
log_assert(!it2.first.empty());
|
||||
it2.second.check(const_module);
|
||||
}
|
||||
for (auto &it2 : it.second->attributes)
|
||||
log_assert(!it2.first.empty());
|
||||
for (auto &it2 : it.second->parameters)
|
||||
log_assert(!it2.first.empty());
|
||||
InternalCellChecker checker(const_module, it.second);
|
||||
checker.check();
|
||||
if (it.second->has_memid()) {
|
||||
log_assert(memory_strings.count(it.second->parameters.at(ID::MEMID).decode_string()));
|
||||
} else if (it.second->is_mem_cell()) {
|
||||
std::string memid = it.second->parameters.at(ID::MEMID).decode_string();
|
||||
log_assert(!memory_strings.count(memid));
|
||||
memids.insert(ctx, std::move(memid));
|
||||
}
|
||||
auto cell_mod = const_module->design->module(it.first);
|
||||
if (cell_mod != nullptr) {
|
||||
// assertion check below to make sure that there are no
|
||||
// cases where a cell has a blackbox attribute since
|
||||
// that is deprecated
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
log_assert(!it.second->get_blackbox_attribute());
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &it : cells_) {
|
||||
log_assert(this == it.second->module);
|
||||
log_assert(it.first == it.second->name);
|
||||
log_assert(!it.first.empty());
|
||||
log_assert(!it.second->type.empty());
|
||||
for (auto &it2 : it.second->connections()) {
|
||||
log_assert(!it2.first.empty());
|
||||
it2.second.check(this);
|
||||
for (int i : ctx.item_range(const_module->wires_size())) {
|
||||
auto it = *const_module->wires_.element(i);
|
||||
log_assert(const_module == it.second->module);
|
||||
log_assert(it.first == it.second->name);
|
||||
log_assert(!it.first.empty());
|
||||
log_assert(it.second->width >= 0);
|
||||
log_assert(it.second->port_id >= 0);
|
||||
for (auto &it2 : it.second->attributes)
|
||||
log_assert(!it2.first.empty());
|
||||
if (it.second->port_id) {
|
||||
log_assert(GetSize(const_module->ports) >= it.second->port_id);
|
||||
log_assert(const_module->ports.at(it.second->port_id-1) == it.first);
|
||||
log_assert(it.second->port_input || it.second->port_output);
|
||||
log_assert(it.second->port_id <= GetSize(ports_declared));
|
||||
bool previously_declared = ports_declared[it.second->port_id-1].set_and_return_old();
|
||||
log_assert(previously_declared == false);
|
||||
} else
|
||||
log_assert(!it.second->port_input && !it.second->port_output);
|
||||
}
|
||||
for (auto &it2 : it.second->attributes)
|
||||
log_assert(!it2.first.empty());
|
||||
for (auto &it2 : it.second->parameters)
|
||||
log_assert(!it2.first.empty());
|
||||
InternalCellChecker checker(this, it.second);
|
||||
checker.check();
|
||||
if (it.second->has_memid()) {
|
||||
log_assert(memories.count(it.second->parameters.at(ID::MEMID).decode_string()));
|
||||
} else if (it.second->is_mem_cell()) {
|
||||
IdString memid = it.second->parameters.at(ID::MEMID).decode_string();
|
||||
log_assert(!memories.count(memid));
|
||||
log_assert(!packed_memids.count(memid));
|
||||
packed_memids.insert(memid);
|
||||
}
|
||||
auto cell_mod = design->module(it.first);
|
||||
if (cell_mod != nullptr) {
|
||||
// assertion check below to make sure that there are no
|
||||
// cases where a cell has a blackbox attribute since
|
||||
// that is deprecated
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
log_assert(!it.second->get_blackbox_attribute());
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
}
|
||||
});
|
||||
for (const MonotonicFlag &port_declared : ports_declared)
|
||||
log_assert(port_declared.load() == true);
|
||||
pool<std::string> memids_pool;
|
||||
for (std::string &memid : memids)
|
||||
log_assert(memids_pool.insert(memid).second);
|
||||
|
||||
for (auto &it : processes) {
|
||||
for (auto &it : module->processes) {
|
||||
log_assert(it.first == it.second->name);
|
||||
log_assert(!it.first.empty());
|
||||
log_assert(it.second->root_case.compare.empty());
|
||||
std::vector<CaseRule*> all_cases = {&it.second->root_case};
|
||||
std::vector<RTLIL::CaseRule*> all_cases = {&it.second->root_case};
|
||||
for (size_t i = 0; i < all_cases.size(); i++) {
|
||||
for (auto &switch_it : all_cases[i]->switches) {
|
||||
for (auto &case_it : switch_it->cases) {
|
||||
|
|
@ -2784,34 +2693,41 @@ void RTLIL::Module::check()
|
|||
}
|
||||
for (auto &sync_it : it.second->syncs) {
|
||||
switch (sync_it->type) {
|
||||
case SyncType::ST0:
|
||||
case SyncType::ST1:
|
||||
case SyncType::STp:
|
||||
case SyncType::STn:
|
||||
case SyncType::STe:
|
||||
case RTLIL::SyncType::ST0:
|
||||
case RTLIL::SyncType::ST1:
|
||||
case RTLIL::SyncType::STp:
|
||||
case RTLIL::SyncType::STn:
|
||||
case RTLIL::SyncType::STe:
|
||||
log_assert(!sync_it->signal.empty());
|
||||
break;
|
||||
case SyncType::STa:
|
||||
case SyncType::STg:
|
||||
case SyncType::STi:
|
||||
case RTLIL::SyncType::STa:
|
||||
case RTLIL::SyncType::STg:
|
||||
case RTLIL::SyncType::STi:
|
||||
log_assert(sync_it->signal.empty());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &it : connections_) {
|
||||
for (auto &it : module->connections_) {
|
||||
log_assert(it.first.size() == it.second.size());
|
||||
log_assert(!it.first.has_const());
|
||||
it.first.check(this);
|
||||
it.second.check(this);
|
||||
it.first.check(module);
|
||||
it.second.check(module);
|
||||
}
|
||||
|
||||
for (auto &it : attributes)
|
||||
for (auto &it : module->attributes)
|
||||
log_assert(!it.first.empty());
|
||||
#endif
|
||||
}
|
||||
|
||||
void RTLIL::Module::check()
|
||||
{
|
||||
int pool_size = ThreadPool::work_pool_size(0, cells_size(), 1000);
|
||||
ParallelDispatchThreadPool thread_pool(pool_size);
|
||||
check_module(this, thread_pool);
|
||||
}
|
||||
|
||||
void RTLIL::Module::optimize()
|
||||
{
|
||||
}
|
||||
|
|
@ -4610,7 +4526,7 @@ bool RTLIL::Cell::is_mem_cell() const
|
|||
}
|
||||
|
||||
bool RTLIL::Cell::is_builtin_ff() const {
|
||||
return builtin_ff_cell_types_internal().count(type) > 0;
|
||||
return StaticCellTypes::categories.is_ff(type);
|
||||
}
|
||||
|
||||
RTLIL::SigChunk::SigChunk(const RTLIL::SigBit &bit)
|
||||
|
|
@ -5074,31 +4990,35 @@ void RTLIL::SigSpec::remove2(const RTLIL::SigSpec &pattern, RTLIL::SigSpec *othe
|
|||
other->unpack();
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
bool other_modified = false;
|
||||
for (int i = GetSize(bits_) - 1; i >= 0; i--)
|
||||
{
|
||||
if (bits_[i].wire == NULL) continue;
|
||||
// Convert pattern to pool for O(1) lookup, avoiding O(n*m) chunk iteration
|
||||
pool<SigBit> pattern_bits;
|
||||
pattern_bits.reserve(pattern.size());
|
||||
for (auto &bit : pattern)
|
||||
if (bit.wire != NULL)
|
||||
pattern_bits.insert(bit);
|
||||
|
||||
for (auto &pattern_chunk : pattern.chunks())
|
||||
if (bits_[i].wire == pattern_chunk.wire &&
|
||||
bits_[i].offset >= pattern_chunk.offset &&
|
||||
bits_[i].offset < pattern_chunk.offset + pattern_chunk.width) {
|
||||
modified = true;
|
||||
bits_.erase(bits_.begin() + i);
|
||||
if (other != NULL) {
|
||||
other_modified = true;
|
||||
other->bits_.erase(other->bits_.begin() + i);
|
||||
}
|
||||
break;
|
||||
// Compact in-place to avoid O(n^2) erase operations
|
||||
size_t write_idx = 0;
|
||||
for (size_t read_idx = 0; read_idx < bits_.size(); read_idx++)
|
||||
{
|
||||
if (!(bits_[read_idx].wire != NULL && pattern_bits.count(bits_[read_idx]))) {
|
||||
if (write_idx != read_idx) {
|
||||
bits_[write_idx] = bits_[read_idx];
|
||||
if (other != NULL)
|
||||
other->bits_[write_idx] = other->bits_[read_idx];
|
||||
}
|
||||
write_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
bool modified = (write_idx < bits_.size());
|
||||
if (modified) {
|
||||
bits_.resize(write_idx);
|
||||
hash_.clear();
|
||||
try_repack();
|
||||
}
|
||||
if (other_modified) {
|
||||
if (other != NULL && modified) {
|
||||
other->bits_.resize(write_idx);
|
||||
other->hash_.clear();
|
||||
other->try_repack();
|
||||
}
|
||||
|
|
@ -5125,24 +5045,27 @@ void RTLIL::SigSpec::remove2(const pool<RTLIL::SigBit> &pattern, RTLIL::SigSpec
|
|||
other->unpack();
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
bool other_modified = false;
|
||||
for (int i = GetSize(bits_) - 1; i >= 0; i--) {
|
||||
if (bits_[i].wire != NULL && pattern.count(bits_[i])) {
|
||||
modified = true;
|
||||
bits_.erase(bits_.begin() + i);
|
||||
if (other != NULL) {
|
||||
other_modified = true;
|
||||
other->bits_.erase(other->bits_.begin() + i);
|
||||
// Avoid O(n^2) complexity by compacting in-place
|
||||
size_t write_idx = 0;
|
||||
for (size_t read_idx = 0; read_idx < bits_.size(); read_idx++) {
|
||||
if (!(bits_[read_idx].wire != NULL && pattern.count(bits_[read_idx]))) {
|
||||
if (write_idx != read_idx) {
|
||||
bits_[write_idx] = bits_[read_idx];
|
||||
if (other != NULL)
|
||||
other->bits_[write_idx] = other->bits_[read_idx];
|
||||
}
|
||||
write_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
bool modified = (write_idx < bits_.size());
|
||||
if (modified) {
|
||||
bits_.resize(write_idx);
|
||||
hash_.clear();
|
||||
try_repack();
|
||||
}
|
||||
if (other_modified) {
|
||||
if (other != NULL && modified) {
|
||||
other->bits_.resize(write_idx);
|
||||
other->hash_.clear();
|
||||
other->try_repack();
|
||||
}
|
||||
|
|
@ -5158,24 +5081,27 @@ void RTLIL::SigSpec::remove2(const std::set<RTLIL::SigBit> &pattern, RTLIL::SigS
|
|||
other->unpack();
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
bool other_modified = false;
|
||||
for (int i = GetSize(bits_) - 1; i >= 0; i--) {
|
||||
if (bits_[i].wire != NULL && pattern.count(bits_[i])) {
|
||||
modified = true;
|
||||
bits_.erase(bits_.begin() + i);
|
||||
if (other != NULL) {
|
||||
other_modified = true;
|
||||
other->bits_.erase(other->bits_.begin() + i);
|
||||
// Avoid O(n^2) complexity by compacting in-place
|
||||
size_t write_idx = 0;
|
||||
for (size_t read_idx = 0; read_idx < bits_.size(); read_idx++) {
|
||||
if (!(bits_[read_idx].wire != NULL && pattern.count(bits_[read_idx]))) {
|
||||
if (write_idx != read_idx) {
|
||||
bits_[write_idx] = bits_[read_idx];
|
||||
if (other != NULL)
|
||||
other->bits_[write_idx] = other->bits_[read_idx];
|
||||
}
|
||||
write_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
bool modified = (write_idx < bits_.size());
|
||||
if (modified) {
|
||||
bits_.resize(write_idx);
|
||||
hash_.clear();
|
||||
try_repack();
|
||||
}
|
||||
if (other_modified) {
|
||||
if (other != NULL && modified) {
|
||||
other->bits_.resize(write_idx);
|
||||
other->hash_.clear();
|
||||
other->try_repack();
|
||||
}
|
||||
|
|
@ -5346,26 +5272,32 @@ RTLIL::SigSpec RTLIL::SigSpec::extract(int offset, int length) const
|
|||
log_assert(length >= 0);
|
||||
log_assert(offset + length <= size());
|
||||
|
||||
SigSpec extracted;
|
||||
Chunks cs = chunks();
|
||||
auto it = cs.begin();
|
||||
for (; offset; offset -= it->width, ++it) {
|
||||
if (offset < it->width) {
|
||||
int chunk_length = min(it->width - offset, length);
|
||||
extracted.append(it->extract(offset, chunk_length));
|
||||
length -= chunk_length;
|
||||
++it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (; length; length -= it->width, ++it) {
|
||||
if (length >= it->width) {
|
||||
extracted.append(*it);
|
||||
std::vector<SigBit> extracted;
|
||||
SigBit first;
|
||||
bool is_packing = true;
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
bool was_packing_before = is_packing;
|
||||
SigBit bit = (*this)[i];
|
||||
if (i == offset) {
|
||||
first = bit;
|
||||
if (!bit.wire)
|
||||
is_packing = false;
|
||||
} else {
|
||||
extracted.append(it->extract(0, length));
|
||||
break;
|
||||
if (bit.wire != first.wire)
|
||||
is_packing = false;
|
||||
if (bit.wire)
|
||||
if (bit.offset != first.offset + (i - offset))
|
||||
is_packing = false;
|
||||
}
|
||||
if (was_packing_before && !is_packing)
|
||||
for (int j = offset; j < i; j++)
|
||||
extracted.push_back((*this)[j]);
|
||||
if (!is_packing)
|
||||
extracted.push_back((*this)[i]);
|
||||
}
|
||||
if (is_packing)
|
||||
return SigChunk(first.wire, first.offset, length);
|
||||
|
||||
return extracted;
|
||||
}
|
||||
|
||||
|
|
@ -5470,7 +5402,7 @@ RTLIL::SigSpec RTLIL::SigSpec::repeat(int num) const
|
|||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void RTLIL::SigSpec::check(Module *mod) const
|
||||
void RTLIL::SigSpec::check(const Module *mod) const
|
||||
{
|
||||
if (rep_ == CHUNK)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -275,6 +275,17 @@ struct RTLIL::IdString
|
|||
*out += std::to_string(-index_);
|
||||
}
|
||||
|
||||
std::string unescape() const {
|
||||
if (index_ < 0) {
|
||||
// Must start with "$auto$" so no unescaping required.
|
||||
return str();
|
||||
}
|
||||
std::string_view str = global_id_storage_.at(index_).str_view();
|
||||
if (str.size() < 2 || str[0] != '\\' || str[1] == '$' || str[1] == '\\' || (str[1] >= '0' && str[1] <= '9'))
|
||||
return std::string(str);
|
||||
return std::string(str.substr(1));
|
||||
}
|
||||
|
||||
class Substrings {
|
||||
std::string_view first_;
|
||||
int suffix_number;
|
||||
|
|
@ -737,6 +748,7 @@ template <> struct IDMacroHelper<-1> {
|
|||
namespace RTLIL {
|
||||
extern dict<std::string, std::string> constpad;
|
||||
|
||||
[[deprecated("use StaticCellTypes::categories.is_ff() instead")]]
|
||||
const pool<IdString> &builtin_ff_cell_types();
|
||||
|
||||
static inline std::string escape_id(const std::string &str) {
|
||||
|
|
@ -758,7 +770,7 @@ namespace RTLIL {
|
|||
}
|
||||
|
||||
static inline std::string unescape_id(RTLIL::IdString str) {
|
||||
return unescape_id(str.str());
|
||||
return str.unescape();
|
||||
}
|
||||
|
||||
static inline const char *id2cstr(RTLIL::IdString str) {
|
||||
|
|
@ -1388,6 +1400,8 @@ struct RTLIL::SigSpecConstIterator
|
|||
struct RTLIL::SigSpec
|
||||
{
|
||||
private:
|
||||
friend class SigSpecRepTest;
|
||||
FRIEND_TEST(SigSpecRepTest, Extract);
|
||||
enum Representation : char {
|
||||
CHUNK,
|
||||
BITS,
|
||||
|
|
@ -1748,9 +1762,9 @@ public:
|
|||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void check(Module *mod = nullptr) const;
|
||||
void check(const Module *mod = nullptr) const;
|
||||
#else
|
||||
void check(Module *mod = nullptr) const { (void)mod; }
|
||||
void check(const Module *mod = nullptr) const { (void)mod; }
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ static int tcl_get_attr(ClientData, Tcl_Interp *interp, int argc, const char *ar
|
|||
ERROR("object not found")
|
||||
|
||||
if (string_flag) {
|
||||
Tcl_SetResult(interp, (char *) obj->get_string_attribute(attr_id).c_str(), TCL_VOLATILE);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(obj->get_string_attribute(attr_id).c_str(), -1));
|
||||
} else if (int_flag || uint_flag || sint_flag) {
|
||||
if (!obj->has_attribute(attr_id))
|
||||
ERROR("attribute missing (required for -int)");
|
||||
|
|
@ -295,7 +295,7 @@ static int tcl_get_attr(ClientData, Tcl_Interp *interp, int argc, const char *ar
|
|||
if (!obj->has_attribute(attr_id))
|
||||
ERROR("attribute missing (required unless -bool or -string)")
|
||||
|
||||
Tcl_SetResult(interp, (char *) obj->attributes.at(attr_id).as_string().c_str(), TCL_VOLATILE);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(obj->attributes.at(attr_id).as_string().c_str(), -1));
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
|
|
@ -341,7 +341,7 @@ static int tcl_has_attr(ClientData, Tcl_Interp *interp, int argc, const char *ar
|
|||
if (!obj)
|
||||
ERROR("object not found")
|
||||
|
||||
Tcl_SetResult(interp, (char *) std::to_string(obj->has_attribute(attr_id)).c_str(), TCL_VOLATILE);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(std::to_string(obj->has_attribute(attr_id)).c_str(), -1));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
|
@ -465,14 +465,14 @@ static int tcl_get_param(ClientData, Tcl_Interp *interp, int argc, const char *a
|
|||
const RTLIL::Const &value = cell->getParam(param_id);
|
||||
|
||||
if (string_flag) {
|
||||
Tcl_SetResult(interp, (char *) value.decode_string().c_str(), TCL_VOLATILE);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(value.decode_string().c_str(), -1));
|
||||
} else if (int_flag || uint_flag || sint_flag) {
|
||||
mp_int value_mp;
|
||||
if (!const_to_mp_int(value, &value_mp, sint_flag, uint_flag))
|
||||
ERROR("bignum manipulation failed");
|
||||
Tcl_SetObjResult(interp, Tcl_NewBignumObj(&value_mp));
|
||||
} else {
|
||||
Tcl_SetResult(interp, (char *) value.as_string().c_str(), TCL_VOLATILE);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(value.as_string().c_str(), -1));
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,20 @@ static int get_max_threads()
|
|||
return max_threads;
|
||||
}
|
||||
|
||||
static int init_work_units_per_thread_override()
|
||||
{
|
||||
const char *v = getenv("YOSYS_WORK_UNITS_PER_THREAD");
|
||||
if (v == nullptr)
|
||||
return 0;
|
||||
return atoi(v);
|
||||
}
|
||||
|
||||
static int get_work_units_per_thread_override()
|
||||
{
|
||||
static int work_units_per_thread = init_work_units_per_thread_override();
|
||||
return work_units_per_thread;
|
||||
}
|
||||
|
||||
void DeferredLogs::flush()
|
||||
{
|
||||
for (auto &m : logs)
|
||||
|
|
@ -37,6 +51,14 @@ int ThreadPool::pool_size(int reserved_cores, int max_worker_threads)
|
|||
#endif
|
||||
}
|
||||
|
||||
int ThreadPool::work_pool_size(int reserved_cores, int work_units, int work_units_per_thread)
|
||||
{
|
||||
int work_units_per_thread_override = get_work_units_per_thread_override();
|
||||
if (work_units_per_thread_override > 0)
|
||||
work_units_per_thread = work_units_per_thread_override;
|
||||
return pool_size(reserved_cores, work_units / work_units_per_thread);
|
||||
}
|
||||
|
||||
ThreadPool::ThreadPool(int pool_size, std::function<void(int)> b)
|
||||
: body(std::move(b))
|
||||
{
|
||||
|
|
@ -57,4 +79,74 @@ ThreadPool::~ThreadPool()
|
|||
#endif
|
||||
}
|
||||
|
||||
IntRange item_range_for_worker(int num_items, int thread_num, int num_threads)
|
||||
{
|
||||
if (num_threads <= 1) {
|
||||
return {0, num_items};
|
||||
}
|
||||
int items_per_thread = num_items / num_threads;
|
||||
int extra_items = num_items % num_threads;
|
||||
// The first `extra_items` threads get one extra item.
|
||||
int start = thread_num * items_per_thread + std::min(thread_num, extra_items);
|
||||
int end = (thread_num + 1) * items_per_thread + std::min(thread_num + 1, extra_items);
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
ParallelDispatchThreadPool::ParallelDispatchThreadPool(int pool_size)
|
||||
: num_worker_threads_(std::max(1, pool_size) - 1)
|
||||
{
|
||||
#ifdef YOSYS_ENABLE_THREADS
|
||||
main_to_workers_signal.resize(num_worker_threads_, 0);
|
||||
#endif
|
||||
// Don't start the threads until we've constructed all our data members.
|
||||
thread_pool = std::make_unique<ThreadPool>(num_worker_threads_, [this](int thread_num){
|
||||
run_worker(thread_num);
|
||||
});
|
||||
}
|
||||
|
||||
ParallelDispatchThreadPool::~ParallelDispatchThreadPool()
|
||||
{
|
||||
#ifdef YOSYS_ENABLE_THREADS
|
||||
if (num_worker_threads_ == 0)
|
||||
return;
|
||||
current_work = nullptr;
|
||||
num_active_worker_threads_.store(num_worker_threads_, std::memory_order_relaxed);
|
||||
signal_workers_start();
|
||||
wait_for_workers_done();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ParallelDispatchThreadPool::run(std::function<void(const RunCtx &)> work, int max_threads)
|
||||
{
|
||||
Multithreading multithreading;
|
||||
int num_active_worker_threads = num_threads(max_threads) - 1;
|
||||
if (num_active_worker_threads == 0) {
|
||||
work({{0}, 1});
|
||||
return;
|
||||
}
|
||||
#ifdef YOSYS_ENABLE_THREADS
|
||||
num_active_worker_threads_.store(num_active_worker_threads, std::memory_order_relaxed);
|
||||
current_work = &work;
|
||||
signal_workers_start();
|
||||
work({{0}, num_active_worker_threads + 1});
|
||||
wait_for_workers_done();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ParallelDispatchThreadPool::run_worker(int thread_num)
|
||||
{
|
||||
#ifdef YOSYS_ENABLE_THREADS
|
||||
while (true)
|
||||
{
|
||||
worker_wait_for_start(thread_num);
|
||||
if (current_work == nullptr)
|
||||
break;
|
||||
int num_active_worker_threads = num_active_worker_threads_.load(std::memory_order_relaxed);
|
||||
(*current_work)({{thread_num + 1}, num_active_worker_threads + 1});
|
||||
signal_worker_done();
|
||||
}
|
||||
signal_worker_done();
|
||||
#endif
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "kernel/yosys_common.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/utils.h"
|
||||
|
||||
#ifndef YOSYS_THREADING_H
|
||||
#define YOSYS_THREADING_H
|
||||
|
|
@ -131,6 +132,11 @@ public:
|
|||
// The result may be 0.
|
||||
static int pool_size(int reserved_cores, int max_worker_threads);
|
||||
|
||||
// Computes the number of worker threads to use, by dividing work_units among threads.
|
||||
// For testing purposes you can set YOSYS_WORK_UNITS_PER_THREAD to override `work_units_per_thread`.
|
||||
// The result may be 0.
|
||||
static int work_pool_size(int reserved_cores, int work_units, int work_units_per_thread);
|
||||
|
||||
// Create a pool of threads running the given closure (parameterized by thread number).
|
||||
// `pool_size` must be the result of a `pool_size()` call.
|
||||
ThreadPool(int pool_size, std::function<void(int)> b);
|
||||
|
|
@ -154,6 +160,142 @@ private:
|
|||
#endif
|
||||
};
|
||||
|
||||
// Divides some number of items into `num_threads` subranges and returns the
|
||||
// `thread_num`'th subrange. If `num_threads` is zero, returns the whole range.
|
||||
IntRange item_range_for_worker(int num_items, int thread_num, int num_threads);
|
||||
|
||||
// A type that encapsulates the index of a thread in some list of threads. Useful for
|
||||
// stronger typechecking and code readability.
|
||||
struct ThreadIndex {
|
||||
int thread_num;
|
||||
};
|
||||
|
||||
// A set of threads with a `run()` API that runs a closure on all of the threads
|
||||
// and wait for all those closures to complete. This is a convenient way to implement
|
||||
// parallel algorithms that use barrier synchronization.
|
||||
class ParallelDispatchThreadPool
|
||||
{
|
||||
public:
|
||||
// Create a pool of threads running the given closure (parameterized by thread number).
|
||||
// `pool_size` must be the result of a `pool_size()` call.
|
||||
// `pool_size` can be zero, which we treat as 1.
|
||||
ParallelDispatchThreadPool(int pool_size);
|
||||
~ParallelDispatchThreadPool();
|
||||
|
||||
// For each thread running a closure, a `RunCtx` is passed to the closure. Currently
|
||||
// it contains the thread index and the total number of threads. It can be passed
|
||||
// directly to any APIs requiring a `ThreadIndex`.
|
||||
struct RunCtx : public ThreadIndex {
|
||||
int num_threads;
|
||||
IntRange item_range(int num_items) const {
|
||||
return item_range_for_worker(num_items, thread_num, num_threads);
|
||||
}
|
||||
};
|
||||
// Sometimes we only want to activate a subset of the threads in the pool. This
|
||||
// class provides a way to do that. It provides the same `num_threads()`
|
||||
// and `run()` APIs as a `ParallelDispatchThreadPool`.
|
||||
class Subpool {
|
||||
public:
|
||||
Subpool(ParallelDispatchThreadPool &parent, int max_threads)
|
||||
: parent(parent), max_threads(max_threads) {}
|
||||
// Returns the number of threads that will be used when calling `run()`.
|
||||
int num_threads() const {
|
||||
return parent.num_threads(max_threads);
|
||||
}
|
||||
void run(std::function<void(const RunCtx &)> work) {
|
||||
parent.run(std::move(work), max_threads);
|
||||
}
|
||||
ParallelDispatchThreadPool &thread_pool() { return parent; }
|
||||
private:
|
||||
ParallelDispatchThreadPool &parent;
|
||||
int max_threads;
|
||||
};
|
||||
|
||||
// Run the `work` function in parallel on each thread in the pool (parameterized by
|
||||
// thread number). Waits for all work functions to complete. Only one `run()` can be
|
||||
// active at a time.
|
||||
// Uses no more than `max_threads` threads (but at least one).
|
||||
void run(std::function<void(const RunCtx &)> work) {
|
||||
run(std::move(work), INT_MAX);
|
||||
}
|
||||
|
||||
// Returns the number of threads that will be used when calling `run()`.
|
||||
int num_threads() const {
|
||||
return num_threads(INT_MAX);
|
||||
}
|
||||
private:
|
||||
friend class Subpool;
|
||||
|
||||
void run(std::function<void(const RunCtx &)> work, int max_threads);
|
||||
int num_threads(int max_threads) const {
|
||||
return std::min(num_worker_threads_ + 1, std::max(1, max_threads));
|
||||
}
|
||||
void run_worker(int thread_num);
|
||||
|
||||
std::function<void(const RunCtx &)> *current_work = nullptr;
|
||||
// Keeps a correct count even when threads are exiting.
|
||||
int num_worker_threads_;
|
||||
// The count of active worker threads for the current `run()`.
|
||||
// This is only written by the main thread, and only written when
|
||||
// no other worker threads are running (i.e. all worker threads have
|
||||
// passed the increment of `done_workers` in `signal_worker_done()`
|
||||
// and not passed the release of the lock in `worker_wait_for_start()`.
|
||||
// Although there can't be any races, we still need to make it atomic
|
||||
// to prevent the compiler reordering accesses so the above invariant
|
||||
// is maintained.
|
||||
std::atomic<int> num_active_worker_threads_ = 0;
|
||||
|
||||
#ifdef YOSYS_ENABLE_THREADS
|
||||
// Not especially efficient for large numbers of threads. Worker wakeup could scale
|
||||
// better by conceptually organising workers into a tree and having workers wake
|
||||
// up their children.
|
||||
std::mutex main_to_workers_signal_mutex;
|
||||
std::condition_variable main_to_workers_signal_cv;
|
||||
std::vector<uint8_t> main_to_workers_signal;
|
||||
void signal_workers_start() {
|
||||
std::unique_lock lock(main_to_workers_signal_mutex);
|
||||
int num_active_worker_threads = num_active_worker_threads_.load(std::memory_order_relaxed);
|
||||
std::fill(main_to_workers_signal.begin(), main_to_workers_signal.begin() + num_active_worker_threads, 1);
|
||||
// When `num_active_worker_threads_` is small compared to `num_worker_threads_`, we have a "thundering herd"
|
||||
// problem here. Fixing that would add complexity so don't worry about it for now.
|
||||
main_to_workers_signal_cv.notify_all();
|
||||
}
|
||||
void worker_wait_for_start(int thread_num) {
|
||||
std::unique_lock lock(main_to_workers_signal_mutex);
|
||||
main_to_workers_signal_cv.wait(lock, [this, thread_num] { return main_to_workers_signal[thread_num] > 0; });
|
||||
main_to_workers_signal[thread_num] = 0;
|
||||
}
|
||||
|
||||
std::atomic<int> done_workers = 0;
|
||||
std::mutex workers_to_main_signal_mutex;
|
||||
std::condition_variable workers_to_main_signal_cv;
|
||||
void signal_worker_done() {
|
||||
// Must read `num_active_worker_threads_` before we increment `d`! Otherwise
|
||||
// it is possible we would increment `d`, and then another worker signals the
|
||||
// main thread that all workers are done, and the main thread writes to
|
||||
// `num_active_worker_threads_` before we check it.
|
||||
int num_active_worker_threads = num_active_worker_threads_.load(std::memory_order_relaxed);
|
||||
int d = done_workers.fetch_add(1, std::memory_order_release);
|
||||
if (d + 1 == num_active_worker_threads) {
|
||||
std::unique_lock lock(workers_to_main_signal_mutex);
|
||||
workers_to_main_signal_cv.notify_all();
|
||||
}
|
||||
}
|
||||
void wait_for_workers_done() {
|
||||
std::unique_lock lock(workers_to_main_signal_mutex);
|
||||
workers_to_main_signal_cv.wait(lock, [this] {
|
||||
int num_active_worker_threads = num_active_worker_threads_.load(std::memory_order_relaxed);
|
||||
return done_workers.load(std::memory_order_acquire) == num_active_worker_threads;
|
||||
});
|
||||
done_workers.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
#endif
|
||||
// Ensure `thread_pool` is destroyed before any other members,
|
||||
// forcing all threads to be joined before destroying the
|
||||
// members (e.g. workers_to_main_signal_mutex) they might be using.
|
||||
std::unique_ptr<ThreadPool> thread_pool;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class ConcurrentStack
|
||||
{
|
||||
|
|
@ -181,6 +323,382 @@ private:
|
|||
std::vector<T> contents;
|
||||
};
|
||||
|
||||
// A vector that is sharded into buckets, one per thread. This lets multiple threads write
|
||||
// efficiently to the vector without synchronization overhead. After all writers have
|
||||
// finished writing, the vector can be iterated over. The iteration order is deterministic:
|
||||
// all the elements written by thread 0 in the order it inserted them, followed by all elements
|
||||
// written by thread 1, etc.
|
||||
template <typename T>
|
||||
class ShardedVector {
|
||||
public:
|
||||
ShardedVector(const ParallelDispatchThreadPool &thread_pool) {
|
||||
init(thread_pool.num_threads());
|
||||
}
|
||||
ShardedVector(const ParallelDispatchThreadPool::Subpool &thread_pool) {
|
||||
init(thread_pool.num_threads());
|
||||
}
|
||||
|
||||
// Insert a value, passing the `ThreadIndex` of the writer thread.
|
||||
// Parallel inserts with different `ThreadIndex` values are fine.
|
||||
// Inserts must not run concurrently with any other methods (e.g.
|
||||
// iteration or `empty()`.)
|
||||
void insert(const ThreadIndex &thread, T value) {
|
||||
buckets[thread.thread_num].emplace_back(std::move(value));
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
for (const std::vector<T> &bucket : buckets)
|
||||
if (!bucket.empty())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
using Buckets = std::vector<std::vector<T>>;
|
||||
class iterator {
|
||||
public:
|
||||
iterator(typename Buckets::iterator bucket_it, typename Buckets::iterator bucket_end)
|
||||
: bucket_it(std::move(bucket_it)), bucket_end(std::move(bucket_end)) {
|
||||
if (bucket_it != bucket_end)
|
||||
inner_it = bucket_it->begin();
|
||||
normalize();
|
||||
}
|
||||
T& operator*() const { return *inner_it.value(); }
|
||||
iterator &operator++() {
|
||||
++*inner_it;
|
||||
normalize();
|
||||
return *this;
|
||||
}
|
||||
bool operator!=(const iterator &other) const {
|
||||
return bucket_it != other.bucket_it || inner_it != other.inner_it;
|
||||
}
|
||||
private:
|
||||
void normalize() {
|
||||
if (bucket_it == bucket_end)
|
||||
return;
|
||||
while (inner_it == bucket_it->end()) {
|
||||
++bucket_it;
|
||||
if (bucket_it == bucket_end) {
|
||||
inner_it.reset();
|
||||
return;
|
||||
}
|
||||
inner_it = bucket_it->begin();
|
||||
}
|
||||
}
|
||||
std::optional<typename std::vector<T>::iterator> inner_it;
|
||||
typename Buckets::iterator bucket_it;
|
||||
typename Buckets::iterator bucket_end;
|
||||
};
|
||||
iterator begin() { return iterator(buckets.begin(), buckets.end()); }
|
||||
iterator end() { return iterator(buckets.end(), buckets.end()); }
|
||||
private:
|
||||
void init(int num_threads) {
|
||||
buckets.resize(num_threads);
|
||||
}
|
||||
Buckets buckets;
|
||||
};
|
||||
|
||||
// This collision handler for `ShardedHashtable` resolves collisions by keeping
|
||||
// the current value and discarding the other. This is correct when all values with the
|
||||
// same key are interchangeable, i.e. when the hashtable is being used as a set instead
|
||||
// of a map.
|
||||
template <typename V>
|
||||
struct SetCollisionHandler {
|
||||
void operator()(typename V::Accumulated &, typename V::Accumulated &) const {}
|
||||
};
|
||||
|
||||
// A hashtable that can be efficiently built in parallel and then looked up concurrently.
|
||||
// `V` is the type of elements that will be added to the hashtable. It must have a
|
||||
// member type `Accumulated` representing the combination of multiple `V` elements. This
|
||||
// can be the same as `V`, but for example `V` could contain a Wire* and `V::Accumulated`
|
||||
// could contain a `pool<Wire*>`. `KeyEquality` is a class containing an `operator()` that
|
||||
// returns true of two `V` elements have equal keys.
|
||||
// `CollisionHandler` is used to reduce two `V::Accumulated` values into a single value.
|
||||
//
|
||||
// To use this, first construct a `Builder` and fill it in (in parallel), then construct
|
||||
// a `ShardedHashtable` from the `Builder`.
|
||||
template <typename V, typename KeyEquality, typename CollisionHandler>
|
||||
class ShardedHashtable {
|
||||
public:
|
||||
// A combination of a `V` and its hash value.
|
||||
struct Value {
|
||||
Value(V value, unsigned int hash) : value(std::move(value)), hash(hash) {}
|
||||
Value(Value &&) = default;
|
||||
Value(const Value &) = delete;
|
||||
Value &operator=(const Value &) = delete;
|
||||
V value;
|
||||
unsigned int hash;
|
||||
};
|
||||
// A combination of a `V::Accumulated` and its hash value.
|
||||
struct AccumulatedValue {
|
||||
AccumulatedValue(typename V::Accumulated value, unsigned int hash) : value(std::move(value)), hash(hash) {}
|
||||
AccumulatedValue(AccumulatedValue &&) = default;
|
||||
#if defined(_MSC_VER)
|
||||
AccumulatedValue(const AccumulatedValue &) {
|
||||
log_error("Copy constructor called on AccumulatedValue");
|
||||
}
|
||||
AccumulatedValue &operator=(const AccumulatedValue &) {
|
||||
log_error("Copy assignment called on AccumulatedValue");
|
||||
return *this;
|
||||
}
|
||||
#else
|
||||
AccumulatedValue(const AccumulatedValue &) = delete;
|
||||
AccumulatedValue &operator=(const AccumulatedValue &) = delete;
|
||||
#endif
|
||||
typename V::Accumulated value;
|
||||
unsigned int hash;
|
||||
};
|
||||
// A class containing an `operator()` that returns true of two `AccumulatedValue`
|
||||
// elements have equal keys.
|
||||
// Required to insert `AccumulatedValue`s into an `std::unordered_set`.
|
||||
struct AccumulatedValueEquality {
|
||||
KeyEquality inner;
|
||||
AccumulatedValueEquality(const KeyEquality &inner) : inner(inner) {}
|
||||
bool operator()(const AccumulatedValue &v1, const AccumulatedValue &v2) const {
|
||||
return inner(v1.value, v2.value);
|
||||
}
|
||||
};
|
||||
// A class containing an `operator()` that returns the hash value of an `AccumulatedValue`.
|
||||
// Required to insert `AccumulatedValue`s into an `std::unordered_set`.
|
||||
struct AccumulatedValueHashOp {
|
||||
size_t operator()(const AccumulatedValue &v) const {
|
||||
return static_cast<size_t>(v.hash);
|
||||
}
|
||||
};
|
||||
using Shard = std::unordered_set<AccumulatedValue, AccumulatedValueHashOp, AccumulatedValueEquality>;
|
||||
|
||||
// First construct one of these. Then populate it in parallel by calling `insert()` from many threads.
|
||||
// Then do another parallel phase calling `process()` from many threads.
|
||||
class Builder {
|
||||
public:
|
||||
Builder(const ParallelDispatchThreadPool &thread_pool, KeyEquality equality = KeyEquality(), CollisionHandler collision_handler = CollisionHandler())
|
||||
: collision_handler(std::move(collision_handler)) {
|
||||
init(thread_pool.num_threads(), std::move(equality));
|
||||
}
|
||||
Builder(const ParallelDispatchThreadPool::Subpool &thread_pool, KeyEquality equality = KeyEquality(), CollisionHandler collision_handler = CollisionHandler())
|
||||
: collision_handler(std::move(collision_handler)) {
|
||||
init(thread_pool.num_threads(), std::move(equality));
|
||||
}
|
||||
// First call `insert` to insert all elements. All inserts must finish
|
||||
// before calling any `process()`.
|
||||
void insert(const ThreadIndex &thread, Value v) {
|
||||
// You might think that for the single-threaded case, we can optimize by
|
||||
// inserting directly into the `std::unordered_set` here. But that slows things down
|
||||
// a lot and I never got around to figuring out why.
|
||||
std::vector<std::vector<Value>> &buckets = all_buckets[thread.thread_num];
|
||||
size_t bucket = static_cast<size_t>(v.hash) % buckets.size();
|
||||
buckets[bucket].emplace_back(std::move(v));
|
||||
}
|
||||
// Then call `process` for each thread. All `process()`s must finish before using
|
||||
// the `Builder` to construct a `ShardedHashtable`.
|
||||
void process(const ThreadIndex &thread) {
|
||||
int size = 0;
|
||||
for (std::vector<std::vector<Value>> &buckets : all_buckets)
|
||||
size += GetSize(buckets[thread.thread_num]);
|
||||
Shard &shard = shards[thread.thread_num];
|
||||
shard.reserve(size);
|
||||
for (std::vector<std::vector<Value>> &buckets : all_buckets) {
|
||||
for (Value &value : buckets[thread.thread_num])
|
||||
accumulate(value, shard);
|
||||
// Free as much memory as we can during the parallel phase.
|
||||
std::vector<Value>().swap(buckets[thread.thread_num]);
|
||||
}
|
||||
}
|
||||
private:
|
||||
friend class ShardedHashtable<V, KeyEquality, CollisionHandler>;
|
||||
void accumulate(Value &value, Shard &shard) {
|
||||
// With C++20 we could make this more efficient using heterogenous lookup
|
||||
AccumulatedValue accumulated_value{std::move(value.value), value.hash};
|
||||
auto [it, inserted] = shard.insert(std::move(accumulated_value));
|
||||
if (!inserted)
|
||||
collision_handler(const_cast<typename V::Accumulated &>(it->value), accumulated_value.value);
|
||||
}
|
||||
void init(int num_threads, KeyEquality equality) {
|
||||
all_buckets.resize(num_threads);
|
||||
for (std::vector<std::vector<Value>> &buckets : all_buckets)
|
||||
buckets.resize(num_threads);
|
||||
for (int i = 0; i < num_threads; ++i)
|
||||
shards.emplace_back(0, AccumulatedValueHashOp(), AccumulatedValueEquality(equality));
|
||||
}
|
||||
const CollisionHandler collision_handler;
|
||||
// A num_threads x num_threads matrix of buckets.
|
||||
// In the first phase, each thread i gemerates elements and writes them to
|
||||
// bucket [i][j] where j = hash(element) % num_threads.
|
||||
// In the second phase, thread i reads from bucket [j][i] for all j, collecting
|
||||
// all elements where i = hash(element) % num_threads.
|
||||
std::vector<std::vector<std::vector<Value>>> all_buckets;
|
||||
std::vector<Shard> shards;
|
||||
};
|
||||
|
||||
// Then finally construct the hashtable:
|
||||
ShardedHashtable(Builder &builder) : shards(std::move(builder.shards)) {
|
||||
// Check that all necessary 'process()' calls were made.
|
||||
for (std::vector<std::vector<Value>> &buckets : builder.all_buckets)
|
||||
for (std::vector<Value> &bucket : buckets)
|
||||
log_assert(bucket.empty());
|
||||
// Free memory.
|
||||
std::vector<std::vector<std::vector<Value>>>().swap(builder.all_buckets);
|
||||
}
|
||||
ShardedHashtable(ShardedHashtable &&other) = default;
|
||||
ShardedHashtable() {}
|
||||
|
||||
ShardedHashtable &operator=(ShardedHashtable &&other) = default;
|
||||
|
||||
// Look up by `AccumulatedValue`. If we switch to C++20 then we could use
|
||||
// heterogenous lookup to support looking up by `Value` here. Returns nullptr
|
||||
// if the key is not found.
|
||||
const typename V::Accumulated *find(const AccumulatedValue &v) const {
|
||||
size_t num_shards = shards.size();
|
||||
if (num_shards == 0)
|
||||
return nullptr;
|
||||
size_t shard = static_cast<size_t>(v.hash) % num_shards;
|
||||
auto it = shards[shard].find(v);
|
||||
if (it == shards[shard].end())
|
||||
return nullptr;
|
||||
return &it->value;
|
||||
}
|
||||
|
||||
// Insert an element into the table. The caller is responsible for ensuring this does not
|
||||
// happen concurrently with any other method calls.
|
||||
void insert(AccumulatedValue v) {
|
||||
size_t num_shards = shards.size();
|
||||
if (num_shards == 0)
|
||||
return;
|
||||
size_t shard = static_cast<size_t>(v.hash) % num_shards;
|
||||
shards[shard].insert(v);
|
||||
}
|
||||
|
||||
// Call this for each shard to implement parallel destruction. For very large `ShardedHashtable`s,
|
||||
// deleting all elements of all shards on a single thread can be a performance bottleneck.
|
||||
void clear(const ThreadIndex &shard) {
|
||||
AccumulatedValueEquality equality = shards[shard.thread_num].key_eq();
|
||||
shards[shard.thread_num] = Shard(0, AccumulatedValueHashOp(), equality);
|
||||
}
|
||||
private:
|
||||
std::vector<Shard> shards;
|
||||
};
|
||||
|
||||
// A concurrent work-queue that can share batches of work across threads.
|
||||
// Uses a naive implementation of work-stealing.
|
||||
template <typename T>
|
||||
class ConcurrentWorkQueue {
|
||||
public:
|
||||
// Create a queue that supports the given number of threads and
|
||||
// groups work into `batch_size` units.
|
||||
ConcurrentWorkQueue(int num_threads, int batch_size = 100)
|
||||
: batch_size(batch_size), thread_states(num_threads) {}
|
||||
int num_threads() const { return GetSize(thread_states); }
|
||||
// Push some work to do. Pushes and pops with the same `thread` must
|
||||
// not happen concurrently.
|
||||
void push(const ThreadIndex &thread, T work) {
|
||||
ThreadState &thread_state = thread_states[thread.thread_num];
|
||||
thread_state.next_batch.emplace_back(std::move(work));
|
||||
if (GetSize(thread_state.next_batch) < batch_size)
|
||||
return;
|
||||
bool was_empty;
|
||||
{
|
||||
std::unique_lock lock(thread_state.batches_lock);
|
||||
was_empty = thread_state.batches.empty();
|
||||
thread_state.batches.push_back(std::move(thread_state.next_batch));
|
||||
}
|
||||
if (was_empty) {
|
||||
std::unique_lock lock(waiters_lock);
|
||||
if (num_waiters > 0) {
|
||||
waiters_cv.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Grab some work to do.
|
||||
// If all threads enter `pop_batch()`, then instead of deadlocking the
|
||||
// queue will return no work. That is the only case in which it will
|
||||
// return no work.
|
||||
std::vector<T> pop_batch(const ThreadIndex &thread) {
|
||||
ThreadState &thread_state = thread_states[thread.thread_num];
|
||||
if (!thread_state.next_batch.empty())
|
||||
return std::move(thread_state.next_batch);
|
||||
// Empty our own work queue first.
|
||||
{
|
||||
std::unique_lock lock(thread_state.batches_lock);
|
||||
if (!thread_state.batches.empty()) {
|
||||
std::vector<T> batch = std::move(thread_state.batches.back());
|
||||
thread_state.batches.pop_back();
|
||||
return batch;
|
||||
}
|
||||
}
|
||||
// From here on in this function, our work queue is empty.
|
||||
while (true) {
|
||||
std::vector<T> batch = try_steal(thread);
|
||||
if (!batch.empty()) {
|
||||
return std::move(batch);
|
||||
}
|
||||
// Termination: if all threads run out of work, then all of
|
||||
// them will eventually enter this loop and there will be no further
|
||||
// notifications on waiters_cv, so all will eventually increment
|
||||
// num_waiters and wait, so num_waiters == num_threads()
|
||||
// will become true.
|
||||
std::unique_lock lock(waiters_lock);
|
||||
++num_waiters;
|
||||
if (num_waiters == num_threads()) {
|
||||
waiters_cv.notify_all();
|
||||
return {};
|
||||
}
|
||||
// As above, it's possible that we'll wait here even when there
|
||||
// are work batches posted by other threads. That's OK.
|
||||
waiters_cv.wait(lock);
|
||||
if (num_waiters == num_threads())
|
||||
return {};
|
||||
--num_waiters;
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::vector<T> try_steal(const ThreadIndex &thread) {
|
||||
for (int i = 1; i < num_threads(); i++) {
|
||||
int other_thread_num = (thread.thread_num + i) % num_threads();
|
||||
ThreadState &other_thread_state = thread_states[other_thread_num];
|
||||
std::unique_lock lock(other_thread_state.batches_lock);
|
||||
if (!other_thread_state.batches.empty()) {
|
||||
std::vector<T> batch = std::move(other_thread_state.batches.front());
|
||||
other_thread_state.batches.pop_front();
|
||||
return batch;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int batch_size;
|
||||
|
||||
struct ThreadState {
|
||||
// Entirely thread-local.
|
||||
std::vector<T> next_batch;
|
||||
|
||||
std::mutex batches_lock;
|
||||
// Only the associated thread ever adds to this, and only at the back.
|
||||
// Other threads can remove elements from the front.
|
||||
std::deque<std::vector<T>> batches;
|
||||
};
|
||||
std::vector<ThreadState> thread_states;
|
||||
|
||||
std::mutex waiters_lock;
|
||||
std::condition_variable waiters_cv;
|
||||
// Number of threads waiting for work. Their queues are empty.
|
||||
int num_waiters = 0;
|
||||
};
|
||||
|
||||
// A monotonic flag. Starts false, and can be set to true in a thread-safe way.
|
||||
// Once `load()` returns true, it will always return true.
|
||||
// Uses relaxed atomics so there are no memory ordering guarantees. Do not use this
|
||||
// to guard access to shared memory.
|
||||
class MonotonicFlag {
|
||||
public:
|
||||
MonotonicFlag() : value(false) {}
|
||||
bool load() const { return value.load(std::memory_order_relaxed); }
|
||||
void set() { value.store(true, std::memory_order_relaxed); }
|
||||
bool set_and_return_old() {
|
||||
return value.exchange(true, std::memory_order_relaxed);
|
||||
}
|
||||
private:
|
||||
std::atomic<bool> value;
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif // YOSYS_THREADING_H
|
||||
|
|
|
|||
|
|
@ -125,20 +125,22 @@ public:
|
|||
};
|
||||
|
||||
|
||||
// ------------------------------------------------
|
||||
// A simple class for topological sorting
|
||||
// ------------------------------------------------
|
||||
// ---------------------------------------------------
|
||||
// Best-effort topological sorting with loop detection
|
||||
// ---------------------------------------------------
|
||||
|
||||
template <typename T, typename C = std::less<T>> class TopoSort
|
||||
{
|
||||
public:
|
||||
static_assert(!(std::is_pointer<T>::value && std::is_same<C, std::less<T>>::value),
|
||||
"std::less is run-to-run unstable for pointers");
|
||||
public:
|
||||
// We use this ordering of the edges in the adjacency matrix for
|
||||
// exact compatibility with an older implementation.
|
||||
struct IndirectCmp {
|
||||
IndirectCmp(const std::vector<T> &nodes) : node_cmp_(), nodes_(nodes) {}
|
||||
IndirectCmp(const std::vector<T> &nodes) : node_cmp_(), nodes_(nodes) {}
|
||||
bool operator()(int a, int b) const
|
||||
{
|
||||
log_assert(static_cast<size_t>(a) < nodes_.size());
|
||||
log_assert(static_cast<size_t>(a) < nodes_.size());
|
||||
log_assert(static_cast<size_t>(b) < nodes_.size());
|
||||
return node_cmp_(nodes_[a], nodes_[b]);
|
||||
}
|
||||
|
|
@ -147,7 +149,9 @@ template <typename T, typename C = std::less<T>> class TopoSort
|
|||
};
|
||||
|
||||
bool analyze_loops;
|
||||
// The stability doesn't rely on std::less of T, so pointers are safe
|
||||
std::map<T, int, C> node_to_index;
|
||||
// edges[i] is the set of nodes with an edge into node i
|
||||
std::vector<std::set<int, IndirectCmp>> edges;
|
||||
std::vector<T> sorted;
|
||||
std::set<std::vector<T>> loops;
|
||||
|
|
@ -160,10 +164,10 @@ template <typename T, typename C = std::less<T>> class TopoSort
|
|||
|
||||
int node(T n)
|
||||
{
|
||||
auto rv = node_to_index.emplace(n, static_cast<int>(nodes.size()));
|
||||
if (rv.second) {
|
||||
nodes.push_back(n);
|
||||
edges.push_back(std::set<int, IndirectCmp>(indirect_cmp));
|
||||
auto rv = node_to_index.emplace(n, static_cast<int>(nodes.size()));
|
||||
if (rv.second) {
|
||||
nodes.push_back(n);
|
||||
edges.push_back(std::set<int, IndirectCmp>(indirect_cmp));
|
||||
}
|
||||
return rv.first->second;
|
||||
}
|
||||
|
|
@ -183,13 +187,14 @@ template <typename T, typename C = std::less<T>> class TopoSort
|
|||
sorted.clear();
|
||||
found_loops = false;
|
||||
|
||||
std::vector<bool> marked_cells(edges.size(), false);
|
||||
std::vector<bool> active_cells(edges.size(), false);
|
||||
std::vector<int> active_stack;
|
||||
std::vector<bool> node_is_sorted(edges.size(), false);
|
||||
std::vector<bool> node_is_on_stack(edges.size(), false);
|
||||
// Only used with analyze_loops
|
||||
std::vector<int> stack;
|
||||
sorted.reserve(edges.size());
|
||||
|
||||
for (const auto &it : node_to_index)
|
||||
sort_worker(it.second, marked_cells, active_cells, active_stack);
|
||||
sort_worker(it.second, node_is_sorted, node_is_on_stack, stack);
|
||||
|
||||
log_assert(GetSize(sorted) == GetSize(nodes));
|
||||
|
||||
|
|
@ -211,19 +216,20 @@ template <typename T, typename C = std::less<T>> class TopoSort
|
|||
return database;
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
bool found_loops;
|
||||
std::vector<T> nodes;
|
||||
const IndirectCmp indirect_cmp;
|
||||
|
||||
void sort_worker(const int root_index, std::vector<bool> &marked_cells, std::vector<bool> &active_cells, std::vector<int> &active_stack)
|
||||
void sort_worker(const int root_index, std::vector<bool> &node_is_sorted, std::vector<bool> &node_is_on_stack, std::vector<int> &stack)
|
||||
{
|
||||
if (active_cells[root_index]) {
|
||||
if (node_is_on_stack[root_index]) {
|
||||
// We've been here before, meaning we have a loop
|
||||
found_loops = true;
|
||||
if (analyze_loops) {
|
||||
std::vector<T> loop;
|
||||
for (int i = GetSize(active_stack) - 1; i >= 0; i--) {
|
||||
const int index = active_stack[i];
|
||||
for (int i = GetSize(stack) - 1; i >= 0; i--) {
|
||||
const int index = stack[i];
|
||||
loop.push_back(nodes[index]);
|
||||
if (index == root_index)
|
||||
break;
|
||||
|
|
@ -233,23 +239,24 @@ template <typename T, typename C = std::less<T>> class TopoSort
|
|||
return;
|
||||
}
|
||||
|
||||
if (marked_cells[root_index])
|
||||
// We're done if we've already sorted this subgraph
|
||||
if (node_is_sorted[root_index])
|
||||
return;
|
||||
|
||||
if (!edges[root_index].empty()) {
|
||||
if (analyze_loops)
|
||||
active_stack.push_back(root_index);
|
||||
active_cells[root_index] = true;
|
||||
stack.push_back(root_index);
|
||||
node_is_on_stack[root_index] = true;
|
||||
|
||||
for (int left_n : edges[root_index])
|
||||
sort_worker(left_n, marked_cells, active_cells, active_stack);
|
||||
sort_worker(left_n, node_is_sorted, node_is_on_stack, stack);
|
||||
|
||||
if (analyze_loops)
|
||||
active_stack.pop_back();
|
||||
active_cells[root_index] = false;
|
||||
stack.pop_back();
|
||||
node_is_on_stack[root_index] = false;
|
||||
}
|
||||
|
||||
marked_cells[root_index] = true;
|
||||
node_is_sorted[root_index] = true;
|
||||
sorted.push_back(nodes[root_index]);
|
||||
}
|
||||
};
|
||||
|
|
@ -299,6 +306,24 @@ auto reversed(const T& container) {
|
|||
return reverse_view{container};
|
||||
}
|
||||
|
||||
// A range of integers [start_, end_) that can be iterated over with a
|
||||
// C++ range-based for loop.
|
||||
struct IntRange {
|
||||
int start_;
|
||||
int end_;
|
||||
struct Int {
|
||||
int v;
|
||||
int operator*() const { return v; }
|
||||
Int &operator++() { ++v; return *this; }
|
||||
bool operator!=(const Int &other) const { return v != other.v; }
|
||||
};
|
||||
Int begin() const { return {start_}; }
|
||||
Int end() const { return {end_}; }
|
||||
|
||||
bool operator==(const IntRange &other) const { return start_ == other.start_ && end_ == other.end_; }
|
||||
bool operator!=(const IntRange &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
|
|||
112
kernel/wallace_tree.h
Normal file
112
kernel/wallace_tree.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* Wallace tree utilities for multi-operand addition using carry-save adders
|
||||
*
|
||||
* Terminology:
|
||||
* - compressor: $fa viewed as reducing 3 inputs to 2 outputs (sum + shifted carry) (3:2 compressor)
|
||||
* - level: A stage of parallel compression operations
|
||||
* - depth: Maximum number of 3:2 compressor levels from any input to a signal
|
||||
*
|
||||
* References:
|
||||
* - "Binary Adder Architectures for Cell-Based VLSI and their Synthesis" (https://iis-people.ee.ethz.ch/~zimmi/publications/adder_arch.pdf)
|
||||
* - "A Suggestion for a Fast Multiplier" (https://www.ece.ucdavis.edu/~vojin/CLASSES/EEC280/Web-page/papers/Arithmetic/Wallace_mult.pdf)
|
||||
*/
|
||||
|
||||
#ifndef WALLACE_TREE_H
|
||||
#define WALLACE_TREE_H
|
||||
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/yosys.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
inline std::pair<SigSpec, SigSpec> emit_fa(Module *module, SigSpec a, SigSpec b, SigSpec c, int width)
|
||||
{
|
||||
SigSpec sum = module->addWire(NEW_ID, width);
|
||||
SigSpec cout = module->addWire(NEW_ID, width);
|
||||
|
||||
module->addFa(NEW_ID, a, b, c, cout, sum);
|
||||
|
||||
SigSpec carry;
|
||||
carry.append(State::S0);
|
||||
carry.append(cout.extract(0, width - 1));
|
||||
return {sum, carry};
|
||||
}
|
||||
|
||||
/**
|
||||
* wallace_reduce_scheduled() - Reduce multiple operands to two using a Wallace tree
|
||||
* @module: The Yosys module to which the compressors will be added
|
||||
* @sigs: Vector of input signals (operands) to be reduced
|
||||
* @width: Target bit-width to which all operands will be zero-extended
|
||||
* @compressor_count: Optional pointer to return the number of $fa cells emitted
|
||||
*
|
||||
* Return: The final two reduced operands, that are to be fed into an adder
|
||||
*/
|
||||
inline std::pair<SigSpec, SigSpec> wallace_reduce_scheduled(Module *module, std::vector<SigSpec> &sigs, int width, int *compressor_count = nullptr)
|
||||
{
|
||||
struct DepthSig {
|
||||
SigSpec sig;
|
||||
int depth;
|
||||
};
|
||||
|
||||
for (auto &s : sigs)
|
||||
s.extend_u0(width);
|
||||
|
||||
std::vector<DepthSig> operands;
|
||||
operands.reserve(sigs.size());
|
||||
for (auto &s : sigs)
|
||||
operands.push_back({s, 0});
|
||||
|
||||
// Number of $fa's emitted
|
||||
if (compressor_count)
|
||||
*compressor_count = 0;
|
||||
|
||||
// Only compress operands ready at current level
|
||||
for (int level = 0; operands.size() > 2; level++) {
|
||||
// Partition operands into ready and waiting
|
||||
std::vector<DepthSig> ready, waiting;
|
||||
for (auto &op : operands) {
|
||||
if (op.depth <= level)
|
||||
ready.push_back(op);
|
||||
else
|
||||
waiting.push_back(op);
|
||||
}
|
||||
|
||||
if (ready.size() < 3)
|
||||
continue;
|
||||
|
||||
// Apply compressors to ready operands
|
||||
std::vector<DepthSig> compressed;
|
||||
size_t i = 0;
|
||||
while (i + 2 < ready.size()) {
|
||||
auto [sum, carry] = emit_fa(module, ready[i].sig, ready[i + 1].sig, ready[i + 2].sig, width);
|
||||
int new_depth = std::max({ready[i].depth, ready[i + 1].depth, ready[i + 2].depth}) + 1;
|
||||
compressed.push_back({sum, new_depth});
|
||||
compressed.push_back({carry, new_depth});
|
||||
if (compressor_count)
|
||||
(*compressor_count)++;
|
||||
i += 3;
|
||||
}
|
||||
// Uncompressed operands pass through to next level
|
||||
for (; i < ready.size(); i++)
|
||||
compressed.push_back(ready[i]);
|
||||
// Merge compressed with waiting operands
|
||||
for (auto &op : waiting)
|
||||
compressed.push_back(op);
|
||||
|
||||
operands = std::move(compressed);
|
||||
}
|
||||
|
||||
if (operands.size() == 0)
|
||||
return {SigSpec(State::S0, width), SigSpec(State::S0, width)};
|
||||
else if (operands.size() == 1)
|
||||
return {operands[0].sig, SigSpec(State::S0, width)};
|
||||
else {
|
||||
log_assert(operands.size() == 2);
|
||||
log(" Wallace tree depth: %d levels of $fa + 1 final $add\n", std::max(operands[0].depth, operands[1].depth));
|
||||
return {operands[0].sig, operands[1].sig};
|
||||
}
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
@ -18,8 +18,8 @@
|
|||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
#ifdef YOSYS_ENABLE_READLINE
|
||||
# include <readline/readline.h>
|
||||
|
|
@ -92,7 +92,7 @@ const char* yosys_maybe_version() {
|
|||
}
|
||||
|
||||
RTLIL::Design *yosys_design = NULL;
|
||||
CellTypes yosys_celltypes;
|
||||
NewCellTypes yosys_celltypes;
|
||||
|
||||
#ifdef YOSYS_ENABLE_TCL
|
||||
Tcl_Interp *yosys_tcl_interp = NULL;
|
||||
|
|
@ -262,7 +262,7 @@ void yosys_setup()
|
|||
|
||||
Pass::init_register();
|
||||
yosys_design = new RTLIL::Design;
|
||||
yosys_celltypes.setup();
|
||||
yosys_celltypes.static_cell_types = StaticCellTypes::categories.is_known;
|
||||
log_push();
|
||||
}
|
||||
|
||||
|
|
@ -291,8 +291,6 @@ void yosys_shutdown()
|
|||
log_errfile = NULL;
|
||||
log_files.clear();
|
||||
|
||||
yosys_celltypes.clear();
|
||||
|
||||
#ifdef YOSYS_ENABLE_TCL
|
||||
if (yosys_tcl_interp != NULL) {
|
||||
if (!Tcl_InterpDeleted(yosys_tcl_interp)) {
|
||||
|
|
@ -473,17 +471,30 @@ struct TclPass : public Pass {
|
|||
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__CYGWIN__)
|
||||
#if defined(__linux__) || defined(__CYGWIN__) || defined(__gnu_hurd__)
|
||||
std::string proc_self_dirname()
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
ssize_t buflen = readlink("/proc/self/exe", path, sizeof(path));
|
||||
std::string path(4096, '\0');
|
||||
ssize_t buflen = -1;
|
||||
// Double until sucess, while avoiding endless loop. Give up
|
||||
// when symlink is longer than 4096*(2^30) = 4398046511104
|
||||
// bytes.
|
||||
for (int tries = 30; 0 < tries; tries--) {
|
||||
buflen = readlink("/proc/self/exe", path.data(), path.size());
|
||||
if (buflen < (ssize_t)path.size())
|
||||
break;
|
||||
else
|
||||
path.resize(path.size() * 2);
|
||||
}
|
||||
if (buflen < 0) {
|
||||
log_error("readlink(\"/proc/self/exe\") failed: %s\n", strerror(errno));
|
||||
path.resize(0);
|
||||
} else {
|
||||
while (buflen > 0 && path[buflen-1] != '/')
|
||||
buflen--;
|
||||
path.resize(buflen);
|
||||
}
|
||||
while (buflen > 0 && path[buflen-1] != '/')
|
||||
buflen--;
|
||||
return std::string(path, buflen);
|
||||
return path;
|
||||
}
|
||||
#elif defined(__FreeBSD__) || defined(__NetBSD__)
|
||||
std::string proc_self_dirname()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
vcxsrc="$1"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "kernel/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celledges.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/utils.h"
|
||||
#include "kernel/log_help.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "kernel/yosys.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/ff.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
|
|
@ -123,7 +124,7 @@ struct LibertyStubber {
|
|||
return;
|
||||
}
|
||||
|
||||
if (RTLIL::builtin_ff_cell_types().count(base_name))
|
||||
if (StaticCellTypes::categories.is_ff(base_name))
|
||||
return liberty_flop(base, derived, f);
|
||||
|
||||
auto& base_type = ct.cell_types[base_name];
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/log_help.h"
|
||||
|
||||
|
|
@ -488,7 +488,7 @@ static int parse_comma_list(std::set<RTLIL::IdString> &tokens, const std::string
|
|||
}
|
||||
}
|
||||
|
||||
static int select_op_expand(RTLIL::Design *design, RTLIL::Selection &lhs, std::vector<expand_rule_t> &rules, std::set<RTLIL::IdString> &limits, int max_objects, char mode, CellTypes &ct, bool eval_only)
|
||||
static int select_op_expand(RTLIL::Design *design, RTLIL::Selection &lhs, std::vector<expand_rule_t> &rules, std::set<RTLIL::IdString> &limits, int max_objects, char mode, NewCellTypes &ct, bool eval_only)
|
||||
{
|
||||
int sel_objects = 0;
|
||||
bool is_input, is_output;
|
||||
|
|
@ -564,7 +564,7 @@ static void select_op_expand(RTLIL::Design *design, const std::string &arg, char
|
|||
std::vector<expand_rule_t> rules;
|
||||
std::set<RTLIL::IdString> limits;
|
||||
|
||||
CellTypes ct;
|
||||
NewCellTypes ct;
|
||||
|
||||
if (mode != 'x')
|
||||
ct.setup(design);
|
||||
|
|
|
|||
|
|
@ -310,6 +310,8 @@ struct SetundefPass : public Pass {
|
|||
|
||||
RTLIL::SigSpec sig = undriven_signals.export_all();
|
||||
for (auto &c : sig.chunks()) {
|
||||
if (!design->selected(module, c.wire))
|
||||
continue;
|
||||
RTLIL::Wire * wire;
|
||||
if (c.wire->width == c.width) {
|
||||
wire = c.wire;
|
||||
|
|
@ -328,12 +330,12 @@ struct SetundefPass : public Pass {
|
|||
SigMap sigmap(module);
|
||||
SigPool undriven_signals;
|
||||
|
||||
for (auto &it : module->wires_)
|
||||
undriven_signals.add(sigmap(it.second));
|
||||
for (auto wire : module->selected_wires())
|
||||
undriven_signals.add(sigmap(wire));
|
||||
|
||||
for (auto &it : module->wires_)
|
||||
if (it.second->port_input)
|
||||
undriven_signals.del(sigmap(it.second));
|
||||
for (auto wire : module->selected_wires())
|
||||
if (wire->port_input)
|
||||
undriven_signals.del(sigmap(wire));
|
||||
|
||||
CellTypes ct(design);
|
||||
for (auto &it : module->cells_)
|
||||
|
|
@ -367,6 +369,14 @@ struct SetundefPass : public Pass {
|
|||
if (!cell->is_builtin_ff())
|
||||
continue;
|
||||
|
||||
bool cell_selected = design->selected(module, cell);
|
||||
bool wire_selected = false;
|
||||
for (auto bit : sigmap(cell->getPort(ID::Q)))
|
||||
if (bit.wire && design->selected(module, bit.wire))
|
||||
wire_selected = true;
|
||||
if (!cell_selected && !wire_selected)
|
||||
continue;
|
||||
|
||||
for (auto bit : sigmap(cell->getPort(ID::Q)))
|
||||
ffbits.insert(bit);
|
||||
}
|
||||
|
|
@ -502,14 +512,21 @@ struct SetundefPass : public Pass {
|
|||
}
|
||||
}
|
||||
|
||||
for (auto &it : module->cells_)
|
||||
if (!it.second->get_bool_attribute(ID::xprop_decoder))
|
||||
it.second->rewrite_sigspecs(worker);
|
||||
for (auto &it : module->processes)
|
||||
it.second->rewrite_sigspecs(worker);
|
||||
for (auto cell : module->selected_cells())
|
||||
if (!cell->get_bool_attribute(ID::xprop_decoder))
|
||||
cell->rewrite_sigspecs(worker);
|
||||
for (auto proc : module->selected_processes())
|
||||
proc->rewrite_sigspecs(worker);
|
||||
for (auto &it : module->connections_) {
|
||||
worker(it.first);
|
||||
worker(it.second);
|
||||
SigSpec lhs = it.first;
|
||||
bool selected = false;
|
||||
for (auto &chunk : lhs.chunks())
|
||||
if (chunk.wire && module->design->selected(module, chunk.wire))
|
||||
selected = true;
|
||||
if (selected) {
|
||||
worker(it.first);
|
||||
worker(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
if (worker.next_bit_mode == MODE_ANYSEQ || worker.next_bit_mode == MODE_ANYCONST)
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ struct statdata_t {
|
|||
}
|
||||
}
|
||||
|
||||
if (tech == "xilinx") {
|
||||
if (tech == "xilinx" || tech == "analogdevices") {
|
||||
log("\n");
|
||||
log(" Estimated number of LCs: %10u\n", estimate_xilinx_lc());
|
||||
}
|
||||
|
|
@ -628,7 +628,7 @@ struct statdata_t {
|
|||
first_line = false;
|
||||
}
|
||||
log("\n }\n");
|
||||
if (tech == "xilinx") {
|
||||
if (tech == "xilinx" || tech == "analogdevices") {
|
||||
log(" \"estimated_num_lc\": %u,\n", estimate_xilinx_lc());
|
||||
}
|
||||
if (tech == "cmos") {
|
||||
|
|
@ -710,7 +710,7 @@ struct statdata_t {
|
|||
log("\n");
|
||||
log(" }");
|
||||
}
|
||||
if (tech == "xilinx") {
|
||||
if (tech == "xilinx" || tech == "analogdevices") {
|
||||
log(",\n");
|
||||
log(" \"estimated_num_lc\": %u", estimate_xilinx_lc());
|
||||
}
|
||||
|
|
@ -908,7 +908,7 @@ struct StatPass : public Pass {
|
|||
log("\n");
|
||||
log(" -tech <technology>\n");
|
||||
log(" print area estimate for the specified technology. Currently supported\n");
|
||||
log(" values for <technology>: xilinx, cmos\n");
|
||||
log(" values for <technology>: xilinx, analogdevices, cmos\n");
|
||||
log("\n");
|
||||
log(" -width\n");
|
||||
log(" annotate internal cell types with their word width.\n");
|
||||
|
|
@ -968,7 +968,7 @@ struct StatPass : public Pass {
|
|||
if (!json_mode)
|
||||
log_header(design, "Printing statistics.\n");
|
||||
|
||||
if (techname != "" && techname != "xilinx" && techname != "cmos" && !json_mode)
|
||||
if (techname != "" && techname != "xilinx" && techname != "analogdevices" && techname != "cmos" && !json_mode)
|
||||
log_cmd_error("Unsupported technology: '%s'\n", techname);
|
||||
|
||||
if (json_mode) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/utils.h"
|
||||
#include "kernel/log_help.h"
|
||||
|
|
|
|||
|
|
@ -463,6 +463,10 @@ struct XpropWorker
|
|||
return;
|
||||
}
|
||||
|
||||
if (cell->type.in(ID($scopeinfo))) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_warning("Unhandled cell %s (%s) during maybe-x marking\n", log_id(cell), log_id(cell->type));
|
||||
mark_outputs_maybe_x(cell);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "kernel/yosys_common.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/satgen.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ struct EquivInductPass : public Pass {
|
|||
log("Only selected $equiv cells are proven and only selected cells are used to\n");
|
||||
log("perform the proof.\n");
|
||||
log("\n");
|
||||
EquivBasicConfig::help("4");
|
||||
log("%s", EquivBasicConfig::help("4"));
|
||||
log("\n");
|
||||
log("This command is very effective in proving complex sequential circuits, when\n");
|
||||
log("the internal state of the circuit quickly propagates to $equiv cells.\n");
|
||||
|
|
|
|||
|
|
@ -428,7 +428,7 @@ struct EquivSimplePass : public Pass {
|
|||
log("\n");
|
||||
log("This command tries to prove $equiv cells using a simple direct SAT approach.\n");
|
||||
log("\n");
|
||||
EquivSimpleConfig::help("1");
|
||||
log("%s", EquivSimpleConfig::help("1"));
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, Design *design) override
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ OBJS += passes/opt/opt_muxtree.o
|
|||
OBJS += passes/opt/opt_reduce.o
|
||||
OBJS += passes/opt/opt_dff.o
|
||||
OBJS += passes/opt/opt_share.o
|
||||
OBJS += passes/opt/opt_clean.o
|
||||
OBJS += passes/opt/opt_expr.o
|
||||
OBJS += passes/opt/opt_hier.o
|
||||
|
||||
|
|
@ -40,3 +39,5 @@ PEEPOPT_PATTERN += passes/opt/peepopt_formal_clockgateff.pmg
|
|||
passes/opt/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN)
|
||||
$(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p peepopt $(filter-out $<,$^)
|
||||
endif
|
||||
|
||||
include $(YOSYS_SRC)/passes/opt/opt_clean/Makefile.inc
|
||||
|
|
@ -38,6 +38,9 @@ struct ExclusiveDatabase
|
|||
pool<Cell*> reduce_or;
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type == ID($eq)) {
|
||||
SigSpec y_sig = sigmap(cell->getPort(ID::Y));
|
||||
if (GetSize(y_sig) == 0)
|
||||
continue;
|
||||
nonconst_sig = sigmap(cell->getPort(ID::A));
|
||||
const_sig = sigmap(cell->getPort(ID::B));
|
||||
if (!const_sig.is_fully_const()) {
|
||||
|
|
@ -45,12 +48,15 @@ struct ExclusiveDatabase
|
|||
continue;
|
||||
std::swap(nonconst_sig, const_sig);
|
||||
}
|
||||
y_port = sigmap(cell->getPort(ID::Y));
|
||||
y_port = y_sig[0];
|
||||
}
|
||||
else if (cell->type == ID($logic_not)) {
|
||||
SigSpec y_sig = sigmap(cell->getPort(ID::Y));
|
||||
if (GetSize(y_sig) == 0)
|
||||
continue;
|
||||
nonconst_sig = sigmap(cell->getPort(ID::A));
|
||||
const_sig = Const(State::S0, GetSize(nonconst_sig));
|
||||
y_port = sigmap(cell->getPort(ID::Y));
|
||||
y_port = y_sig[0];
|
||||
}
|
||||
else if (cell->type == ID($reduce_or)) {
|
||||
reduce_or.insert(cell);
|
||||
|
|
@ -84,7 +90,10 @@ struct ExclusiveDatabase
|
|||
}
|
||||
if (nonconst_sig.empty())
|
||||
continue;
|
||||
y_port = sigmap(cell->getPort(ID::Y));
|
||||
SigSpec y_sig = sigmap(cell->getPort(ID::Y));
|
||||
if (GetSize(y_sig) == 0)
|
||||
continue;
|
||||
y_port = y_sig[0];
|
||||
sig_cmp_prev[y_port] = std::make_pair(nonconst_sig,std::move(values));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,793 +0,0 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/ffinit.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <set>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
using RTLIL::id2cstr;
|
||||
|
||||
struct keep_cache_t
|
||||
{
|
||||
Design *design;
|
||||
dict<Module*, bool> cache;
|
||||
bool purge_mode = false;
|
||||
|
||||
void reset(Design *design = nullptr, bool purge_mode = false)
|
||||
{
|
||||
this->design = design;
|
||||
this->purge_mode = purge_mode;
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
bool query(Module *module)
|
||||
{
|
||||
log_assert(design != nullptr);
|
||||
|
||||
if (module == nullptr)
|
||||
return false;
|
||||
|
||||
if (cache.count(module))
|
||||
return cache.at(module);
|
||||
|
||||
cache[module] = true;
|
||||
if (!module->get_bool_attribute(ID::keep)) {
|
||||
bool found_keep = false;
|
||||
for (auto cell : module->cells())
|
||||
if (query(cell, true /* ignore_specify */)) {
|
||||
found_keep = true;
|
||||
break;
|
||||
}
|
||||
for (auto wire : module->wires())
|
||||
if (wire->get_bool_attribute(ID::keep)) {
|
||||
found_keep = true;
|
||||
break;
|
||||
}
|
||||
cache[module] = found_keep;
|
||||
}
|
||||
|
||||
return cache[module];
|
||||
}
|
||||
|
||||
bool query(Cell *cell, bool ignore_specify = false)
|
||||
{
|
||||
if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover)))
|
||||
return true;
|
||||
|
||||
if (cell->type.in(ID($overwrite_tag)))
|
||||
return true;
|
||||
|
||||
if (!ignore_specify && cell->type.in(ID($specify2), ID($specify3), ID($specrule)))
|
||||
return true;
|
||||
|
||||
if (cell->type == ID($print) || cell->type == ID($check))
|
||||
return true;
|
||||
|
||||
if (cell->has_keep_attr())
|
||||
return true;
|
||||
|
||||
if (!purge_mode && cell->type == ID($scopeinfo))
|
||||
return true;
|
||||
|
||||
if (cell->module && cell->module->design)
|
||||
return query(cell->module->design->module(cell->type));
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keep_cache_t keep_cache;
|
||||
CellTypes ct_reg, ct_all;
|
||||
int count_rm_cells, count_rm_wires;
|
||||
|
||||
void rmunused_module_cells(Module *module, bool verbose)
|
||||
{
|
||||
SigMap sigmap(module);
|
||||
dict<IdString, pool<Cell*>> mem2cells;
|
||||
pool<IdString> mem_unused;
|
||||
pool<Cell*> queue, unused;
|
||||
pool<SigBit> used_raw_bits;
|
||||
dict<SigBit, pool<Cell*>> wire2driver;
|
||||
dict<SigBit, vector<string>> driver_driver_logs;
|
||||
FfInitVals ffinit(&sigmap, module);
|
||||
|
||||
SigMap raw_sigmap;
|
||||
for (auto &it : module->connections_) {
|
||||
for (int i = 0; i < GetSize(it.second); i++) {
|
||||
if (it.second[i].wire != nullptr)
|
||||
raw_sigmap.add(it.first[i], it.second[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &it : module->memories) {
|
||||
mem_unused.insert(it.first);
|
||||
}
|
||||
|
||||
for (Cell *cell : module->cells()) {
|
||||
if (cell->type.in(ID($memwr), ID($memwr_v2), ID($meminit), ID($meminit_v2))) {
|
||||
IdString mem_id = cell->getParam(ID::MEMID).decode_string();
|
||||
mem2cells[mem_id].insert(cell);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &it : module->cells_) {
|
||||
Cell *cell = it.second;
|
||||
for (auto &it2 : cell->connections()) {
|
||||
if (ct_all.cell_known(cell->type) && !ct_all.cell_output(cell->type, it2.first))
|
||||
continue;
|
||||
for (auto raw_bit : it2.second) {
|
||||
if (raw_bit.wire == nullptr)
|
||||
continue;
|
||||
auto bit = sigmap(raw_bit);
|
||||
if (bit.wire == nullptr && ct_all.cell_known(cell->type))
|
||||
driver_driver_logs[raw_sigmap(raw_bit)].push_back(stringf("Driver-driver conflict "
|
||||
"for %s between cell %s.%s and constant %s in %s: Resolved using constant.",
|
||||
log_signal(raw_bit), log_id(cell), log_id(it2.first), log_signal(bit), log_id(module)));
|
||||
if (bit.wire != nullptr)
|
||||
wire2driver[bit].insert(cell);
|
||||
}
|
||||
}
|
||||
if (keep_cache.query(cell))
|
||||
queue.insert(cell);
|
||||
else
|
||||
unused.insert(cell);
|
||||
}
|
||||
|
||||
for (auto &it : module->wires_) {
|
||||
Wire *wire = it.second;
|
||||
if (wire->port_output || wire->get_bool_attribute(ID::keep)) {
|
||||
for (auto bit : sigmap(wire))
|
||||
for (auto c : wire2driver[bit])
|
||||
queue.insert(c), unused.erase(c);
|
||||
for (auto raw_bit : SigSpec(wire))
|
||||
used_raw_bits.insert(raw_sigmap(raw_bit));
|
||||
}
|
||||
}
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
pool<SigBit> bits;
|
||||
pool<IdString> mems;
|
||||
for (auto cell : queue) {
|
||||
for (auto &it : cell->connections())
|
||||
if (!ct_all.cell_known(cell->type) || ct_all.cell_input(cell->type, it.first))
|
||||
for (auto bit : sigmap(it.second))
|
||||
bits.insert(bit);
|
||||
|
||||
if (cell->type.in(ID($memrd), ID($memrd_v2))) {
|
||||
IdString mem_id = cell->getParam(ID::MEMID).decode_string();
|
||||
if (mem_unused.count(mem_id)) {
|
||||
mem_unused.erase(mem_id);
|
||||
mems.insert(mem_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue.clear();
|
||||
|
||||
for (auto bit : bits)
|
||||
for (auto c : wire2driver[bit])
|
||||
if (unused.count(c))
|
||||
queue.insert(c), unused.erase(c);
|
||||
|
||||
for (auto mem : mems)
|
||||
for (auto c : mem2cells[mem])
|
||||
if (unused.count(c))
|
||||
queue.insert(c), unused.erase(c);
|
||||
}
|
||||
|
||||
unused.sort(RTLIL::sort_by_name_id<RTLIL::Cell>());
|
||||
|
||||
for (auto cell : unused) {
|
||||
if (verbose)
|
||||
log_debug(" removing unused `%s' cell `%s'.\n", cell->type, cell->name);
|
||||
module->design->scratchpad_set_bool("opt.did_something", true);
|
||||
if (cell->is_builtin_ff())
|
||||
ffinit.remove_init(cell->getPort(ID::Q));
|
||||
module->remove(cell);
|
||||
count_rm_cells++;
|
||||
}
|
||||
|
||||
for (auto it : mem_unused)
|
||||
{
|
||||
if (verbose)
|
||||
log_debug(" removing unused memory `%s'.\n", it);
|
||||
delete module->memories.at(it);
|
||||
module->memories.erase(it);
|
||||
}
|
||||
|
||||
for (auto &it : module->cells_) {
|
||||
Cell *cell = it.second;
|
||||
for (auto &it2 : cell->connections()) {
|
||||
if (ct_all.cell_known(cell->type) && !ct_all.cell_input(cell->type, it2.first))
|
||||
continue;
|
||||
for (auto raw_bit : raw_sigmap(it2.second))
|
||||
used_raw_bits.insert(raw_bit);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it : driver_driver_logs) {
|
||||
if (used_raw_bits.count(it.first))
|
||||
for (auto msg : it.second)
|
||||
log_warning("%s\n", msg);
|
||||
}
|
||||
}
|
||||
|
||||
int count_nontrivial_wire_attrs(RTLIL::Wire *w)
|
||||
{
|
||||
int count = w->attributes.size();
|
||||
count -= w->attributes.count(ID::src);
|
||||
count -= w->attributes.count(ID::hdlname);
|
||||
count -= w->attributes.count(ID(scopename));
|
||||
count -= w->attributes.count(ID::unused_bits);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Should we pick `s2` over `s1` to represent a signal?
|
||||
bool compare_signals(RTLIL::SigBit &s1, RTLIL::SigBit &s2, SigPool ®s, SigPool &conns, pool<RTLIL::Wire*> &direct_wires)
|
||||
{
|
||||
RTLIL::Wire *w1 = s1.wire;
|
||||
RTLIL::Wire *w2 = s2.wire;
|
||||
|
||||
if (w1 == NULL || w2 == NULL)
|
||||
return w2 == NULL;
|
||||
|
||||
if (w1->port_input != w2->port_input)
|
||||
return w2->port_input;
|
||||
|
||||
if ((w1->port_input && w1->port_output) != (w2->port_input && w2->port_output))
|
||||
return !(w2->port_input && w2->port_output);
|
||||
|
||||
if (w1->name.isPublic() && w2->name.isPublic()) {
|
||||
if (regs.check(s1) != regs.check(s2))
|
||||
return regs.check(s2);
|
||||
if (direct_wires.count(w1) != direct_wires.count(w2))
|
||||
return direct_wires.count(w2) != 0;
|
||||
if (conns.check_any(s1) != conns.check_any(s2))
|
||||
return conns.check_any(s2);
|
||||
}
|
||||
|
||||
if (w1 == w2)
|
||||
return s2.offset < s1.offset;
|
||||
|
||||
if (w1->port_output != w2->port_output)
|
||||
return w2->port_output;
|
||||
|
||||
if (w1->name[0] != w2->name[0])
|
||||
return w2->name.isPublic();
|
||||
|
||||
int attrs1 = count_nontrivial_wire_attrs(w1);
|
||||
int attrs2 = count_nontrivial_wire_attrs(w2);
|
||||
|
||||
if (attrs1 != attrs2)
|
||||
return attrs2 > attrs1;
|
||||
|
||||
return w2->name.lt_by_name(w1->name);
|
||||
}
|
||||
|
||||
bool check_public_name(RTLIL::IdString id)
|
||||
{
|
||||
if (id.begins_with("$"))
|
||||
return false;
|
||||
const std::string &id_str = id.str();
|
||||
if (id.begins_with("\\_") && (id.ends_with("_") || id_str.find("_[") != std::string::npos))
|
||||
return false;
|
||||
if (id_str.find(".$") != std::string::npos)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rmunused_module_signals(RTLIL::Module *module, bool purge_mode, bool verbose)
|
||||
{
|
||||
// `register_signals` and `connected_signals` will help us decide later on
|
||||
// on picking representatives out of groups of connected signals
|
||||
SigPool register_signals;
|
||||
SigPool connected_signals;
|
||||
if (!purge_mode)
|
||||
for (auto &it : module->cells_) {
|
||||
RTLIL::Cell *cell = it.second;
|
||||
if (ct_reg.cell_known(cell->type)) {
|
||||
bool clk2fflogic = cell->get_bool_attribute(ID(clk2fflogic));
|
||||
for (auto &it2 : cell->connections())
|
||||
if (clk2fflogic ? it2.first == ID::D : ct_reg.cell_output(cell->type, it2.first))
|
||||
register_signals.add(it2.second);
|
||||
}
|
||||
for (auto &it2 : cell->connections())
|
||||
connected_signals.add(it2.second);
|
||||
}
|
||||
|
||||
SigMap assign_map(module);
|
||||
|
||||
// construct a pool of wires which are directly driven by a known celltype,
|
||||
// this will influence our choice of representatives
|
||||
pool<RTLIL::Wire*> direct_wires;
|
||||
{
|
||||
pool<RTLIL::SigSpec> direct_sigs;
|
||||
for (auto &it : module->cells_) {
|
||||
RTLIL::Cell *cell = it.second;
|
||||
if (ct_all.cell_known(cell->type))
|
||||
for (auto &it2 : cell->connections())
|
||||
if (ct_all.cell_output(cell->type, it2.first))
|
||||
direct_sigs.insert(assign_map(it2.second));
|
||||
}
|
||||
for (auto &it : module->wires_) {
|
||||
if (direct_sigs.count(assign_map(it.second)) || it.second->port_input)
|
||||
direct_wires.insert(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
// weight all options for representatives with `compare_signals`,
|
||||
// the one that wins will be what `assign_map` maps to
|
||||
for (auto &it : module->wires_) {
|
||||
RTLIL::Wire *wire = it.second;
|
||||
for (int i = 0; i < wire->width; i++) {
|
||||
RTLIL::SigBit s1 = RTLIL::SigBit(wire, i), s2 = assign_map(s1);
|
||||
if (compare_signals(s2, s1, register_signals, connected_signals, direct_wires))
|
||||
assign_map.add(s1);
|
||||
}
|
||||
}
|
||||
|
||||
// we are removing all connections
|
||||
module->connections_.clear();
|
||||
|
||||
// used signals sigmapped
|
||||
SigPool used_signals;
|
||||
// used signals pre-sigmapped
|
||||
SigPool raw_used_signals;
|
||||
// used signals sigmapped, ignoring drivers (we keep track of this to set `unused_bits`)
|
||||
SigPool used_signals_nodrivers;
|
||||
|
||||
// gather the usage information for cells
|
||||
for (auto &it : module->cells_) {
|
||||
RTLIL::Cell *cell = it.second;
|
||||
for (auto &it2 : cell->connections_) {
|
||||
assign_map.apply(it2.second); // modify the cell connection in place
|
||||
raw_used_signals.add(it2.second);
|
||||
used_signals.add(it2.second);
|
||||
if (!ct_all.cell_output(cell->type, it2.first))
|
||||
used_signals_nodrivers.add(it2.second);
|
||||
}
|
||||
}
|
||||
|
||||
// gather the usage information for ports, wires with `keep`,
|
||||
// also gather init bits
|
||||
dict<RTLIL::SigBit, RTLIL::State> init_bits;
|
||||
for (auto &it : module->wires_) {
|
||||
RTLIL::Wire *wire = it.second;
|
||||
if (wire->port_id > 0) {
|
||||
RTLIL::SigSpec sig = RTLIL::SigSpec(wire);
|
||||
raw_used_signals.add(sig);
|
||||
assign_map.apply(sig);
|
||||
used_signals.add(sig);
|
||||
if (!wire->port_input)
|
||||
used_signals_nodrivers.add(sig);
|
||||
}
|
||||
if (wire->get_bool_attribute(ID::keep)) {
|
||||
RTLIL::SigSpec sig = RTLIL::SigSpec(wire);
|
||||
assign_map.apply(sig);
|
||||
used_signals.add(sig);
|
||||
}
|
||||
auto it2 = wire->attributes.find(ID::init);
|
||||
if (it2 != wire->attributes.end()) {
|
||||
RTLIL::Const &val = it2->second;
|
||||
SigSpec sig = assign_map(wire);
|
||||
for (int i = 0; i < GetSize(val) && i < GetSize(sig); i++)
|
||||
if (val[i] != State::Sx)
|
||||
init_bits[sig[i]] = val[i];
|
||||
wire->attributes.erase(it2);
|
||||
}
|
||||
}
|
||||
|
||||
// set init attributes on all wires of a connected group
|
||||
for (auto wire : module->wires()) {
|
||||
bool found = false;
|
||||
Const val(State::Sx, wire->width);
|
||||
for (int i = 0; i < wire->width; i++) {
|
||||
auto it = init_bits.find(RTLIL::SigBit(wire, i));
|
||||
if (it != init_bits.end()) {
|
||||
val.set(i, it->second);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
wire->attributes[ID::init] = val;
|
||||
}
|
||||
|
||||
// now decide for each wire if we should be deleting it
|
||||
pool<RTLIL::Wire*> del_wires_queue;
|
||||
for (auto wire : module->wires())
|
||||
{
|
||||
SigSpec s1 = SigSpec(wire), s2 = assign_map(s1);
|
||||
log_assert(GetSize(s1) == GetSize(s2));
|
||||
|
||||
Const initval;
|
||||
if (wire->attributes.count(ID::init))
|
||||
initval = wire->attributes.at(ID::init);
|
||||
if (GetSize(initval) != GetSize(wire))
|
||||
initval.resize(GetSize(wire), State::Sx);
|
||||
if (initval.is_fully_undef())
|
||||
wire->attributes.erase(ID::init);
|
||||
|
||||
if (GetSize(wire) == 0) {
|
||||
// delete zero-width wires, unless they are module ports
|
||||
if (wire->port_id == 0)
|
||||
goto delete_this_wire;
|
||||
} else
|
||||
if (wire->port_id != 0 || wire->get_bool_attribute(ID::keep) || !initval.is_fully_undef()) {
|
||||
// do not delete anything with "keep" or module ports or initialized wires
|
||||
} else
|
||||
if (!purge_mode && check_public_name(wire->name) && (raw_used_signals.check_any(s1) || used_signals.check_any(s2) || s1 != s2)) {
|
||||
// do not get rid of public names unless in purge mode or if the wire is entirely unused, not even aliased
|
||||
} else
|
||||
if (!raw_used_signals.check_any(s1)) {
|
||||
// delete wires that aren't used by anything directly
|
||||
goto delete_this_wire;
|
||||
}
|
||||
|
||||
if (0)
|
||||
{
|
||||
delete_this_wire:
|
||||
del_wires_queue.insert(wire);
|
||||
}
|
||||
else
|
||||
{
|
||||
RTLIL::SigSig new_conn;
|
||||
for (int i = 0; i < GetSize(s1); i++)
|
||||
if (s1[i] != s2[i]) {
|
||||
if (s2[i] == State::Sx && (initval[i] == State::S0 || initval[i] == State::S1)) {
|
||||
s2[i] = initval[i];
|
||||
initval.set(i, State::Sx);
|
||||
}
|
||||
new_conn.first.append(s1[i]);
|
||||
new_conn.second.append(s2[i]);
|
||||
}
|
||||
if (new_conn.first.size() > 0) {
|
||||
if (initval.is_fully_undef())
|
||||
wire->attributes.erase(ID::init);
|
||||
else
|
||||
wire->attributes.at(ID::init) = initval;
|
||||
module->connect(new_conn);
|
||||
}
|
||||
|
||||
if (!used_signals_nodrivers.check_all(s2)) {
|
||||
std::string unused_bits;
|
||||
for (int i = 0; i < GetSize(s2); i++) {
|
||||
if (s2[i].wire == NULL)
|
||||
continue;
|
||||
if (!used_signals_nodrivers.check(s2[i])) {
|
||||
if (!unused_bits.empty())
|
||||
unused_bits += " ";
|
||||
unused_bits += stringf("%d", i);
|
||||
}
|
||||
}
|
||||
if (unused_bits.empty() || wire->port_id != 0)
|
||||
wire->attributes.erase(ID::unused_bits);
|
||||
else
|
||||
wire->attributes[ID::unused_bits] = RTLIL::Const(unused_bits);
|
||||
} else {
|
||||
wire->attributes.erase(ID::unused_bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int del_temp_wires_count = 0;
|
||||
for (auto wire : del_wires_queue) {
|
||||
if (ys_debug() || (check_public_name(wire->name) && verbose))
|
||||
log_debug(" removing unused non-port wire %s.\n", wire->name);
|
||||
else
|
||||
del_temp_wires_count++;
|
||||
}
|
||||
|
||||
module->remove(del_wires_queue);
|
||||
count_rm_wires += GetSize(del_wires_queue);
|
||||
|
||||
if (verbose && del_temp_wires_count)
|
||||
log_debug(" removed %d unused temporary wires.\n", del_temp_wires_count);
|
||||
|
||||
if (!del_wires_queue.empty())
|
||||
module->design->scratchpad_set_bool("opt.did_something", true);
|
||||
|
||||
return !del_wires_queue.empty();
|
||||
}
|
||||
|
||||
bool rmunused_module_init(RTLIL::Module *module, bool verbose)
|
||||
{
|
||||
bool did_something = false;
|
||||
CellTypes fftypes;
|
||||
fftypes.setup_internals_mem();
|
||||
|
||||
SigMap sigmap(module);
|
||||
dict<SigBit, State> qbits;
|
||||
|
||||
for (auto cell : module->cells())
|
||||
if (fftypes.cell_known(cell->type) && cell->hasPort(ID::Q))
|
||||
{
|
||||
SigSpec sig = cell->getPort(ID::Q);
|
||||
|
||||
for (int i = 0; i < GetSize(sig); i++)
|
||||
{
|
||||
SigBit bit = sig[i];
|
||||
|
||||
if (bit.wire == nullptr || bit.wire->attributes.count(ID::init) == 0)
|
||||
continue;
|
||||
|
||||
Const init = bit.wire->attributes.at(ID::init);
|
||||
|
||||
if (i >= GetSize(init) || init[i] == State::Sx || init[i] == State::Sz)
|
||||
continue;
|
||||
|
||||
sigmap.add(bit);
|
||||
qbits[bit] = init[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (auto wire : module->wires())
|
||||
{
|
||||
if (wire->attributes.count(ID::init) == 0)
|
||||
continue;
|
||||
|
||||
Const init = wire->attributes.at(ID::init);
|
||||
|
||||
for (int i = 0; i < GetSize(wire) && i < GetSize(init); i++)
|
||||
{
|
||||
if (init[i] == State::Sx || init[i] == State::Sz)
|
||||
continue;
|
||||
|
||||
SigBit wire_bit = SigBit(wire, i);
|
||||
SigBit mapped_wire_bit = sigmap(wire_bit);
|
||||
|
||||
if (wire_bit == mapped_wire_bit)
|
||||
goto next_wire;
|
||||
|
||||
if (mapped_wire_bit.wire) {
|
||||
if (qbits.count(mapped_wire_bit) == 0)
|
||||
goto next_wire;
|
||||
|
||||
if (qbits.at(mapped_wire_bit) != init[i])
|
||||
goto next_wire;
|
||||
}
|
||||
else {
|
||||
if (mapped_wire_bit == State::Sx || mapped_wire_bit == State::Sz)
|
||||
goto next_wire;
|
||||
|
||||
if (mapped_wire_bit != init[i]) {
|
||||
log_warning("Initial value conflict for %s resolving to %s but with init %s.\n", log_signal(wire_bit), log_signal(mapped_wire_bit), log_signal(init[i]));
|
||||
goto next_wire;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
log_debug(" removing redundant init attribute on %s.\n", log_id(wire));
|
||||
|
||||
wire->attributes.erase(ID::init);
|
||||
did_something = true;
|
||||
next_wire:;
|
||||
}
|
||||
|
||||
if (did_something)
|
||||
module->design->scratchpad_set_bool("opt.did_something", true);
|
||||
|
||||
return did_something;
|
||||
}
|
||||
|
||||
void rmunused_module(RTLIL::Module *module, bool purge_mode, bool verbose, bool rminit)
|
||||
{
|
||||
if (verbose)
|
||||
log("Finding unused cells or wires in module %s..\n", module->name);
|
||||
|
||||
std::vector<RTLIL::Cell*> delcells;
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type.in(ID($pos), ID($_BUF_), ID($buf)) && !cell->has_keep_attr()) {
|
||||
bool is_signed = cell->type == ID($pos) && cell->getParam(ID::A_SIGNED).as_bool();
|
||||
RTLIL::SigSpec a = cell->getPort(ID::A);
|
||||
RTLIL::SigSpec y = cell->getPort(ID::Y);
|
||||
a.extend_u0(GetSize(y), is_signed);
|
||||
|
||||
if (a.has_const(State::Sz)) {
|
||||
SigSpec new_a;
|
||||
SigSpec new_y;
|
||||
for (int i = 0; i < GetSize(a); ++i) {
|
||||
SigBit b = a[i];
|
||||
if (b == State::Sz)
|
||||
continue;
|
||||
new_a.append(b);
|
||||
new_y.append(y[i]);
|
||||
}
|
||||
a = std::move(new_a);
|
||||
y = std::move(new_y);
|
||||
}
|
||||
if (!y.empty())
|
||||
module->connect(y, a);
|
||||
delcells.push_back(cell);
|
||||
} else if (cell->type.in(ID($connect)) && !cell->has_keep_attr()) {
|
||||
RTLIL::SigSpec a = cell->getPort(ID::A);
|
||||
RTLIL::SigSpec b = cell->getPort(ID::B);
|
||||
if (a.has_const() && !b.has_const())
|
||||
std::swap(a, b);
|
||||
module->connect(a, b);
|
||||
delcells.push_back(cell);
|
||||
} else if (cell->type.in(ID($input_port)) && !cell->has_keep_attr()) {
|
||||
delcells.push_back(cell);
|
||||
}
|
||||
}
|
||||
for (auto cell : delcells) {
|
||||
if (verbose) {
|
||||
if (cell->type == ID($connect))
|
||||
log_debug(" removing connect cell `%s': %s <-> %s\n", cell->name,
|
||||
log_signal(cell->getPort(ID::A)), log_signal(cell->getPort(ID::B)));
|
||||
else if (cell->type == ID($input_port))
|
||||
log_debug(" removing input port marker cell `%s': %s\n", cell->name,
|
||||
log_signal(cell->getPort(ID::Y)));
|
||||
else
|
||||
log_debug(" removing buffer cell `%s': %s = %s\n", cell->name,
|
||||
log_signal(cell->getPort(ID::Y)), log_signal(cell->getPort(ID::A)));
|
||||
}
|
||||
module->remove(cell);
|
||||
}
|
||||
if (!delcells.empty())
|
||||
module->design->scratchpad_set_bool("opt.did_something", true);
|
||||
|
||||
rmunused_module_cells(module, verbose);
|
||||
while (rmunused_module_signals(module, purge_mode, verbose)) { }
|
||||
|
||||
if (rminit && rmunused_module_init(module, verbose))
|
||||
while (rmunused_module_signals(module, purge_mode, verbose)) { }
|
||||
}
|
||||
|
||||
struct OptCleanPass : public Pass {
|
||||
OptCleanPass() : Pass("opt_clean", "remove unused cells and wires") { }
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" opt_clean [options] [selection]\n");
|
||||
log("\n");
|
||||
log("This pass identifies wires and cells that are unused and removes them. Other\n");
|
||||
log("passes often remove cells but leave the wires in the design or reconnect the\n");
|
||||
log("wires but leave the old cells in the design. This pass can be used to clean up\n");
|
||||
log("after the passes that do the actual work.\n");
|
||||
log("\n");
|
||||
log("This pass only operates on completely selected modules without processes.\n");
|
||||
log("\n");
|
||||
log(" -purge\n");
|
||||
log(" also remove internal nets if they have a public name\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
bool purge_mode = false;
|
||||
|
||||
log_header(design, "Executing OPT_CLEAN pass (remove unused cells and wires).\n");
|
||||
log_push();
|
||||
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||
if (args[argidx] == "-purge") {
|
||||
purge_mode = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
keep_cache.reset(design, purge_mode);
|
||||
|
||||
ct_reg.setup_internals_mem();
|
||||
ct_reg.setup_internals_anyinit();
|
||||
ct_reg.setup_stdcells_mem();
|
||||
|
||||
ct_all.setup(design);
|
||||
|
||||
count_rm_cells = 0;
|
||||
count_rm_wires = 0;
|
||||
|
||||
for (auto module : design->selected_whole_modules_warn()) {
|
||||
if (module->has_processes_warn())
|
||||
continue;
|
||||
rmunused_module(module, purge_mode, true, true);
|
||||
}
|
||||
|
||||
if (count_rm_cells > 0 || count_rm_wires > 0)
|
||||
log("Removed %d unused cells and %d unused wires.\n", count_rm_cells, count_rm_wires);
|
||||
|
||||
design->optimize();
|
||||
design->check();
|
||||
|
||||
keep_cache.reset();
|
||||
ct_reg.clear();
|
||||
ct_all.clear();
|
||||
log_pop();
|
||||
|
||||
request_garbage_collection();
|
||||
}
|
||||
} OptCleanPass;
|
||||
|
||||
struct CleanPass : public Pass {
|
||||
CleanPass() : Pass("clean", "remove unused cells and wires") { }
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" clean [options] [selection]\n");
|
||||
log("\n");
|
||||
log("This is identical to 'opt_clean', but less verbose.\n");
|
||||
log("\n");
|
||||
log("When commands are separated using the ';;' token, this command will be executed\n");
|
||||
log("between the commands.\n");
|
||||
log("\n");
|
||||
log("When commands are separated using the ';;;' token, this command will be executed\n");
|
||||
log("in -purge mode between the commands.\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
bool purge_mode = false;
|
||||
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||
if (args[argidx] == "-purge") {
|
||||
purge_mode = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
keep_cache.reset(design);
|
||||
|
||||
ct_reg.setup_internals_mem();
|
||||
ct_reg.setup_internals_anyinit();
|
||||
ct_reg.setup_stdcells_mem();
|
||||
|
||||
ct_all.setup(design);
|
||||
|
||||
count_rm_cells = 0;
|
||||
count_rm_wires = 0;
|
||||
|
||||
for (auto module : design->selected_unboxed_whole_modules()) {
|
||||
if (module->has_processes())
|
||||
continue;
|
||||
rmunused_module(module, purge_mode, ys_debug(), true);
|
||||
}
|
||||
|
||||
log_suppressed();
|
||||
if (count_rm_cells > 0 || count_rm_wires > 0)
|
||||
log("Removed %d unused cells and %d unused wires.\n", count_rm_cells, count_rm_wires);
|
||||
|
||||
design->optimize();
|
||||
design->check();
|
||||
|
||||
keep_cache.reset();
|
||||
ct_reg.clear();
|
||||
ct_all.clear();
|
||||
|
||||
request_garbage_collection();
|
||||
}
|
||||
} CleanPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
10
passes/opt/opt_clean/Makefile.inc
Normal file
10
passes/opt/opt_clean/Makefile.inc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
OPT_CLEAN_OBJS =
|
||||
OPT_CLEAN_OBJS += passes/opt/opt_clean/cells_all.o
|
||||
OPT_CLEAN_OBJS += passes/opt/opt_clean/cells_temp.o
|
||||
OPT_CLEAN_OBJS += passes/opt/opt_clean/wires.o
|
||||
OPT_CLEAN_OBJS += passes/opt/opt_clean/inits.o
|
||||
OPT_CLEAN_OBJS += passes/opt/opt_clean/opt_clean.o
|
||||
|
||||
$(OPT_CLEAN_OBJS): passes/opt/opt_clean/opt_clean.h
|
||||
|
||||
OBJS += $(OPT_CLEAN_OBJS)
|
||||
373
passes/opt/opt_clean/cells_all.cc
Normal file
373
passes/opt/opt_clean/cells_all.cc
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/ffinit.h"
|
||||
#include "kernel/yosys_common.h"
|
||||
#include "passes/opt/opt_clean/opt_clean.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
unsigned int hash_bit(const SigBit &bit) {
|
||||
return static_cast<unsigned int>(hash_ops<SigBit>::hash(bit).yield());
|
||||
}
|
||||
|
||||
SigMap wire_sigmap(const RTLIL::Module* mod) {
|
||||
SigMap map;
|
||||
for (auto &it : mod->connections_) {
|
||||
for (int i = 0; i < GetSize(it.second); i++) {
|
||||
if (it.second[i].wire != nullptr)
|
||||
map.add(it.first[i], it.second[i]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
struct WireDrivers;
|
||||
// Maps from a SigBit to a unique driver cell.
|
||||
struct WireDriver {
|
||||
using Accumulated = WireDrivers;
|
||||
SigBit bit;
|
||||
int driver_cell;
|
||||
};
|
||||
// Maps from a SigBit to one or more driver cells.
|
||||
struct WireDrivers {
|
||||
WireDrivers() : driver_cell(0) {}
|
||||
WireDrivers(WireDriver driver) : bit(driver.bit), driver_cell(driver.driver_cell) {}
|
||||
WireDrivers(SigBit bit) : bit(bit), driver_cell(0) {}
|
||||
WireDrivers(WireDrivers &&other) = default;
|
||||
|
||||
class const_iterator {
|
||||
public:
|
||||
const_iterator(const WireDrivers &drivers, bool end)
|
||||
: driver_cell(drivers.driver_cell), in_extra_cells(end) {
|
||||
if (drivers.extra_driver_cells) {
|
||||
if (end) {
|
||||
extra_it = drivers.extra_driver_cells->end();
|
||||
} else {
|
||||
extra_it = drivers.extra_driver_cells->begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
int operator*() const {
|
||||
if (in_extra_cells)
|
||||
return **extra_it;
|
||||
return driver_cell;
|
||||
}
|
||||
const_iterator& operator++() {
|
||||
if (in_extra_cells)
|
||||
++*extra_it;
|
||||
else
|
||||
in_extra_cells = true;
|
||||
return *this;
|
||||
}
|
||||
bool operator!=(const const_iterator &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool operator==(const const_iterator &other) const {
|
||||
return in_extra_cells == other.in_extra_cells &&
|
||||
extra_it == other.extra_it;
|
||||
}
|
||||
private:
|
||||
std::optional<pool<int>::iterator> extra_it;
|
||||
int driver_cell;
|
||||
bool in_extra_cells;
|
||||
};
|
||||
|
||||
const_iterator begin() const { return const_iterator(*this, false); }
|
||||
const_iterator end() const { return const_iterator(*this, true); }
|
||||
|
||||
SigBit bit;
|
||||
int driver_cell;
|
||||
std::unique_ptr<pool<int>> extra_driver_cells;
|
||||
};
|
||||
struct WireDriversKeyEquality {
|
||||
bool operator()(const WireDrivers &a, const WireDrivers &b) const {
|
||||
return a.bit == b.bit;
|
||||
}
|
||||
};
|
||||
struct WireDriversCollisionHandler {
|
||||
void operator()(WireDrivers &incumbent, WireDrivers &new_value) const {
|
||||
log_assert(new_value.extra_driver_cells == nullptr);
|
||||
if (!incumbent.extra_driver_cells)
|
||||
incumbent.extra_driver_cells.reset(new pool<int>());
|
||||
incumbent.extra_driver_cells->insert(new_value.driver_cell);
|
||||
}
|
||||
};
|
||||
using Wire2Drivers = ShardedHashtable<WireDriver, WireDriversKeyEquality, WireDriversCollisionHandler>;
|
||||
|
||||
struct ConflictLogs {
|
||||
ShardedVector<std::pair<SigBit, std::string>> logs;
|
||||
ConflictLogs(ParallelDispatchThreadPool::Subpool &subpool) : logs(subpool) {}
|
||||
void print_warnings(pool<SigBit>& used_raw_bits, const SigMap& wire_map, const RTLIL::Module* mod, CleanRunContext &clean_ctx) {
|
||||
if (!logs.empty()) {
|
||||
// We could do this in parallel but hopefully this is rare.
|
||||
for (auto [_, cell] : mod->cells_) {
|
||||
for (auto &[port, sig] : cell->connections()) {
|
||||
if (clean_ctx.ct_all.cell_known(cell->type) && !clean_ctx.ct_all.cell_input(cell->type, port))
|
||||
continue;
|
||||
for (auto raw_bit : wire_map(sig))
|
||||
used_raw_bits.insert(raw_bit);
|
||||
}
|
||||
}
|
||||
for (std::pair<SigBit, std::string> &it : logs) {
|
||||
if (used_raw_bits.count(it.first))
|
||||
log_warning("%s\n", it.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct CellTraversal {
|
||||
ConcurrentWorkQueue<int> queue;
|
||||
Wire2Drivers wire2driver;
|
||||
dict<std::string, pool<int>> mem2cells;
|
||||
CellTraversal(int num_threads) : queue(num_threads), wire2driver(), mem2cells() {}
|
||||
};
|
||||
|
||||
struct CellAnalysis {
|
||||
ShardedVector<Wire*> keep_wires;
|
||||
std::vector<std::atomic<bool>> unused;
|
||||
|
||||
CellAnalysis(AnalysisContext& actx)
|
||||
: keep_wires(actx.subpool), unused(actx.mod->cells_size()) {}
|
||||
|
||||
pool<SigBit> analyze_kept_wires(CellTraversal& traversal, const SigMap& sigmap, const SigMap& wire_map, int num_threads) {
|
||||
// Also enqueue cells that drive kept wires into cell_queue
|
||||
// and mark those cells as used
|
||||
// and mark all bits of those wires as used
|
||||
pool<SigBit> used_raw_bits;
|
||||
int i = 0;
|
||||
for (Wire *wire : keep_wires) {
|
||||
for (auto bit : sigmap(wire)) {
|
||||
const WireDrivers *drivers = traversal.wire2driver.find({{bit}, hash_bit(bit)});
|
||||
if (drivers != nullptr)
|
||||
for (int cell_index : *drivers)
|
||||
if (unused[cell_index].exchange(false, std::memory_order_relaxed)) {
|
||||
ThreadIndex fake_thread_index = {i++ % num_threads};
|
||||
traversal.queue.push(fake_thread_index, cell_index);
|
||||
}
|
||||
}
|
||||
for (auto raw_bit : SigSpec(wire))
|
||||
used_raw_bits.insert(wire_map(raw_bit));
|
||||
}
|
||||
return used_raw_bits;
|
||||
}
|
||||
|
||||
void mark_used_and_enqueue(int cell_idx, ConcurrentWorkQueue<int>& queue, const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
if (unused[cell_idx].exchange(false, std::memory_order_relaxed))
|
||||
queue.push(ctx, cell_idx);
|
||||
}
|
||||
};
|
||||
|
||||
ConflictLogs explore(CellAnalysis& analysis, CellTraversal& traversal, const SigMap& wire_map, AnalysisContext& actx, CleanRunContext &clean_ctx) {
|
||||
ConflictLogs logs(actx.subpool);
|
||||
Wire2Drivers::Builder wire2driver_builder(actx.subpool);
|
||||
ShardedVector<std::pair<std::string, int>> mem2cells_vector(actx.subpool);
|
||||
|
||||
// Enqueue kept cells into traversal.queue
|
||||
// Prepare input cone traversal into traversal.wire2driver
|
||||
// Prepare "input cone" traversal from memory to write port or meminit as analysis.mem2cells
|
||||
// Also check driver conflicts
|
||||
// Also mark cells unused to true unless keep (we override this later)
|
||||
actx.subpool.run([&analysis, &traversal, &logs, &wire_map, &mem2cells_vector, &wire2driver_builder, &actx, &clean_ctx](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(actx.mod->cells_size())) {
|
||||
Cell *cell = actx.mod->cell_at(i);
|
||||
if (cell->type.in(ID($memwr), ID($memwr_v2), ID($meminit), ID($meminit_v2)))
|
||||
mem2cells_vector.insert(ctx, {cell->getParam(ID::MEMID).decode_string(), i});
|
||||
|
||||
for (auto &it2 : cell->connections()) {
|
||||
if (clean_ctx.ct_all.cell_known(cell->type) && !clean_ctx.ct_all.cell_output(cell->type, it2.first))
|
||||
continue;
|
||||
for (auto raw_bit : it2.second) {
|
||||
if (raw_bit.wire == nullptr)
|
||||
continue;
|
||||
auto bit = actx.assign_map(raw_bit);
|
||||
if (bit.wire == nullptr && clean_ctx.ct_all.cell_known(cell->type)) {
|
||||
std::string msg = stringf("Driver-driver conflict "
|
||||
"for %s between cell %s.%s and constant %s in %s: Resolved using constant.",
|
||||
log_signal(raw_bit), cell->name.unescape(), it2.first.unescape(), log_signal(bit), actx.mod->name.unescape());
|
||||
logs.logs.insert(ctx, {wire_map(raw_bit), msg});
|
||||
}
|
||||
if (bit.wire != nullptr)
|
||||
wire2driver_builder.insert(ctx, {{bit, i}, hash_bit(bit)});
|
||||
}
|
||||
}
|
||||
bool keep = clean_ctx.keep_cache.query(cell);
|
||||
analysis.unused[i].store(!keep, std::memory_order_relaxed);
|
||||
if (keep)
|
||||
traversal.queue.push(ctx, i);
|
||||
}
|
||||
for (int i : ctx.item_range(actx.mod->wires_size())) {
|
||||
Wire *wire = actx.mod->wire_at(i);
|
||||
if (wire->port_output || wire->get_bool_attribute(ID::keep))
|
||||
analysis.keep_wires.insert(ctx, wire);
|
||||
}
|
||||
});
|
||||
// Finish by merging per-thread collected data
|
||||
actx.subpool.run([&wire2driver_builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
wire2driver_builder.process(ctx);
|
||||
});
|
||||
traversal.wire2driver = wire2driver_builder;
|
||||
|
||||
for (std::pair<std::string, int> &mem2cell : mem2cells_vector)
|
||||
traversal.mem2cells[mem2cell.first].insert(mem2cell.second);
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
struct MemAnalysis {
|
||||
std::vector<std::atomic<bool>> unused;
|
||||
dict<std::string, int> indices;
|
||||
MemAnalysis(const RTLIL::Module* mod) : unused(mod->memories.size()), indices() {
|
||||
for (int i = 0; i < GetSize(mod->memories); ++i) {
|
||||
indices[mod->memories.element(i)->first.str()] = i;
|
||||
unused[i].store(true, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void fixup_unused_cells_and_mems(CellAnalysis& analysis, MemAnalysis& mem_analysis, CellTraversal& traversal, AnalysisContext& actx, CleanRunContext &clean_ctx) {
|
||||
// Processes the cell queue in batches, traversing input cones by enqueuing more cells
|
||||
// Discover and mark used memories and cells
|
||||
actx.subpool.run([&analysis, &mem_analysis, &traversal, &actx, &clean_ctx](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
pool<SigBit> bits;
|
||||
pool<std::string> mems;
|
||||
while (true) {
|
||||
std::vector<int> cell_indices = traversal.queue.pop_batch(ctx);
|
||||
if (cell_indices.empty())
|
||||
return;
|
||||
for (auto cell_index : cell_indices) {
|
||||
Cell *cell = actx.mod->cell_at(cell_index);
|
||||
for (auto &it : cell->connections())
|
||||
if (!clean_ctx.ct_all.cell_known(cell->type) || clean_ctx.ct_all.cell_input(cell->type, it.first))
|
||||
for (auto bit : actx.assign_map(it.second))
|
||||
bits.insert(bit);
|
||||
|
||||
if (cell->type.in(ID($memrd), ID($memrd_v2))) {
|
||||
std::string mem_id = cell->getParam(ID::MEMID).decode_string();
|
||||
if (mem_analysis.indices.count(mem_id)) {
|
||||
int mem_index = mem_analysis.indices[mem_id];
|
||||
// Memory fixup
|
||||
if (mem_analysis.unused[mem_index].exchange(false, std::memory_order_relaxed))
|
||||
mems.insert(mem_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto bit : bits) {
|
||||
// Cells fixup
|
||||
const WireDrivers *drivers = traversal.wire2driver.find({{bit}, hash_bit(bit)});
|
||||
if (drivers != nullptr)
|
||||
for (int cell_idx : *drivers)
|
||||
analysis.mark_used_and_enqueue(cell_idx, traversal.queue, ctx);
|
||||
}
|
||||
bits.clear();
|
||||
|
||||
for (auto mem : mems) {
|
||||
if (traversal.mem2cells.count(mem) == 0)
|
||||
continue;
|
||||
// Cells fixup
|
||||
for (int cell_idx : traversal.mem2cells.at(mem))
|
||||
analysis.mark_used_and_enqueue(cell_idx, traversal.queue, ctx);
|
||||
}
|
||||
mems.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pool<Cell*> all_unused_cells(const Module *mod, const CellAnalysis& analysis, Wire2Drivers& wire2driver, ParallelDispatchThreadPool::Subpool &subpool) {
|
||||
pool<Cell*> unused_cells;
|
||||
ShardedVector<int> sharded_unused_cells(subpool);
|
||||
subpool.run([mod, &analysis, &wire2driver, &sharded_unused_cells](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
// Parallel destruction of `wire2driver`
|
||||
wire2driver.clear(ctx);
|
||||
for (int i : ctx.item_range(mod->cells_size()))
|
||||
if (analysis.unused[i].load(std::memory_order_relaxed))
|
||||
sharded_unused_cells.insert(ctx, i);
|
||||
});
|
||||
for (int cell_index : sharded_unused_cells)
|
||||
unused_cells.insert(mod->cell_at(cell_index));
|
||||
unused_cells.sort(RTLIL::sort_by_name_id<RTLIL::Cell>());
|
||||
return unused_cells;
|
||||
}
|
||||
|
||||
void remove_cells(RTLIL::Module* mod, FfInitVals& ffinit, const pool<Cell*>& cells, bool verbose, RmStats& stats) {
|
||||
for (auto cell : cells) {
|
||||
if (verbose)
|
||||
log_debug(" removing unused `%s' cell `%s'.\n", cell->type, cell->name);
|
||||
mod->design->scratchpad_set_bool("opt.did_something", true);
|
||||
if (cell->is_builtin_ff())
|
||||
ffinit.remove_init(cell->getPort(ID::Q));
|
||||
mod->remove(cell);
|
||||
stats.count_rm_cells++;
|
||||
}
|
||||
}
|
||||
|
||||
void remove_mems(RTLIL::Module* mod, const MemAnalysis& mem_analysis, bool verbose) {
|
||||
for (const auto &it : mem_analysis.indices) {
|
||||
if (!mem_analysis.unused[it.second].load(std::memory_order_relaxed))
|
||||
continue;
|
||||
RTLIL::IdString id(it.first);
|
||||
if (verbose)
|
||||
log_debug(" removing unused memory `%s'.\n", id.unescape());
|
||||
delete mod->memories.at(id);
|
||||
mod->memories.erase(id);
|
||||
}
|
||||
}
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
void rmunused_module_cells(Module *module, ParallelDispatchThreadPool::Subpool &subpool, CleanRunContext &clean_ctx)
|
||||
{
|
||||
AnalysisContext actx(module, subpool);
|
||||
|
||||
// Used for logging warnings only
|
||||
SigMap wire_map = wire_sigmap(module);
|
||||
|
||||
CellAnalysis analysis(actx);
|
||||
CellTraversal traversal(subpool.num_threads());
|
||||
// Mark all unkept cells as unused initially
|
||||
// and queue up cell traversal from those cells
|
||||
auto logs = explore(analysis, traversal, wire_map, actx, clean_ctx);
|
||||
// Mark cells that drive kept wires into cell_queue and those bits as used
|
||||
// and queue up cell traversal from those cells
|
||||
pool<SigBit> used_raw_bits = analysis.analyze_kept_wires(traversal, actx.assign_map, wire_map, subpool.num_threads());
|
||||
|
||||
// Mark all memories as unused initially
|
||||
MemAnalysis mem_analysis(module);
|
||||
// Marked all used cells and mems as used by traversing with cell queue
|
||||
fixup_unused_cells_and_mems(analysis, mem_analysis, traversal, actx, clean_ctx);
|
||||
// Analyses are now fully correct
|
||||
|
||||
// unused_cells.contains(foo) iff analysis.used[foo] == true
|
||||
// wire2driver is passed in only to destroy it
|
||||
pool<Cell*> unused_cells = all_unused_cells(module, analysis, traversal.wire2driver, subpool);
|
||||
|
||||
FfInitVals ffinit;
|
||||
ffinit.set_parallel(&actx.assign_map, subpool.thread_pool(), module);
|
||||
// Now we know what to kill
|
||||
remove_cells(module, ffinit, unused_cells, clean_ctx.flags.verbose, clean_ctx.stats);
|
||||
remove_mems(module, mem_analysis, clean_ctx.flags.verbose);
|
||||
logs.print_warnings(used_raw_bits, wire_map, module, clean_ctx);
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
104
passes/opt/opt_clean/cells_temp.cc
Normal file
104
passes/opt/opt_clean/cells_temp.cc
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "passes/opt/opt_clean/opt_clean.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
bool is_signed(RTLIL::Cell* cell) {
|
||||
return cell->type == ID($pos) && cell->getParam(ID::A_SIGNED).as_bool();
|
||||
}
|
||||
|
||||
bool trim_buf(RTLIL::Cell* cell, ShardedVector<RTLIL::SigSig>& new_connections, const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
RTLIL::SigSpec a = cell->getPort(ID::A);
|
||||
RTLIL::SigSpec y = cell->getPort(ID::Y);
|
||||
a.extend_u0(GetSize(y), is_signed(cell));
|
||||
|
||||
if (a.has_const(State::Sz)) {
|
||||
RTLIL::SigSpec new_a;
|
||||
RTLIL::SigSpec new_y;
|
||||
for (int i = 0; i < GetSize(a); ++i) {
|
||||
RTLIL::SigBit b = a[i];
|
||||
if (b == State::Sz)
|
||||
return false;
|
||||
new_a.append(b);
|
||||
new_y.append(y[i]);
|
||||
}
|
||||
a = std::move(new_a);
|
||||
y = std::move(new_y);
|
||||
}
|
||||
if (!y.empty())
|
||||
new_connections.insert(ctx, {y, a});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove(ShardedVector<RTLIL::Cell*>& cells, RTLIL::Module* mod, bool verbose) {
|
||||
bool did_something = false;
|
||||
for (RTLIL::Cell *cell : cells) {
|
||||
if (verbose) {
|
||||
if (cell->type == ID($connect))
|
||||
log_debug(" removing connect cell `%s': %s <-> %s\n", cell->name,
|
||||
log_signal(cell->getPort(ID::A)), log_signal(cell->getPort(ID::B)));
|
||||
else if (cell->type == ID($input_port))
|
||||
log_debug(" removing input port marker cell `%s': %s\n", cell->name,
|
||||
log_signal(cell->getPort(ID::Y)));
|
||||
else
|
||||
log_debug(" removing buffer cell `%s': %s = %s\n", cell->name,
|
||||
log_signal(cell->getPort(ID::Y)), log_signal(cell->getPort(ID::A)));
|
||||
}
|
||||
mod->remove(cell);
|
||||
did_something = true;
|
||||
}
|
||||
return did_something;
|
||||
}
|
||||
PRIVATE_NAMESPACE_END
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
void remove_temporary_cells(RTLIL::Module *module, ParallelDispatchThreadPool::Subpool &subpool, bool verbose)
|
||||
{
|
||||
ShardedVector<RTLIL::Cell*> delcells(subpool);
|
||||
ShardedVector<RTLIL::SigSig> new_connections(subpool);
|
||||
const RTLIL::Module *const_module = module;
|
||||
subpool.run([const_module, &delcells, &new_connections](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(const_module->cells_size())) {
|
||||
RTLIL::Cell *cell = const_module->cell_at(i);
|
||||
if (cell->type.in(ID($pos), ID($_BUF_), ID($buf)) && !cell->has_keep_attr()) {
|
||||
if (trim_buf(cell, new_connections, ctx))
|
||||
delcells.insert(ctx, cell);
|
||||
} else if (cell->type.in(ID($connect)) && !cell->has_keep_attr()) {
|
||||
RTLIL::SigSpec a = cell->getPort(ID::A);
|
||||
RTLIL::SigSpec b = cell->getPort(ID::B);
|
||||
if (a.has_const() && !b.has_const())
|
||||
std::swap(a, b);
|
||||
new_connections.insert(ctx, {a, b});
|
||||
delcells.insert(ctx, cell);
|
||||
} else if (cell->type.in(ID($input_port)) && !cell->has_keep_attr()) {
|
||||
delcells.insert(ctx, cell);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (RTLIL::SigSig &connection : new_connections) {
|
||||
module->connect(connection);
|
||||
}
|
||||
if (remove(delcells, module, verbose))
|
||||
module->design->scratchpad_set_bool("opt.did_something", true);
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
137
passes/opt/opt_clean/inits.cc
Normal file
137
passes/opt/opt_clean/inits.cc
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "passes/opt/opt_clean/opt_clean.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
ShardedVector<std::pair<SigBit, State>> build_inits(AnalysisContext& actx) {
|
||||
ShardedVector<std::pair<SigBit, State>> results(actx.subpool);
|
||||
actx.subpool.run([&results, &actx](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(actx.mod->cells_size())) {
|
||||
RTLIL::Cell *cell = actx.mod->cell_at(i);
|
||||
if (StaticCellTypes::Compat::internals_mem_ff(cell->type) && cell->hasPort(ID::Q))
|
||||
{
|
||||
SigSpec sig = cell->getPort(ID::Q);
|
||||
|
||||
for (int i = 0; i < GetSize(sig); i++)
|
||||
{
|
||||
SigBit bit = sig[i];
|
||||
|
||||
if (bit.wire == nullptr || bit.wire->attributes.count(ID::init) == 0)
|
||||
continue;
|
||||
|
||||
Const init = bit.wire->attributes.at(ID::init);
|
||||
|
||||
if (i >= GetSize(init) || init[i] == State::Sx || init[i] == State::Sz)
|
||||
continue;
|
||||
|
||||
results.insert(ctx, {bit, init[i]});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
dict<SigBit, State> qbits_from_inits(ShardedVector<std::pair<SigBit, State>>& inits, SigMap& assign_map) {
|
||||
dict<SigBit, State> qbits;
|
||||
for (std::pair<SigBit, State> &p : inits) {
|
||||
assign_map.add(p.first);
|
||||
qbits[p.first] = p.second;
|
||||
}
|
||||
return qbits;
|
||||
}
|
||||
|
||||
ShardedVector<RTLIL::Wire*> deferred_init_transfer(const dict<SigBit, State>& qbits, AnalysisContext& actx) {
|
||||
ShardedVector<RTLIL::Wire*> wire_results(actx.subpool);
|
||||
actx.subpool.run([&actx, &qbits, &wire_results](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int j : ctx.item_range(actx.mod->wires_size())) {
|
||||
RTLIL::Wire *wire = actx.mod->wire_at(j);
|
||||
if (wire->attributes.count(ID::init) == 0)
|
||||
continue;
|
||||
Const init = wire->attributes.at(ID::init);
|
||||
|
||||
for (int i = 0; i < GetSize(wire) && i < GetSize(init); i++)
|
||||
{
|
||||
if (init[i] == State::Sx || init[i] == State::Sz)
|
||||
continue;
|
||||
|
||||
SigBit wire_bit = SigBit(wire, i);
|
||||
SigBit mapped_wire_bit = actx.assign_map(wire_bit);
|
||||
|
||||
if (wire_bit == mapped_wire_bit)
|
||||
goto next_wire;
|
||||
|
||||
if (mapped_wire_bit.wire) {
|
||||
if (qbits.count(mapped_wire_bit) == 0)
|
||||
goto next_wire;
|
||||
|
||||
if (qbits.at(mapped_wire_bit) != init[i])
|
||||
goto next_wire;
|
||||
}
|
||||
else {
|
||||
if (mapped_wire_bit == State::Sx || mapped_wire_bit == State::Sz)
|
||||
goto next_wire;
|
||||
|
||||
if (mapped_wire_bit != init[i]) {
|
||||
log_warning("Initial value conflict for %s resolving to %s but with init %s.\n", log_signal(wire_bit), log_signal(mapped_wire_bit), log_signal(init[i]));
|
||||
goto next_wire;
|
||||
}
|
||||
}
|
||||
}
|
||||
wire_results.insert(ctx, wire);
|
||||
|
||||
next_wire:;
|
||||
}
|
||||
});
|
||||
return wire_results;
|
||||
}
|
||||
|
||||
bool remove_redundant_inits(ShardedVector<RTLIL::Wire*> wires, bool verbose) {
|
||||
bool did_something = false;
|
||||
for (RTLIL::Wire *wire : wires) {
|
||||
if (verbose)
|
||||
log_debug(" removing redundant init attribute on %s.\n", log_id(wire));
|
||||
wire->attributes.erase(ID::init);
|
||||
did_something = true;
|
||||
}
|
||||
return did_something;
|
||||
}
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
bool rmunused_module_init(RTLIL::Module *module, ParallelDispatchThreadPool::Subpool &subpool, bool verbose)
|
||||
{
|
||||
AnalysisContext actx(module, subpool);
|
||||
|
||||
ShardedVector<std::pair<SigBit, State>> inits = build_inits(actx);
|
||||
dict<SigBit, State> qbits = qbits_from_inits(inits, actx.assign_map);
|
||||
ShardedVector<RTLIL::Wire*> inits_to_transfer = deferred_init_transfer(qbits, actx);
|
||||
|
||||
bool did_something = remove_redundant_inits(inits_to_transfer, verbose);
|
||||
if (did_something)
|
||||
module->design->scratchpad_set_bool("opt.did_something", true);
|
||||
|
||||
return did_something;
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
167
passes/opt/opt_clean/keep_cache.h
Normal file
167
passes/opt/opt_clean/keep_cache.h
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/rtlil.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/threading.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/yosys_common.h"
|
||||
|
||||
#ifndef OPT_CLEAN_KEEP_CACHE_H
|
||||
#define OPT_CLEAN_KEEP_CACHE_H
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
struct KeepCache
|
||||
{
|
||||
dict<Module*, bool> keep_modules;
|
||||
bool purge_mode;
|
||||
|
||||
KeepCache(bool purge_mode, ParallelDispatchThreadPool &thread_pool, const std::vector<RTLIL::Module *> &selected_modules)
|
||||
: purge_mode(purge_mode) {
|
||||
|
||||
std::vector<RTLIL::Module *> scan_modules_worklist;
|
||||
dict<RTLIL::Module *, std::vector<RTLIL::Module*>> dependents;
|
||||
std::vector<RTLIL::Module *> propagate_kept_modules_worklist;
|
||||
for (RTLIL::Module *module : selected_modules) {
|
||||
if (keep_modules.count(module))
|
||||
continue;
|
||||
bool keep = scan_module(module, thread_pool, dependents, ALL_CELLS, scan_modules_worklist);
|
||||
keep_modules[module] = keep;
|
||||
if (keep)
|
||||
propagate_kept_modules_worklist.push_back(module);
|
||||
}
|
||||
|
||||
while (!scan_modules_worklist.empty()) {
|
||||
RTLIL::Module *module = scan_modules_worklist.back();
|
||||
scan_modules_worklist.pop_back();
|
||||
if (keep_modules.count(module))
|
||||
continue;
|
||||
bool keep = scan_module(module, thread_pool, dependents, MINIMUM_CELLS, scan_modules_worklist);
|
||||
keep_modules[module] = keep;
|
||||
if (keep)
|
||||
propagate_kept_modules_worklist.push_back(module);
|
||||
}
|
||||
|
||||
while (!propagate_kept_modules_worklist.empty()) {
|
||||
RTLIL::Module *module = propagate_kept_modules_worklist.back();
|
||||
propagate_kept_modules_worklist.pop_back();
|
||||
for (RTLIL::Module *dependent : dependents[module]) {
|
||||
if (keep_modules[dependent])
|
||||
continue;
|
||||
keep_modules[dependent] = true;
|
||||
propagate_kept_modules_worklist.push_back(dependent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool query(Cell *cell) const
|
||||
{
|
||||
if (keep_cell(cell, purge_mode))
|
||||
return true;
|
||||
if (cell->type.in(ID($specify2), ID($specify3), ID($specrule)))
|
||||
return true;
|
||||
if (cell->module && cell->module->design) {
|
||||
RTLIL::Module *cell_module = cell->module->design->module(cell->type);
|
||||
return cell_module != nullptr && keep_modules.at(cell_module);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
enum ScanCells {
|
||||
// Scan every cell to see if it uses a module that is kept.
|
||||
ALL_CELLS,
|
||||
// Stop scanning cells if we determine early that this module is kept.
|
||||
MINIMUM_CELLS,
|
||||
};
|
||||
bool scan_module(Module *module, ParallelDispatchThreadPool &thread_pool, dict<RTLIL::Module *, std::vector<RTLIL::Module*>> &dependents,
|
||||
ScanCells scan_cells, std::vector<Module*> &worklist) const
|
||||
{
|
||||
MonotonicFlag keep_module;
|
||||
if (module->get_bool_attribute(ID::keep)) {
|
||||
if (scan_cells == MINIMUM_CELLS)
|
||||
return true;
|
||||
keep_module.set();
|
||||
}
|
||||
|
||||
ParallelDispatchThreadPool::Subpool subpool(thread_pool, ThreadPool::work_pool_size(0, module->cells_size(), 1000));
|
||||
ShardedVector<Module*> deps(subpool);
|
||||
const RTLIL::Module *const_module = module;
|
||||
bool purge_mode = this->purge_mode;
|
||||
subpool.run([purge_mode, const_module, scan_cells, &deps, &keep_module](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
bool keep = false;
|
||||
for (int i : ctx.item_range(const_module->cells_size())) {
|
||||
Cell *cell = const_module->cell_at(i);
|
||||
if (keep_cell(cell, purge_mode)) {
|
||||
if (scan_cells == MINIMUM_CELLS) {
|
||||
keep_module.set();
|
||||
return;
|
||||
}
|
||||
keep = true;
|
||||
}
|
||||
if (const_module->design) {
|
||||
RTLIL::Module *cell_module = const_module->design->module(cell->type);
|
||||
if (cell_module != nullptr)
|
||||
deps.insert(ctx, cell_module);
|
||||
}
|
||||
}
|
||||
if (keep) {
|
||||
keep_module.set();
|
||||
return;
|
||||
}
|
||||
for (int i : ctx.item_range(const_module->wires_size())) {
|
||||
Wire *wire = const_module->wire_at(i);
|
||||
if (wire->get_bool_attribute(ID::keep)) {
|
||||
keep_module.set();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (scan_cells == MINIMUM_CELLS && keep_module.load())
|
||||
return true;
|
||||
for (Module *dep : deps) {
|
||||
dependents[dep].push_back(module);
|
||||
worklist.push_back(dep);
|
||||
}
|
||||
return keep_module.load();
|
||||
}
|
||||
|
||||
static bool keep_cell(Cell *cell, bool purge_mode)
|
||||
{
|
||||
if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover)))
|
||||
return true;
|
||||
|
||||
if (cell->type.in(ID($overwrite_tag)))
|
||||
return true;
|
||||
|
||||
if (cell->type == ID($print) || cell->type == ID($check))
|
||||
return true;
|
||||
|
||||
if (cell->has_keep_attr())
|
||||
return true;
|
||||
|
||||
if (!purge_mode && cell->type == ID($scopeinfo))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
#endif /* OPT_CLEAN_KEEP_CACHE_H */
|
||||
151
passes/opt/opt_clean/opt_clean.cc
Normal file
151
passes/opt/opt_clean/opt_clean.cc
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/register.h"
|
||||
#include "kernel/log.h"
|
||||
#include "passes/opt/opt_clean/opt_clean.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
void rmunused_module(RTLIL::Module *module, bool rminit, CleanRunContext &clean_ctx)
|
||||
{
|
||||
if (clean_ctx.flags.verbose)
|
||||
log("Finding unused cells or wires in module %s..\n", module->name);
|
||||
|
||||
// Use no more than one worker per thousand cells, rounded down, so
|
||||
// we only start multithreading with at least 2000 cells.
|
||||
int num_worker_threads = ThreadPool::work_pool_size(0, module->cells_size(), 10000);
|
||||
ParallelDispatchThreadPool::Subpool subpool(clean_ctx.thread_pool, num_worker_threads);
|
||||
remove_temporary_cells(module, subpool, clean_ctx.flags.verbose);
|
||||
rmunused_module_cells(module, subpool, clean_ctx);
|
||||
while (rmunused_module_signals(module, subpool, clean_ctx)) { }
|
||||
|
||||
if (rminit && rmunused_module_init(module, subpool, clean_ctx.flags.verbose))
|
||||
while (rmunused_module_signals(module, subpool, clean_ctx)) { }
|
||||
}
|
||||
|
||||
struct OptCleanPass : public Pass {
|
||||
OptCleanPass() : Pass("opt_clean", "remove unused cells and wires") { }
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" opt_clean [options] [selection]\n");
|
||||
log("\n");
|
||||
log("This pass identifies wires and cells that are unused and removes them. Other\n");
|
||||
log("passes often remove cells but leave the wires in the design or reconnect the\n");
|
||||
log("wires but leave the old cells in the design. This pass can be used to clean up\n");
|
||||
log("after the passes that do the actual work.\n");
|
||||
log("\n");
|
||||
log("This pass only operates on completely selected modules without processes.\n");
|
||||
log("\n");
|
||||
log(" -purge\n");
|
||||
log(" also remove internal nets if they have a public name\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
bool purge_mode = false;
|
||||
|
||||
log_header(design, "Executing OPT_CLEAN pass (remove unused cells and wires).\n");
|
||||
log_push();
|
||||
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||
if (args[argidx] == "-purge") {
|
||||
purge_mode = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
{
|
||||
std::vector<RTLIL::Module*> selected_modules;
|
||||
for (auto module : design->selected_whole_modules_warn())
|
||||
if (!module->has_processes_warn())
|
||||
selected_modules.push_back(module);
|
||||
CleanRunContext clean_ctx(design, selected_modules, {purge_mode, true});
|
||||
for (auto module : selected_modules)
|
||||
rmunused_module(module, true, clean_ctx);
|
||||
clean_ctx.stats.log();
|
||||
|
||||
design->optimize();
|
||||
design->check();
|
||||
}
|
||||
|
||||
log_pop();
|
||||
|
||||
request_garbage_collection();
|
||||
}
|
||||
} OptCleanPass;
|
||||
|
||||
struct CleanPass : public Pass {
|
||||
CleanPass() : Pass("clean", "remove unused cells and wires") { }
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" clean [options] [selection]\n");
|
||||
log("\n");
|
||||
log("This is identical to 'opt_clean', but less verbose.\n");
|
||||
log("\n");
|
||||
log("When commands are separated using the ';;' token, this command will be executed\n");
|
||||
log("between the commands.\n");
|
||||
log("\n");
|
||||
log("When commands are separated using the ';;;' token, this command will be executed\n");
|
||||
log("in -purge mode between the commands.\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
bool purge_mode = false;
|
||||
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||
if (args[argidx] == "-purge") {
|
||||
purge_mode = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
{
|
||||
std::vector<RTLIL::Module*> selected_modules;
|
||||
for (auto module : design->selected_unboxed_whole_modules())
|
||||
if (!module->has_processes())
|
||||
selected_modules.push_back(module);
|
||||
CleanRunContext clean_ctx(design, selected_modules, {purge_mode, ys_debug()});
|
||||
for (auto module : selected_modules)
|
||||
rmunused_module(module, true, clean_ctx);
|
||||
|
||||
log_suppressed();
|
||||
clean_ctx.stats.log();
|
||||
|
||||
design->optimize();
|
||||
design->check();
|
||||
}
|
||||
|
||||
request_garbage_collection();
|
||||
}
|
||||
} CleanPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
92
passes/opt/opt_clean/opt_clean.h
Normal file
92
passes/opt/opt_clean/opt_clean.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/rtlil.h"
|
||||
#include "kernel/threading.h"
|
||||
#include "passes/opt/opt_clean/keep_cache.h"
|
||||
|
||||
#ifndef OPT_CLEAN_SHARED_H
|
||||
#define OPT_CLEAN_SHARED_H
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
struct AnalysisContext {
|
||||
SigMap assign_map;
|
||||
const RTLIL::Module *mod;
|
||||
ParallelDispatchThreadPool::Subpool &subpool;
|
||||
AnalysisContext(RTLIL::Module* m, ParallelDispatchThreadPool::Subpool &p) : assign_map(m), mod(m), subpool(p) {}
|
||||
};
|
||||
|
||||
struct RmStats {
|
||||
int count_rm_cells = 0;
|
||||
int count_rm_wires = 0;
|
||||
|
||||
void log()
|
||||
{
|
||||
if (count_rm_cells > 0 || count_rm_wires > 0)
|
||||
YOSYS_NAMESPACE_PREFIX log("Removed %d unused cells and %d unused wires.\n", count_rm_cells, count_rm_wires);
|
||||
}
|
||||
};
|
||||
|
||||
struct Flags {
|
||||
bool purge = false;
|
||||
bool verbose = false;
|
||||
};
|
||||
struct CleanRunContext {
|
||||
static constexpr auto ct_reg = StaticCellTypes::Categories::join(
|
||||
StaticCellTypes::Compat::mem_ff,
|
||||
StaticCellTypes::categories.is_anyinit);
|
||||
NewCellTypes ct_all;
|
||||
RmStats stats;
|
||||
ParallelDispatchThreadPool thread_pool;
|
||||
KeepCache keep_cache;
|
||||
Flags flags;
|
||||
|
||||
private:
|
||||
// Helper to compute thread pool size
|
||||
static int compute_thread_pool_size(const std::vector<RTLIL::Module*>& selected_modules) {
|
||||
int thread_pool_size = 0;
|
||||
for (auto module : selected_modules)
|
||||
thread_pool_size = std::max(thread_pool_size,
|
||||
ThreadPool::work_pool_size(0, module->cells_size(), 10000));
|
||||
return thread_pool_size;
|
||||
}
|
||||
|
||||
public:
|
||||
CleanRunContext(RTLIL::Design* design, const std::vector<RTLIL::Module*>& selected_modules, Flags f)
|
||||
: thread_pool(compute_thread_pool_size(selected_modules)),
|
||||
keep_cache(f.purge, thread_pool, selected_modules),
|
||||
flags(f)
|
||||
{
|
||||
ct_all.setup(design);
|
||||
}
|
||||
|
||||
~CleanRunContext() {
|
||||
ct_all.clear();
|
||||
}
|
||||
};
|
||||
|
||||
void remove_temporary_cells(RTLIL::Module *module, ParallelDispatchThreadPool::Subpool &subpool, bool verbose);
|
||||
void rmunused_module_cells(Module *module, ParallelDispatchThreadPool::Subpool &subpool, CleanRunContext &clean_ctx);
|
||||
bool rmunused_module_signals(RTLIL::Module *module, ParallelDispatchThreadPool::Subpool &subpool, CleanRunContext &clean_ctx);
|
||||
bool rmunused_module_init(RTLIL::Module *module, ParallelDispatchThreadPool::Subpool &subpool, bool verbose);
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif /* OPT_CLEAN_SHARED_H */
|
||||
585
passes/opt/opt_clean/wires.cc
Normal file
585
passes/opt/opt_clean/wires.cc
Normal file
|
|
@ -0,0 +1,585 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "passes/opt/opt_clean/opt_clean.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
// No collision handler for these, since we will use them such that collisions don't happen
|
||||
struct ShardedSigBit {
|
||||
using Accumulated = ShardedSigBit;
|
||||
RTLIL::SigBit bit;
|
||||
ShardedSigBit() = default;
|
||||
ShardedSigBit(const RTLIL::SigBit &bit) : bit(bit) {}
|
||||
};
|
||||
struct ShardedSigBitEquality {
|
||||
bool operator()(const ShardedSigBit &b1, const ShardedSigBit &b2) const {
|
||||
return b1.bit == b2.bit;
|
||||
}
|
||||
};
|
||||
using ShardedSigPool = ShardedHashtable<ShardedSigBit, ShardedSigBitEquality, SetCollisionHandler<ShardedSigBit>>;
|
||||
|
||||
struct ShardedSigSpec {
|
||||
using Accumulated = ShardedSigSpec;
|
||||
RTLIL::SigSpec spec;
|
||||
ShardedSigSpec() = default;
|
||||
ShardedSigSpec(RTLIL::SigSpec spec) : spec(std::move(spec)) {}
|
||||
ShardedSigSpec(ShardedSigSpec &&) = default;
|
||||
};
|
||||
struct ShardedSigSpecEquality {
|
||||
bool operator()(const ShardedSigSpec &s1, const ShardedSigSpec &s2) const {
|
||||
return s1.spec == s2.spec;
|
||||
}
|
||||
};
|
||||
using ShardedSigSpecPool = ShardedHashtable<ShardedSigSpec, ShardedSigSpecEquality, SetCollisionHandler<ShardedSigSpec>>;
|
||||
|
||||
struct ExactCellWires {
|
||||
const ShardedSigSpecPool &exact_cells;
|
||||
const SigMap &assign_map;
|
||||
dict<RTLIL::Wire *, bool> cache;
|
||||
|
||||
ExactCellWires(const ShardedSigSpecPool &exact_cells, const SigMap &assign_map) : exact_cells(exact_cells), assign_map(assign_map) {}
|
||||
void cache_result_for_bit(const SigBit &bit) {
|
||||
if (bit.wire != nullptr)
|
||||
(void)is_exactly_cell_driven(bit.wire);
|
||||
}
|
||||
bool is_exactly_cell_driven(RTLIL::Wire *wire) {
|
||||
if (wire->port_input)
|
||||
return true;
|
||||
auto it = cache.find(wire);
|
||||
if (it != cache.end())
|
||||
return it->second;
|
||||
SigSpec sig = assign_map(wire);
|
||||
bool direct = exact_cells.find({sig, sig.hash_into(Hasher()).yield()}) != nullptr;
|
||||
cache.insert({wire, direct});
|
||||
return direct;
|
||||
}
|
||||
void cache_all(ShardedVector<RTLIL::SigBit> &bits) {
|
||||
for (RTLIL::SigBit candidate : bits) {
|
||||
cache_result_for_bit(candidate);
|
||||
cache_result_for_bit(assign_map(candidate));
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
int count_nontrivial_wire_attrs(RTLIL::Wire *w)
|
||||
{
|
||||
int count = w->attributes.size();
|
||||
count -= w->attributes.count(ID::src);
|
||||
count -= w->attributes.count(ID::hdlname);
|
||||
count -= w->attributes.count(ID::scopename);
|
||||
count -= w->attributes.count(ID::unused_bits);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Should we pick `s2` over `s1` to represent a signal?
|
||||
bool compare_signals(const RTLIL::SigBit &s1, const RTLIL::SigBit &s2, const ShardedSigPool ®s, const ShardedSigPool &conns, ExactCellWires &cell_wires)
|
||||
{
|
||||
if (s1 == s2)
|
||||
return false;
|
||||
|
||||
RTLIL::Wire *w1 = s1.wire;
|
||||
RTLIL::Wire *w2 = s2.wire;
|
||||
|
||||
if (w1 == NULL || w2 == NULL)
|
||||
return w2 == NULL;
|
||||
|
||||
if (w1->port_input != w2->port_input)
|
||||
return w2->port_input;
|
||||
|
||||
if ((w1->port_input && w1->port_output) != (w2->port_input && w2->port_output))
|
||||
return !(w2->port_input && w2->port_output);
|
||||
|
||||
if (w1->name.isPublic() && w2->name.isPublic()) {
|
||||
ShardedSigPool::AccumulatedValue s1_val = {s1, s1.hash_top().yield()};
|
||||
ShardedSigPool::AccumulatedValue s2_val = {s2, s2.hash_top().yield()};
|
||||
bool regs1 = regs.find(s1_val) != nullptr;
|
||||
bool regs2 = regs.find(s2_val) != nullptr;
|
||||
if (regs1 != regs2)
|
||||
return regs2;
|
||||
bool w1_exact = cell_wires.is_exactly_cell_driven(w1);
|
||||
bool w2_exact = cell_wires.is_exactly_cell_driven(w2);
|
||||
if (w1_exact != w2_exact)
|
||||
return w2_exact;
|
||||
bool conns1 = conns.find(s1_val) != nullptr;
|
||||
bool conns2 = conns.find(s2_val) != nullptr;
|
||||
if (conns1 != conns2)
|
||||
return conns2;
|
||||
}
|
||||
|
||||
if (w1 == w2)
|
||||
return s2.offset < s1.offset;
|
||||
|
||||
if (w1->port_output != w2->port_output)
|
||||
return w2->port_output;
|
||||
|
||||
if (w1->name[0] != w2->name[0])
|
||||
return w2->name.isPublic();
|
||||
|
||||
int attrs1 = count_nontrivial_wire_attrs(w1);
|
||||
int attrs2 = count_nontrivial_wire_attrs(w2);
|
||||
|
||||
if (attrs1 != attrs2)
|
||||
return attrs2 > attrs1;
|
||||
|
||||
return w2->name.lt_by_name(w1->name);
|
||||
}
|
||||
|
||||
bool check_public_name(RTLIL::IdString id)
|
||||
{
|
||||
if (id.begins_with("$"))
|
||||
return false;
|
||||
const std::string &id_str = id.str();
|
||||
if (id.begins_with("\\_") && (id.ends_with("_") || id_str.find("_[") != std::string::npos))
|
||||
return false;
|
||||
if (id_str.find(".$") != std::string::npos)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void add_spec(ShardedSigPool::Builder &builder, const ThreadIndex &thread, const RTLIL::SigSpec &spec) {
|
||||
for (SigBit bit : spec)
|
||||
if (bit.wire != nullptr)
|
||||
builder.insert(thread, {bit, bit.hash_top().yield()});
|
||||
}
|
||||
|
||||
bool check_any(const ShardedSigPool &sigs, const RTLIL::SigSpec &spec) {
|
||||
for (SigBit b : spec)
|
||||
if (sigs.find({b, b.hash_top().yield()}) != nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool check_all(const ShardedSigPool &sigs, const RTLIL::SigSpec &spec) {
|
||||
for (SigBit b : spec)
|
||||
if (sigs.find({b, b.hash_top().yield()}) == nullptr)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct UpdateConnection {
|
||||
RTLIL::Cell *cell;
|
||||
RTLIL::IdString port;
|
||||
RTLIL::SigSpec spec;
|
||||
};
|
||||
void fixup_cell_ports(ShardedVector<UpdateConnection> &update_connections)
|
||||
{
|
||||
for (UpdateConnection &update : update_connections)
|
||||
update.cell->connections_.at(update.port) = std::move(update.spec);
|
||||
}
|
||||
|
||||
struct InitBits {
|
||||
dict<SigBit, RTLIL::State> values;
|
||||
// Wires that appear in the keys of the `values` dict
|
||||
pool<Wire*> wires;
|
||||
|
||||
// Set init attributes on all wires of a connected group
|
||||
void apply_normalised_inits() {
|
||||
for (RTLIL::Wire *wire : wires) {
|
||||
bool found = false;
|
||||
Const val(State::Sx, wire->width);
|
||||
for (int i = 0; i < wire->width; i++) {
|
||||
auto it = values.find(RTLIL::SigBit(wire, i));
|
||||
if (it != values.end()) {
|
||||
val.set(i, it->second);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
wire->attributes[ID::init] = val;
|
||||
}
|
||||
}
|
||||
};
|
||||
static InitBits consume_inits(ShardedVector<RTLIL::Wire*> &initialized_wires, const SigMap &assign_map)
|
||||
{
|
||||
InitBits init_bits;
|
||||
for (RTLIL::Wire *initialized_wire : initialized_wires) {
|
||||
auto it = initialized_wire->attributes.find(ID::init);
|
||||
RTLIL::Const &val = it->second;
|
||||
SigSpec sig = assign_map(initialized_wire);
|
||||
for (int i = 0; i < GetSize(val) && i < GetSize(sig); i++)
|
||||
if (val[i] != State::Sx && sig[i].wire != nullptr) {
|
||||
init_bits.values[sig[i]] = val[i];
|
||||
init_bits.wires.insert(sig[i].wire);
|
||||
}
|
||||
initialized_wire->attributes.erase(it);
|
||||
}
|
||||
return init_bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* What kinds of things are signals connected to?
|
||||
* Helps pick representatives out of groups of connected signals */
|
||||
struct SigConnKinds {
|
||||
// Wire bits directly driven by registers (with clk2fflogic exception)
|
||||
ShardedSigPool raw_registers;
|
||||
// Wire bits directly connected to any cell port
|
||||
ShardedSigPool raw_cell_connected;
|
||||
|
||||
// Signals exactly driven by a known cell output,
|
||||
// this will influence only our choice of representatives.
|
||||
// A signal is exactly driven by a cell output iff all its bits are driven by this output
|
||||
// and all bits of this output drive a bit of this signal.
|
||||
// Additionally, all signals that sigmap to this signal are exactly driven by the port, too
|
||||
ShardedSigSpecPool exact_cells;
|
||||
|
||||
SigConnKinds(bool purge_mode, const AnalysisContext& actx, CleanRunContext& clean_ctx) {
|
||||
ShardedSigPool::Builder raw_register_builder(actx.subpool);
|
||||
ShardedSigPool::Builder raw_cell_connected_builder(actx.subpool);
|
||||
ShardedSigSpecPool::Builder exact_cell_output_builder(actx.subpool);
|
||||
actx.subpool.run([&exact_cell_output_builder, &raw_register_builder, &raw_cell_connected_builder, purge_mode, &actx, &clean_ctx](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
|
||||
for (int i : ctx.item_range(actx.mod->cells_size())) {
|
||||
RTLIL::Cell *cell = actx.mod->cell_at(i);
|
||||
if (!purge_mode) {
|
||||
if (clean_ctx.ct_reg(cell->type)) {
|
||||
// Improve witness signal naming when clk2fflogic used
|
||||
// see commit message e36c71b5
|
||||
bool clk2fflogic = cell->get_bool_attribute(ID::clk2fflogic);
|
||||
for (auto &[port, sig] : cell->connections())
|
||||
if (clk2fflogic ? port == ID::D : clean_ctx.ct_all.cell_output(cell->type, port))
|
||||
add_spec(raw_register_builder, ctx, sig);
|
||||
}
|
||||
for (auto &[_, sig] : cell->connections())
|
||||
add_spec(raw_cell_connected_builder, ctx, sig);
|
||||
}
|
||||
if (clean_ctx.ct_all.cell_known(cell->type))
|
||||
for (auto &[port, sig] : cell->connections())
|
||||
if (clean_ctx.ct_all.cell_output(cell->type, port)) {
|
||||
RTLIL::SigSpec spec = actx.assign_map(sig);
|
||||
unsigned int hash = spec.hash_into(Hasher()).yield();
|
||||
exact_cell_output_builder.insert(ctx, {std::move(spec), hash});
|
||||
}
|
||||
}
|
||||
});
|
||||
actx.subpool.run([&raw_register_builder, &raw_cell_connected_builder, &exact_cell_output_builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
raw_register_builder.process(ctx);
|
||||
raw_cell_connected_builder.process(ctx);
|
||||
exact_cell_output_builder.process(ctx);
|
||||
});
|
||||
raw_registers = raw_register_builder;
|
||||
raw_cell_connected = raw_cell_connected_builder;
|
||||
exact_cells = exact_cell_output_builder;
|
||||
}
|
||||
void clear(const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
raw_registers.clear(ctx);
|
||||
raw_cell_connected.clear(ctx);
|
||||
exact_cells.clear(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
ShardedVector<RTLIL::SigBit> build_candidates(ExactCellWires& cell_wires, const SigConnKinds& sig_analysis, const AnalysisContext& actx) {
|
||||
ShardedVector<RTLIL::SigBit> candidates(actx.subpool);
|
||||
actx.subpool.run([&actx, &sig_analysis, &candidates, &cell_wires](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
std::optional<ExactCellWires> local_cell_wires;
|
||||
ExactCellWires *this_thread_cell_wires = &cell_wires;
|
||||
if (ctx.thread_num > 0) {
|
||||
local_cell_wires.emplace(sig_analysis.exact_cells, actx.assign_map);
|
||||
this_thread_cell_wires = &local_cell_wires.value();
|
||||
}
|
||||
for (int i : ctx.item_range(actx.mod->wires_size())) {
|
||||
RTLIL::Wire *wire = actx.mod->wire_at(i);
|
||||
for (int j = 0; j < wire->width; ++j) {
|
||||
RTLIL::SigBit s1(wire, j);
|
||||
RTLIL::SigBit s2 = actx.assign_map(s1);
|
||||
if (compare_signals(s2, s1, sig_analysis.raw_registers, sig_analysis.raw_cell_connected, *this_thread_cell_wires))
|
||||
candidates.insert(ctx, s1);
|
||||
}
|
||||
}
|
||||
});
|
||||
return candidates;
|
||||
}
|
||||
|
||||
void update_assign_map(SigMap& assign_map, ShardedVector<RTLIL::SigBit>& sigmap_canonical_candidates, ExactCellWires& cell_wires, const SigConnKinds& sig_analysis) {
|
||||
for (RTLIL::SigBit candidate : sigmap_canonical_candidates) {
|
||||
RTLIL::SigBit current_canonical = assign_map(candidate);
|
||||
// Resolves if two threads in build_candidates found different candidates
|
||||
// for the same set
|
||||
// TODO adds effort for single-threaded?
|
||||
if (compare_signals(current_canonical, candidate, sig_analysis.raw_registers, sig_analysis.raw_cell_connected, cell_wires))
|
||||
assign_map.add(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
struct DeferredUpdates {
|
||||
// Deferred updates to the assign_map
|
||||
ShardedVector<UpdateConnection> update_connections;
|
||||
// Wires we should remove init from
|
||||
ShardedVector<RTLIL::Wire*> initialized_wires;
|
||||
DeferredUpdates(ParallelDispatchThreadPool::Subpool &subpool) : update_connections(subpool), initialized_wires(subpool) {}
|
||||
};
|
||||
struct UsedSignals {
|
||||
// here, "connected" means "driven or driving something"
|
||||
// meanwhile, "used" means "driving something"
|
||||
// sigmapped
|
||||
ShardedSigPool connected;
|
||||
// pre-sigmapped
|
||||
ShardedSigPool raw_connected;
|
||||
// sigmapped
|
||||
ShardedSigPool used;
|
||||
|
||||
void clear(ParallelDispatchThreadPool::Subpool &subpool) {
|
||||
subpool.run([this](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
connected.clear(ctx);
|
||||
raw_connected.clear(ctx);
|
||||
used.clear(ctx);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analysis, const AnalysisContext& actx, CleanRunContext &clean_ctx) {
|
||||
DeferredUpdates deferred(actx.subpool);
|
||||
ShardedSigPool::Builder conn_builder(actx.subpool);
|
||||
ShardedSigPool::Builder raw_conn_builder(actx.subpool);
|
||||
ShardedSigPool::Builder used_builder(actx.subpool);
|
||||
|
||||
// gather the usage information for cells and update cell connections with the altered sigmap
|
||||
// also gather the usage information for ports, wires with `keep`
|
||||
// also gather init bits
|
||||
actx.subpool.run([&deferred, &conn_builder, &raw_conn_builder, &used_builder, &sig_analysis, &actx, &clean_ctx](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
// Parallel destruction of these sharded structures
|
||||
sig_analysis.clear(ctx);
|
||||
|
||||
for (int i : ctx.item_range(actx.mod->cells_size())) {
|
||||
RTLIL::Cell *cell = actx.mod->cell_at(i);
|
||||
for (const auto &[port, sig] : cell->connections_) {
|
||||
SigSpec spec = actx.assign_map(sig);
|
||||
if (spec != sig)
|
||||
deferred.update_connections.insert(ctx, {cell, port, spec});
|
||||
add_spec(raw_conn_builder, ctx, spec);
|
||||
add_spec(conn_builder, ctx, spec);
|
||||
if (!clean_ctx.ct_all.cell_output(cell->type, port))
|
||||
add_spec(used_builder, ctx, spec);
|
||||
}
|
||||
}
|
||||
for (int i : ctx.item_range(actx.mod->wires_size())) {
|
||||
RTLIL::Wire *wire = actx.mod->wire_at(i);
|
||||
if (wire->port_id > 0) {
|
||||
RTLIL::SigSpec sig = RTLIL::SigSpec(wire);
|
||||
add_spec(raw_conn_builder, ctx, sig);
|
||||
actx.assign_map.apply(sig);
|
||||
add_spec(conn_builder, ctx, sig);
|
||||
if (!wire->port_input)
|
||||
add_spec(used_builder, ctx, sig);
|
||||
}
|
||||
if (wire->get_bool_attribute(ID::keep)) {
|
||||
RTLIL::SigSpec sig = RTLIL::SigSpec(wire);
|
||||
actx.assign_map.apply(sig);
|
||||
add_spec(conn_builder, ctx, sig);
|
||||
}
|
||||
auto it = wire->attributes.find(ID::init);
|
||||
if (it != wire->attributes.end())
|
||||
deferred.initialized_wires.insert(ctx, wire);
|
||||
}
|
||||
});
|
||||
actx.subpool.run([&conn_builder, &raw_conn_builder, &used_builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
conn_builder.process(ctx);
|
||||
raw_conn_builder.process(ctx);
|
||||
used_builder.process(ctx);
|
||||
});
|
||||
used = {conn_builder, raw_conn_builder, used_builder};
|
||||
return deferred;
|
||||
}
|
||||
|
||||
struct WireDeleter {
|
||||
pool<RTLIL::Wire*> del_wires_queue;
|
||||
ShardedVector<RTLIL::Wire*> remove_init;
|
||||
ShardedVector<std::pair<RTLIL::Wire*, RTLIL::Const>> set_init;
|
||||
ShardedVector<RTLIL::SigSig> new_connections;
|
||||
ShardedVector<RTLIL::Wire*> remove_unused_bits;
|
||||
ShardedVector<std::pair<RTLIL::Wire*, RTLIL::Const>> set_unused_bits;
|
||||
WireDeleter(UsedSignals& used_sig_analysis, bool purge_mode, const AnalysisContext& actx) :
|
||||
remove_init(actx.subpool),
|
||||
set_init(actx.subpool),
|
||||
new_connections(actx.subpool),
|
||||
remove_unused_bits(actx.subpool),
|
||||
set_unused_bits(actx.subpool) {
|
||||
ShardedVector<RTLIL::Wire*> del_wires(actx.subpool);
|
||||
actx.subpool.run([&actx, purge_mode, &del_wires, &used_sig_analysis, this](const ParallelDispatchThreadPool::RunCtx &ctx) {
|
||||
for (int i : ctx.item_range(actx.mod->wires_size())) {
|
||||
RTLIL::Wire *wire = actx.mod->wire_at(i);
|
||||
SigSpec s1 = SigSpec(wire), s2 = actx.assign_map(s1);
|
||||
log_assert(GetSize(s1) == GetSize(s2));
|
||||
|
||||
Const initval;
|
||||
bool has_init_attribute = wire->attributes.count(ID::init);
|
||||
bool init_changed = false;
|
||||
if (has_init_attribute)
|
||||
initval = wire->attributes.at(ID::init);
|
||||
if (GetSize(initval) != GetSize(wire)) {
|
||||
initval.resize(GetSize(wire), State::Sx);
|
||||
init_changed = true;
|
||||
}
|
||||
|
||||
if (GetSize(wire) == 0) {
|
||||
// delete zero-width wires, unless they are module ports
|
||||
if (wire->port_id == 0)
|
||||
goto delete_this_wire;
|
||||
} else
|
||||
if (wire->port_id != 0 || wire->get_bool_attribute(ID::keep) || !initval.is_fully_undef()) {
|
||||
// do not delete anything with "keep" or module ports or initialized wires
|
||||
} else
|
||||
if (!purge_mode && check_public_name(wire->name) && (check_any(used_sig_analysis.raw_connected, s1) || check_any(used_sig_analysis.connected, s2) || s1 != s2)) {
|
||||
// do not get rid of public names unless in purge mode or if the wire is entirely unused, not even aliased
|
||||
} else
|
||||
if (!check_any(used_sig_analysis.raw_connected, s1)) {
|
||||
// delete wires that aren't used by anything directly
|
||||
goto delete_this_wire;
|
||||
}
|
||||
|
||||
if (0)
|
||||
{
|
||||
delete_this_wire:
|
||||
del_wires.insert(ctx, wire);
|
||||
}
|
||||
else
|
||||
{
|
||||
RTLIL::SigSig new_conn;
|
||||
for (int i = 0; i < GetSize(s1); i++)
|
||||
if (s1[i] != s2[i]) {
|
||||
if (s2[i] == State::Sx && (initval[i] == State::S0 || initval[i] == State::S1)) {
|
||||
s2[i] = initval[i];
|
||||
initval.set(i, State::Sx);
|
||||
init_changed = true;
|
||||
}
|
||||
new_conn.first.append(s1[i]);
|
||||
new_conn.second.append(s2[i]);
|
||||
}
|
||||
if (new_conn.first.size() > 0)
|
||||
new_connections.insert(ctx, std::move(new_conn));
|
||||
if (initval.is_fully_undef()) {
|
||||
if (has_init_attribute)
|
||||
remove_init.insert(ctx, wire);
|
||||
} else
|
||||
if (init_changed)
|
||||
set_init.insert(ctx, {wire, std::move(initval)});
|
||||
|
||||
std::string unused_bits;
|
||||
if (!check_all(used_sig_analysis.used, s2)) {
|
||||
for (int i = 0; i < GetSize(s2); i++) {
|
||||
if (s2[i].wire == NULL)
|
||||
continue;
|
||||
SigBit b = s2[i];
|
||||
if (used_sig_analysis.used.find({b, b.hash_top().yield()}) == nullptr) {
|
||||
if (!unused_bits.empty())
|
||||
unused_bits += " ";
|
||||
unused_bits += stringf("%d", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unused_bits.empty() || wire->port_id != 0) {
|
||||
if (wire->attributes.count(ID::unused_bits))
|
||||
remove_unused_bits.insert(ctx, wire);
|
||||
} else {
|
||||
RTLIL::Const unused_bits_const(std::move(unused_bits));
|
||||
if (wire->attributes.count(ID::unused_bits)) {
|
||||
RTLIL::Const &unused_bits_attr = wire->attributes.at(ID::unused_bits);
|
||||
if (unused_bits_attr != unused_bits_const)
|
||||
set_unused_bits.insert(ctx, {wire, std::move(unused_bits_const)});
|
||||
} else
|
||||
set_unused_bits.insert(ctx, {wire, std::move(unused_bits_const)});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
del_wires_queue.insert(del_wires.begin(), del_wires.end());
|
||||
}
|
||||
// Decide for each wire if we should be deleting it
|
||||
// and fix up attributes
|
||||
void commit_changes(RTLIL::Module* mod) {
|
||||
for (RTLIL::Wire *wire : remove_init)
|
||||
wire->attributes.erase(ID::init);
|
||||
for (auto &p : set_init)
|
||||
p.first->attributes[ID::init] = std::move(p.second);
|
||||
for (auto &conn : new_connections)
|
||||
mod->connect(std::move(conn));
|
||||
for (RTLIL::Wire *wire : remove_unused_bits)
|
||||
wire->attributes.erase(ID::unused_bits);
|
||||
for (auto &p : set_unused_bits)
|
||||
p.first->attributes[ID::unused_bits] = std::move(p.second);
|
||||
}
|
||||
int delete_wires(RTLIL::Module* mod, bool verbose) {
|
||||
int deleted_and_unreported = 0;
|
||||
for (auto wire : del_wires_queue) {
|
||||
if (ys_debug() || (check_public_name(wire->name) && verbose))
|
||||
log_debug(" removing unused non-port wire %s.\n", wire->name);
|
||||
else
|
||||
deleted_and_unreported++;
|
||||
}
|
||||
mod->remove(del_wires_queue);
|
||||
return deleted_and_unreported;
|
||||
}
|
||||
};
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
bool rmunused_module_signals(RTLIL::Module *module, ParallelDispatchThreadPool::Subpool &subpool, CleanRunContext &clean_ctx)
|
||||
{
|
||||
// Passing actx to function == function does parallel work
|
||||
// Not passing module as function argument == function does not modify module
|
||||
// The above sentence signals intent; it's not enforced due to constness laundering in wire_at / cell_at
|
||||
AnalysisContext actx(module, subpool);
|
||||
SigConnKinds conn_kinds(clean_ctx.flags.purge, actx, clean_ctx);
|
||||
|
||||
ExactCellWires cell_wires(conn_kinds.exact_cells, actx.assign_map);
|
||||
// Collect sigmap representative candidates as built in parallel
|
||||
// With parallel runs, this creates redundant candidates that have to resolve in update_assign_map
|
||||
ShardedVector<RTLIL::SigBit> new_sigmap_rep_candidates = build_candidates(cell_wires, conn_kinds, actx);
|
||||
|
||||
// Cache all the cell_wires results that we might possible need. This avoids the results
|
||||
// changing when we update `assign_map` below.
|
||||
cell_wires.cache_all(new_sigmap_rep_candidates);
|
||||
// Modify assign_map to reflect the connectivity we want, not the one we have
|
||||
// this changes representative selection in assign_map
|
||||
update_assign_map(actx.assign_map, new_sigmap_rep_candidates, cell_wires, conn_kinds);
|
||||
|
||||
// Remove all wire-wire connections
|
||||
module->connections_.clear();
|
||||
|
||||
UsedSignals used;
|
||||
DeferredUpdates deferred = analyse_connectivity(used, conn_kinds, actx, clean_ctx);
|
||||
fixup_cell_ports(deferred.update_connections);
|
||||
// Rip up and re-apply init attributes onto representative wires with x-bits
|
||||
// in place of unset init bits
|
||||
consume_inits(deferred.initialized_wires, actx.assign_map).apply_normalised_inits();
|
||||
|
||||
WireDeleter deleter(used, clean_ctx.flags.purge, actx);
|
||||
|
||||
used.clear(subpool);
|
||||
|
||||
deleter.commit_changes(module);
|
||||
int deleted_and_unreported = deleter.delete_wires(module, clean_ctx.flags.verbose);
|
||||
int deleted_total = GetSize(deleter.del_wires_queue);
|
||||
|
||||
clean_ctx.stats.count_rm_wires += deleted_total;
|
||||
|
||||
if (clean_ctx.flags.verbose && deleted_and_unreported)
|
||||
log_debug(" removed %d unused temporary wires.\n", deleted_and_unreported);
|
||||
|
||||
if (deleted_total)
|
||||
module->design->scratchpad_set_bool("opt.did_something", true);
|
||||
|
||||
return deleted_total != 0;
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
#include "kernel/register.h"
|
||||
#include "kernel/sigtools.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/utils.h"
|
||||
#include "kernel/log.h"
|
||||
#include <stdlib.h>
|
||||
|
|
@ -31,7 +32,7 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
|
||||
bool did_something;
|
||||
|
||||
void replace_undriven(RTLIL::Module *module, const CellTypes &ct)
|
||||
void replace_undriven(RTLIL::Module *module, const NewCellTypes &ct)
|
||||
{
|
||||
SigMap sigmap(module);
|
||||
SigPool driven_signals;
|
||||
|
|
@ -407,9 +408,6 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
|
|||
}
|
||||
}
|
||||
|
||||
CellTypes ct_memcells;
|
||||
ct_memcells.setup_stdcells_mem();
|
||||
|
||||
if (!noclkinv)
|
||||
for (auto cell : module->cells())
|
||||
if (design->selected(module, cell)) {
|
||||
|
|
@ -433,7 +431,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
|
|||
if (cell->type.in(ID($dffe), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce), ID($dffsre), ID($dlatch), ID($adlatch), ID($dlatchsr)))
|
||||
handle_polarity_inv(cell, ID::EN, ID::EN_POLARITY, assign_map, invert_map);
|
||||
|
||||
if (!ct_memcells.cell_known(cell->type))
|
||||
if (!StaticCellTypes::Compat::stdcells_mem(cell->type))
|
||||
continue;
|
||||
|
||||
handle_clkpol_celltype_swap(cell, "$_SR_N?_", "$_SR_P?_", ID::S, assign_map, invert_map);
|
||||
|
|
@ -2294,7 +2292,7 @@ struct OptExprPass : public Pass {
|
|||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
CellTypes ct(design);
|
||||
NewCellTypes ct(design);
|
||||
for (auto module : design->selected_modules())
|
||||
{
|
||||
log("Optimizing module %s.\n", log_id(module));
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue