From 358e83ba8002209b419eb191fd50db6173dae402 Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Thu, 26 Mar 2026 17:47:55 -0700 Subject: [PATCH] Big update from upstream --- .github/actions/setup-build-env/action.yml | 2 +- .github/workflows/codeql.yml | 6 +- .github/workflows/extra-builds.yml | 58 +- .github/workflows/prepare-docs.yml | 23 +- .github/workflows/source-vendor.yml | 11 +- .github/workflows/test-build.yml | 71 +- .github/workflows/test-compile.yml | 41 +- .github/workflows/test-sanitizers.yml | 42 +- .github/workflows/test-verific-cfg.yml | 109 ++ .github/workflows/test-verific.yml | 50 +- .github/workflows/update-flake-lock.yml | 25 - .github/workflows/version.yml | 34 - .github/workflows/wheels.yml | 6 +- .vscode/settings.json | 3 +- CHANGELOG | 17 +- Makefile | 34 +- abc | 2 +- backends/aiger/aiger.cc | 4 +- backends/aiger2/aiger.cc | 119 +- backends/blif/blif.cc | 4 +- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 4 +- backends/edif/edif.cc | 4 +- backends/intersynth/intersynth.cc | 4 +- backends/smt2/smt2.cc | 4 +- backends/smv/smv.cc | 4 +- docs/source/conf.py | 2 +- .../more_scripting/model_checking.rst | 35 +- frontends/aiger/aigerparse.cc | 22 +- frontends/ast/genrtlil.cc | 98 +- frontends/ast/simplify.cc | 194 +++ frontends/rtlil/rtlil_frontend.cc | 6 +- frontends/verilog/verilog_parser.y | 18 +- kernel/calc.cc | 2 +- kernel/celltypes.h | 38 +- kernel/consteval.h | 6 +- kernel/driver.cc | 9 +- kernel/drivertools.h | 6 +- kernel/ffinit.h | 60 +- kernel/log.cc | 14 +- kernel/log.h | 7 +- kernel/modtools.h | 38 +- kernel/newcelltypes.h | 651 +++++++++ kernel/register.cc | 17 +- kernel/rtlil.cc | 532 ++++---- kernel/rtlil.h | 18 +- kernel/satgen.cc | 22 +- kernel/satgen.h | 2 + kernel/tclapi.cc | 10 +- kernel/threading.cc | 90 ++ kernel/threading.h | 499 +++++++ kernel/utils.h | 18 + kernel/yosys.cc | 8 +- passes/cmds/check.cc | 2 +- passes/cmds/icell_liberty.cc | 3 +- passes/cmds/rename.cc | 15 +- passes/cmds/select.cc | 6 +- passes/cmds/setundef.cc | 41 +- passes/cmds/stat.cc | 7 +- passes/cmds/torder.cc | 2 +- passes/equiv/equiv.h | 68 + passes/equiv/equiv_induct.cc | 66 +- passes/equiv/equiv_simple.cc | 123 +- passes/memory/memory_libmap.cc | 15 +- passes/opt/Makefile.inc | 3 +- passes/opt/opt_clean.cc | 800 ----------- passes/opt/opt_clean/Makefile.inc | 10 + passes/opt/opt_clean/cells_all.cc | 373 +++++ passes/opt/opt_clean/cells_temp.cc | 104 ++ passes/opt/opt_clean/inits.cc | 137 ++ passes/opt/opt_clean/keep_cache.h | 167 +++ passes/opt/opt_clean/opt_clean.cc | 151 ++ passes/opt/opt_clean/opt_clean.h | 92 ++ passes/opt/opt_clean/wires.cc | 585 ++++++++ passes/opt/opt_expr.cc | 10 +- passes/opt/opt_lut_ins.cc | 13 +- passes/opt/share.cc | 128 +- passes/sat/sat.cc | 14 +- passes/sat/sim.cc | 47 +- passes/techmap/abc.cc | 24 +- passes/techmap/abc9.cc | 56 +- passes/techmap/abc9_exe.cc | 38 +- passes/techmap/abc9_ops.cc | 116 +- passes/techmap/constmap.cc | 2 + techlibs/analogdevices/Makefile.inc | 21 + techlibs/analogdevices/abc9_model.v | 39 + techlibs/analogdevices/arith_map.v | 173 +++ techlibs/analogdevices/brams.txt | 285 ++++ techlibs/analogdevices/brams_defs.vh | 561 ++++++++ techlibs/analogdevices/brams_map.v | 238 ++++ techlibs/analogdevices/cells_map.v | 364 +++++ techlibs/analogdevices/cells_sim.v | 1216 +++++++++++++++++ techlibs/analogdevices/dsp_map.v | 60 + techlibs/analogdevices/ff_map.v | 63 + techlibs/analogdevices/lut_map.v | 79 ++ techlibs/analogdevices/lutrams.txt | 20 + techlibs/analogdevices/lutrams_map.v | 139 ++ techlibs/analogdevices/mux_map.v | 74 + techlibs/analogdevices/retarget_map.v | 49 + techlibs/analogdevices/synth_analogdevices.cc | 516 +++++++ techlibs/common/synth.cc | 8 +- techlibs/fabulous/prims.v | 24 + techlibs/gowin/Makefile.inc | 1 + techlibs/gowin/brams.txt | 5 - techlibs/gowin/brams_map.v | 2 +- techlibs/gowin/brams_map_gw5a.v | 2 +- techlibs/gowin/cells_latch.v | 37 + techlibs/gowin/cells_sim.v | 95 ++ techlibs/gowin/synth_gowin.cc | 11 +- techlibs/quicklogic/synth_quicklogic.cc | 1 + tests/arch/analogdevices/.gitignore | 1 + tests/arch/analogdevices/add_sub.ys | 13 + tests/arch/analogdevices/adffs.ys | 47 + tests/arch/analogdevices/asym_ram_sdp.ys | 50 + .../analogdevices/asym_ram_sdp_read_wider.v | 73 + .../analogdevices/asym_ram_sdp_write_wider.v | 72 + tests/arch/analogdevices/attributes_test.ys | 39 + tests/arch/analogdevices/blockram.ys | 77 ++ tests/arch/analogdevices/bug1460.ys | 34 + tests/arch/analogdevices/bug1462.ys | 11 + tests/arch/analogdevices/bug1480.ys | 18 + tests/arch/analogdevices/bug1598.ys | 16 + tests/arch/analogdevices/counter.ys | 13 + tests/arch/analogdevices/dffs.ys | 41 + tests/arch/analogdevices/dsp_abc9.ys | 42 + tests/arch/analogdevices/fsm.ys | 20 + tests/arch/analogdevices/logic.ys | 11 + tests/arch/analogdevices/lutram.ys | 113 ++ tests/arch/analogdevices/mem_gen.py | 121 ++ tests/arch/analogdevices/mul.ys | 9 + tests/arch/analogdevices/mul_unsigned.v | 30 + tests/arch/analogdevices/mul_unsigned.ys | 10 + tests/arch/analogdevices/mux.ys | 50 + tests/arch/analogdevices/opt_lut_ins.ys | 26 + tests/arch/analogdevices/run-test.sh | 5 + tests/arch/analogdevices/shifter.ys | 10 + tests/arch/common/blockram.v | 80 ++ tests/arch/ecp5/opt_lut_ins.ys | 2 +- tests/arch/gowin/bug5688.ys | 31 + tests/arch/gowin/latches.ys | 25 + tests/arch/ice40/fsm.ys | 2 +- tests/arch/ice40/ice40_dsp_const.ys | 1 + tests/opt/opt_clean_init_const.ys | 9 + tests/opt/opt_hier.tcl | 2 +- tests/pyosys/test_dict.py | 9 - tests/svtypes/array_assign.sv | 87 ++ tests/techmap/abc9-nonbox-loop-with-box.ys | 19 + tests/techmap/abc9.ys | 3 +- tests/tools/rtlil-fuzz-grammar.json | 4 +- tests/unit/Makefile | 4 +- tests/unit/kernel/cellTypesTest.cc | 87 ++ tests/unit/kernel/modindexTest.cc | 47 + tests/unit/kernel/sigspecRemove2Test.cc | 333 +++++ tests/unit/kernel/threadingTest.cc | 442 ++++++ tests/various/const_shift_empty_arg.ys | 49 + tests/various/setundef_selection.ys | 32 + tests/various/setundef_selection_ff.il | 19 + tests/various/setundef_selection_undriven.v | 4 + tests/various/specify.ys | 4 +- tests/verilog/automatic_lifetime.ys | 104 ++ 159 files changed, 10928 insertions(+), 1827 deletions(-) create mode 100644 .github/workflows/test-verific-cfg.yml delete mode 100644 .github/workflows/update-flake-lock.yml delete mode 100644 .github/workflows/version.yml create mode 100644 kernel/newcelltypes.h create mode 100644 passes/equiv/equiv.h delete mode 100644 passes/opt/opt_clean.cc create mode 100644 passes/opt/opt_clean/Makefile.inc create mode 100644 passes/opt/opt_clean/cells_all.cc create mode 100644 passes/opt/opt_clean/cells_temp.cc create mode 100644 passes/opt/opt_clean/inits.cc create mode 100644 passes/opt/opt_clean/keep_cache.h create mode 100644 passes/opt/opt_clean/opt_clean.cc create mode 100644 passes/opt/opt_clean/opt_clean.h create mode 100644 passes/opt/opt_clean/wires.cc create mode 100644 techlibs/analogdevices/Makefile.inc create mode 100644 techlibs/analogdevices/abc9_model.v create mode 100644 techlibs/analogdevices/arith_map.v create mode 100644 techlibs/analogdevices/brams.txt create mode 100644 techlibs/analogdevices/brams_defs.vh create mode 100644 techlibs/analogdevices/brams_map.v create mode 100644 techlibs/analogdevices/cells_map.v create mode 100644 techlibs/analogdevices/cells_sim.v create mode 100644 techlibs/analogdevices/dsp_map.v create mode 100644 techlibs/analogdevices/ff_map.v create mode 100644 techlibs/analogdevices/lut_map.v create mode 100644 techlibs/analogdevices/lutrams.txt create mode 100644 techlibs/analogdevices/lutrams_map.v create mode 100644 techlibs/analogdevices/mux_map.v create mode 100644 techlibs/analogdevices/retarget_map.v create mode 100644 techlibs/analogdevices/synth_analogdevices.cc create mode 100644 techlibs/gowin/cells_latch.v create mode 100644 tests/arch/analogdevices/.gitignore create mode 100644 tests/arch/analogdevices/add_sub.ys create mode 100644 tests/arch/analogdevices/adffs.ys create mode 100644 tests/arch/analogdevices/asym_ram_sdp.ys create mode 100644 tests/arch/analogdevices/asym_ram_sdp_read_wider.v create mode 100644 tests/arch/analogdevices/asym_ram_sdp_write_wider.v create mode 100644 tests/arch/analogdevices/attributes_test.ys create mode 100644 tests/arch/analogdevices/blockram.ys create mode 100644 tests/arch/analogdevices/bug1460.ys create mode 100644 tests/arch/analogdevices/bug1462.ys create mode 100644 tests/arch/analogdevices/bug1480.ys create mode 100644 tests/arch/analogdevices/bug1598.ys create mode 100644 tests/arch/analogdevices/counter.ys create mode 100644 tests/arch/analogdevices/dffs.ys create mode 100644 tests/arch/analogdevices/dsp_abc9.ys create mode 100644 tests/arch/analogdevices/fsm.ys create mode 100644 tests/arch/analogdevices/logic.ys create mode 100644 tests/arch/analogdevices/lutram.ys create mode 100644 tests/arch/analogdevices/mem_gen.py create mode 100644 tests/arch/analogdevices/mul.ys create mode 100644 tests/arch/analogdevices/mul_unsigned.v create mode 100644 tests/arch/analogdevices/mul_unsigned.ys create mode 100644 tests/arch/analogdevices/mux.ys create mode 100644 tests/arch/analogdevices/opt_lut_ins.ys create mode 100755 tests/arch/analogdevices/run-test.sh create mode 100644 tests/arch/analogdevices/shifter.ys create mode 100644 tests/arch/gowin/bug5688.ys create mode 100644 tests/arch/gowin/latches.ys create mode 100644 tests/opt/opt_clean_init_const.ys create mode 100644 tests/svtypes/array_assign.sv create mode 100644 tests/techmap/abc9-nonbox-loop-with-box.ys create mode 100644 tests/unit/kernel/cellTypesTest.cc create mode 100644 tests/unit/kernel/modindexTest.cc create mode 100644 tests/unit/kernel/sigspecRemove2Test.cc create mode 100644 tests/unit/kernel/threadingTest.cc create mode 100644 tests/various/const_shift_empty_arg.ys create mode 100644 tests/various/setundef_selection.ys create mode 100644 tests/various/setundef_selection_ff.il create mode 100644 tests/various/setundef_selection_undriven.v create mode 100644 tests/verilog/automatic_lifetime.ys diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index c6ea64e2e21..7c1f7f60c63 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -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: gawk git make python3 bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libnsl-dev libdwarf-dev libelf-dev elfutils libdw-dev ccache libgtest-dev + packages: gawk git make python3 bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev libnsl-dev libdwarf-dev libelf-dev elfutils libdw-dev ccache libgtest-dev libgmock-dev version: ${{ inputs.runs-on }}-buildys - name: Linux docs dependencies diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 847c9e1237c..be90cecd8d5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,7 +12,7 @@ jobs: permissions: write-all steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true persist-credentials: false @@ -28,7 +28,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 @@ -40,4 +40,4 @@ jobs: run: make yosys -j6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/extra-builds.yml b/.github/workflows/extra-builds.yml index 5d0ac72d48d..d6bf5226c7b 100644 --- a/.github/workflows/extra-builds.yml +++ b/.github/workflows/extra-builds.yml @@ -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,10 +70,10 @@ 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 @@ -103,6 +108,7 @@ jobs: ENABLE_ZLIB := 0 CXXFLAGS += -I$(pwd)/flex-prefix/include + LINKFLAGS += -Wl,-z,stack-size=8388608 -Wl,--stack-first -Wl,--strip-all END make -C build -f ../Makefile CXX=clang -j$(nproc) @@ -110,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 @@ -125,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" diff --git a/.github/workflows/prepare-docs.yml b/.github/workflows/prepare-docs.yml index e3d91794298..e2ebe9e6925 100644 --- a/.github/workflows/prepare-docs.yml +++ b/.github/workflows/prepare-docs.yml @@ -1,17 +1,24 @@ 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/') }} steps: - id: skip_check + if: ${{ github.event_name != 'merge_group' }} uses: fkirc/skip-duplicate-actions@v5 with: paths_ignore: '["**/README.md"]' @@ -22,11 +29,19 @@ jobs: - 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 +90,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 }} diff --git a/.github/workflows/source-vendor.yml b/.github/workflows/source-vendor.yml index 03750b05b31..cdab2300805 100644 --- a/.github/workflows/source-vendor.yml +++ b/.github/workflows/source-vendor.yml @@ -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 @@ -28,7 +33,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 diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 331c062d548..ac7f58c026e 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -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 ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} @@ -88,7 +102,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 @@ -107,7 +121,7 @@ jobs: fail-fast: false steps: - name: Checkout Yosys - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} @@ -120,7 +134,7 @@ jobs: get-iverilog: true - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: build-${{ matrix.os }} @@ -159,7 +173,9 @@ jobs: os: [ubuntu-latest] steps: - name: Checkout Yosys - uses: actions/checkout@v4 + uses: actions/checkout@v5 + with: + persist-credentials: false - name: Setup environment uses: ./.github/actions/setup-build-env @@ -167,7 +183,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 }} @@ -197,7 +213,7 @@ jobs: fail-fast: false steps: - name: Checkout Yosys - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} @@ -210,7 +226,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 }} @@ -235,7 +251,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] @@ -274,3 +290,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" diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index 000d1c4009a..9c75784bf7a 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -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" diff --git a/.github/workflows/test-sanitizers.yml b/.github/workflows/test-sanitizers.yml index 22c725fac59..98d7f9f6702 100644 --- a/.github/workflows/test-sanitizers.yml +++ b/.github/workflows/test-sanitizers.yml @@ -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 @@ -74,3 +83,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" \ No newline at end of file diff --git a/.github/workflows/test-verific-cfg.yml b/.github/workflows/test-verific-cfg.yml new file mode 100644 index 00000000000..232ca6bd762 --- /dev/null +++ b/.github/workflows/test-verific-cfg.yml @@ -0,0 +1,109 @@ +name: Build various Verific configurations + +on: + workflow_dispatch: + +jobs: + test-verific-cfg: + if: github.repository_owner == 'YosysHQ' + runs-on: [self-hosted, linux, x64, fast] + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + persist-credentials: false + submodules: true + - name: Runtime environment + run: | + echo "procs=$(nproc)" >> $GITHUB_ENV + + - name: verific [SV] + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_VHDL := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_HIER_TREE := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + make -j$procs + + - name: verific [VHDL] + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_SYSTEMVERILOG := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_HIER_TREE := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + make -j$procs + + - name: verific [SV + VHDL] + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_HIER_TREE := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + make -j$procs + + - name: verific [SV + HIER] + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_VHDL := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + make -j$procs + + - name: verific [VHDL + HIER] + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_SYSTEMVERILOG := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + make -j$procs + + - name: verific [SV + VHDL + HIER] + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + make -j$procs + + - name: verific [SV + VHDL + HIER + EDIF + LIBERTY] + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + make -j$procs diff --git a/.github/workflows/test-verific.yml b/.github/workflows/test-verific.yml index cd2545cc8a4..65efca79fc9 100644 --- a/.github/workflows/test-verific.yml +++ b/.github/workflows/test-verific.yml @@ -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 @@ -76,13 +81,13 @@ jobs: cd tests/svtypes && bash run-test.sh - 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" diff --git a/.github/workflows/update-flake-lock.yml b/.github/workflows/update-flake-lock.yml deleted file mode 100644 index b32498baf20..00000000000 --- a/.github/workflows/update-flake-lock.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: update-flake-lock -on: - workflow_dispatch: # allows manual triggering - schedule: - - cron: '0 0 * * 0' # runs weekly on Sunday at 00:00 - -jobs: - lockfile: - if: github.repository == 'YosysHQ/Yosys' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@main - - name: Update flake.lock - uses: DeterminateSystems/update-flake-lock@main - with: - token: ${{CI_CREATE_PR_TOKEN}} - pr-title: "Update flake.lock" # Title of PR to be created - pr-labels: | # Labels to be set on the PR - dependencies - automated diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml deleted file mode 100644 index 78d34db4673..00000000000 --- a/.github/workflows/version.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Bump version - -on: - workflow_dispatch: - schedule: - - cron: '0 0 * * *' - -jobs: - bump-version: - if: github.repository == 'YosysHQ/Yosys' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: true - persist-credentials: false - - name: Take last commit - id: log - run: echo "message=$(git log --no-merges -1 --oneline)" >> $GITHUB_OUTPUT - - name: Bump version - if: ${{ !contains(steps.log.outputs.message, 'Bump version') }} - run: | - make bumpversion - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add Makefile - git commit -m "Bump version" - - name: Push changes # push the output folder to your repo - if: ${{ !contains(steps.log.outputs.message, 'Bump version') }} - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8e055f526b5..aef87db28f2 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -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,7 +108,7 @@ 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 @@ -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-* diff --git a/.vscode/settings.json b/.vscode/settings.json index 25ffb81ba04..6a30c737f77 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,6 +71,7 @@ "variant": "cpp", "algorithm": "cpp", "*.inc": "cpp", - "tuple": "cpp" + "tuple": "cpp", + "typeindex": "cpp" } } diff --git a/CHANGELOG b/CHANGELOG index e345a851483..372a59d58fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,9 +2,24 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.62 .. Yosys 0.63-dev +Yosys 0.63 .. Yosys 0.64-dev -------------------------- +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 diff --git a/Makefile b/Makefile index ec93a0bb62d..cc274d24371 100644 --- a/Makefile +++ b/Makefile @@ -176,7 +176,19 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.62+55 +YOSYS_VER := 0.63 + +ifneq (, $(shell command -v git 2>/dev/null)) +ifneq (, $(shell git rev-parse --git-dir 2>/dev/null)) + GIT_COMMIT_COUNT := $(or $(shell git rev-list --count v$(YOSYS_VER)..HEAD 2>/dev/null),0) + ifneq ($(GIT_COMMIT_COUNT),0) + YOSYS_VER := $(YOSYS_VER)+$(GIT_COMMIT_COUNT) + endif +else + YOSYS_VER := $(YOSYS_VER)+post +endif +endif + YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'.' -f3) @@ -634,6 +646,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)) @@ -846,6 +859,24 @@ endif $(Q) mkdir -p $(dir $@) $(P) $(CXX) -o $@ -c $(CPPFLAGS) $(CXXFLAGS) $< +YOSYS_REPO := +ifneq (, $(shell command -v git 2>/dev/null)) +ifneq (, $(shell git rev-parse --git-dir 2>/dev/null)) + GIT_REMOTE := $(strip $(shell git config --get remote.origin.url 2>/dev/null | $(AWK) '{print tolower($$0)}')) + ifneq ($(strip $(GIT_REMOTE)),) + YOSYS_REPO := $(strip $(shell echo $(GIT_REMOTE) | $(AWK) -F '[:/]' '{gsub(/\.git$$/, "", $$NF); printf "%s/%s", $$(NF-1), $$NF}')) + endif + ifeq ($(strip $(YOSYS_REPO)),yosyshq/yosys) + YOSYS_REPO := + endif + GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) + ifeq ($(filter main HEAD release/v%,$(GIT_BRANCH)),) + YOSYS_REPO := $(YOSYS_REPO) at $(GIT_BRANCH) + endif + YOSYS_REPO := $(strip $(YOSYS_REPO)) +endif +endif + YOSYS_GIT_STR := $(GIT_REV)$(GIT_DIRTY) YOSYS_COMPILER := $(notdir $(CXX)) $(shell $(CXX) --version | tr ' ()' '\n' | grep '^[0-9]' | head -n1) $(filter -f% -m% -O% -DNDEBUG,$(CXXFLAGS)) YOSYS_VER_STR := Preqorsor $(YOSYS_VER) (git sha1 $(YOSYS_GIT_STR), $(YOSYS_COMPILER)) @@ -942,6 +973,7 @@ endif # Tests that generate .mk with tests/gen-tests-makefile.sh MK_TEST_DIRS = +MK_TEST_DIRS += tests/arch/analogdevices MK_TEST_DIRS += tests/arch/anlogic MK_TEST_DIRS += tests/arch/ecp5 MK_TEST_DIRS += tests/arch/efinix diff --git a/abc b/abc index bb9bc209ff2..0bec3bfb316 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit bb9bc209ff23e6690865935a70bcc9294101c5ce +Subproject commit 0bec3bfb316ca87b3ba97836759f04324a3e8f28 diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc index 95f4c19e286..0c49e84b8c8 100644 --- a/backends/aiger/aiger.cc +++ b/backends/aiger/aiger.cc @@ -930,7 +930,9 @@ struct AigerBackend : public Backend { log(" make indexes zero based, enable using map files with smt solvers.\n"); log("\n"); log(" -ywmap \n"); - log(" write a map file for conversion to and from yosys witness traces.\n"); + log(" write a map file for conversion to and from yosys witness traces,\n"); + log(" also allows for mapping AIGER bad-state properties and invariant\n"); + log(" constraints back to individual formal properties by name.\n"); log("\n"); log(" -I, -O, -B, -L\n"); log(" If the design contains no input/output/assert/flip-flop then create one\n"); diff --git a/backends/aiger2/aiger.cc b/backends/aiger2/aiger.cc index 3c22ad210bd..7d193c29aa2 100644 --- a/backends/aiger2/aiger.cc +++ b/backends/aiger2/aiger.cc @@ -23,7 +23,7 @@ // - zero-width operands #include "kernel/register.h" -#include "kernel/celltypes.h" +#include "kernel/newcelltypes.h" #include "kernel/rtlil.h" USING_YOSYS_NAMESPACE @@ -45,8 +45,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 struct Index { @@ -92,7 +106,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 +118,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 @@ -269,7 +283,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 @@ -579,7 +593,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); @@ -916,15 +930,15 @@ struct XAigerWriter : AigerWriter { std::vector pos; std::vector 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 driven_by_opaque_box; @@ -1331,41 +1345,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 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 +1446,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 \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 \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"); } diff --git a/backends/blif/blif.cc b/backends/blif/blif.cc index 85db8679e45..cc339bcbc86 100644 --- a/backends/blif/blif.cc +++ b/backends/blif/blif.cc @@ -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 @@ -61,7 +61,7 @@ struct BlifDumper RTLIL::Module *module; RTLIL::Design *design; BlifDumperConfig *config; - CellTypes ct; + NewCellTypes ct; SigMap sigmap; dict init_bits; diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index fbbe2373f63..521cce4b0f3 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -614,7 +614,7 @@ struct value : public expr_base> { int64_t divisor_shift = divisor.ctlz() - dividend.ctlz(); assert(divisor_shift >= 0); divisor = divisor.shl(value{(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{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)) diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc index 61d6ee254b8..145477b6bf1 100644 --- a/backends/edif/edif.cc +++ b/backends/edif/edif.cc @@ -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 @@ -138,7 +138,7 @@ struct EdifBackend : public Backend { bool lsbidx = false; std::map> lib_cell_ports; bool nogndvcc = false, gndvccy = false, keepmode = false; - CellTypes ct(design); + NewCellTypes ct(design); EdifNames edif_names; size_t argidx; diff --git a/backends/intersynth/intersynth.cc b/backends/intersynth/intersynth.cc index 78eab17da83..ad16d50abc1 100644 --- a/backends/intersynth/intersynth.cc +++ b/backends/intersynth/intersynth.cc @@ -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 @@ -117,7 +117,7 @@ struct IntersynthBackend : public Backend { std::set conntypes_code, celltypes_code; std::string netlists_code; - CellTypes ct(design); + NewCellTypes ct(design); for (auto lib : libs) ct.setup_design(lib); diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index d80622029a6..9d0ebc2aa65 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -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; diff --git a/backends/smv/smv.cc b/backends/smv/smv.cc index a6ccbf27f0a..acefad0605b 100644 --- a/backends/smv/smv.cc +++ b/backends/smv/smv.cc @@ -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 @@ -29,7 +29,7 @@ PRIVATE_NAMESPACE_BEGIN struct SmvWorker { - CellTypes ct; + NewCellTypes ct; SigMap sigmap; RTLIL::Module *module; std::ostream &f; diff --git a/docs/source/conf.py b/docs/source/conf.py index a7da22d972b..458040db05b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,7 +6,7 @@ project = 'YosysHQ Yosys' author = 'YosysHQ GmbH' copyright ='2026 YosysHQ GmbH' -yosys_ver = "0.62" +yosys_ver = "0.63" # select HTML theme html_theme = 'furo-ys' diff --git a/docs/source/using_yosys/more_scripting/model_checking.rst b/docs/source/using_yosys/more_scripting/model_checking.rst index 799c99b6f16..cbf5dc7b01e 100644 --- a/docs/source/using_yosys/more_scripting/model_checking.rst +++ b/docs/source/using_yosys/more_scripting/model_checking.rst @@ -3,9 +3,9 @@ Symbolic model checking .. todo:: check text context -.. note:: - - While it is possible to perform model checking directly in Yosys, it +.. note:: + + While it is possible to perform model checking directly in Yosys, it is highly recommended to use SBY or EQY for formal hardware verification. Symbolic Model Checking (SMC) is used to formally prove that a circuit has (or @@ -117,3 +117,32 @@ Result with fixed :file:`axis_master.v`: Solving problem with 159144 variables and 441626 clauses.. SAT proof finished - no model found: SUCCESS! + +Witness framework and per-property tracking +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using AIGER-based formal verification flows (such as the ``abc`` engine in +SBY), Yosys provides infrastructure for tracking individual formal +properties through the verification pipeline. + +The `rename -witness` pass assigns public names to all cells that participate in +the witness framework: + +- Witness signal cells: `$anyconst`, `$anyseq`, `$anyinit`, + `$allconst`, `$allseq` +- Formal property cells: `$assert`, `$assume`, `$cover`, `$live`, + `$fair`, `$check` + +These public names allow downstream tools to refer to individual properties by +their hierarchical path rather than by anonymous internal identifiers. + +The `write_aiger -ywmap` option generates a map file for conversion to and from +Yosys witness traces, and also allows for mapping AIGER bad-state properties and +invariant constraints back to individual formal properties by name. This enables +features like per-property pass/fail reporting (e.g. ``abc pdr`` with +``--keep-going`` mode). + +The `write_smt2` backend similarly uses the public witness names when emitting +SMT2 comments. Cells whose ``hdlname`` attribute contains the ``_witness_`` +marker are treated as having private names for comment purposes, keeping solver +output clean. diff --git a/frontends/aiger/aigerparse.cc b/frontends/aiger/aigerparse.cc index 4df37c0cda9..9931ef78fad 100644 --- a/frontends/aiger/aigerparse.cc +++ b/frontends/aiger/aigerparse.cc @@ -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 @@ -286,10 +286,15 @@ void AigerReader::parse_aiger() RTLIL::IdString escaped_s = stringf("\\%s", s); RTLIL::Wire* wire; - if (c == 'i') wire = inputs[l1]; - else if (c == 'l') wire = latches[l1]; - else if (c == 'o') { + if (c == 'i') { + log_assert(l1 < inputs.size()); + wire = inputs[l1]; + } else if (c == 'l') { + log_assert(l1 < latches.size()); + wire = latches[l1]; + } else if (c == 'o') { wire = module->wire(escaped_s); + log_assert(l1 < outputs.size()); if (wire) { // Could have been renamed by a latch module->swap_names(wire, outputs[l1]); @@ -297,9 +302,9 @@ void AigerReader::parse_aiger() goto next; } wire = outputs[l1]; - } - else if (c == 'b') wire = bad_properties[l1]; - else log_abort(); + } else if (c == 'b') { + wire = bad_properties[l1]; + } else log_abort(); module->rename(wire, escaped_s); } @@ -652,6 +657,9 @@ void AigerReader::parse_aiger_binary() unsigned l1, l2, l3; std::string line; + if (M != I + L + A) + log_error("Binary AIGER input is malformed: maximum variable index M is %u, but number of inputs, latches and AND gates adds up to %u.\n", M, I + L + A); + // Parse inputs int digits = decimal_digits(I); for (unsigned i = 1; i <= I; ++i) { diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index d9eb51a9ccc..5e0356fd305 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -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 current_case_assigned_bits; + ProcessGenerator(std::unique_ptr 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 &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 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 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 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; diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index a5b8c77ac83..f314ff3d5d4 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -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 array_indices_from_position(AstNode *mem, const std::vector &position) +{ + int num_dims = mem->unpacked_dimensions; + log_assert(GetSize(position) == num_dims); + + std::vector 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&)> callback) +{ + int num_dims = mem->unpacked_dimensions; + if (num_dims == 0) { + callback({}); + return; + } + + std::vector position(num_dims, 0); + std::vector 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 id, const std::vector& indices) { + if (num_dims == 1) { + // Single dimension: use AST_RANGE + id->children.push_back(std::make_unique(location, AST_RANGE, + mkconst_int(location, indices[0], true))); + } else { + // Multiple dimensions: use AST_MULTIRANGE + auto multirange = std::make_unique(location, AST_MULTIRANGE); + for (int idx : indices) { + multirange->children.push_back(std::make_unique(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> assignments; + + foreach_array_position(lhs_mem, [&](const std::vector& 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 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(location, AST_TERNARY, + cond->clone(), std::move(true_idx), std::move(false_idx)); + } + + auto assign = std::make_unique(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(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 && diff --git a/frontends/rtlil/rtlil_frontend.cc b/frontends/rtlil/rtlil_frontend.cc index a1412d98325..7e2ec546026 100644 --- a/frontends/rtlil/rtlil_frontend.cc +++ b/frontends/rtlil/rtlil_frontend.cc @@ -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); diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index 684727d5b64..148a6cc63c9 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -84,7 +84,7 @@ int current_function_or_task_port_id; std::vector 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(@$, AST_WIRE); extra->current_wire_rand = false; extra->current_wire_const = false; } + { extra->astbuf3 = std::make_unique(@$, 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(@$, 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(@$, 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; diff --git a/kernel/calc.cc b/kernel/calc.cc index 9b0885db9ac..84ac100ab59 100644 --- a/kernel/calc.cc +++ b/kernel/calc.cc @@ -291,7 +291,7 @@ static RTLIL::Const const_shift_worker(const RTLIL::Const &arg1, const RTLIL::Co if (pos < 0) result.set(i, vacant_bits); else if (pos >= BigInteger(GetSize(arg1))) - result.set(i, sign_ext ? arg1.back() : vacant_bits); + result.set(i, sign_ext && !arg1.empty() ? arg1.back() : vacant_bits); else result.set(i, arg1[pos.toInt()]); } diff --git a/kernel/celltypes.h b/kernel/celltypes.h index 0cdce6b4e81..b51d20952bd 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -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($assert), {ID::A, ID::EN}, pool(), true); - setup_type(ID($assume), {ID::A, ID::EN}, pool(), true); - setup_type(ID($live), {ID::A, ID::EN}, pool(), true); - setup_type(ID($fair), {ID::A, ID::EN}, pool(), true); - setup_type(ID($cover), {ID::A, ID::EN}, pool(), true); - setup_type(ID($initstate), pool(), {ID::Y}, true); - setup_type(ID($anyconst), pool(), {ID::Y}, true); - setup_type(ID($anyseq), pool(), {ID::Y}, true); - setup_type(ID($allconst), pool(), {ID::Y}, true); - setup_type(ID($allseq), pool(), {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(), true); - setup_type(ID($specify3), {ID::EN, ID::SRC, ID::DST, ID::DAT}, pool(), true); - setup_type(ID($specrule), {ID::EN_SRC, ID::EN_DST, ID::SRC, ID::DST}, pool(), true); + setup_type(ID($tribuf), {ID::A, ID::EN}, {ID::Y}); + + setup_type(ID($assert), {ID::A, ID::EN}, pool()); + setup_type(ID($assume), {ID::A, ID::EN}, pool()); + setup_type(ID($live), {ID::A, ID::EN}, pool()); + setup_type(ID($fair), {ID::A, ID::EN}, pool()); + setup_type(ID($cover), {ID::A, ID::EN}, pool()); + setup_type(ID($initstate), pool(), {ID::Y}); + setup_type(ID($anyconst), pool(), {ID::Y}); + setup_type(ID($anyseq), pool(), {ID::Y}); + setup_type(ID($allconst), pool(), {ID::Y}); + setup_type(ID($allseq), pool(), {ID::Y}); + setup_type(ID($equiv), {ID::A, ID::B}, {ID::Y}); + setup_type(ID($specify2), {ID::EN, ID::SRC, ID::DST}, pool()); + setup_type(ID($specify3), {ID::EN, ID::SRC, ID::DST, ID::DAT}, pool()); + setup_type(ID($specrule), {ID::EN_SRC, ID::EN_DST, ID::SRC, ID::DST}, pool()); setup_type(ID($print), {ID::EN, ID::ARGS, ID::TRG}, pool()); setup_type(ID($check), {ID::A, ID::EN, ID::ARGS, ID::TRG}, pool()); setup_type(ID($set_tag), {ID::A, ID::SET, ID::CLR}, {ID::Y}); @@ -196,7 +197,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() @@ -549,9 +550,6 @@ struct CellTypes } }; -// initialized by yosys_setup() -extern CellTypes yosys_celltypes; - YOSYS_NAMESPACE_END #endif diff --git a/kernel/consteval.h b/kernel/consteval.h index ca04d722f25..d00ae8f33f0 100644 --- a/kernel/consteval.h +++ b/kernel/consteval.h @@ -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)) diff --git a/kernel/driver.cc b/kernel/driver.cc index fa78bad5992..d76841909bf 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -23,6 +23,7 @@ #define CXXOPTS_VECTOR_DELIMITER '\0' #include "libs/cxxopts/include/cxxopts.hpp" #include +#include #ifdef YOSYS_ENABLE_READLINE # include @@ -195,6 +196,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 vlog_defines; @@ -700,8 +702,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( + 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()); diff --git a/kernel/drivertools.h b/kernel/drivertools.h index ba7b2aa8485..28d3be91eb0 100644 --- a/kernel/drivertools.h +++ b/kernel/drivertools.h @@ -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 @@ struct DriveSpec 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: diff --git a/kernel/ffinit.h b/kernel/ffinit.h index 920fba3079f..8b4758f6092 100644 --- a/kernel/ffinit.h +++ b/kernel/ffinit.h @@ -22,6 +22,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" +#include "kernel/threading.h" YOSYS_NAMESPACE_BEGIN @@ -35,32 +36,53 @@ 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) - continue; + SigBit bit = wirebits[i]; + State val = initval[i]; - SigSpec wirebits = (*sigmap)(wire); - Const initval = wire->attributes.at(ID::init); + if (val != State::S0 && val != State::S1 && bit.wire != nullptr) + continue; - for (int i = 0; i < GetSize(wirebits) && i < GetSize(initval); i++) - { - SigBit bit = wirebits[i]; - State val = initval[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; + } - if (val != State::S0 && val != State::S1 && bit.wire != nullptr) - 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; - } + void set_parallel(const SigMapView *sigmap_, ParallelDispatchThreadPool &thread_pool, RTLIL::Module *module) + { + sigmap = sigmap_; + initbits.clear(); - initbits[bit] = std::make_pair(val,SigBit(wire,i)); + const RTLIL::Module *const_module = module; + ParallelDispatchThreadPool::Subpool subpool(thread_pool, ThreadPool::work_pool_size(0, module->wires_size(), 1000)); + ShardedVector 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 diff --git a/kernel/log.cc b/kernel/log.cc index 018a1908196..b114f1eafc0 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -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 = "\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(); diff --git a/kernel/log.h b/kernel/log.h index 8d7d1576f94..673c27ce281 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -206,12 +206,7 @@ template log_formatted_cmd_error(fmt.format(args...)); } -static inline void log_suppressed() { - if (log_debug_suppressed && !log_make_debug) { - log("\n", log_debug_suppressed); - log_debug_suppressed = 0; - } -} +void log_suppressed(); struct LogMakeDebugHdl { bool status = false; diff --git a/kernel/modtools.h b/kernel/modtools.h index a081c75568b..285f22b2a89 100644 --- a/kernel/modtools.h +++ b/kernel/modtools.h @@ -23,11 +23,28 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/celltypes.h" +#include "kernel/newcelltypes.h" YOSYS_NAMESPACE_BEGIN struct ModIndex : public RTLIL::Monitor { + struct PointerOrderedSigBit : public RTLIL::SigBit { + PointerOrderedSigBit(SigBit s) { + wire = s.wire; + if (wire) + offset = s.offset; + else + data = s.data; + } + inline bool operator<(const RTLIL::SigBit &other) const { + if (wire == other.wire) + return wire ? (offset < other.offset) : (data < other.data); + if (wire != nullptr && other.wire != nullptr) + return wire < other.wire; // look here + return (wire != nullptr) < (other.wire != nullptr); + } + }; struct PortInfo { RTLIL::Cell* cell; RTLIL::IdString port; @@ -77,7 +94,7 @@ struct ModIndex : public RTLIL::Monitor SigMap sigmap; RTLIL::Module *module; - std::map database; + std::map database; int auto_reload_counter; bool auto_reload_module; @@ -94,8 +111,11 @@ struct ModIndex : public RTLIL::Monitor { for (int i = 0; i < GetSize(sig); i++) { RTLIL::SigBit bit = sigmap(sig[i]); - if (bit.wire) + if (bit.wire) { database[bit].ports.erase(PortInfo(cell, port, i)); + if (!database[bit].is_input && !database[bit].is_output && database[bit].ports.empty()) + database.erase(bit); + } } } @@ -132,11 +152,11 @@ struct ModIndex : public RTLIL::Monitor } } - void check() + bool ok() { #ifndef NDEBUG if (auto_reload_module) - return; + return true; for (auto it : database) log_assert(it.first == sigmap(it.first)); @@ -156,9 +176,15 @@ struct ModIndex : public RTLIL::Monitor else if (!(it.second == database_bak.at(it.first))) log("ModuleIndex::check(): Different content for database[%s].\n", log_signal(it.first)); - log_assert(database == database_bak); + return false; } #endif + return true; + } + + void check() + { + log_assert(ok()); } void notify_connect(RTLIL::Cell *cell, RTLIL::IdString port, const RTLIL::SigSpec &old_sig, const RTLIL::SigSpec &sig) override @@ -332,7 +358,7 @@ struct ModWalker RTLIL::Design *design; RTLIL::Module *module; - CellTypes ct; + NewCellTypes ct; SigMap sigmap; dict> signal_drivers; diff --git a/kernel/newcelltypes.h b/kernel/newcelltypes.h new file mode 100644 index 00000000000..bb14293a308 --- /dev/null +++ b/kernel/newcelltypes.h @@ -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 ports{}; + size_t count = 0; + constexpr PortList() = default; + constexpr PortList(std::initializer_list 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 cells{}; + size_t count = 0; + + constexpr void setup_type(RTLIL::IdString type, std::initializer_list inputs, std::initializer_list 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::EN_SRC, ID::EN_DST, 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 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 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 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 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 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(id.hash_top().yield()); + } + }; + StaticCellTypes::Categories::Category static_cell_types = StaticCellTypes::categories.empty; + std::unordered_map 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 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 &inputs, const pool &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 diff --git a/kernel/register.cc b/kernel/register.cc index abde8f47e32..cba6d5f99f4 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -22,6 +22,7 @@ #include "kernel/json.h" #include "kernel/gzip.h" #include "kernel/log_help.h" +#include "kernel/newcelltypes.h" #include #include @@ -975,16 +976,18 @@ struct HelpPass : public Pass { json.entry("generator", yosys_maybe_version()); dict> groups; - dict> cells; + dict> 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(cell_help, it.second); + auto cell_pair = pair(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 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) { diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 622c3d93c52..96b432d098c 100644 --- a/kernel/rtlil.cc +++ b/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" @@ -144,9 +145,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(RTLIL::StaticId::STATIC_ID_END); + struct IdStringCollector { + IdStringCollector(std::vector &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 void trace(const T* v) { trace(*v); @@ -180,10 +189,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); @@ -192,15 +197,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()) @@ -236,7 +232,8 @@ struct IdStringCollector { trace(action.memid); } - std::unordered_set live; + std::vector &live_ids; + std::vector live_autoidx_ids; }; int64_t RTLIL::OwningIdString::gc_ns; @@ -245,20 +242,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 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_); - for (int i = static_cast(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; + std::vector live_ids(size - STATIC_ID_END); + std::vector 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 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); @@ -270,8 +302,13 @@ void RTLIL::OwningIdString::collect_garbage() global_free_idx_list_.push_back(i); } + std::unordered_set 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; } @@ -290,159 +327,17 @@ void RTLIL::OwningIdString::collect_garbage() dict RTLIL::constpad; -static const pool &builtin_ff_cell_types_internal() { - static const pool 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 &RTLIL::builtin_ff_cell_types() { - return builtin_ff_cell_types_internal(); + static const pool res = []() { + pool 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") @@ -1470,15 +1365,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 } @@ -1714,11 +1615,11 @@ size_t RTLIL::Module::count_id(RTLIL::IdString id) namespace { struct InternalCellChecker { - RTLIL::Module *module; + const RTLIL::Module *module; RTLIL::Cell *cell; pool 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) { @@ -2703,88 +2604,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 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 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()); - } - - pool packed_memids; - - 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 (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 + memory_strings.insert(it.second->name.str()); + } + + std::vector ports_declared(GetSize(module->ports)); + ShardedVector 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 : processes) { + 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 (const MonotonicFlag &port_declared : ports_declared) + log_assert(port_declared.load() == true); + pool memids_pool; + for (std::string &memid : memids) + log_assert(memids_pool.insert(memid).second); + + 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 all_cases = {&it.second->root_case}; + std::vector 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) { @@ -2797,34 +2706,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() { } @@ -4702,7 +4618,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) @@ -5166,31 +5082,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--) + // Convert pattern to pool for O(1) lookup, avoiding O(n*m) chunk iteration + pool pattern_bits; + pattern_bits.reserve(pattern.size()); + for (auto &bit : pattern) + if (bit.wire != NULL) + pattern_bits.insert(bit); + + // 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_[i].wire == NULL) continue; - - 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; + 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(); } @@ -5217,24 +5137,27 @@ void RTLIL::SigSpec::remove2(const pool &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(); } @@ -5250,24 +5173,27 @@ void RTLIL::SigSpec::remove2(const std::set &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(); } @@ -5562,7 +5488,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) { diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 7964ac5a5ee..7e44ab3e680 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -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 constpad; + [[deprecated("use StaticCellTypes::categories.is_ff() instead")]] const pool &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) { @@ -1758,9 +1770,9 @@ struct RTLIL::SigSpec } #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 }; diff --git a/kernel/satgen.cc b/kernel/satgen.cc index f2c1e00c25c..7fbcba1b296 100644 --- a/kernel/satgen.cc +++ b/kernel/satgen.cc @@ -19,6 +19,7 @@ #include "kernel/satgen.h" #include "kernel/ff.h" +#include "kernel/yosys_common.h" USING_YOSYS_NAMESPACE @@ -1378,7 +1379,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) return true; } - if (cell->type == ID($scopeinfo)) + if (cell->type == ID($scopeinfo) || cell->type == ID($input_port)) { return true; } @@ -1387,3 +1388,22 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) // .. and all sequential cells with asynchronous inputs return false; } + +namespace Yosys { + +void report_missing_model(bool warn_only, RTLIL::Cell* cell) +{ + std::string s; + if (cell->is_builtin_ff()) + s = stringf("No SAT model available for async FF cell %s (%s). Consider running `async2sync` or `clk2fflogic` first.\n", log_id(cell), log_id(cell->type)); + else + s = stringf("No SAT model available for cell %s (%s).\n", log_id(cell), log_id(cell->type)); + + if (warn_only) { + log_formatted_warning_noprefix(s); + } else { + log_formatted_error(s); + } +} + +} diff --git a/kernel/satgen.h b/kernel/satgen.h index c53d20fe0ee..c11d480a400 100644 --- a/kernel/satgen.h +++ b/kernel/satgen.h @@ -293,6 +293,8 @@ struct SatGen bool importCell(RTLIL::Cell *cell, int timestep = -1); }; +void report_missing_model(bool warn_only, RTLIL::Cell* cell); + YOSYS_NAMESPACE_END #endif diff --git a/kernel/tclapi.cc b/kernel/tclapi.cc index ec0483a4a02..9866f5c9820 100644 --- a/kernel/tclapi.cc +++ b/kernel/tclapi.cc @@ -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; } diff --git a/kernel/threading.cc b/kernel/threading.cc index dcc044c896f..3766c4ddfe0 100644 --- a/kernel/threading.cc +++ b/kernel/threading.cc @@ -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 b) : body(std::move(b)) { @@ -57,4 +79,72 @@ 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(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_ = num_worker_threads_; + signal_workers_start(); + wait_for_workers_done(); +#endif +} + +void ParallelDispatchThreadPool::run(std::function work, int max_threads) +{ + Multithreading multithreading; + num_active_worker_threads_ = num_threads(max_threads) - 1; + if (num_active_worker_threads_ == 0) { + work({{0}, 1}); + return; + } +#ifdef YOSYS_ENABLE_THREADS + 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; + (*current_work)({{thread_num + 1}, num_active_worker_threads_ + 1}); + signal_worker_done(); + } + signal_worker_done(); +#endif +} + YOSYS_NAMESPACE_END diff --git a/kernel/threading.h b/kernel/threading.h index b8cd62f8781..fb1602cf558 100644 --- a/kernel/threading.h +++ b/kernel/threading.h @@ -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 @@ class ThreadPool // 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 b); @@ -154,6 +160,123 @@ class ThreadPool #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 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 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 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::unique_ptr thread_pool; + std::function *current_work = nullptr; + // Keeps a correct count even when threads are exiting. + int num_worker_threads_; + // The count of active workerthreads for the current `run()`. + 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 main_to_workers_signal; + void signal_workers_start() { + std::unique_lock lock(main_to_workers_signal_mutex); + 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 done_workers = 0; + std::mutex workers_to_main_signal_mutex; + std::condition_variable workers_to_main_signal_cv; + void signal_worker_done() { + 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] { return done_workers.load(std::memory_order_acquire) == num_active_worker_threads_; }); + done_workers.store(0, std::memory_order_relaxed); + } +#endif +}; + template class ConcurrentStack { @@ -181,6 +304,382 @@ class ConcurrentStack std::vector 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 +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 &bucket : buckets) + if (!bucket.empty()) + return false; + return true; + } + + using Buckets = std::vector>; + 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::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 +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`. `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 +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(v.hash); + } + }; + using Shard = std::unordered_set; + + // 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> &buckets = all_buckets[thread.thread_num]; + size_t bucket = static_cast(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> &buckets : all_buckets) + size += GetSize(buckets[thread.thread_num]); + Shard &shard = shards[thread.thread_num]; + shard.reserve(size); + for (std::vector> &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().swap(buckets[thread.thread_num]); + } + } + private: + friend class ShardedHashtable; + 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(it->value), accumulated_value.value); + } + void init(int num_threads, KeyEquality equality) { + all_buckets.resize(num_threads); + for (std::vector> &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>> all_buckets; + std::vector 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> &buckets : builder.all_buckets) + for (std::vector &bucket : buckets) + log_assert(bucket.empty()); + // Free memory. + std::vector>>().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(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(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[0].key_eq(); + shards[shard.thread_num] = Shard(0, AccumulatedValueHashOp(), equality); + } +private: + std::vector shards; +}; + +// A concurrent work-queue that can share batches of work across threads. +// Uses a naive implementation of work-stealing. +template +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 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 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 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 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 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 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> batches; + }; + std::vector 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 value; +}; + YOSYS_NAMESPACE_END #endif // YOSYS_THREADING_H diff --git a/kernel/utils.h b/kernel/utils.h index e90ba09d869..46a196789d7 100644 --- a/kernel/utils.h +++ b/kernel/utils.h @@ -299,6 +299,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 diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 7b9ffed6c34..d582d3f113a 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -18,8 +18,8 @@ */ #include "kernel/yosys.h" -#include "kernel/celltypes.h" #include "kernel/log.h" +#include "kernel/newcelltypes.h" #include "libs/backward-cpp/backward.hpp" @@ -94,7 +94,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; @@ -265,7 +265,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(); } @@ -294,8 +294,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)) { diff --git a/passes/cmds/check.cc b/passes/cmds/check.cc index b7a5feb57a4..1019c2955ee 100644 --- a/passes/cmds/check.cc +++ b/passes/cmds/check.cc @@ -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" diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index a928e5d588d..1d3628f1f42 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -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]; diff --git a/passes/cmds/rename.cc b/passes/cmds/rename.cc index 078ffb769af..a07d588c4a0 100644 --- a/passes/cmds/rename.cc +++ b/passes/cmds/rename.cc @@ -254,18 +254,17 @@ struct RenamePass : public Pass { log("\n"); log(" rename -enumerate [-pattern ] [selection]\n"); log("\n"); - log("Assign short auto-generated names to all selected wires and cells with private\n"); - log("names. The -pattern option can be used to set the pattern for the new names.\n"); - log("The character %% in the pattern is replaced with a integer number. The default\n"); - log("pattern is '_%%_'.\n"); + log("Assigns auto-generated names to objects used in formal verification\n"); + log("that do not have a public name. This applies to all formal property\n"); + log("cells, $any*/$all* output wires, and their containing cells.\n"); log("\n"); log("\n"); log(" rename -witness\n"); log("\n"); - log("Assigns auto-generated names to all $any*/$all* output wires and containing\n"); - log("cells that do not have a public name. This ensures that, during formal\n"); - log("verification, a solver-found trace can be fully specified using a public\n"); - log("hierarchical names.\n"); + log("Assigns auto-generated names to objects used in formal verification\n"); + log("that do not have a public name. This applies to all formal property\n"); + log("cells ($assert, $assume, $cover, $live, $fair, $check), $any*/$all*\n"); + log("output wires, and their containing cells.\n"); log("\n"); log("\n"); log(" rename -hide [selection]\n"); diff --git a/passes/cmds/select.cc b/passes/cmds/select.cc index 0df47664f46..2359efe0374 100644 --- a/passes/cmds/select.cc +++ b/passes/cmds/select.cc @@ -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 &tokens, const std::string } } -static int select_op_expand(RTLIL::Design *design, RTLIL::Selection &lhs, std::vector &rules, std::set &limits, int max_objects, char mode, CellTypes &ct, bool eval_only) +static int select_op_expand(RTLIL::Design *design, RTLIL::Selection &lhs, std::vector &rules, std::set &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 rules; std::set limits; - CellTypes ct; + NewCellTypes ct; if (mode != 'x') ct.setup(design); diff --git a/passes/cmds/setundef.cc b/passes/cmds/setundef.cc index 5d2ccfcc84c..99a223bdc77 100644 --- a/passes/cmds/setundef.cc +++ b/passes/cmds/setundef.cc @@ -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) diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index 23c2a060b5e..163eda8f514 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -267,8 +267,7 @@ struct statdata_t log(" of which used for sequential elements: %f (%.2f%%)\n", sequential_area, 100.0*sequential_area/area); } - if (tech == "xilinx") - { + if (tech == "xilinx" || tech == "analogdevices") { log("\n"); log(" Estimated number of LCs: %10u\n", estimate_xilinx_lc()); } @@ -435,7 +434,7 @@ struct StatPass : public Pass { log("\n"); log(" -tech \n"); log(" print area estimate for the specified technology. Currently supported\n"); - log(" values for : xilinx, cmos\n"); + log(" values for : xilinx, analogdevices, cmos\n"); log("\n"); log(" -width\n"); log(" annotate internal cell types with their word width.\n"); @@ -494,7 +493,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) { diff --git a/passes/cmds/torder.cc b/passes/cmds/torder.cc index 537b6793de6..52c00072f29 100644 --- a/passes/cmds/torder.cc +++ b/passes/cmds/torder.cc @@ -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" diff --git a/passes/equiv/equiv.h b/passes/equiv/equiv.h new file mode 100644 index 00000000000..055dc440b65 --- /dev/null +++ b/passes/equiv/equiv.h @@ -0,0 +1,68 @@ +#ifndef EQUIV_H +#define EQUIV_H + +#include "kernel/log.h" +#include "kernel/yosys_common.h" +#include "kernel/sigtools.h" +#include "kernel/satgen.h" +#include "kernel/newcelltypes.h" + +YOSYS_NAMESPACE_BEGIN + +struct EquivBasicConfig { + bool model_undef = false; + int max_seq = 1; + bool set_assumes = false; + bool ignore_unknown_cells = false; + + bool parse(const std::vector& args, size_t& idx) { + if (args[idx] == "-undef") { + model_undef = true; + return true; + } + if (args[idx] == "-seq" && idx+1 < args.size()) { + max_seq = atoi(args[++idx].c_str()); + return true; + } + if (args[idx] == "-set-assumes") { + set_assumes = true; + return true; + } + if (args[idx] == "-ignore-unknown-cells") { + ignore_unknown_cells = true; + return true; + } + return false; + } + static std::string help(const char* default_seq) { + return stringf( + " -undef\n" + " enable modelling of undef states\n" + "\n" + " -seq \n" + " the max. number of time steps to be considered (default = %s)\n" + "\n" + " -set-assumes\n" + " set all assumptions provided via $assume cells\n" + "\n" + " -ignore-unknown-cells\n" + " ignore all cells that can not be matched to a SAT model\n" + , default_seq); + } +}; + +template +struct EquivWorker { + RTLIL::Module *module; + + ezSatPtr ez; + SatGen satgen; + Config cfg; + + EquivWorker(RTLIL::Module *module, const SigMap *sigmap, Config cfg) : module(module), satgen(ez.get(), sigmap), cfg(cfg) { + satgen.model_undef = cfg.model_undef; + } +}; + +YOSYS_NAMESPACE_END +#endif // EQUIV_H diff --git a/passes/equiv/equiv_induct.cc b/passes/equiv/equiv_induct.cc index e1a3a7990de..e4480c89304 100644 --- a/passes/equiv/equiv_induct.cc +++ b/passes/equiv/equiv_induct.cc @@ -18,49 +18,34 @@ */ #include "kernel/yosys.h" -#include "kernel/satgen.h" -#include "kernel/sigtools.h" +#include "passes/equiv/equiv.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -struct EquivInductWorker +struct EquivInductWorker : public EquivWorker<> { - Module *module; SigMap sigmap; vector cells; pool workset; - ezSatPtr ez; - SatGen satgen; - - int max_seq; int success_counter; - bool set_assumes; dict ez_step_is_consistent; - pool cell_warn_cache; SigPool undriven_signals; - EquivInductWorker(Module *module, const pool &unproven_equiv_cells, bool model_undef, int max_seq, bool set_assumes) : module(module), sigmap(module), + EquivInductWorker(Module *module, const pool &unproven_equiv_cells, EquivBasicConfig cfg) : EquivWorker<>(module, &sigmap, cfg), sigmap(module), cells(module->selected_cells()), workset(unproven_equiv_cells), - satgen(ez.get(), &sigmap), max_seq(max_seq), success_counter(0), set_assumes(set_assumes) - { - satgen.model_undef = model_undef; - } + success_counter(0) {} void create_timestep(int step) { vector ez_equal_terms; for (auto cell : cells) { - if (!satgen.importCell(cell, step) && !cell_warn_cache.count(cell)) { - if (cell->is_builtin_ff()) - log_warning("No SAT model available for async FF cell %s (%s). Consider running `async2sync` or `clk2fflogic` first.\n", log_id(cell), log_id(cell->type)); - else - log_warning("No SAT model available for cell %s (%s).\n", log_id(cell), log_id(cell->type)); - cell_warn_cache.insert(cell); + if (!satgen.importCell(cell, step)) { + report_missing_model(cfg.ignore_unknown_cells, cell); } if (cell->type == ID($equiv)) { SigBit bit_a = sigmap(cell->getPort(ID::A)).as_bit(); @@ -78,7 +63,7 @@ struct EquivInductWorker } } - if (set_assumes) { + if (cfg.set_assumes) { if (step == 1) { RTLIL::SigSpec assumes_a, assumes_en; satgen.getAssumes(assumes_a, assumes_en, step); @@ -123,7 +108,7 @@ struct EquivInductWorker GetSize(satgen.initial_state), GetSize(undriven_signals)); } - for (int step = 1; step <= max_seq; step++) + for (int step = 1; step <= cfg.max_seq; step++) { ez->assume(ez_step_is_consistent[step]); @@ -146,7 +131,7 @@ struct EquivInductWorker return; } - log(" Proof for induction step failed. %s\n", step != max_seq ? "Extending to next time step." : "Trying to prove individual $equiv from workset."); + log(" Proof for induction step failed. %s\n", step != cfg.max_seq ? "Extending to next time step." : "Trying to prove individual $equiv from workset."); } workset.sort(); @@ -158,12 +143,12 @@ struct EquivInductWorker log(" Trying to prove $equiv for %s:", log_signal(sigmap(cell->getPort(ID::Y)))); - int ez_a = satgen.importSigBit(bit_a, max_seq+1); - int ez_b = satgen.importSigBit(bit_b, max_seq+1); + int ez_a = satgen.importSigBit(bit_a, cfg.max_seq+1); + int ez_b = satgen.importSigBit(bit_b, cfg.max_seq+1); int cond = ez->XOR(ez_a, ez_b); if (satgen.model_undef) - cond = ez->AND(cond, ez->NOT(satgen.importUndefSigBit(bit_a, max_seq+1))); + cond = ez->AND(cond, ez->NOT(satgen.importUndefSigBit(bit_a, cfg.max_seq+1))); if (!ez->solve(cond)) { log(" success!\n"); @@ -189,14 +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"); - log(" -undef\n"); - log(" enable modelling of undef states\n"); - log("\n"); - log(" -seq \n"); - log(" the max. number of time steps to be considered (default = 4)\n"); - log("\n"); - log(" -set-assumes\n"); - log(" set all assumptions provided via $assume cells\n"); + 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"); @@ -214,25 +192,15 @@ struct EquivInductPass : public Pass { void execute(std::vector args, Design *design) override { int success_counter = 0; - bool model_undef = false, set_assumes = false; - int max_seq = 4; + EquivBasicConfig cfg {}; + cfg.max_seq = 4; log_header(design, "Executing EQUIV_INDUCT pass.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-undef") { - model_undef = true; + if (cfg.parse(args, argidx)) continue; - } - if (args[argidx] == "-seq" && argidx+1 < args.size()) { - max_seq = atoi(args[++argidx].c_str()); - continue; - } - if (args[argidx] == "-set-assumes") { - set_assumes = true; - continue; - } break; } extra_args(args, argidx, design); @@ -253,7 +221,7 @@ struct EquivInductPass : public Pass { continue; } - EquivInductWorker worker(module, unproven_equiv_cells, model_undef, max_seq, set_assumes); + EquivInductWorker worker(module, unproven_equiv_cells, cfg); worker.run(); success_counter += worker.success_counter; } diff --git a/passes/equiv/equiv_simple.cc b/passes/equiv/equiv_simple.cc index 97f95ac63fe..e498928c31a 100644 --- a/passes/equiv/equiv_simple.cc +++ b/passes/equiv/equiv_simple.cc @@ -17,15 +17,51 @@ * */ +#include "kernel/log.h" #include "kernel/yosys.h" -#include "kernel/satgen.h" +#include "passes/equiv/equiv.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -struct EquivSimpleWorker +struct EquivSimpleConfig : EquivBasicConfig { + bool verbose = false; + bool short_cones = false; + bool group = true; + bool parse(const std::vector& args, size_t& idx) { + if (EquivBasicConfig::parse(args, idx)) + return true; + if (args[idx] == "-v") { + verbose = true; + return true; + } + if (args[idx] == "-short") { + short_cones = true; + return true; + } + if (args[idx] == "-nogroup") { + group = false; + return true; + } + return false; + } + static std::string help(const char* default_seq) { + return EquivBasicConfig::help(default_seq) + + " -v\n" + " verbose output\n" + "\n" + " -short\n" + " create shorter input cones that stop at shared nodes. This yields\n" + " simpler SAT problems but sometimes fails to prove equivalence.\n" + "\n" + " -nogroup\n" + " disabling grouping of $equiv cells by output wire\n" + "\n"; + } +}; + +struct EquivSimpleWorker : public EquivWorker { - Module *module; const vector &equiv_cells; const vector &assume_cells; struct Cone { @@ -43,27 +79,11 @@ struct EquivSimpleWorker }; DesignModel model; - ezSatPtr ez; - SatGen satgen; - - struct Config { - bool verbose = false; - bool short_cones = false; - bool model_undef = false; - bool nogroup = false; - bool set_assumes = false; - int max_seq = 1; - }; - Config cfg; - pool> imported_cells_cache; - EquivSimpleWorker(const vector &equiv_cells, const vector &assume_cells, DesignModel model, Config cfg) : - module(equiv_cells.front()->module), equiv_cells(equiv_cells), assume_cells(assume_cells), - model(model), satgen(ez.get(), &model.sigmap), cfg(cfg) - { - satgen.model_undef = cfg.model_undef; - } + EquivSimpleWorker(const vector &equiv_cells, const vector &assume_cells, DesignModel model, EquivSimpleConfig cfg) : + EquivWorker(equiv_cells.front()->module, &model.sigmap, cfg), equiv_cells(equiv_cells), assume_cells(assume_cells), + model(model) {} struct ConeFinder { DesignModel model; @@ -229,14 +249,6 @@ struct EquivSimpleWorker return extra_problem_cells; } - static void report_missing_model(Cell* cell) - { - if (cell->is_builtin_ff()) - log_cmd_error("No SAT model available for async FF cell %s (%s). Consider running `async2sync` or `clk2fflogic` first.\n", log_id(cell), log_id(cell->type)); - else - log_cmd_error("No SAT model available for cell %s (%s).\n", log_id(cell), log_id(cell->type)); - } - void prepare_ezsat(int ez_context, SigBit bit_a, SigBit bit_b) { if (satgen.model_undef) @@ -257,7 +269,9 @@ struct EquivSimpleWorker } void construct_ezsat(const pool& input_bits, int step) { + log("ezsat\n"); if (cfg.set_assumes) { + log("yep assume\n"); if (cfg.verbose && step == cfg.max_seq) { RTLIL::SigSpec assumes_a, assumes_en; satgen.getAssumes(assumes_a, assumes_en, step+1); @@ -323,7 +337,7 @@ struct EquivSimpleWorker for (auto cell : problem_cells) { auto key = pair(cell, step+1); if (!imported_cells_cache.count(key) && !satgen.importCell(cell, step+1)) { - report_missing_model(cell); + report_missing_model(cfg.ignore_unknown_cells, cell); } imported_cells_cache.insert(key); } @@ -414,59 +428,20 @@ struct EquivSimplePass : public Pass { log("\n"); log("This command tries to prove $equiv cells using a simple direct SAT approach.\n"); log("\n"); - log(" -v\n"); - log(" verbose output\n"); - log("\n"); - log(" -undef\n"); - log(" enable modelling of undef states\n"); - log("\n"); - log(" -short\n"); - log(" create shorter input cones that stop at shared nodes. This yields\n"); - log(" simpler SAT problems but sometimes fails to prove equivalence.\n"); - log("\n"); - log(" -nogroup\n"); - log(" disabling grouping of $equiv cells by output wire\n"); - log("\n"); - log(" -seq \n"); - log(" the max. number of time steps to be considered (default = 1)\n"); - log("\n"); - log(" -set-assumes\n"); - log(" set all assumptions provided via $assume cells\n"); + log("%s", EquivSimpleConfig::help("1")); log("\n"); } void execute(std::vector args, Design *design) override { - EquivSimpleWorker::Config cfg = {}; + EquivSimpleConfig cfg {}; int success_counter = 0; log_header(design, "Executing EQUIV_SIMPLE pass.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-v") { - cfg.verbose = true; - continue; - } - if (args[argidx] == "-short") { - cfg.short_cones = true; - continue; - } - if (args[argidx] == "-undef") { - cfg.model_undef = true; - continue; - } - if (args[argidx] == "-nogroup") { - cfg.nogroup = true; - continue; - } - if (args[argidx] == "-seq" && argidx+1 < args.size()) { - cfg.max_seq = atoi(args[++argidx].c_str()); - continue; - } - if (args[argidx] == "-set-assumes") { - cfg.set_assumes = true; + if (cfg.parse(args, argidx)) continue; - } break; } extra_args(args, argidx, design); @@ -489,7 +464,7 @@ struct EquivSimplePass : public Pass { if (cell->type == ID($equiv) && cell->getPort(ID::A) != cell->getPort(ID::B)) { auto bit = sigmap(cell->getPort(ID::Y).as_bit()); auto bit_group = bit; - if (!cfg.nogroup && bit_group.wire) + if (cfg.group && bit_group.wire) bit_group.offset = 0; unproven_equiv_cells[bit_group][bit] = cell; unproven_cells_counter++; diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index c3c10363bef..87adaa26d41 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -39,6 +39,7 @@ struct PassOptions { bool no_auto_distributed; bool no_auto_block; bool no_auto_huge; + bool force_params; double logic_cost_rom; double logic_cost_ram; }; @@ -1859,7 +1860,7 @@ void MemMapping::emit_port(const MemConfig &cfg, std::vector &cells, cons cell->setParam(stringf("\\PORT_%s_WR_BE_WIDTH", name), GetSize(hw_wren)); } else { cell->setPort(stringf("\\PORT_%s_WR_EN", name), hw_wren); - if (cfg.def->byte != 0 && cfg.def->width_mode != WidthMode::Single) + if (cfg.def->byte != 0 && (cfg.def->width_mode != WidthMode::Single || opts.force_params)) cell->setParam(stringf("\\PORT_%s_WR_EN_WIDTH", name), GetSize(hw_wren)); } } @@ -2068,8 +2069,10 @@ void MemMapping::emit(const MemConfig &cfg) { std::vector cells; for (int rd = 0; rd < cfg.repl_d; rd++) { Cell *cell = mem.module->addCell(stringf("%s.%d.%d", mem.memid, rp, rd), cfg.def->id); - if (cfg.def->width_mode == WidthMode::Global) + if (cfg.def->width_mode == WidthMode::Global || opts.force_params) cell->setParam(ID::WIDTH, cfg.def->dbits[cfg.base_width_log2]); + if (opts.force_params) + cell->setParam(ID::ABITS, cfg.def->abits); if (cfg.def->widthscale) { std::vector val; for (auto &bit: init_swz.bits[rd]) @@ -2179,6 +2182,9 @@ struct MemoryLibMapPass : public Pass { log(" Disables automatic mapping of given kind of RAMs. Manual mapping\n"); log(" (using ram_style or other attributes) is still supported.\n"); log("\n"); + log(" -force-params\n"); + log(" Always generate memories with WIDTH and ABITS parameters.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { @@ -2188,6 +2194,7 @@ struct MemoryLibMapPass : public Pass { opts.no_auto_distributed = false; opts.no_auto_block = false; opts.no_auto_huge = false; + opts.force_params = false; opts.logic_cost_ram = 1.0; opts.logic_cost_rom = 1.0/16.0; log_header(design, "Executing MEMORY_LIBMAP pass (mapping memories to cells).\n"); @@ -2214,6 +2221,10 @@ struct MemoryLibMapPass : public Pass { opts.no_auto_huge = true; continue; } + if (args[argidx] == "-force-params") { + opts.force_params = true; + continue; + } if (args[argidx] == "-logic-cost-rom" && argidx+1 < args.size()) { opts.logic_cost_rom = strtod(args[++argidx].c_str(), nullptr); continue; diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 53c123b79bf..e1a30ab5d24 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -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 @@ -41,3 +40,5 @@ PEEPOPT_PATTERN += passes/opt/peepopt_sub_neg.pmg passes/opt/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN) $(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p peepopt $(filter-out $<,$^) + +include $(YOSYS_SRC)/passes/opt/opt_clean/Makefile.inc diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc deleted file mode 100644 index 237d6cf0d9c..00000000000 --- a/passes/opt/opt_clean.cc +++ /dev/null @@ -1,800 +0,0 @@ -/* - * yosys -- Yosys Open SYnthesis Suite - * - * Copyright (C) 2012 Claire Xenia Wolf - * - * 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 -#include -#include - -USING_YOSYS_NAMESPACE -PRIVATE_NAMESPACE_BEGIN - -using RTLIL::id2cstr; - -struct keep_cache_t -{ - Design *design; - dict 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> mem2cells; - pool mem_unused; - pool queue, unused; - pool used_raw_bits; - dict> wire2driver; - dict> 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 bits; - pool 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()); - - 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 &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->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 unusedbitsattr_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 direct_wires; - { - pool 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(s1, s2, 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 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 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 if (unusedbitsattr_mode) - 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 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 unusedbitsattr_mode, bool verbose, bool rminit) -{ - if (verbose) - log("Finding unused cells or wires in module %s..\n", module->name); - - std::vector 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, unusedbitsattr_mode, verbose)) { } - - if (rminit && rmunused_module_init(module, verbose)) - while (rmunused_module_signals(module, purge_mode, unusedbitsattr_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"); - log(" -unusedbitsattr\n"); - log(" add an unused_bits attribute onto wires for bits that are not used\n"); - log("\n"); - } - void execute(std::vector args, RTLIL::Design *design) override - { - bool purge_mode = false; - bool unusedbitsattr_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; - } - if (args[argidx] == "-unusedbitsattr") { - unusedbitsattr_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, unusedbitsattr_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->sort(); - 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 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, false, 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->sort(); - design->check(); - - keep_cache.reset(); - ct_reg.clear(); - ct_all.clear(); - - request_garbage_collection(); - } -} CleanPass; - -PRIVATE_NAMESPACE_END diff --git a/passes/opt/opt_clean/Makefile.inc b/passes/opt/opt_clean/Makefile.inc new file mode 100644 index 00000000000..b4bb5b070d7 --- /dev/null +++ b/passes/opt/opt_clean/Makefile.inc @@ -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) diff --git a/passes/opt/opt_clean/cells_all.cc b/passes/opt/opt_clean/cells_all.cc new file mode 100644 index 00000000000..aa97851e3c1 --- /dev/null +++ b/passes/opt/opt_clean/cells_all.cc @@ -0,0 +1,373 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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(hash_ops::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::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> 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()); + incumbent.extra_driver_cells->insert(new_value.driver_cell); + } +}; +using Wire2Drivers = ShardedHashtable; + +struct ConflictLogs { + ShardedVector> logs; + ConflictLogs(ParallelDispatchThreadPool::Subpool &subpool) : logs(subpool) {} + void print_warnings(pool& 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 &it : logs) { + if (used_raw_bits.count(it.first)) + log_warning("%s\n", it.second); + } + } + } +}; + +struct CellTraversal { + ConcurrentWorkQueue queue; + Wire2Drivers wire2driver; + dict> mem2cells; + CellTraversal(int num_threads) : queue(num_threads), wire2driver(), mem2cells() {} +}; + +struct CellAnalysis { + ShardedVector keep_wires; + std::vector> unused; + + CellAnalysis(AnalysisContext& actx) + : keep_wires(actx.subpool), unused(actx.mod->cells_size()) {} + + pool 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 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& 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> 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 &mem2cell : mem2cells_vector) + traversal.mem2cells[mem2cell.first].insert(mem2cell.second); + + return logs; +} + +struct MemAnalysis { + std::vector> unused; + dict 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 bits; + pool mems; + while (true) { + std::vector 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 all_unused_cells(const Module *mod, const CellAnalysis& analysis, Wire2Drivers& wire2driver, ParallelDispatchThreadPool::Subpool &subpool) { + pool unused_cells; + ShardedVector 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()); + return unused_cells; +} + +void remove_cells(RTLIL::Module* mod, FfInitVals& ffinit, const pool& 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 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 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 diff --git a/passes/opt/opt_clean/cells_temp.cc b/passes/opt/opt_clean/cells_temp.cc new file mode 100644 index 00000000000..b325b68d9ff --- /dev/null +++ b/passes/opt/opt_clean/cells_temp.cc @@ -0,0 +1,104 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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& 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& 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 delcells(subpool); + ShardedVector 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 diff --git a/passes/opt/opt_clean/inits.cc b/passes/opt/opt_clean/inits.cc new file mode 100644 index 00000000000..70c2ef9e285 --- /dev/null +++ b/passes/opt/opt_clean/inits.cc @@ -0,0 +1,137 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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> build_inits(AnalysisContext& actx) { + ShardedVector> 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 qbits_from_inits(ShardedVector>& inits, SigMap& assign_map) { + dict qbits; + for (std::pair &p : inits) { + assign_map.add(p.first); + qbits[p.first] = p.second; + } + return qbits; +} + +ShardedVector deferred_init_transfer(const dict& qbits, AnalysisContext& actx) { + ShardedVector 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 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> inits = build_inits(actx); + dict qbits = qbits_from_inits(inits, actx.assign_map); + ShardedVector 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 diff --git a/passes/opt/opt_clean/keep_cache.h b/passes/opt/opt_clean/keep_cache.h new file mode 100644 index 00000000000..7405f24fcc9 --- /dev/null +++ b/passes/opt/opt_clean/keep_cache.h @@ -0,0 +1,167 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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 keep_modules; + bool purge_mode; + + KeepCache(bool purge_mode, ParallelDispatchThreadPool &thread_pool, const std::vector &selected_modules) + : purge_mode(purge_mode) { + + std::vector scan_modules_worklist; + dict> dependents; + std::vector 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> &dependents, + ScanCells scan_cells, std::vector &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 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 */ diff --git a/passes/opt/opt_clean/opt_clean.cc b/passes/opt/opt_clean/opt_clean.cc new file mode 100644 index 00000000000..87597d721e2 --- /dev/null +++ b/passes/opt/opt_clean/opt_clean.cc @@ -0,0 +1,151 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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 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 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 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 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 diff --git a/passes/opt/opt_clean/opt_clean.h b/passes/opt/opt_clean/opt_clean.h new file mode 100644 index 00000000000..c48a8188a9f --- /dev/null +++ b/passes/opt/opt_clean/opt_clean.h @@ -0,0 +1,92 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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& 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& 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 */ diff --git a/passes/opt/opt_clean/wires.cc b/passes/opt/opt_clean/wires.cc new file mode 100644 index 00000000000..28c79293638 --- /dev/null +++ b/passes/opt/opt_clean/wires.cc @@ -0,0 +1,585 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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>; + +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>; + +struct ExactCellWires { + const ShardedSigSpecPool &exact_cells; + const SigMap &assign_map; + dict 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 &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 &update_connections) +{ + for (UpdateConnection &update : update_connections) + update.cell->connections_.at(update.port) = std::move(update.spec); +} + +struct InitBits { + dict values; + // Wires that appear in the keys of the `values` dict + pool 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 &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 build_candidates(ExactCellWires& cell_wires, const SigConnKinds& sig_analysis, const AnalysisContext& actx) { + ShardedVector candidates(actx.subpool); + actx.subpool.run([&actx, &sig_analysis, &candidates, &cell_wires](const ParallelDispatchThreadPool::RunCtx &ctx) { + std::optional 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& 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 update_connections; + // Wires we should remove init from + ShardedVector 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 del_wires_queue; + ShardedVector remove_init; + ShardedVector> set_init; + ShardedVector new_connections; + ShardedVector remove_unused_bits; + ShardedVector> 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 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 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 diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 41927ecf04c..bf55bfb6c2f 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -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 @@ -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; @@ -410,9 +411,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)) { @@ -436,7 +434,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); @@ -2329,7 +2327,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)); diff --git a/passes/opt/opt_lut_ins.cc b/passes/opt/opt_lut_ins.cc index fa8eb563b6e..580853b5176 100644 --- a/passes/opt/opt_lut_ins.cc +++ b/passes/opt/opt_lut_ins.cc @@ -39,7 +39,8 @@ struct OptLutInsPass : public Pass { log("\n"); log(" -tech \n"); log(" Instead of generic $lut cells, operate on LUT cells specific\n"); - log(" to the given technology. Valid values are: xilinx, lattice, gowin.\n"); + log(" to the given technology. Valid values are: xilinx, lattice,\n"); + log(" gowin, analogdevices.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override @@ -58,7 +59,7 @@ struct OptLutInsPass : public Pass { } extra_args(args, argidx, design); - if (techname != "" && techname != "xilinx" && techname != "lattice" && techname != "ecp5" && techname != "gowin") + if (techname != "" && techname != "xilinx" && techname != "lattice" && techname != "analogdevices" && techname != "gowin") log_cmd_error("Unsupported technology: '%s'\n", techname); for (auto module : design->selected_modules()) @@ -81,7 +82,7 @@ struct OptLutInsPass : public Pass { inputs = cell->getPort(ID::A); output = cell->getPort(ID::Y); lut = cell->getParam(ID::LUT); - } else if (techname == "xilinx" || techname == "gowin") { + } else if (techname == "xilinx" || techname == "gowin" || techname == "analogdevices") { if (cell->type == ID(LUT1)) { inputs = { cell->getPort(ID(I0)), @@ -126,11 +127,11 @@ struct OptLutInsPass : public Pass { continue; } lut = cell->getParam(ID::INIT); - if (techname == "xilinx") + if (techname == "xilinx" || techname == "analogdevices") output = cell->getPort(ID::O); else output = cell->getPort(ID::F); - } else if (techname == "lattice" || techname == "ecp5") { + } else if (techname == "lattice") { if (cell->type == ID(LUT4)) { inputs = { cell->getPort(ID::A), @@ -236,7 +237,7 @@ struct OptLutInsPass : public Pass { } else { // xilinx, gowin cell->setParam(ID::INIT, new_lut); - if (techname == "xilinx") + if (techname == "xilinx" || techname == "analogdevices") log_assert(GetSize(new_inputs) <= 6); else log_assert(GetSize(new_inputs) <= 4); diff --git a/passes/opt/share.cc b/passes/opt/share.cc index 307cd299bd5..f7843cc0809 100644 --- a/passes/opt/share.cc +++ b/passes/opt/share.cc @@ -23,6 +23,7 @@ #include "kernel/modtools.h" #include "kernel/utils.h" #include "kernel/macc.h" +#include "kernel/newcelltypes.h" #include USING_YOSYS_NAMESPACE @@ -38,19 +39,18 @@ struct ShareWorkerConfig bool opt_force; bool opt_aggressive; bool opt_fast; - pool generic_uni_ops, generic_bin_ops, generic_cbin_ops, generic_other_ops; + StaticCellTypes::Categories::Category generic_uni_ops, generic_bin_ops, generic_cbin_ops, generic_other_ops; }; struct ShareWorker { const ShareWorkerConfig config; int limit; - pool generic_ops; + StaticCellTypes::Categories::Category generic_ops; RTLIL::Design *design; RTLIL::Module *module; - CellTypes fwd_ct, cone_ct; ModWalker modwalker; pool cells_to_remove; @@ -75,7 +75,7 @@ struct ShareWorker queue_bits.insert(modwalker.signal_outputs.begin(), modwalker.signal_outputs.end()); for (auto &it : module->cells_) - if (!fwd_ct.cell_known(it.second->type)) { + if (!StaticCellTypes::Compat::internals_nomem_noff(it.second->type)) { pool &bits = modwalker.cell_inputs[it.second]; queue_bits.insert(bits.begin(), bits.end()); } @@ -95,7 +95,7 @@ struct ShareWorker queue_bits.insert(bits.begin(), bits.end()); visited_cells.insert(pbit.cell); } - if (fwd_ct.cell_known(pbit.cell->type) && visited_cells.count(pbit.cell) == 0) { + if (StaticCellTypes::Compat::internals_nomem_noff(pbit.cell->type) && visited_cells.count(pbit.cell) == 0) { pool &bits = modwalker.cell_inputs[pbit.cell]; terminal_bits.insert(bits.begin(), bits.end()); queue_bits.insert(bits.begin(), bits.end()); @@ -388,7 +388,7 @@ struct ShareWorker continue; } - if (generic_ops.count(cell->type)) { + if (generic_ops(cell->type)) { if (config.opt_aggressive) shareable_cells.insert(cell); continue; @@ -412,7 +412,7 @@ struct ShareWorker return true; } - if (config.generic_uni_ops.count(c1->type)) + if (config.generic_uni_ops(c1->type)) { if (!config.opt_aggressive) { @@ -429,7 +429,7 @@ struct ShareWorker return true; } - if (config.generic_bin_ops.count(c1->type) || c1->type == ID($alu)) + if (config.generic_bin_ops(c1->type) || c1->type == ID($alu)) { if (!config.opt_aggressive) { @@ -449,7 +449,7 @@ struct ShareWorker return true; } - if (config.generic_cbin_ops.count(c1->type)) + if (config.generic_cbin_ops(c1->type)) { if (!config.opt_aggressive) { @@ -511,7 +511,7 @@ struct ShareWorker { log_assert(c1->type == c2->type); - if (config.generic_uni_ops.count(c1->type)) + if (config.generic_uni_ops(c1->type)) { if (c1->parameters.at(ID::A_SIGNED).as_bool() != c2->parameters.at(ID::A_SIGNED).as_bool()) { @@ -560,11 +560,11 @@ struct ShareWorker return supercell; } - if (config.generic_bin_ops.count(c1->type) || config.generic_cbin_ops.count(c1->type) || c1->type == ID($alu)) + if (config.generic_bin_ops(c1->type) || config.generic_cbin_ops(c1->type) || c1->type == ID($alu)) { bool modified_src_cells = false; - if (config.generic_cbin_ops.count(c1->type)) + if (config.generic_cbin_ops(c1->type)) { int score_unflipped = max(c1->parameters.at(ID::A_WIDTH).as_int(), c2->parameters.at(ID::A_WIDTH).as_int()) + max(c1->parameters.at(ID::B_WIDTH).as_int(), c2->parameters.at(ID::B_WIDTH).as_int()); @@ -758,7 +758,7 @@ struct ShareWorker recursion_state.insert(cell); for (auto c : consumer_cells) - if (fwd_ct.cell_known(c->type)) { + if (StaticCellTypes::Compat::internals_nomem_noff(c->type)) { const pool &bits = find_forbidden_controls(c); forbidden_controls_cache[cell].insert(bits.begin(), bits.end()); } @@ -897,7 +897,7 @@ struct ShareWorker return activation_patterns_cache.at(cell); } for (auto &pbit : modwalker.signal_consumers[bit]) { - log_assert(fwd_ct.cell_known(pbit.cell->type)); + log_assert(StaticCellTypes::Compat::internals_nomem_noff(pbit.cell->type)); if ((pbit.cell->type == ID($mux) || pbit.cell->type == ID($pmux)) && (pbit.port == ID::A || pbit.port == ID::B)) driven_data_muxes.insert(pbit.cell); else @@ -1214,24 +1214,10 @@ struct ShareWorker ShareWorker(ShareWorkerConfig config, RTLIL::Design* design) : config(config), design(design), modwalker(design) { - generic_ops.insert(config.generic_uni_ops.begin(), config.generic_uni_ops.end()); - generic_ops.insert(config.generic_bin_ops.begin(), config.generic_bin_ops.end()); - generic_ops.insert(config.generic_cbin_ops.begin(), config.generic_cbin_ops.end()); - generic_ops.insert(config.generic_other_ops.begin(), config.generic_other_ops.end()); - - fwd_ct.setup_internals(); - - cone_ct.setup_internals(); - cone_ct.cell_types.erase(ID($mul)); - cone_ct.cell_types.erase(ID($mod)); - cone_ct.cell_types.erase(ID($div)); - cone_ct.cell_types.erase(ID($modfloor)); - cone_ct.cell_types.erase(ID($divfloor)); - cone_ct.cell_types.erase(ID($pow)); - cone_ct.cell_types.erase(ID($shl)); - cone_ct.cell_types.erase(ID($shr)); - cone_ct.cell_types.erase(ID($sshl)); - cone_ct.cell_types.erase(ID($sshr)); + generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_uni_ops); + generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_bin_ops); + generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_cbin_ops); + generic_ops = StaticCellTypes::Categories::join(generic_ops, config.generic_other_ops); } void operator()(RTLIL::Module *module) { @@ -1561,45 +1547,45 @@ struct SharePass : public Pass { config.opt_aggressive = false; config.opt_fast = false; - config.generic_uni_ops.insert(ID($not)); - // config.generic_uni_ops.insert(ID($pos)); - config.generic_uni_ops.insert(ID($neg)); - - config.generic_cbin_ops.insert(ID($and)); - config.generic_cbin_ops.insert(ID($or)); - config.generic_cbin_ops.insert(ID($xor)); - config.generic_cbin_ops.insert(ID($xnor)); - - config.generic_bin_ops.insert(ID($shl)); - config.generic_bin_ops.insert(ID($shr)); - config.generic_bin_ops.insert(ID($sshl)); - config.generic_bin_ops.insert(ID($sshr)); - - config.generic_bin_ops.insert(ID($lt)); - config.generic_bin_ops.insert(ID($le)); - config.generic_bin_ops.insert(ID($eq)); - config.generic_bin_ops.insert(ID($ne)); - config.generic_bin_ops.insert(ID($eqx)); - config.generic_bin_ops.insert(ID($nex)); - config.generic_bin_ops.insert(ID($ge)); - config.generic_bin_ops.insert(ID($gt)); - - config.generic_cbin_ops.insert(ID($add)); - config.generic_cbin_ops.insert(ID($mul)); - - config.generic_bin_ops.insert(ID($sub)); - config.generic_bin_ops.insert(ID($div)); - config.generic_bin_ops.insert(ID($mod)); - config.generic_bin_ops.insert(ID($divfloor)); - config.generic_bin_ops.insert(ID($modfloor)); - // config.generic_bin_ops.insert(ID($pow)); - - config.generic_uni_ops.insert(ID($logic_not)); - config.generic_cbin_ops.insert(ID($logic_and)); - config.generic_cbin_ops.insert(ID($logic_or)); - - config.generic_other_ops.insert(ID($alu)); - config.generic_other_ops.insert(ID($macc)); + config.generic_uni_ops.set_id(ID($not)); + // config.generic_uni_ops.set_id(ID($pos)); + config.generic_uni_ops.set_id(ID($neg)); + + config.generic_cbin_ops.set_id(ID($and)); + config.generic_cbin_ops.set_id(ID($or)); + config.generic_cbin_ops.set_id(ID($xor)); + config.generic_cbin_ops.set_id(ID($xnor)); + + config.generic_bin_ops.set_id(ID($shl)); + config.generic_bin_ops.set_id(ID($shr)); + config.generic_bin_ops.set_id(ID($sshl)); + config.generic_bin_ops.set_id(ID($sshr)); + + config.generic_bin_ops.set_id(ID($lt)); + config.generic_bin_ops.set_id(ID($le)); + config.generic_bin_ops.set_id(ID($eq)); + config.generic_bin_ops.set_id(ID($ne)); + config.generic_bin_ops.set_id(ID($eqx)); + config.generic_bin_ops.set_id(ID($nex)); + config.generic_bin_ops.set_id(ID($ge)); + config.generic_bin_ops.set_id(ID($gt)); + + config.generic_cbin_ops.set_id(ID($add)); + config.generic_cbin_ops.set_id(ID($mul)); + + config.generic_bin_ops.set_id(ID($sub)); + config.generic_bin_ops.set_id(ID($div)); + config.generic_bin_ops.set_id(ID($mod)); + config.generic_bin_ops.set_id(ID($divfloor)); + config.generic_bin_ops.set_id(ID($modfloor)); + // config.generic_bin_ops.set_id(ID($pow)); + + config.generic_uni_ops.set_id(ID($logic_not)); + config.generic_cbin_ops.set_id(ID($logic_and)); + config.generic_cbin_ops.set_id(ID($logic_or)); + + config.generic_other_ops.set_id(ID($alu)); + config.generic_other_ops.set_id(ID($macc)); log_header(design, "Executing SHARE pass (SAT-based resource sharing).\n"); diff --git a/passes/sat/sat.cc b/passes/sat/sat.cc index 8a0a45dcf07..9988eef62cb 100644 --- a/passes/sat/sat.cc +++ b/passes/sat/sat.cc @@ -243,16 +243,12 @@ struct SatHelper int import_cell_counter = 0; for (auto cell : module->cells()) if (design->selected(module, cell)) { - // log("Import cell: %s\n", RTLIL::id2cstr(cell->name)); if (satgen.importCell(cell, timestep)) { for (auto &p : cell->connections()) if (ct.cell_output(cell->type, p.first)) show_drivers.insert(sigmap(p.second), cell); import_cell_counter++; - } else if (ignore_unknown_cells) - log_warning("Failed to import cell %s (type %s) to SAT database.\n", RTLIL::id2cstr(cell->name), RTLIL::id2cstr(cell->type)); - else - log_error("Failed to import cell %s (type %s) to SAT database.\n", RTLIL::id2cstr(cell->name), RTLIL::id2cstr(cell->type)); + } else report_missing_model(ignore_unknown_cells, cell); } log("Imported %d cells to SAT database.\n", import_cell_counter); @@ -977,10 +973,10 @@ struct SatPass : public Pass { log(" -show-regs, -show-public, -show-all\n"); log(" show all registers, show signals with 'public' names, show all signals\n"); log("\n"); - log(" -ignore_div_by_zero\n"); + log(" -ignore-div-by-zero\n"); log(" ignore all solutions that involve a division by zero\n"); log("\n"); - log(" -ignore_unknown_cells\n"); + log(" -ignore-unknown-cells\n"); log(" ignore all cells that can not be matched to a SAT model\n"); log("\n"); log("The following options can be used to set up a sequential problem:\n"); @@ -1167,7 +1163,7 @@ struct SatPass : public Pass { stepsize = max(1, atoi(args[++argidx].c_str())); continue; } - if (args[argidx] == "-ignore_div_by_zero") { + if (args[argidx] == "-ignore-div-by-zero" || args[argidx] == "-ignore_div_by_zero") { ignore_div_by_zero = true; continue; } @@ -1342,7 +1338,7 @@ struct SatPass : public Pass { show_all = true; continue; } - if (args[argidx] == "-ignore_unknown_cells") { + if (args[argidx] == "-ignore-unknown-cells" || args[argidx] == "-ignore_unknown_cells") { ignore_unknown_cells = true; continue; } diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index d470d760fcc..5ec7a1baacb 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -20,6 +20,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/celltypes.h" +#include "kernel/newcelltypes.h" #include "kernel/mem.h" #include "kernel/fstdata.h" #include "kernel/ff.h" @@ -217,7 +218,13 @@ struct SimInstance std::vector memories; - dict> signal_database; + struct signal_entry_t { + int id; + Const last_value; + SigSpec mapped_sig; + }; + + dict signal_database; dict>> trace_mem_database; dict, Const> trace_mem_init_database; dict fst_handles; @@ -421,11 +428,11 @@ struct SimInstance return result; } - Const get_state(SigSpec sig) + Const get_state_mapped(const SigSpec &mapped_sig) { - Const::Builder builder(GetSize(sig)); + Const::Builder builder(GetSize(mapped_sig)); - for (auto bit : sigmap(sig)) + for (auto bit : mapped_sig) if (bit.wire == nullptr) builder.push_back(bit.data); else if (state_nets.count(bit)) @@ -433,7 +440,12 @@ struct SimInstance else builder.push_back(State::Sz); - Const value = builder.build(); + return builder.build(); + } + + Const get_state(SigSpec sig) + { + Const value = get_state_mapped(sigmap(sig)); if (shared->debug) log("[%s] get %s: %s\n", hiername(), log_signal(sig), log_signal(value)); return value; @@ -1018,7 +1030,7 @@ struct SimInstance if (shared->hide_internal && wire->name[0] == '$') continue; - signal_database[wire] = make_pair(id, Const()); + signal_database[wire] = {id, Const(), sigmap(wire)}; id++; } @@ -1059,11 +1071,11 @@ struct SimInstance hdlname.pop_back(); for (auto name : hdlname) enter_scope("\\" + name); - register_signal(signal_name.c_str(), GetSize(signal.first), signal.first, signal.second.first, registers.count(signal.first)!=0); + register_signal(signal_name.c_str(), GetSize(signal.first), signal.first, signal.second.id, registers.count(signal.first)!=0); for (auto name : hdlname) exit_scope(); } else - register_signal(log_id(signal.first->name), GetSize(signal.first), signal.first, signal.second.first, registers.count(signal.first)!=0); + register_signal(log_id(signal.first->name), GetSize(signal.first), signal.first, signal.second.id, registers.count(signal.first)!=0); } for (auto &trace_mem : trace_mem_database) @@ -1135,15 +1147,14 @@ struct SimInstance { for (auto &it : signal_database) { - Wire *wire = it.first; - Const value = get_state(wire); - int id = it.second.first; + signal_entry_t &entry = it.second; + Const value = get_state_mapped(entry.mapped_sig); - if (it.second.second == value) + if (entry.last_value == value) continue; - it.second.second = value; - data->emplace(id, value); + entry.last_value = value; + data->emplace(entry.id, value); } for (auto &trace_mem : trace_mem_database) @@ -1262,6 +1273,10 @@ struct SimInstance bool checkSignals() { + // No checks performed when using stimulus + if (shared->sim_mode == SimulationMode::sim) + return false; + bool retVal = false; for(auto &item : fst_handles) { if (item.second==0) continue; // Ignore signals not found @@ -1271,9 +1286,7 @@ struct SimInstance log_warning("Signal '%s.%s' size is different in gold and gate.\n", scope, log_id(item.first)); continue; } - if (shared->sim_mode == SimulationMode::sim) { - // No checks performed when using stimulus - } else if (shared->sim_mode == SimulationMode::gate && !fst_val.is_fully_def()) { // FST data contains X + if (shared->sim_mode == SimulationMode::gate && !fst_val.is_fully_def()) { // FST data contains X for(int i=0;i dont_use_cells; bool cleanup = true; bool keepff = false; @@ -1049,7 +1048,7 @@ void AbcModuleState::prepare_module(RTLIL::Design *design, RTLIL::Module *module all_luts_cost_same = false; abc_script += config.fast_mode ? ABC_FAST_COMMAND_LUT : ABC_COMMAND_LUT; if (all_luts_cost_same && !config.fast_mode) - abc_script += "; lutpack {S}"; + abc_script += "; lutpack -S 1"; } else if (!config.liberty_files.empty() || !config.genlib_files.empty()) abc_script += config.constr_file.empty() ? (config.fast_mode ? ABC_FAST_COMMAND_LIB : ABC_COMMAND_LIB) : (config.fast_mode ? ABC_FAST_COMMAND_CTR : ABC_COMMAND_CTR); @@ -1071,8 +1070,6 @@ void AbcModuleState::prepare_module(RTLIL::Design *design, RTLIL::Module *module for (size_t pos = abc_script.find("{P}"); pos != std::string::npos; pos = abc_script.find("{P}", pos)) abc_script = abc_script.substr(0, pos) + config.sop_products + abc_script.substr(pos+3); - for (size_t pos = abc_script.find("{S}"); pos != std::string::npos; pos = abc_script.find("{S}", pos)) - abc_script = abc_script.substr(0, pos) + config.lutin_shared + abc_script.substr(pos+3); if (config.abc_dress) abc_script += stringf("; dress \"%s/input.blif\"", run_abc.per_run_tempdir_name); abc_script += stringf("; write_blif %s/output.blif", run_abc.per_run_tempdir_name); @@ -1894,7 +1891,7 @@ struct AbcPass : public Pass { log("%s\n", fold_abc_cmd(ABC_COMMAND_CTR)); log("\n"); log(" for -lut/-luts (only one LUT size):\n"); - log("%s\n", fold_abc_cmd(ABC_COMMAND_LUT "; lutpack {S}")); + log("%s\n", fold_abc_cmd(ABC_COMMAND_LUT "; lutpack -S 1")); log("\n"); log(" for -lut/-luts (different LUT sizes):\n"); log("%s\n", fold_abc_cmd(ABC_COMMAND_LUT)); @@ -1962,10 +1959,6 @@ struct AbcPass : public Pass { log(" maximum number of SOP products.\n"); log(" (replaces {P} in the default scripts above)\n"); log("\n"); - log(" -S \n"); - log(" maximum number of LUT inputs shared.\n"); - log(" (replaces {S} in the default scripts above, default: -S 1)\n"); - log("\n"); log(" -lut \n"); log(" generate netlist using luts of (max) the specified width.\n"); log("\n"); @@ -2086,11 +2079,6 @@ struct AbcPass : public Pass { if (design->scratchpad.count("abc.P")) { config.sop_products = "-P " + design->scratchpad_get_string("abc.P"); } - if (design->scratchpad.count("abc.S")) { - config.lutin_shared = "-S " + design->scratchpad_get_string("abc.S"); - } else { - config.lutin_shared = "-S 1"; - } lut_arg = design->scratchpad_get_string("abc.lut", lut_arg); luts_arg = design->scratchpad_get_string("abc.luts", luts_arg); config.sop_mode = design->scratchpad_get_bool("abc.sop", false); @@ -2177,10 +2165,6 @@ struct AbcPass : public Pass { config.sop_products = "-P " + args[++argidx]; continue; } - if (arg == "-S" && argidx+1 < args.size()) { - config.lutin_shared = "-S " + args[++argidx]; - continue; - } if (arg == "-lut" && argidx+1 < args.size()) { lut_arg = args[++argidx]; continue; @@ -2651,7 +2635,7 @@ struct AbcPass : public Pass { sig2src[bit] = bit.wire->get_src_attribute(); } - CellTypes ct(design); + NewCellTypes ct(design); std::vector all_cells = mod->selected_cells(); pool unassigned_cells(all_cells.begin(), all_cells.end()); diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc index 16df82bb670..90c48ae341e 100644 --- a/passes/techmap/abc9.cc +++ b/passes/techmap/abc9.cc @@ -38,53 +38,53 @@ struct Abc9Pass : public ScriptPass Abc9Pass() : ScriptPass("abc9", "use ABC9 for technology mapping") { } void on_register() override { - RTLIL::constpad["abc9.script.default"] = "+&scorr; &sweep; &dc2; &dch -f -r; &ps; &if {C} {W} {D} {R} -v; &mfs"; - RTLIL::constpad["abc9.script.default.area"] = "+&scorr; &sweep; &dc2; &dch -f -r; &ps; &if {C} {W} {D} {R} -a -v; &mfs"; - RTLIL::constpad["abc9.script.default.fast"] = "+&if {C} {W} {D} {R} -v"; + RTLIL::constpad["abc9.script.default"] = "+&scorr; &sweep; &dc2; &dch -f -r; &ps; &if {W} {D} {R} -v; &mfs"; + RTLIL::constpad["abc9.script.default.area"] = "+&scorr; &sweep; &dc2; &dch -f -r; &ps; &if {W} {D} {R} -a -v; &mfs"; + RTLIL::constpad["abc9.script.default.fast"] = "+&if {W} {D} {R} -v"; // Based on ABC's &flow RTLIL::constpad["abc9.script.flow"] = "+&scorr; &sweep;" \ "&dch -C 500;" \ /* Round 1 */ \ - /* Map 1 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 1 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ "&st; &dsdb;" \ - /* Map 2 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 2 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ "&st; &syn2 -m -R 10; &dsdb;" \ "&blut -a -K 6;" \ - /* Map 3 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 3 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ /* Round 2 */ \ "&st; &sopb;" \ - /* Map 1 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 1 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ "&st; &dsdb;" \ - /* Map 2 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 2 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ "&st; &syn2 -m -R 10; &dsdb;" \ "&blut -a -K 6;" \ - /* Map 3 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 3 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ /* Round 3 */ \ - /* Map 1 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 1 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ "&st; &dsdb;" \ - /* Map 2 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;" \ + /* Map 2 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;" \ "&st; &syn2 -m -R 10; &dsdb;" \ "&blut -a -K 6;" \ - /* Map 3 */ "&unmap; &if {C} {W} {D} {R} -v; &save; &load; &mfs;"; + /* Map 3 */ "&unmap; &if {W} {D} {R} -v; &save; &load; &mfs;"; // Based on ABC's &flow2 RTLIL::constpad["abc9.script.flow2"] = "+&scorr; &sweep;" \ - /* Comm1 */ "&synch2 -K 6 -C 500; &if -m {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\ - /* Comm2 */ "&dch -C 500; &if -m {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\ + /* Comm1 */ "&synch2 -K 6 -C 500; &if -m {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\ + /* Comm2 */ "&dch -C 500; &if -m {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\ "&load; &st; &sopb -R 10 -C 4; " \ - /* Comm3 */ "&synch2 -K 6 -C 500; &if -m "/*"-E 5"*/" {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\ - /* Comm2 */ "&dch -C 500; &if -m {C} {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save; "\ + /* Comm3 */ "&synch2 -K 6 -C 500; &if -m "/*"-E 5"*/" {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save;"\ + /* Comm2 */ "&dch -C 500; &if -m {W} {D} {R} -v; &mfs "/*"-W 4 -M 500 -C 7000"*/"; &save; "\ "&load"; // Based on ABC's &flow3 -m RTLIL::constpad["abc9.script.flow3"] = "+&scorr; &sweep;" \ - "&if {C} {W} {D}; &save; &st; &syn2; &if {C} {W} {D} {R} -v; &save; &load;"\ - "&st; &if {C} -g -K 6; &dch -f; &if {C} {W} {D} {R} -v; &save; &load;"\ - "&st; &if {C} -g -K 6; &synch2; &if {C} {W} {D} {R} -v; &save; &load;"\ + "&if {W} {D}; &save; &st; &syn2; &if {W} {D} {R} -v; &save; &load;"\ + "&st; &if -g -K 6; &dch -f; &if {W} {D} {R} -v; &save; &load;"\ + "&st; &if -g -K 6; &synch2; &if {W} {D} {R} -v; &save; &load;"\ "&mfs"; // As above, but with &mfs calls as in the original &flow3 RTLIL::constpad["abc9.script.flow3mfs"] = "+&scorr; &sweep;" \ - "&if {C} {W} {D}; &save; &st; &syn2; &if {C} {W} {D} {R} -v; &save; &load;"\ - "&st; &if {C} -g -K 6; &dch -f; &if {C} {W} {D} {R} -v; &mfs; &save; &load;"\ - "&st; &if {C} -g -K 6; &synch2; &if {C} {W} {D} {R} -v; &mfs; &save; &load;"\ + "&if {W} {D}; &save; &st; &syn2; &if {W} {D} {R} -v; &save; &load;"\ + "&st; &if -g -K 6; &dch -f; &if {W} {D} {R} -v; &mfs; &save; &load;"\ + "&st; &if -g -K 6; &synch2; &if {W} {D} {R} -v; &mfs; &save; &load;"\ "&mfs"; } void help() override @@ -121,20 +121,11 @@ struct Abc9Pass : public ScriptPass log(" if no -script parameter is given, the following scripts are used:\n"); log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos))); log("\n"); - log(" -fast\n"); - log(" use different default scripts that are slightly faster (at the cost\n"); - log(" of output quality):\n"); - log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos))); - log("\n"); log(" -D \n"); log(" set delay target. the string {D} in the default scripts above is\n"); log(" replaced by this option when used, and an empty string otherwise\n"); log(" (indicating best possible delay).\n"); log("\n"); -// log(" -S \n"); -// log(" maximum number of LUT inputs shared.\n"); -// log(" (replaces {S} in the default scripts above, default: -S 1)\n"); -// log("\n"); log(" -lut \n"); log(" generate netlist using luts of (max) the specified width.\n"); log("\n"); @@ -226,8 +217,7 @@ struct Abc9Pass : public ScriptPass exe_cmd << " " << arg << " " << args[++argidx]; continue; } - if (arg == "-fast" || /* arg == "-dff" || */ - /* arg == "-nocleanup" || */ arg == "-showtmp") { + if (arg == "-showtmp") { exe_cmd << " " << arg; continue; } diff --git a/passes/techmap/abc9_exe.cc b/passes/techmap/abc9_exe.cc index 4449065f892..592f717c143 100644 --- a/passes/techmap/abc9_exe.cc +++ b/passes/techmap/abc9_exe.cc @@ -165,7 +165,7 @@ struct abc9_output_filter }; void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe_file, - vector lut_costs, bool dff_mode, std::string delay_target, std::string /*lutin_shared*/, bool fast_mode, + vector lut_costs, bool dff_mode, std::string delay_target, bool show_tempdir, std::string box_file, std::string lut_file, std::vector liberty_files, std::string wire_delay, std::string tempdir_name, std::string constr_file, std::vector dont_use_cells, std::vector genlib_files) @@ -210,26 +210,16 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe } else abc9_script += stringf("source %s", script_file); } else if (!lut_costs.empty() || !lut_file.empty()) { - abc9_script += fast_mode ? RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos) - : RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos); + abc9_script += RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos); } else log_abort(); for (size_t pos = abc9_script.find("{D}"); pos != std::string::npos; pos = abc9_script.find("{D}", pos)) abc9_script = abc9_script.substr(0, pos) + delay_target + abc9_script.substr(pos+3); - //for (size_t pos = abc9_script.find("{S}"); pos != std::string::npos; pos = abc9_script.find("{S}", pos)) - // abc9_script = abc9_script.substr(0, pos) + lutin_shared + abc9_script.substr(pos+3); - for (size_t pos = abc9_script.find("{W}"); pos != std::string::npos; pos = abc9_script.find("{W}", pos)) abc9_script = abc9_script.substr(0, pos) + wire_delay + abc9_script.substr(pos+3); - std::string C; - if (design->scratchpad.count("abc9.if.C")) - C = "-C " + design->scratchpad_get_string("abc9.if.C"); - for (size_t pos = abc9_script.find("{C}"); pos != std::string::npos; pos = abc9_script.find("{C}", pos)) - abc9_script = abc9_script.substr(0, pos) + C + abc9_script.substr(pos+3); - std::string R; if (design->scratchpad.count("abc9.if.R")) R = "-R " + design->scratchpad_get_string("abc9.if.R"); @@ -248,7 +238,7 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe } abc9_script += stringf("; &ps -l; &write -n %s/output.aig", tempdir_name); - if (design->scratchpad_get_bool("abc9.verify")) { + if (design->scratchpad_get_bool("abc9.verify", true)) { if (dff_mode) abc9_script += "; &verify -s"; else @@ -369,11 +359,6 @@ struct Abc9ExePass : public Pass { log(" if no -script parameter is given, the following scripts are used:\n"); log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default").substr(1,std::string::npos))); log("\n"); - log(" -fast\n"); - log(" use different default scripts that are slightly faster (at the cost\n"); - log(" of output quality):\n"); - log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos))); - log("\n"); log(" -constr \n"); log(" pass this file with timing constraints to ABC.\n"); log(" use with -liberty.\n"); @@ -453,9 +438,9 @@ struct Abc9ExePass : public Pass { std::string exe_file = yosys_abc_executable; std::string script_file, clk_str, box_file, lut_file, constr_file; std::vector liberty_files, genlib_files, dont_use_cells; - std::string delay_target, lutin_shared = "-S 1", wire_delay; + std::string delay_target, wire_delay; std::string tempdir_name; - bool fast_mode = false, dff_mode = false; + bool dff_mode = false; bool show_tempdir = false; vector lut_costs; @@ -472,7 +457,6 @@ struct Abc9ExePass : public Pass { } lut_arg = design->scratchpad_get_string("abc9.lut", lut_arg); luts_arg = design->scratchpad_get_string("abc9.luts", luts_arg); - fast_mode = design->scratchpad_get_bool("abc9.fast", fast_mode); dff_mode = design->scratchpad_get_bool("abc9.dff", dff_mode); show_tempdir = design->scratchpad_get_bool("abc9.showtmp", show_tempdir); box_file = design->scratchpad_get_string("abc9.box", box_file); @@ -504,20 +488,12 @@ struct Abc9ExePass : public Pass { delay_target = "-D " + args[++argidx]; continue; } - //if (arg == "-S" && argidx+1 < args.size()) { - // lutin_shared = "-S " + args[++argidx]; - // continue; - //} if (arg == "-lut" && argidx+1 < args.size()) { lut_arg = args[++argidx]; continue; } if (arg == "-luts" && argidx+1 < args.size()) { - lut_arg = args[++argidx]; - continue; - } - if (arg == "-fast") { - fast_mode = true; + luts_arg = args[++argidx]; continue; } if (arg == "-dff") { @@ -622,7 +598,7 @@ struct Abc9ExePass : public Pass { log_cmd_error("abc9_exe '-genlib' is incompatible with '-dont_use'.\n"); abc9_module(design, script_file, exe_file, lut_costs, dff_mode, - delay_target, lutin_shared, fast_mode, show_tempdir, + delay_target, show_tempdir, box_file, lut_file, liberty_files, wire_delay, tempdir_name, constr_file, dont_use_cells, genlib_files); } diff --git a/passes/techmap/abc9_ops.cc b/passes/techmap/abc9_ops.cc index 8d3869ececc..dda3858daf1 100644 --- a/passes/techmap/abc9_ops.cc +++ b/passes/techmap/abc9_ops.cc @@ -21,8 +21,9 @@ #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/utils.h" -#include "kernel/celltypes.h" +#include "kernel/newcelltypes.h" #include "kernel/timinginfo.h" +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -587,6 +588,7 @@ void break_scc(RTLIL::Module *module) auto id = it->second; auto r = ids_seen.insert(id); cell->attributes.erase(it); + // Cut exactly one representative cell per SCC id. if (!r.second) continue; for (auto &c : cell->connections_) { @@ -710,8 +712,6 @@ void prep_xaiger(RTLIL::Module *module, bool dff) SigMap sigmap(module); - dict> bit_drivers, bit_users; - TopoSort toposort; dict> box_ports; for (auto cell : module->cells()) { @@ -750,39 +750,100 @@ void prep_xaiger(RTLIL::Module *module, bool dff) } } } - else if (!yosys_celltypes.cell_known(cell->type)) - continue; - - // TODO: Speed up toposort -- we care about box ordering only - for (auto conn : cell->connections()) { - if (cell->input(conn.first)) - for (auto bit : sigmap(conn.second)) - bit_users[bit].insert(cell->name); - - if (cell->output(conn.first) && !abc9_flop) - for (auto bit : sigmap(conn.second)) - bit_drivers[bit].insert(cell->name); - } - toposort.node(cell->name); } if (box_ports.empty()) return; - for (auto &it : bit_users) - if (bit_drivers.count(it.first)) - for (auto driver_cell : bit_drivers.at(it.first)) - for (auto user_cell : it.second) - toposort.edge(driver_cell, user_cell); + // Build the same topo graph for the initial pass and the optional retry. + auto build_toposort = [&](TopoSort &toposort) { + dict> bit_drivers, bit_users; - if (ys_debug(1)) - toposort.analyze_loops = true; + for (auto cell : module->cells()) { + if (cell->type.in(ID($_DFF_N_), ID($_DFF_P_))) + continue; + if (cell->has_keep_attr()) + continue; - bool no_loops = toposort.sort(); + auto inst_module = design->module(cell->type); + bool abc9_flop = inst_module && inst_module->get_bool_attribute(ID::abc9_flop); + if (abc9_flop && !dff) + continue; + if (!(inst_module && inst_module->get_bool_attribute(ID::abc9_box)) && !yosys_celltypes.cell_known(cell->type)) + continue; + + // TODO: Speed up toposort -- we care about box ordering only + for (auto conn : cell->connections()) { + if (cell->input(conn.first)) + for (auto bit : sigmap(conn.second)) + bit_users[bit].insert(cell->name); + + if (cell->output(conn.first) && !abc9_flop) + for (auto bit : sigmap(conn.second)) + bit_drivers[bit].insert(cell->name); + } + toposort.node(cell->name); + } + + // Build producer -> consumer edges on sigmapped nets. + for (auto &it : bit_users) + if (bit_drivers.count(it.first)) + for (auto driver_cell : bit_drivers.at(it.first)) + for (auto user_cell : it.second) + toposort.edge(driver_cell, user_cell); + if (ys_debug(1)) + toposort.analyze_loops = true; + return toposort.sort(); + }; + + // Build TopoSort in a container, as we may need to conditionally rebuild it on retry. + std::optional> toposort; + toposort.emplace(); + bool no_loops = build_toposort(toposort.value()); + + // Fallback for residual loops after SCC cutting: insert additional + // breakers on non-box loop cells, then re-run toposort checks. + if (!no_loops) { + SigSpec I, O; + pool broken_cells; + + for (auto &loop : toposort.value().loops) + for (auto cell_name : loop) { + // Loop reports can overlap; cut each cell at most once. + if (!broken_cells.insert(cell_name).second) + continue; + auto cell = module->cell(cell_name); + log_assert(cell); + auto inst_module = design->module(cell->type); + if (inst_module && inst_module->get_bool_attribute(ID::abc9_box)) + continue; + for (auto &c : cell->connections_) { + if (c.second.is_fully_const()) continue; + if (cell->output(c.first)) { + Wire *w = module->addWire(NEW_ID, GetSize(c.second)); + I.append(w); + O.append(c.second); + c.second = w; + } + } + } + + if (!I.empty()) { + auto cell = module->addCell(NEW_ID, ID($__ABC9_SCC_BREAKER)); + log_assert(GetSize(I) == GetSize(O)); + cell->setParam(ID::WIDTH, GetSize(I)); + cell->setPort(ID::I, std::move(I)); + cell->setPort(ID::O, std::move(O)); + + // Rebuild topo ordering after inserting the additional breakers. + toposort.emplace(); + no_loops = build_toposort(toposort.value()); + } + } if (ys_debug(1)) { unsigned i = 0; - for (auto &it : toposort.loops) { + for (auto &it : toposort.value().loops) { log(" loop %d\n", i++); for (auto cell_name : it) { auto cell = module->cell(cell_name); @@ -806,7 +867,7 @@ void prep_xaiger(RTLIL::Module *module, bool dff) TimingInfo timing; int port_id = 1, box_count = 0; - for (auto cell_name : toposort.sorted) { + for (auto cell_name : toposort.value().sorted) { RTLIL::Cell *cell = module->cell(cell_name); log_assert(cell); @@ -1559,7 +1620,6 @@ void reintegrate(RTLIL::Module *module, bool dff_mode) } } - //log("ABC RESULTS: internal signals: %8d\n", int(signal_list.size()) - in_wires - out_wires); log("ABC RESULTS: input signals: %8d\n", in_wires); log("ABC RESULTS: output signals: %8d\n", out_wires); diff --git a/passes/techmap/constmap.cc b/passes/techmap/constmap.cc index 6d18b849450..1df50e47d46 100644 --- a/passes/techmap/constmap.cc +++ b/passes/techmap/constmap.cc @@ -71,6 +71,8 @@ struct ConstmapPass : public Pass { } extra_args(args, argidx, design); + if (celltype.empty()) + log_cmd_error("Missing required option -cell.\n"); if (design->has(celltype)) { Module *existing = design->module(celltype); diff --git a/techlibs/analogdevices/Makefile.inc b/techlibs/analogdevices/Makefile.inc new file mode 100644 index 00000000000..5c2d6f05d43 --- /dev/null +++ b/techlibs/analogdevices/Makefile.inc @@ -0,0 +1,21 @@ + +OBJS += techlibs/analogdevices/synth_analogdevices.o + +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/cells_map.v)) +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/cells_sim.v)) + +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/lutrams.txt)) +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/lutrams_map.v)) + +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/brams_defs.vh)) + +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/brams.txt)) +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/brams_map.v)) + +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/arith_map.v)) +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/ff_map.v)) +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/lut_map.v)) +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/mux_map.v)) +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/dsp_map.v)) + +$(eval $(call add_share_file,share/analogdevices,techlibs/analogdevices/abc9_model.v)) diff --git a/techlibs/analogdevices/abc9_model.v b/techlibs/analogdevices/abc9_model.v new file mode 100644 index 00000000000..a5a15026f05 --- /dev/null +++ b/techlibs/analogdevices/abc9_model.v @@ -0,0 +1,39 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * 2019 Eddie Hung + * + * 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. + * + */ + +// ============================================================================ + +// Box containing MUXF7.[AB] + MUXF8, +// Necessary to make these an atomic unit so that +// ABC cannot optimise just one of the MUXF7 away +// and expect to save on its delay +(* abc9_box, lib_whitebox *) +module \$__ANALOGDEVICES_MUXF78 (output O, input I0, I1, I2, I3, S0, S1); + assign O = S1 ? (S0 ? I3 : I2) + : (S0 ? I1 : I0); + specify + (I0 => O) = 294; + (I1 => O) = 297; + (I2 => O) = 311; + (I3 => O) = 317; + (S0 => O) = 390; + (S1 => O) = 273; + endspecify +endmodule diff --git a/techlibs/analogdevices/arith_map.v b/techlibs/analogdevices/arith_map.v new file mode 100644 index 00000000000..394a5a957c4 --- /dev/null +++ b/techlibs/analogdevices/arith_map.v @@ -0,0 +1,173 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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. + * + */ + +// ============================================================================ +// LCU + +(* techmap_celltype = "$lcu" *) +module _80_analogdevices_lcu (P, G, CI, CO); + parameter WIDTH = 2; + + (* force_downto *) + input [WIDTH-1:0] P, G; + input CI; + + (* force_downto *) + output [WIDTH-1:0] CO; + + wire _TECHMAP_FAIL_ = WIDTH <= 2; + + genvar i; + +generate + localparam CARRY4_COUNT = (WIDTH + 3) / 4; + localparam MAX_WIDTH = CARRY4_COUNT * 4; + localparam PAD_WIDTH = MAX_WIDTH - WIDTH; + + (* force_downto *) + wire [MAX_WIDTH-1:0] S = {{PAD_WIDTH{1'b0}}, P & ~G}; + (* force_downto *) + wire [MAX_WIDTH-1:0] GG = {{PAD_WIDTH{1'b0}}, G}; + (* force_downto *) + wire [MAX_WIDTH-1:0] C; + assign CO = C; + + generate for (i = 0; i < CARRY4_COUNT; i = i + 1) begin:slice + if (i == 0) begin + wire INITCO; + + CRY4INIT init + ( + .CYINIT(CI), + .CO (INITCO) + ); + + CRY4 carry4 + ( + .CYINIT(1'd0), + .CI (INITCO), + .DI (GG[i*4 +: 4]), + .S (S [i*4 +: 4]), + .CO (C [i*4 +: 4]), + ); + end else begin + CRY4 carry4 + ( + .CYINIT(1'd0), + .CI (C [i*4 - 1]), + .DI (GG[i*4 +: 4]), + .S (S [i*4 +: 4]), + .CO (C [i*4 +: 4]), + ); + end + end endgenerate +endgenerate + +endmodule + + +// ============================================================================ +// ALU + +(* techmap_celltype = "$alu" *) +module _80_analogdevices_alu (A, B, CI, BI, X, Y, CO); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 1; + parameter B_WIDTH = 1; + parameter Y_WIDTH = 1; + parameter _TECHMAP_CONSTVAL_CI_ = 0; + parameter _TECHMAP_CONSTMSK_CI_ = 0; + + (* force_downto *) + input [A_WIDTH-1:0] A; + (* force_downto *) + input [B_WIDTH-1:0] B; + (* force_downto *) + output [Y_WIDTH-1:0] X, Y; + + input CI, BI; + (* force_downto *) + output [Y_WIDTH-1:0] CO; + + wire _TECHMAP_FAIL_ = Y_WIDTH <= 2; + + (* force_downto *) + wire [Y_WIDTH-1:0] A_buf, B_buf; + \$pos #(.A_SIGNED(A_SIGNED), .A_WIDTH(A_WIDTH), .Y_WIDTH(Y_WIDTH)) A_conv (.A(A), .Y(A_buf)); + \$pos #(.A_SIGNED(B_SIGNED), .A_WIDTH(B_WIDTH), .Y_WIDTH(Y_WIDTH)) B_conv (.A(B), .Y(B_buf)); + + (* force_downto *) + wire [Y_WIDTH-1:0] AA = A_buf; + (* force_downto *) + wire [Y_WIDTH-1:0] BB = BI ? ~B_buf : B_buf; + + genvar i; + + localparam CARRY4_COUNT = (Y_WIDTH + 3) / 4; + localparam MAX_WIDTH = CARRY4_COUNT * 4; + localparam PAD_WIDTH = MAX_WIDTH - Y_WIDTH; + + (* force_downto *) + wire [MAX_WIDTH-1:0] S = {{PAD_WIDTH{1'b0}}, AA ^ BB}; + (* force_downto *) + wire [MAX_WIDTH-1:0] DI = {{PAD_WIDTH{1'b0}}, AA}; + + (* force_downto *) + wire [MAX_WIDTH-1:0] O; + (* force_downto *) + wire [MAX_WIDTH-1:0] C; + assign Y = O, CO = C; + + genvar i; + generate for (i = 0; i < CARRY4_COUNT; i = i + 1) begin:slice + if (i == 0) begin + wire INITCO; + + CRY4INIT init + ( + .CYINIT(CI), + .CO (INITCO) + ); + + CRY4 carry4 + ( + .CYINIT(1'd0), + .CI (INITCO), + .DI (DI[i*4 +: 4]), + .S (S [i*4 +: 4]), + .O (O [i*4 +: 4]), + .CO (C [i*4 +: 4]) + ); + end else begin + CRY4 carry4 + ( + .CYINIT(1'd0), + .CI (C [i*4 - 1]), + .DI (DI[i*4 +: 4]), + .S (S [i*4 +: 4]), + .O (O [i*4 +: 4]), + .CO (C [i*4 +: 4]) + ); + end + end endgenerate + + assign X = S; +endmodule + diff --git a/techlibs/analogdevices/brams.txt b/techlibs/analogdevices/brams.txt new file mode 100644 index 00000000000..10cdadefbbe --- /dev/null +++ b/techlibs/analogdevices/brams.txt @@ -0,0 +1,285 @@ +ifdef IS_T16FFC { + ram block $__ANALOGDEVICES_BLOCKRAM_FULL_ { + option "ERR" "ECC" { + style "ECC"; + option "SIZE" "2048x32" { + abits 11; + width 32; + byte 32; + option "MODE" "TDP" cost 4502; + option "MODE" "SDP" forbid; + option "MODE" "SP" forbid; + } + option "SIZE" "1024x32" { + abits 10; + width 32; + byte 32; + option "MODE" "TDP" forbid; + option "MODE" "SDP" cost 2402; + option "MODE" "SP" forbid; + } + } + option "ERR" "BP" { + style "BP"; + option "SIZE" "2048x36" { + abits 11; + width 36; + byte 9; + option "MODE" "TDP" cost 4504; + option "MODE" "SDP" forbid; + option "MODE" "SP" forbid; + } + option "SIZE" "1024x36" { + abits 10; + width 36; + byte 9; + option "MODE" "TDP" forbid; + option "MODE" "SDP" cost 2404; + option "MODE" "SP" forbid; + } + } + option "ERR" "FP" { + style "FP"; + option "SIZE" "2048x18" { + abits 11; + width 18; + byte 18; + option "MODE" "TDP" cost 2501; + option "MODE" "SDP" cost 2401; + option "MODE" "SP" forbid; + } + } + option "ERR" "NONE" { + option "SIZE" "8192x05" { + abits 13; + width 5; + byte 1; + option "MODE" "TDP" cost 2505; + option "MODE" "SDP" forbid; + option "MODE" "SP" forbid; + } + option "SIZE" "4096x09" { + abits 12; + width 9; + byte 1; + option "MODE" "TDP" cost 2509; + option "MODE" "SDP" forbid; + option "MODE" "SP" forbid; + } + option "SIZE" "4096x10" { + abits 12; + width 10; + byte 1; + option "MODE" "TDP" forbid; + option "MODE" "SDP" cost 2410; + option "MODE" "SP" forbid; + } + option "SIZE" "2048x20" { + abits 11; + width 20; + byte 1; + option "MODE" "TDP" forbid; + option "MODE" "SDP" forbid; + option "MODE" "SP" cost 2320; + } + option "SIZE" "2048x40" { + abits 11; + width 40; + byte 8; + option "MODE" "TDP" cost 4505; + option "MODE" "SDP" forbid; + option "MODE" "SP" forbid; + } + } + + # supports any initialization value, but need to export memory files + init any; + + option "MODE" "TDP" { + port srsw "A" { + clock anyedge; + clken; + rdwr no_change; + } + port srsw "B" { + clock anyedge; + clken; + rdwr no_change; + } + } + option "MODE" "SDP" { + port sw "A" { + clock anyedge; + clken; + } + port sr "B" { + clock anyedge; + clken; + } + } + option "MODE" "SP" { + port srsw "A" { + clock anyedge; + clken; + rdwr no_change; + } + } + } +} + +ram block $__ANALOGDEVICES_BLOCKRAM_HALF_ { + option "ERR" "ECC" { + style "ECC"; + option "SIZE" "1024x32" { + abits 10; + width 32; + byte 32; + option "MODE" "SDP" cost 2402; + option "MODE" "SP" forbid; + option "MODE" "SP2" forbid; + } + option "SIZE" "512x32" { + abits 9; + width 32; + byte 32; + option "MODE" "SDP" forbid; + option "MODE" "SP" cost 2302; + option "MODE" "SP2" forbid; + } + } + option "ERR" "BP" { + style "BP"; + option "SIZE" "1024x36" { + abits 10; + width 36; + byte 9; + option "MODE" "SDP" cost 2404; + option "MODE" "SP" forbid; + option "MODE" "SP2" forbid; + } + option "SIZE" "512x36" { + abits 9; + width 36; + byte 9; + option "MODE" "SDP" forbid; + option "MODE" "SP" cost 2304; + option "MODE" "SP2" forbid; + } + } + option "ERR" "FP" { + style "FP"; + option "SIZE" "1024x18" { + abits 10; + width 18; + byte 18; + option "MODE" "SDP" forbid; + option "MODE" "SP" forbid; + option "MODE" "SP2" cost 2301; + } + } + option "ERR" "NONE" { + option "SIZE" "4096x05" { + abits 12; + width 5; + byte 1; + option "MODE" "SDP" cost 2405; + option "MODE" "SP" cost 2305; + option "MODE" "SP2" forbid; + } + option "SIZE" "2048x09" { + abits 11; + width 9; + byte 1; + option "MODE" "SDP" cost 2409; + option "MODE" "SP" forbid; + option "MODE" "SP2" cost 2309; + } + option "SIZE" "2048x10" { + abits 11; + width 10; + byte 1; + option "MODE" "SDP" cost 2410; + option "MODE" "SP" forbid; + option "MODE" "SP2" forbid; + } + option "SIZE" "1024x20" { + abits 10; + width 20; + byte 1; + option "MODE" "SDP" forbid; + option "MODE" "SP" cost 2320; + option "MODE" "SP2" forbid; + } + option "SIZE" "1024x40" { + abits 10; + width 40; + byte 8; + option "MODE" "SDP" cost 2405; + option "MODE" "SP" forbid; + option "MODE" "SP2" forbid; + } + } + + option "MODE" "SDP" { + ifdef IS_T16FFC forbid; + port sw "A" { + clock anyedge; + clken; + } + port sr "B" { + clock anyedge; + clken; + } + } + option "MODE" "SP" { + ifdef IS_T16FFC forbid; + port srsw "A" { + clock anyedge; + clken; + rdwr no_change; + } + } + option "MODE" "SP2" { + ifdef IS_T40LP forbid; + port srsw "A" { + clock anyedge; + clken; + rdwr no_change; + } + } +} + +ifdef IS_T40LP { + ram block $__ANALOGDEVICES_BLOCKRAM_QUARTER_ { + option "ERR" "BP" { + style "BP"; + option "SIZE" "512x18" { + abits 9; + width 18; + byte 9; + option "MODE" "SP2" cost 2202; + } + } + option "ERR" "NONE" { + option "SIZE" "2048x05" { + abits 11; + width 5; + byte 1; + option "MODE" "SP2" cost 2205; + } + option "SIZE" "1024x09" { + abits 10; + width 9; + byte 1; + option "MODE" "SP2" cost 2209; + } + } + option "MODE" "SP2" { + port srsw "A" { + clock anyedge; + clken; + rdwr no_change; + } + } + } +} diff --git a/techlibs/analogdevices/brams_defs.vh b/techlibs/analogdevices/brams_defs.vh new file mode 100644 index 00000000000..69fe5d716d0 --- /dev/null +++ b/techlibs/analogdevices/brams_defs.vh @@ -0,0 +1,561 @@ +`define PARAMS_INIT_9 \ + .INIT_00(slice_init('h00)), \ + .INIT_01(slice_init('h01)), \ + .INIT_02(slice_init('h02)), \ + .INIT_03(slice_init('h03)), \ + .INIT_04(slice_init('h04)), \ + .INIT_05(slice_init('h05)), \ + .INIT_06(slice_init('h06)), \ + .INIT_07(slice_init('h07)), \ + .INIT_08(slice_init('h08)), \ + .INIT_09(slice_init('h09)), \ + .INIT_0A(slice_init('h0a)), \ + .INIT_0B(slice_init('h0b)), \ + .INIT_0C(slice_init('h0c)), \ + .INIT_0D(slice_init('h0d)), \ + .INIT_0E(slice_init('h0e)), \ + .INIT_0F(slice_init('h0f)), \ + .INIT_10(slice_init('h10)), \ + .INIT_11(slice_init('h11)), \ + .INIT_12(slice_init('h12)), \ + .INIT_13(slice_init('h13)), \ + .INIT_14(slice_init('h14)), \ + .INIT_15(slice_init('h15)), \ + .INIT_16(slice_init('h16)), \ + .INIT_17(slice_init('h17)), \ + .INIT_18(slice_init('h18)), \ + .INIT_19(slice_init('h19)), \ + .INIT_1A(slice_init('h1a)), \ + .INIT_1B(slice_init('h1b)), \ + .INIT_1C(slice_init('h1c)), \ + .INIT_1D(slice_init('h1d)), \ + .INIT_1E(slice_init('h1e)), \ + .INIT_1F(slice_init('h1f)), + +`define PARAMS_INITP_9 \ + .INITP_00(slice_initp('h00)), \ + .INITP_01(slice_initp('h01)), \ + .INITP_02(slice_initp('h02)), \ + .INITP_03(slice_initp('h03)), + +`define PARAMS_INIT_18 \ + .INIT_00(slice_init('h00)), \ + .INIT_01(slice_init('h01)), \ + .INIT_02(slice_init('h02)), \ + .INIT_03(slice_init('h03)), \ + .INIT_04(slice_init('h04)), \ + .INIT_05(slice_init('h05)), \ + .INIT_06(slice_init('h06)), \ + .INIT_07(slice_init('h07)), \ + .INIT_08(slice_init('h08)), \ + .INIT_09(slice_init('h09)), \ + .INIT_0A(slice_init('h0a)), \ + .INIT_0B(slice_init('h0b)), \ + .INIT_0C(slice_init('h0c)), \ + .INIT_0D(slice_init('h0d)), \ + .INIT_0E(slice_init('h0e)), \ + .INIT_0F(slice_init('h0f)), \ + .INIT_10(slice_init('h10)), \ + .INIT_11(slice_init('h11)), \ + .INIT_12(slice_init('h12)), \ + .INIT_13(slice_init('h13)), \ + .INIT_14(slice_init('h14)), \ + .INIT_15(slice_init('h15)), \ + .INIT_16(slice_init('h16)), \ + .INIT_17(slice_init('h17)), \ + .INIT_18(slice_init('h18)), \ + .INIT_19(slice_init('h19)), \ + .INIT_1A(slice_init('h1a)), \ + .INIT_1B(slice_init('h1b)), \ + .INIT_1C(slice_init('h1c)), \ + .INIT_1D(slice_init('h1d)), \ + .INIT_1E(slice_init('h1e)), \ + .INIT_1F(slice_init('h1f)), \ + .INIT_20(slice_init('h20)), \ + .INIT_21(slice_init('h21)), \ + .INIT_22(slice_init('h22)), \ + .INIT_23(slice_init('h23)), \ + .INIT_24(slice_init('h24)), \ + .INIT_25(slice_init('h25)), \ + .INIT_26(slice_init('h26)), \ + .INIT_27(slice_init('h27)), \ + .INIT_28(slice_init('h28)), \ + .INIT_29(slice_init('h29)), \ + .INIT_2A(slice_init('h2a)), \ + .INIT_2B(slice_init('h2b)), \ + .INIT_2C(slice_init('h2c)), \ + .INIT_2D(slice_init('h2d)), \ + .INIT_2E(slice_init('h2e)), \ + .INIT_2F(slice_init('h2f)), \ + .INIT_30(slice_init('h30)), \ + .INIT_31(slice_init('h31)), \ + .INIT_32(slice_init('h32)), \ + .INIT_33(slice_init('h33)), \ + .INIT_34(slice_init('h34)), \ + .INIT_35(slice_init('h35)), \ + .INIT_36(slice_init('h36)), \ + .INIT_37(slice_init('h37)), \ + .INIT_38(slice_init('h38)), \ + .INIT_39(slice_init('h39)), \ + .INIT_3A(slice_init('h3a)), \ + .INIT_3B(slice_init('h3b)), \ + .INIT_3C(slice_init('h3c)), \ + .INIT_3D(slice_init('h3d)), \ + .INIT_3E(slice_init('h3e)), \ + .INIT_3F(slice_init('h3f)), + +`define PARAMS_INIT_18_U \ + .INIT_00(slice_init('h40)), \ + .INIT_01(slice_init('h41)), \ + .INIT_02(slice_init('h42)), \ + .INIT_03(slice_init('h43)), \ + .INIT_04(slice_init('h44)), \ + .INIT_05(slice_init('h45)), \ + .INIT_06(slice_init('h46)), \ + .INIT_07(slice_init('h47)), \ + .INIT_08(slice_init('h48)), \ + .INIT_09(slice_init('h49)), \ + .INIT_0A(slice_init('h4a)), \ + .INIT_0B(slice_init('h4b)), \ + .INIT_0C(slice_init('h4c)), \ + .INIT_0D(slice_init('h4d)), \ + .INIT_0E(slice_init('h4e)), \ + .INIT_0F(slice_init('h4f)), \ + .INIT_10(slice_init('h50)), \ + .INIT_11(slice_init('h51)), \ + .INIT_12(slice_init('h52)), \ + .INIT_13(slice_init('h53)), \ + .INIT_14(slice_init('h54)), \ + .INIT_15(slice_init('h55)), \ + .INIT_16(slice_init('h56)), \ + .INIT_17(slice_init('h57)), \ + .INIT_18(slice_init('h58)), \ + .INIT_19(slice_init('h59)), \ + .INIT_1A(slice_init('h5a)), \ + .INIT_1B(slice_init('h5b)), \ + .INIT_1C(slice_init('h5c)), \ + .INIT_1D(slice_init('h5d)), \ + .INIT_1E(slice_init('h5e)), \ + .INIT_1F(slice_init('h5f)), \ + .INIT_20(slice_init('h60)), \ + .INIT_21(slice_init('h61)), \ + .INIT_22(slice_init('h62)), \ + .INIT_23(slice_init('h63)), \ + .INIT_24(slice_init('h64)), \ + .INIT_25(slice_init('h65)), \ + .INIT_26(slice_init('h66)), \ + .INIT_27(slice_init('h67)), \ + .INIT_28(slice_init('h68)), \ + .INIT_29(slice_init('h69)), \ + .INIT_2A(slice_init('h6a)), \ + .INIT_2B(slice_init('h6b)), \ + .INIT_2C(slice_init('h6c)), \ + .INIT_2D(slice_init('h6d)), \ + .INIT_2E(slice_init('h6e)), \ + .INIT_2F(slice_init('h6f)), \ + .INIT_30(slice_init('h70)), \ + .INIT_31(slice_init('h71)), \ + .INIT_32(slice_init('h72)), \ + .INIT_33(slice_init('h73)), \ + .INIT_34(slice_init('h74)), \ + .INIT_35(slice_init('h75)), \ + .INIT_36(slice_init('h76)), \ + .INIT_37(slice_init('h77)), \ + .INIT_38(slice_init('h78)), \ + .INIT_39(slice_init('h79)), \ + .INIT_3A(slice_init('h7a)), \ + .INIT_3B(slice_init('h7b)), \ + .INIT_3C(slice_init('h7c)), \ + .INIT_3D(slice_init('h7d)), \ + .INIT_3E(slice_init('h7e)), \ + .INIT_3F(slice_init('h7f)), + +`define PARAMS_INITP_18 \ + .INITP_00(slice_initp('h00)), \ + .INITP_01(slice_initp('h01)), \ + .INITP_02(slice_initp('h02)), \ + .INITP_03(slice_initp('h03)), \ + .INITP_04(slice_initp('h04)), \ + .INITP_05(slice_initp('h05)), \ + .INITP_06(slice_initp('h06)), \ + .INITP_07(slice_initp('h07)), + +`define PARAMS_INIT_36 \ + .INIT_00(slice_init('h00)), \ + .INIT_01(slice_init('h01)), \ + .INIT_02(slice_init('h02)), \ + .INIT_03(slice_init('h03)), \ + .INIT_04(slice_init('h04)), \ + .INIT_05(slice_init('h05)), \ + .INIT_06(slice_init('h06)), \ + .INIT_07(slice_init('h07)), \ + .INIT_08(slice_init('h08)), \ + .INIT_09(slice_init('h09)), \ + .INIT_0A(slice_init('h0a)), \ + .INIT_0B(slice_init('h0b)), \ + .INIT_0C(slice_init('h0c)), \ + .INIT_0D(slice_init('h0d)), \ + .INIT_0E(slice_init('h0e)), \ + .INIT_0F(slice_init('h0f)), \ + .INIT_10(slice_init('h10)), \ + .INIT_11(slice_init('h11)), \ + .INIT_12(slice_init('h12)), \ + .INIT_13(slice_init('h13)), \ + .INIT_14(slice_init('h14)), \ + .INIT_15(slice_init('h15)), \ + .INIT_16(slice_init('h16)), \ + .INIT_17(slice_init('h17)), \ + .INIT_18(slice_init('h18)), \ + .INIT_19(slice_init('h19)), \ + .INIT_1A(slice_init('h1a)), \ + .INIT_1B(slice_init('h1b)), \ + .INIT_1C(slice_init('h1c)), \ + .INIT_1D(slice_init('h1d)), \ + .INIT_1E(slice_init('h1e)), \ + .INIT_1F(slice_init('h1f)), \ + .INIT_20(slice_init('h20)), \ + .INIT_21(slice_init('h21)), \ + .INIT_22(slice_init('h22)), \ + .INIT_23(slice_init('h23)), \ + .INIT_24(slice_init('h24)), \ + .INIT_25(slice_init('h25)), \ + .INIT_26(slice_init('h26)), \ + .INIT_27(slice_init('h27)), \ + .INIT_28(slice_init('h28)), \ + .INIT_29(slice_init('h29)), \ + .INIT_2A(slice_init('h2a)), \ + .INIT_2B(slice_init('h2b)), \ + .INIT_2C(slice_init('h2c)), \ + .INIT_2D(slice_init('h2d)), \ + .INIT_2E(slice_init('h2e)), \ + .INIT_2F(slice_init('h2f)), \ + .INIT_30(slice_init('h30)), \ + .INIT_31(slice_init('h31)), \ + .INIT_32(slice_init('h32)), \ + .INIT_33(slice_init('h33)), \ + .INIT_34(slice_init('h34)), \ + .INIT_35(slice_init('h35)), \ + .INIT_36(slice_init('h36)), \ + .INIT_37(slice_init('h37)), \ + .INIT_38(slice_init('h38)), \ + .INIT_39(slice_init('h39)), \ + .INIT_3A(slice_init('h3a)), \ + .INIT_3B(slice_init('h3b)), \ + .INIT_3C(slice_init('h3c)), \ + .INIT_3D(slice_init('h3d)), \ + .INIT_3E(slice_init('h3e)), \ + .INIT_3F(slice_init('h3f)), \ + .INIT_40(slice_init('h40)), \ + .INIT_41(slice_init('h41)), \ + .INIT_42(slice_init('h42)), \ + .INIT_43(slice_init('h43)), \ + .INIT_44(slice_init('h44)), \ + .INIT_45(slice_init('h45)), \ + .INIT_46(slice_init('h46)), \ + .INIT_47(slice_init('h47)), \ + .INIT_48(slice_init('h48)), \ + .INIT_49(slice_init('h49)), \ + .INIT_4A(slice_init('h4a)), \ + .INIT_4B(slice_init('h4b)), \ + .INIT_4C(slice_init('h4c)), \ + .INIT_4D(slice_init('h4d)), \ + .INIT_4E(slice_init('h4e)), \ + .INIT_4F(slice_init('h4f)), \ + .INIT_50(slice_init('h50)), \ + .INIT_51(slice_init('h51)), \ + .INIT_52(slice_init('h52)), \ + .INIT_53(slice_init('h53)), \ + .INIT_54(slice_init('h54)), \ + .INIT_55(slice_init('h55)), \ + .INIT_56(slice_init('h56)), \ + .INIT_57(slice_init('h57)), \ + .INIT_58(slice_init('h58)), \ + .INIT_59(slice_init('h59)), \ + .INIT_5A(slice_init('h5a)), \ + .INIT_5B(slice_init('h5b)), \ + .INIT_5C(slice_init('h5c)), \ + .INIT_5D(slice_init('h5d)), \ + .INIT_5E(slice_init('h5e)), \ + .INIT_5F(slice_init('h5f)), \ + .INIT_60(slice_init('h60)), \ + .INIT_61(slice_init('h61)), \ + .INIT_62(slice_init('h62)), \ + .INIT_63(slice_init('h63)), \ + .INIT_64(slice_init('h64)), \ + .INIT_65(slice_init('h65)), \ + .INIT_66(slice_init('h66)), \ + .INIT_67(slice_init('h67)), \ + .INIT_68(slice_init('h68)), \ + .INIT_69(slice_init('h69)), \ + .INIT_6A(slice_init('h6a)), \ + .INIT_6B(slice_init('h6b)), \ + .INIT_6C(slice_init('h6c)), \ + .INIT_6D(slice_init('h6d)), \ + .INIT_6E(slice_init('h6e)), \ + .INIT_6F(slice_init('h6f)), \ + .INIT_70(slice_init('h70)), \ + .INIT_71(slice_init('h71)), \ + .INIT_72(slice_init('h72)), \ + .INIT_73(slice_init('h73)), \ + .INIT_74(slice_init('h74)), \ + .INIT_75(slice_init('h75)), \ + .INIT_76(slice_init('h76)), \ + .INIT_77(slice_init('h77)), \ + .INIT_78(slice_init('h78)), \ + .INIT_79(slice_init('h79)), \ + .INIT_7A(slice_init('h7a)), \ + .INIT_7B(slice_init('h7b)), \ + .INIT_7C(slice_init('h7c)), \ + .INIT_7D(slice_init('h7d)), \ + .INIT_7E(slice_init('h7e)), \ + .INIT_7F(slice_init('h7f)), + +`define PARAMS_INIT_36_U \ + .INIT_00(slice_init('h80)), \ + .INIT_01(slice_init('h81)), \ + .INIT_02(slice_init('h82)), \ + .INIT_03(slice_init('h83)), \ + .INIT_04(slice_init('h84)), \ + .INIT_05(slice_init('h85)), \ + .INIT_06(slice_init('h86)), \ + .INIT_07(slice_init('h87)), \ + .INIT_08(slice_init('h88)), \ + .INIT_09(slice_init('h89)), \ + .INIT_0A(slice_init('h8a)), \ + .INIT_0B(slice_init('h8b)), \ + .INIT_0C(slice_init('h8c)), \ + .INIT_0D(slice_init('h8d)), \ + .INIT_0E(slice_init('h8e)), \ + .INIT_0F(slice_init('h8f)), \ + .INIT_10(slice_init('h90)), \ + .INIT_11(slice_init('h91)), \ + .INIT_12(slice_init('h92)), \ + .INIT_13(slice_init('h93)), \ + .INIT_14(slice_init('h94)), \ + .INIT_15(slice_init('h95)), \ + .INIT_16(slice_init('h96)), \ + .INIT_17(slice_init('h97)), \ + .INIT_18(slice_init('h98)), \ + .INIT_19(slice_init('h99)), \ + .INIT_1A(slice_init('h9a)), \ + .INIT_1B(slice_init('h9b)), \ + .INIT_1C(slice_init('h9c)), \ + .INIT_1D(slice_init('h9d)), \ + .INIT_1E(slice_init('h9e)), \ + .INIT_1F(slice_init('h9f)), \ + .INIT_20(slice_init('ha0)), \ + .INIT_21(slice_init('ha1)), \ + .INIT_22(slice_init('ha2)), \ + .INIT_23(slice_init('ha3)), \ + .INIT_24(slice_init('ha4)), \ + .INIT_25(slice_init('ha5)), \ + .INIT_26(slice_init('ha6)), \ + .INIT_27(slice_init('ha7)), \ + .INIT_28(slice_init('ha8)), \ + .INIT_29(slice_init('ha9)), \ + .INIT_2A(slice_init('haa)), \ + .INIT_2B(slice_init('hab)), \ + .INIT_2C(slice_init('hac)), \ + .INIT_2D(slice_init('had)), \ + .INIT_2E(slice_init('hae)), \ + .INIT_2F(slice_init('haf)), \ + .INIT_30(slice_init('hb0)), \ + .INIT_31(slice_init('hb1)), \ + .INIT_32(slice_init('hb2)), \ + .INIT_33(slice_init('hb3)), \ + .INIT_34(slice_init('hb4)), \ + .INIT_35(slice_init('hb5)), \ + .INIT_36(slice_init('hb6)), \ + .INIT_37(slice_init('hb7)), \ + .INIT_38(slice_init('hb8)), \ + .INIT_39(slice_init('hb9)), \ + .INIT_3A(slice_init('hba)), \ + .INIT_3B(slice_init('hbb)), \ + .INIT_3C(slice_init('hbc)), \ + .INIT_3D(slice_init('hbd)), \ + .INIT_3E(slice_init('hbe)), \ + .INIT_3F(slice_init('hbf)), \ + .INIT_40(slice_init('hc0)), \ + .INIT_41(slice_init('hc1)), \ + .INIT_42(slice_init('hc2)), \ + .INIT_43(slice_init('hc3)), \ + .INIT_44(slice_init('hc4)), \ + .INIT_45(slice_init('hc5)), \ + .INIT_46(slice_init('hc6)), \ + .INIT_47(slice_init('hc7)), \ + .INIT_48(slice_init('hc8)), \ + .INIT_49(slice_init('hc9)), \ + .INIT_4A(slice_init('hca)), \ + .INIT_4B(slice_init('hcb)), \ + .INIT_4C(slice_init('hcc)), \ + .INIT_4D(slice_init('hcd)), \ + .INIT_4E(slice_init('hce)), \ + .INIT_4F(slice_init('hcf)), \ + .INIT_50(slice_init('hd0)), \ + .INIT_51(slice_init('hd1)), \ + .INIT_52(slice_init('hd2)), \ + .INIT_53(slice_init('hd3)), \ + .INIT_54(slice_init('hd4)), \ + .INIT_55(slice_init('hd5)), \ + .INIT_56(slice_init('hd6)), \ + .INIT_57(slice_init('hd7)), \ + .INIT_58(slice_init('hd8)), \ + .INIT_59(slice_init('hd9)), \ + .INIT_5A(slice_init('hda)), \ + .INIT_5B(slice_init('hdb)), \ + .INIT_5C(slice_init('hdc)), \ + .INIT_5D(slice_init('hdd)), \ + .INIT_5E(slice_init('hde)), \ + .INIT_5F(slice_init('hdf)), \ + .INIT_60(slice_init('he0)), \ + .INIT_61(slice_init('he1)), \ + .INIT_62(slice_init('he2)), \ + .INIT_63(slice_init('he3)), \ + .INIT_64(slice_init('he4)), \ + .INIT_65(slice_init('he5)), \ + .INIT_66(slice_init('he6)), \ + .INIT_67(slice_init('he7)), \ + .INIT_68(slice_init('he8)), \ + .INIT_69(slice_init('he9)), \ + .INIT_6A(slice_init('hea)), \ + .INIT_6B(slice_init('heb)), \ + .INIT_6C(slice_init('hec)), \ + .INIT_6D(slice_init('hed)), \ + .INIT_6E(slice_init('hee)), \ + .INIT_6F(slice_init('hef)), \ + .INIT_70(slice_init('hf0)), \ + .INIT_71(slice_init('hf1)), \ + .INIT_72(slice_init('hf2)), \ + .INIT_73(slice_init('hf3)), \ + .INIT_74(slice_init('hf4)), \ + .INIT_75(slice_init('hf5)), \ + .INIT_76(slice_init('hf6)), \ + .INIT_77(slice_init('hf7)), \ + .INIT_78(slice_init('hf8)), \ + .INIT_79(slice_init('hf9)), \ + .INIT_7A(slice_init('hfa)), \ + .INIT_7B(slice_init('hfb)), \ + .INIT_7C(slice_init('hfc)), \ + .INIT_7D(slice_init('hfd)), \ + .INIT_7E(slice_init('hfe)), \ + .INIT_7F(slice_init('hff)), + +`define PARAMS_INITP_36 \ + .INITP_00(slice_initp('h00)), \ + .INITP_01(slice_initp('h01)), \ + .INITP_02(slice_initp('h02)), \ + .INITP_03(slice_initp('h03)), \ + .INITP_04(slice_initp('h04)), \ + .INITP_05(slice_initp('h05)), \ + .INITP_06(slice_initp('h06)), \ + .INITP_07(slice_initp('h07)), \ + .INITP_08(slice_initp('h08)), \ + .INITP_09(slice_initp('h09)), \ + .INITP_0A(slice_initp('h0a)), \ + .INITP_0B(slice_initp('h0b)), \ + .INITP_0C(slice_initp('h0c)), \ + .INITP_0D(slice_initp('h0d)), \ + .INITP_0E(slice_initp('h0e)), \ + .INITP_0F(slice_initp('h0f)), + +`define MAKE_DO(do, dop, rdata) \ + wire [63:0] do; \ + wire [7:0] dop; \ + assign rdata = { \ + dop[7], \ + do[63:56], \ + dop[6], \ + do[55:48], \ + dop[5], \ + do[47:40], \ + dop[4], \ + do[39:32], \ + dop[3], \ + do[31:24], \ + dop[2], \ + do[23:16], \ + dop[1], \ + do[15:8], \ + dop[0], \ + do[7:0] \ + }; + +`define MAKE_DI(di, dip, wdata) \ + wire [63:0] di; \ + wire [7:0] dip; \ + assign { \ + dip[7], \ + di[63:56], \ + dip[6], \ + di[55:48], \ + dip[5], \ + di[47:40], \ + dip[4], \ + di[39:32], \ + dip[3], \ + di[31:24], \ + dip[2], \ + di[23:16], \ + dip[1], \ + di[15:8], \ + dip[0], \ + di[7:0] \ + } = wdata; + +function [71:0] ival; + input integer width; + input [71:0] val; + if (width == 72) + ival = { + val[71], + val[62], + val[53], + val[44], + val[35], + val[26], + val[17], + val[8], + val[70:63], + val[61:54], + val[52:45], + val[43:36], + val[34:27], + val[25:18], + val[16:9], + val[7:0] + }; + else if (width == 36) + ival = { + val[35], + val[26], + val[17], + val[8], + val[34:27], + val[25:18], + val[16:9], + val[7:0] + }; + else if (width == 18) + ival = { + val[17], + val[8], + val[16:9], + val[7:0] + }; + else + ival = val; +endfunction + +function [255:0] slice_init; + input integer idx; + integer i; + for (i = 0; i < 32; i = i + 1) + slice_init[i*8+:8] = INIT[(idx * 32 + i)*9+:8]; +endfunction + +function [255:0] slice_initp; + input integer idx; + integer i; + for (i = 0; i < 256; i = i + 1) + slice_initp[i] = INIT[(idx * 256 + i)*9+8]; +endfunction diff --git a/techlibs/analogdevices/brams_map.v b/techlibs/analogdevices/brams_map.v new file mode 100644 index 00000000000..f1acaaf78f0 --- /dev/null +++ b/techlibs/analogdevices/brams_map.v @@ -0,0 +1,238 @@ +module $__ANALOGDEVICES_BLOCKRAM_FULL_ (...); + // libmap params + parameter INIT = 0; + parameter OPTION_MODE = "NONE"; + parameter OPTION_SIZE = "NONE"; + parameter OPTION_ERR = "NONE"; + parameter PORT_A_WR_EN_WIDTH = 1; + parameter PORT_A_CLK_POL = 1; + parameter PORT_B_WR_EN_WIDTH = PORT_A_WR_EN_WIDTH; + parameter PORT_B_CLK_POL = 1; + + // needs -force-params + parameter WIDTH = 40; + parameter ABITS = 13; + + // non libmap params +`ifdef IS_T40LP + localparam NODE = "T40LP_Gen2.4"; +`endif +`ifdef IS_T16FFC + localparam NODE = "T16FFC_Gen2.4"; +`endif + // localparam BRAM_MODE = "SDP_2048x36_BP"; + localparam BRAM_MODE = (OPTION_ERR!="NONE") ? {OPTION_MODE, "_", OPTION_SIZE, "_", OPTION_ERR} : + {OPTION_MODE, "_", OPTION_SIZE}; + localparam PBITS = (OPTION_ERR=="BP") ? PORT_A_WR_EN_WIDTH : 1; + + // libmap ports + input PORT_A_CLK; + input PORT_A_CLK_EN; + input [ABITS-1:0] PORT_A_ADDR; + input [WIDTH-1:0] PORT_A_WR_DATA; + output [WIDTH-1:0] PORT_A_RD_DATA; + input [PORT_A_WR_EN_WIDTH-1:0] PORT_A_WR_EN; + + input PORT_B_CLK; + input PORT_B_CLK_EN; + input [ABITS-1:0] PORT_B_ADDR; + input [WIDTH-1:0] PORT_B_WR_DATA; + output [WIDTH-1:0] PORT_B_RD_DATA; + input [PORT_B_WR_EN_WIDTH-1:0] PORT_B_WR_EN; + +`ifdef IS_T40LP + RBRAM +`endif +`ifdef IS_T16FFC + RBRAM2 +`endif + #( + .TARGET_NODE(NODE), + .BRAM_MODE(BRAM_MODE), + .QA_REG((OPTION_ERR=="ECC") ? 1 : 0), + .QB_REG((OPTION_ERR=="ECC") ? 1 : 0), + .CLKA_INV(!PORT_A_CLK_POL), + .CLKB_INV(!PORT_B_CLK_POL), + .DATA_WIDTH(WIDTH), + .ADDR_WIDTH(ABITS), + .WE_WIDTH(PORT_A_WR_EN_WIDTH), + .PERR_WIDTH(PBITS), + ) + _TECHMAP_REPLACE_ + ( + .QA(PORT_A_RD_DATA), + .DA(PORT_A_WR_DATA), + .CEA(PORT_A_CLK_EN), + .WEA(PORT_A_WR_EN), + .AA(PORT_A_ADDR), + .CLKA(PORT_A_CLK), + .QB(PORT_B_RD_DATA), + .DB(PORT_B_WR_DATA), + .CEB(PORT_B_CLK_EN), + .WEB(PORT_B_WR_EN), + .AB(PORT_B_ADDR), + .CLKB(PORT_B_CLK), + ); + + // check config + generate + if (PORT_A_WR_EN_WIDTH == PORT_B_WR_EN_WIDTH) + case (BRAM_MODE) + `ifdef IS_T40LP + "SDP_1024x18_FP", + "SDP_1024x16_BP", + "SDP_2048x09", + "SDP_4096x05", + "SDP_1024x32_ECC", + "SDP_1024x40", + "SDP_1024x36_BP", + "SDP_512x32_ECC", + "SDP_512x36_BP", + "SDP_2048x10", + "SP_512x32_ECC", + "SP_512x36_BP", + "SP_1024x20", + "SP2_512x18_BP", + "SP2_1024x09", + "SP2_2048x05": wire _TECHMAP_FAIL_ = 0; + `endif + `ifdef IS_T16FFC + "TDP_2048x18_FP", + "TDP_2048x16_BP", + "TDP_4096x09", + "TDP_8192x05", + "TDP_2048x32_ECC", + "TDP_2048x40", + "TDP_2048x36_BP", + "SDP_2048x18_FP", + "SDP_2048x16_BP", + // The following are rejected in eXpreso + // "SDP_4096x09", + // "SDP_8192x05", + // "SDP_2048x32_ECC", + // "SDP_2048x40", + // "SDP_2048x36_BP", + "SDP_1024x32_ECC", + "SDP_1024x36_BP", + "SDP_4096x10", + "SP_1024x32_ECC", + "SP_1024x36_BP", + "SP_2048x20", + "SP2_1024x18_BP", + "SP2_2048x09", + "SP2_4096x05": wire _TECHMAP_FAIL_ = 0; + `endif + default: wire _TECHMAP_FAIL_ = 1; + endcase + else + wire _TECHMAP_FAIL_ = 1; + endgenerate + +endmodule + +module $__ANALOGDEVICES_BLOCKRAM_HALF_ (...); + // libmap params + parameter INIT = 0; + parameter OPTION_MODE = "NONE"; + parameter OPTION_SIZE = "NONE"; + parameter OPTION_ERR = "NONE"; + parameter PORT_A_WR_EN_WIDTH = 1; + parameter PORT_A_CLK_POL = 1; + parameter PORT_B_WR_EN_WIDTH = PORT_A_WR_EN_WIDTH; + parameter PORT_B_CLK_POL = 1; + + // needs -force-params + parameter WIDTH = 40; + parameter ABITS = 13; + + // libmap ports + input PORT_A_CLK; + input PORT_A_CLK_EN; + input [ABITS-1:0] PORT_A_ADDR; + input [WIDTH-1:0] PORT_A_WR_DATA; + output [WIDTH-1:0] PORT_A_RD_DATA; + input [PORT_A_WR_EN_WIDTH-1:0] PORT_A_WR_EN; + + input PORT_B_CLK; + input PORT_B_CLK_EN; + input [ABITS-1:0] PORT_B_ADDR; + input [WIDTH-1:0] PORT_B_WR_DATA; + output [WIDTH-1:0] PORT_B_RD_DATA; + input [PORT_B_WR_EN_WIDTH-1:0] PORT_B_WR_EN; + + $__ANALOGDEVICES_BLOCKRAM_FULL_ + # ( + .INIT(INIT), + .OPTION_MODE(OPTION_MODE), + .OPTION_SIZE(OPTION_SIZE), + .OPTION_ERR(OPTION_ERR), + .PORT_A_WR_EN_WIDTH(PORT_A_WR_EN_WIDTH), + .PORT_A_CLK_POL(PORT_A_CLK_POL), + .PORT_B_WR_EN_WIDTH(PORT_B_WR_EN_WIDTH), + .PORT_B_CLK_POL(PORT_B_CLK_POL), + .WIDTH(WIDTH), + .ABITS(ABITS) + ) + _TECHMAP_REPLACE_ + ( + .PORT_A_CLK(PORT_A_CLK), + .PORT_A_CLK_EN(PORT_A_CLK_EN), + .PORT_A_ADDR(PORT_A_ADDR), + .PORT_A_WR_DATA(PORT_A_WR_DATA), + .PORT_A_RD_DATA(PORT_A_RD_DATA), + .PORT_A_WR_EN(PORT_A_WR_EN), + .PORT_B_CLK(PORT_B_CLK), + .PORT_B_CLK_EN(PORT_B_CLK_EN), + .PORT_B_ADDR(PORT_B_ADDR), + .PORT_B_WR_DATA(PORT_B_WR_DATA), + .PORT_B_RD_DATA(PORT_B_RD_DATA), + .PORT_B_WR_EN(PORT_B_WR_EN) + ); +endmodule + +module $__ANALOGDEVICES_BLOCKRAM_QUARTER_ (...); + // libmap params + parameter INIT = 0; + parameter OPTION_MODE = "NONE"; + parameter OPTION_SIZE = "NONE"; + parameter OPTION_ERR = "NONE"; + parameter PORT_A_WR_EN_WIDTH = 1; + parameter PORT_A_CLK_POL = 1; + parameter PORT_B_WR_EN_WIDTH = PORT_A_WR_EN_WIDTH; + parameter PORT_B_CLK_POL = 1; + + // needs -force-params + parameter WIDTH = 40; + parameter ABITS = 13; + + // libmap ports + input PORT_A_CLK; + input PORT_A_CLK_EN; + input [ABITS-1:0] PORT_A_ADDR; + input [WIDTH-1:0] PORT_A_WR_DATA; + output [WIDTH-1:0] PORT_A_RD_DATA; + input [PORT_A_WR_EN_WIDTH-1:0] PORT_A_WR_EN; + + $__ANALOGDEVICES_BLOCKRAM_FULL_ + # ( + .INIT(INIT), + .OPTION_MODE(OPTION_MODE), + .OPTION_SIZE(OPTION_SIZE), + .OPTION_ERR(OPTION_ERR), + .PORT_A_WR_EN_WIDTH(PORT_A_WR_EN_WIDTH), + .PORT_A_CLK_POL(PORT_A_CLK_POL), + .PORT_B_WR_EN_WIDTH(PORT_B_WR_EN_WIDTH), + .PORT_B_CLK_POL(PORT_B_CLK_POL), + .WIDTH(WIDTH), + .ABITS(ABITS) + ) + _TECHMAP_REPLACE_ + ( + .PORT_A_CLK(PORT_A_CLK), + .PORT_A_CLK_EN(PORT_A_CLK_EN), + .PORT_A_ADDR(PORT_A_ADDR), + .PORT_A_WR_DATA(PORT_A_WR_DATA), + .PORT_A_RD_DATA(PORT_A_RD_DATA), + .PORT_A_WR_EN(PORT_A_WR_EN), + ); +endmodule diff --git a/techlibs/analogdevices/cells_map.v b/techlibs/analogdevices/cells_map.v new file mode 100644 index 00000000000..b8d362e4f9a --- /dev/null +++ b/techlibs/analogdevices/cells_map.v @@ -0,0 +1,364 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * 2019 Eddie Hung + * + * 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. + * + */ + +module \$__SHREG_ (input C, input D, input E, output Q); + parameter DEPTH = 0; + parameter [DEPTH-1:0] INIT = 0; + parameter CLKPOL = 1; + parameter ENPOL = 2; + + \$__XILINX_SHREG_ #(.DEPTH(DEPTH), .INIT(INIT), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) _TECHMAP_REPLACE_ (.C(C), .D(D), .L(DEPTH-1), .E(E), .Q(Q)); +endmodule + +module \$__XILINX_SHREG_ (input C, input D, input [31:0] L, input E, output Q, output SO); + parameter DEPTH = 0; + parameter [DEPTH-1:0] INIT = 0; + parameter CLKPOL = 1; + parameter ENPOL = 2; + + // shregmap's INIT parameter shifts out LSB first; + // however Analog Devices expects MSB first + function [DEPTH-1:0] brev; + input [DEPTH-1:0] din; + integer i; + begin + for (i = 0; i < DEPTH; i=i+1) + brev[i] = din[DEPTH-1-i]; + end + endfunction + localparam [DEPTH-1:0] INIT_R = brev(INIT); + + parameter _TECHMAP_CONSTMSK_L_ = 0; + + wire CE; + generate + if (ENPOL == 0) + assign CE = ~E; + else if (ENPOL == 1) + assign CE = E; + else + assign CE = 1'b1; + if (DEPTH == 1) begin + if (CLKPOL) + FFRE #(.INIT(INIT_R)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(CE), .R(1'b0)); + else + FFRE_N #(.INIT(INIT_R)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(CE), .R(1'b0)); + end else + if (DEPTH <= 16) begin + SRL16E #(.INIT(INIT_R), .IS_CLK_INVERTED(~CLKPOL[0])) _TECHMAP_REPLACE_ (.A0(L[0]), .A1(L[1]), .A2(L[2]), .A3(L[3]), .CE(CE), .CLK(C), .D(D), .Q(Q)); + end else + if (DEPTH > 17 && DEPTH <= 32) begin + SRLC32E #(.INIT(INIT_R), .IS_CLK_INVERTED(~CLKPOL[0])) _TECHMAP_REPLACE_ (.A(L[4:0]), .CE(CE), .CLK(C), .D(D), .Q(Q)); + end else + if (DEPTH > 33 && DEPTH <= 64) begin + wire T0, T1, T2; + SRLC32E #(.INIT(INIT_R[32-1:0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D(D), .Q(T0), .Q31(T1)); + \$__XILINX_SHREG_ #(.DEPTH(DEPTH-32), .INIT(INIT[DEPTH-32-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_1 (.C(C), .D(T1), .L(L), .E(E), .Q(T2)); + if (&_TECHMAP_CONSTMSK_L_) + assign Q = T2; + else + LUTMUX7 fpga_mux_0 (.O(Q), .I0(T0), .I1(T2), .S(L[5])); + end else + if (DEPTH > 65 && DEPTH <= 96) begin + wire T0, T1, T2, T3, T4, T5, T6; + SRLC32E #(.INIT(INIT_R[32-1: 0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D( D), .Q(T0), .Q31(T1)); + SRLC32E #(.INIT(INIT_R[64-1:32]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_1 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T1), .Q(T2), .Q31(T3)); + \$__XILINX_SHREG_ #(.DEPTH(DEPTH-64), .INIT(INIT[DEPTH-64-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_2 (.C(C), .D(T3), .L(L[4:0]), .E(E), .Q(T4)); + if (&_TECHMAP_CONSTMSK_L_) + assign Q = T4; + else + \$__ANALOGDEVICES_LUTMUX78 fpga_hard_mux (.I0(T0), .I1(T2), .I2(T4), .I3(1'bx), .S0(L[5]), .S1(L[6]), .O(Q)); + end else + if (DEPTH > 97 && DEPTH < 128) begin + wire T0, T1, T2, T3, T4, T5, T6, T7, T8; + SRLC32E #(.INIT(INIT_R[32-1: 0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D( D), .Q(T0), .Q31(T1)); + SRLC32E #(.INIT(INIT_R[64-1:32]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_1 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T1), .Q(T2), .Q31(T3)); + SRLC32E #(.INIT(INIT_R[96-1:64]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_2 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T3), .Q(T4), .Q31(T5)); + \$__XILINX_SHREG_ #(.DEPTH(DEPTH-96), .INIT(INIT[DEPTH-96-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_3 (.C(C), .D(T5), .L(L[4:0]), .E(E), .Q(T6)); + if (&_TECHMAP_CONSTMSK_L_) + assign Q = T6; + else + \$__ANALOGDEVICES_LUTMUX78 fpga_hard_mux (.I0(T0), .I1(T2), .I2(T4), .I3(T6), .S0(L[5]), .S1(L[6]), .O(Q)); + end + else if (DEPTH == 128) begin + wire T0, T1, T2, T3, T4, T5, T6; + SRLC32E #(.INIT(INIT_R[ 32-1: 0]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_0 (.A(L[4:0]), .CE(CE), .CLK(C), .D( D), .Q(T0), .Q31(T1)); + SRLC32E #(.INIT(INIT_R[ 64-1:32]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_1 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T1), .Q(T2), .Q31(T3)); + SRLC32E #(.INIT(INIT_R[ 96-1:64]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_2 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T3), .Q(T4), .Q31(T5)); + SRLC32E #(.INIT(INIT_R[128-1:96]), .IS_CLK_INVERTED(~CLKPOL[0])) fpga_srl_3 (.A(L[4:0]), .CE(CE), .CLK(C), .D(T5), .Q(T6), .Q31(SO)); + if (&_TECHMAP_CONSTMSK_L_) + assign Q = T6; + else + \$__ANALOGDEVICES_LUTMUX78 fpga_hard_mux (.I0(T0), .I1(T2), .I2(T4), .I3(T6), .S0(L[5]), .S1(L[6]), .O(Q)); + end + // For fixed length, if just 1 over a convenient value, decompose + else if (DEPTH <= 129 && &_TECHMAP_CONSTMSK_L_) begin + wire T; + \$__XILINX_SHREG_ #(.DEPTH(DEPTH-1), .INIT(INIT[DEPTH-1:1]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl (.C(C), .D(D), .L({32{1'b1}}), .E(E), .Q(T)); + \$__XILINX_SHREG_ #(.DEPTH(1), .INIT(INIT[0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_last (.C(C), .D(T), .L(L), .E(E), .Q(Q)); + end + // For variable length, if just 1 over a convenient value, then bump up one more + else if (DEPTH < 129 && ~&_TECHMAP_CONSTMSK_L_) + \$__XILINX_SHREG_ #(.DEPTH(DEPTH+1), .INIT({INIT,1'b0}), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) _TECHMAP_REPLACE_ (.C(C), .D(D), .L(L), .E(E), .Q(Q)); + else begin + localparam depth0 = 128; + localparam num_srl128 = DEPTH / depth0; + localparam depthN = DEPTH % depth0; + wire [num_srl128 + (depthN > 0 ? 1 : 0) - 1:0] T; + wire [num_srl128 + (depthN > 0 ? 1 : 0) :0] S; + assign S[0] = D; + genvar i; + for (i = 0; i < num_srl128; i++) + \$__XILINX_SHREG_ #(.DEPTH(depth0), .INIT(INIT[DEPTH-1-i*depth0-:depth0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl (.C(C), .D(S[i]), .L(L[$clog2(depth0)-1:0]), .E(E), .Q(T[i]), .SO(S[i+1])); + + if (depthN > 0) + \$__XILINX_SHREG_ #(.DEPTH(depthN), .INIT(INIT[depthN-1:0]), .CLKPOL(CLKPOL), .ENPOL(ENPOL)) fpga_srl_last (.C(C), .D(S[num_srl128]), .L(L[$clog2(depth0)-1:0]), .E(E), .Q(T[num_srl128])); + + if (&_TECHMAP_CONSTMSK_L_) + assign Q = T[num_srl128 + (depthN > 0 ? 1 : 0) - 1]; + else + assign Q = T[L[DEPTH-1:$clog2(depth0)]]; + end + endgenerate +endmodule + +`ifdef MIN_MUX_INPUTS +module \$__ANALOGDEVICES_SHIFTX (A, B, Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 1; + parameter B_WIDTH = 1; + parameter Y_WIDTH = 1; + + (* force_downto *) + input [A_WIDTH-1:0] A; + (* force_downto *) + input [B_WIDTH-1:0] B; + (* force_downto *) + output [Y_WIDTH-1:0] Y; + + parameter [A_WIDTH-1:0] _TECHMAP_CONSTMSK_A_ = 0; + parameter [A_WIDTH-1:0] _TECHMAP_CONSTVAL_A_ = 0; + parameter [B_WIDTH-1:0] _TECHMAP_CONSTMSK_B_ = 0; + parameter [B_WIDTH-1:0] _TECHMAP_CONSTVAL_B_ = 0; + + function integer A_WIDTH_trimmed; + input integer start; + begin + A_WIDTH_trimmed = start; + while (A_WIDTH_trimmed > 0 && _TECHMAP_CONSTMSK_A_[A_WIDTH_trimmed-1] && _TECHMAP_CONSTVAL_A_[A_WIDTH_trimmed-1] === 1'bx) + A_WIDTH_trimmed = A_WIDTH_trimmed - 1; + end + endfunction + + generate + genvar i, j; + // Bit-blast + if (Y_WIDTH > 1) begin + for (i = 0; i < Y_WIDTH; i++) + \$__ANALOGDEVICES_SHIFTX #(.A_SIGNED(A_SIGNED), .B_SIGNED(B_SIGNED), .A_WIDTH(A_WIDTH-Y_WIDTH+1), .B_WIDTH(B_WIDTH), .Y_WIDTH(1'd1)) bitblast (.A(A[A_WIDTH-Y_WIDTH+i:i]), .B(B), .Y(Y[i])); + end + // If the LSB of B is constant zero (and Y_WIDTH is 1) then + // we can optimise by removing every other entry from A + // and popping the constant zero from B + else if (_TECHMAP_CONSTMSK_B_[0] && !_TECHMAP_CONSTVAL_B_[0]) begin + wire [(A_WIDTH+1)/2-1:0] A_i; + for (i = 0; i < (A_WIDTH+1)/2; i++) + assign A_i[i] = A[i*2]; + \$__ANALOGDEVICES_SHIFTX #(.A_SIGNED(A_SIGNED), .B_SIGNED(B_SIGNED), .A_WIDTH((A_WIDTH+1'd1)/2'd2), .B_WIDTH(B_WIDTH-1'd1), .Y_WIDTH(Y_WIDTH)) _TECHMAP_REPLACE_ (.A(A_i), .B(B[B_WIDTH-1:1]), .Y(Y)); + end + // Trim off any leading 1'bx -es in A + else if (_TECHMAP_CONSTMSK_A_[A_WIDTH-1] && _TECHMAP_CONSTVAL_A_[A_WIDTH-1] === 1'bx) begin + localparam A_WIDTH_new = A_WIDTH_trimmed(A_WIDTH-1); + if (A_WIDTH_new == 0) + assign Y = 1'bx; + else + \$__ANALOGDEVICES_SHIFTX #(.A_SIGNED(A_SIGNED), .B_SIGNED(B_SIGNED), .A_WIDTH(A_WIDTH_new), .B_WIDTH(B_WIDTH), .Y_WIDTH(Y_WIDTH)) _TECHMAP_REPLACE_ (.A(A[A_WIDTH_new-1:0]), .B(B), .Y(Y)); + end + else if (A_WIDTH < `MIN_MUX_INPUTS) begin + wire _TECHMAP_FAIL_ = 1; + end + else if (A_WIDTH == 2) begin + LUTMUX7 fpga_hard_mux (.I0(A[0]), .I1(A[1]), .S(B[0]), .O(Y)); + end + else if (A_WIDTH <= 4) begin + wire [4-1:0] Ax; + if (A_WIDTH == 4) + assign Ax = A; + else + // Rather than extend with 1'bx which gets flattened to 1'b0 + // causing the "don't care" status to get lost, extend with + // the same driver of F7B.I0 so that we can optimise F7B away + // later + assign Ax = {A[1], A}; + \$__ANALOGDEVICES_LUTMUX78 fpga_hard_mux (.I0(Ax[0]), .I1(Ax[2]), .I2(Ax[1]), .I3(Ax[3]), .S0(B[1]), .S1(B[0]), .O(Y)); + end + // Note that the following decompositions are 'backwards' in that + // the LSBs are placed on the hard resources, and the soft resources + // are used for MSBs. + // This has the effect of more effectively utilising the hard mux; + // take for example a 5:1 multiplexer, currently this would map as: + // + // A[0] \___ __ A[0] \__ __ + // A[4] / \| \ whereas the more A[1] / \| \ + // A[1] _____| | obvious mapping A[2] \___| | + // A[2] _____| |-- of MSBs to hard A[3] / | |__ + // A[3]______| | resources would A[4] ____| | + // |__/ lead to: 1'bx ____| | + // || |__/ + // || || + // B[1:0] B[1:2] + // + // Expectation would be that the 'forward' mapping (right) is more + // area efficient (consider a 9:1 multiplexer using 2x4:1 multiplexers + // on its I0 and I1 inputs, and A[8] and 1'bx on its I2 and I3 inputs) + // but that the 'backwards' mapping (left) is more delay efficient + // since smaller LUTs are faster than wider ones. + else if (A_WIDTH <= 8) begin + wire [8-1:0] Ax = {{{8-A_WIDTH}{1'bx}}, A}; + wire T0 = B[2] ? Ax[4] : Ax[0]; + wire T1 = B[2] ? Ax[5] : Ax[1]; + wire T2 = B[2] ? Ax[6] : Ax[2]; + wire T3 = B[2] ? Ax[7] : Ax[3]; + \$__ANALOGDEVICES_LUTMUX78 fpga_hard_mux (.I0(T0), .I1(T2), .I2(T1), .I3(T3), .S0(B[1]), .S1(B[0]), .O(Y)); + end + else if (A_WIDTH <= 16) begin + wire [16-1:0] Ax = {{{16-A_WIDTH}{1'bx}}, A}; + wire T0 = B[2] ? B[3] ? Ax[12] : Ax[4] + : B[3] ? Ax[ 8] : Ax[0]; + wire T1 = B[2] ? B[3] ? Ax[13] : Ax[5] + : B[3] ? Ax[ 9] : Ax[1]; + wire T2 = B[2] ? B[3] ? Ax[14] : Ax[6] + : B[3] ? Ax[10] : Ax[2]; + wire T3 = B[2] ? B[3] ? Ax[15] : Ax[7] + : B[3] ? Ax[11] : Ax[3]; + \$__ANALOGDEVICES_LUTMUX78 fpga_hard_mux (.I0(T0), .I1(T2), .I2(T1), .I3(T3), .S0(B[1]), .S1(B[0]), .O(Y)); + end + else begin + localparam num_mux16 = (A_WIDTH+15) / 16; + localparam clog2_num_mux16 = $clog2(num_mux16); + wire [num_mux16-1:0] T; + wire [num_mux16*16-1:0] Ax = {{(num_mux16*16-A_WIDTH){1'bx}}, A}; + for (i = 0; i < num_mux16; i++) + \$__ANALOGDEVICES_SHIFTX #( + .A_SIGNED(A_SIGNED), + .B_SIGNED(B_SIGNED), + .A_WIDTH(16), + .B_WIDTH(4), + .Y_WIDTH(Y_WIDTH) + ) fpga_mux ( + .A(Ax[i*16+:16]), + .B(B[3:0]), + .Y(T[i]) + ); + \$__ANALOGDEVICES_SHIFTX #( + .A_SIGNED(A_SIGNED), + .B_SIGNED(B_SIGNED), + .A_WIDTH(num_mux16), + .B_WIDTH(clog2_num_mux16), + .Y_WIDTH(Y_WIDTH) + ) _TECHMAP_REPLACE_ ( + .A(T), + .B(B[B_WIDTH-1-:clog2_num_mux16]), + .Y(Y)); + end + endgenerate +endmodule + +(* techmap_celltype = "$__ANALOGDEVICES_SHIFTX" *) +module _90__ANALOGDEVICES_SHIFTX (A, B, Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 1; + parameter B_WIDTH = 1; + parameter Y_WIDTH = 1; + + (* force_downto *) + input [A_WIDTH-1:0] A; + (* force_downto *) + input [B_WIDTH-1:0] B; + (* force_downto *) + output [Y_WIDTH-1:0] Y; + + \$shiftx #(.A_SIGNED(A_SIGNED), .B_SIGNED(B_SIGNED), .A_WIDTH(A_WIDTH), .B_WIDTH(B_WIDTH), .Y_WIDTH(Y_WIDTH)) _TECHMAP_REPLACE_ (.A(A), .B(B), .Y(Y)); +endmodule + +module \$_MUX_ (A, B, S, Y); + input A, B, S; + output Y; + generate + if (`MIN_MUX_INPUTS == 2) + \$__ANALOGDEVICES_SHIFTX #(.A_SIGNED(0), .B_SIGNED(0), .A_WIDTH(2), .B_WIDTH(1), .Y_WIDTH(1)) _TECHMAP_REPLACE_ (.A({B,A}), .B(S), .Y(Y)); + else + wire _TECHMAP_FAIL_ = 1; + endgenerate +endmodule + +module \$_MUX4_ (A, B, C, D, S, T, Y); + input A, B, C, D, S, T; + output Y; + \$__ANALOGDEVICES_SHIFTX #(.A_SIGNED(0), .B_SIGNED(0), .A_WIDTH(4), .B_WIDTH(2), .Y_WIDTH(1)) _TECHMAP_REPLACE_ (.A({D,C,B,A}), .B({T,S}), .Y(Y)); +endmodule + +module \$_MUX8_ (A, B, C, D, E, F, G, H, S, T, U, Y); + input A, B, C, D, E, F, G, H, S, T, U; + output Y; + \$__ANALOGDEVICES_SHIFTX #(.A_SIGNED(0), .B_SIGNED(0), .A_WIDTH(8), .B_WIDTH(3), .Y_WIDTH(1)) _TECHMAP_REPLACE_ (.A({H,G,F,E,D,C,B,A}), .B({U,T,S}), .Y(Y)); +endmodule + +module \$_MUX16_ (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, S, T, U, V, Y); + input A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, S, T, U, V; + output Y; + \$__ANALOGDEVICES_SHIFTX #(.A_SIGNED(0), .B_SIGNED(0), .A_WIDTH(16), .B_WIDTH(4), .Y_WIDTH(1)) _TECHMAP_REPLACE_ (.A({P,O,N,M,L,K,J,I,H,G,F,E,D,C,B,A}), .B({V,U,T,S}), .Y(Y)); +endmodule +`endif + +module \$__ANALOGDEVICES_LUTMUX78 (O, I0, I1, I2, I3, S0, S1); + output O; + input I0, I1, I2, I3, S0, S1; + wire T0, T1; + parameter _TECHMAP_BITS_CONNMAP_ = 0; + parameter [_TECHMAP_BITS_CONNMAP_-1:0] _TECHMAP_CONNMAP_I0_ = 0; + parameter [_TECHMAP_BITS_CONNMAP_-1:0] _TECHMAP_CONNMAP_I1_ = 0; + parameter [_TECHMAP_BITS_CONNMAP_-1:0] _TECHMAP_CONNMAP_I2_ = 0; + parameter [_TECHMAP_BITS_CONNMAP_-1:0] _TECHMAP_CONNMAP_I3_ = 0; + parameter _TECHMAP_CONSTMSK_S0_ = 0; + parameter _TECHMAP_CONSTVAL_S0_ = 0; + parameter _TECHMAP_CONSTMSK_S1_ = 0; + parameter _TECHMAP_CONSTVAL_S1_ = 0; + if (_TECHMAP_CONSTMSK_S0_ && _TECHMAP_CONSTVAL_S0_ === 1'b1) + assign T0 = I1; + else if (_TECHMAP_CONSTMSK_S0_ || _TECHMAP_CONNMAP_I0_ === _TECHMAP_CONNMAP_I1_) + assign T0 = I0; + else + LUTMUX7 mux7a (.I0(I0), .I1(I1), .S(S0), .O(T0)); + if (_TECHMAP_CONSTMSK_S0_ && _TECHMAP_CONSTVAL_S0_ === 1'b1) + assign T1 = I3; + else if (_TECHMAP_CONSTMSK_S0_ || _TECHMAP_CONNMAP_I2_ === _TECHMAP_CONNMAP_I3_) + assign T1 = I2; + else + LUTMUX7 mux7b (.I0(I2), .I1(I3), .S(S0), .O(T1)); + if (_TECHMAP_CONSTMSK_S1_ && _TECHMAP_CONSTVAL_S1_ === 1'b1) + assign O = T1; + else if (_TECHMAP_CONSTMSK_S1_ || (_TECHMAP_CONNMAP_I0_ === _TECHMAP_CONNMAP_I1_ && _TECHMAP_CONNMAP_I1_ === _TECHMAP_CONNMAP_I2_ && _TECHMAP_CONNMAP_I2_ === _TECHMAP_CONNMAP_I3_)) + assign O = T0; + else + LUTMUX8 mux8 (.I0(T0), .I1(T1), .S(S1), .O(O)); +endmodule diff --git a/techlibs/analogdevices/cells_sim.v b/techlibs/analogdevices/cells_sim.v new file mode 100644 index 00000000000..2bf958d8fc6 --- /dev/null +++ b/techlibs/analogdevices/cells_sim.v @@ -0,0 +1,1216 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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. + * + */ + +module VDD(output P); + assign P = 1; +endmodule + +module GND(output G); + assign G = 0; +endmodule + +module INBUF( + output O, + (* iopad_external_pin *) + input I); + parameter CCIO_EN = "TRUE"; + parameter CAPACITANCE = "DONT_CARE"; + parameter IBUF_DELAY_VALUE = "0"; + parameter IBUF_LOW_PWR = "TRUE"; + parameter IFD_DELAY_VALUE = "AUTO"; + parameter IOSTANDARD = "DEFAULT"; + assign O = I; +`ifdef IS_T16FFC + specify + (I => O) = 42; + endspecify +`endif +`ifdef IS_T40LP + specify + (I => O) = 187; + endspecify +`endif +endmodule + +module OUTBUF( + (* iopad_external_pin *) + output O, + input I); + parameter CAPACITANCE = "DONT_CARE"; + parameter IOSTANDARD = "DEFAULT"; + parameter DRIVE = 12; + parameter SLEW = "SLOW"; + assign O = I; +`ifdef IS_T16FFC + specify + (I => O) = 42; + endspecify +`endif +`ifdef IS_T40LP + specify + (I => O) = 187; + endspecify +`endif +endmodule + +(* abc9_lut=1 *) +module LUT1(output O, input I0); + parameter [1:0] INIT = 0; + assign O = I0 ? INIT[1] : INIT[0]; +`ifdef IS_T16FFC + specify + (I0 => O) = 42; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 187; + endspecify +`endif +endmodule + +(* abc9_lut=2 *) +module LUT2(output O, input I0, I1); + parameter [3:0] INIT = 0; + wire [ 1: 0] s1 = I1 ? INIT[ 3: 2] : INIT[ 1: 0]; + assign O = I0 ? s1[1] : s1[0]; +`ifdef IS_T16FFC + specify + (I0 => O) = 42; + (I1 => O) = 39; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 187; + (I1 => O) = 176; + endspecify +`endif +endmodule + +(* abc9_lut=3 *) +module LUT3(output O, input I0, I1, I2); + parameter [7:0] INIT = 0; + wire [ 3: 0] s2 = I2 ? INIT[ 7: 4] : INIT[ 3: 0]; + wire [ 1: 0] s1 = I1 ? s2[ 3: 2] : s2[ 1: 0]; + assign O = I0 ? s1[1] : s1[0]; +`ifdef IS_T16FFC + specify + (I0 => O) = 54; + (I1 => O) = 51; + (I2 => O) = 48; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 249; + (I1 => O) = 238; + (I2 => O) = 227; + endspecify +`endif +endmodule + +(* abc9_lut=4 *) +module LUT4(output O, input I0, I1, I2, I3); + parameter [15:0] INIT = 0; + wire [ 7: 0] s3 = I3 ? INIT[15: 8] : INIT[ 7: 0]; + wire [ 3: 0] s2 = I2 ? s3[ 7: 4] : s3[ 3: 0]; + wire [ 1: 0] s1 = I1 ? s2[ 3: 2] : s2[ 1: 0]; + assign O = I0 ? s1[1] : s1[0]; +`ifdef IS_T16FFC + specify + (I0 => O) = 96; + (I1 => O) = 87; + (I2 => O) = 79; + (I3 => O) = 71; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 192; + (I1 => O) = 181; + (I2 => O) = 170; + (I3 => O) = 160; + endspecify +`endif +endmodule + +(* abc9_lut=5 *) +module LUT5(output O, input I0, I1, I2, I3, I4); + parameter [31:0] INIT = 0; + wire [15: 0] s4 = I4 ? INIT[31:16] : INIT[15: 0]; + wire [ 7: 0] s3 = I3 ? s4[15: 8] : s4[ 7: 0]; + wire [ 3: 0] s2 = I2 ? s3[ 7: 4] : s3[ 3: 0]; + wire [ 1: 0] s1 = I1 ? s2[ 3: 2] : s2[ 1: 0]; + assign O = I0 ? s1[1] : s1[0]; +`ifdef IS_T16FFC + specify + (I0 => O) = 99; + (I1 => O) = 90; + (I2 => O) = 82; + (I3 => O) = 74; + (I4 => O) = 66; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 204; + (I1 => O) = 195; + (I2 => O) = 186; + (I3 => O) = 178; + (I4 => O) = 169; + endspecify +`endif +endmodule + +(* abc9_lut=6 *) +module LUT6(output O, input I0, I1, I2, I3, I4, I5); + parameter [63:0] INIT = 0; + wire [31: 0] s5 = I5 ? INIT[63:32] : INIT[31: 0]; + wire [15: 0] s4 = I4 ? s5[31:16] : s5[15: 0]; + wire [ 7: 0] s3 = I3 ? s4[15: 8] : s4[ 7: 0]; + wire [ 3: 0] s2 = I2 ? s3[ 7: 4] : s3[ 3: 0]; + wire [ 1: 0] s1 = I1 ? s2[ 3: 2] : s2[ 1: 0]; + assign O = I0 ? s1[1] : s1[0]; +`ifdef IS_T16FFC + specify + (I0 => O) = 42; + (I1 => O) = 39; + (I2 => O) = 36; + (I3 => O) = 33; + (I4 => O) = 30; + (I5 => O) = 28; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 185; + (I1 => O) = 176; + (I2 => O) = 165; + (I3 => O) = 155; + (I4 => O) = 144; + (I5 => O) = 134; + endspecify +`endif +endmodule + +module LUT6_D(output O6, output O5, input I0, I1, I2, I3, I4, I5); + parameter [63:0] INIT = 0; + wire [31: 0] s5 = I5 ? INIT[63:32] : INIT[31: 0]; + wire [15: 0] s4 = I4 ? s5[31:16] : s5[15: 0]; + wire [ 7: 0] s3 = I3 ? s4[15: 8] : s4[ 7: 0]; + wire [ 3: 0] s2 = I2 ? s3[ 7: 4] : s3[ 3: 0]; + wire [ 1: 0] s1 = I1 ? s2[ 3: 2] : s2[ 1: 0]; + assign O6 = I0 ? s1[1] : s1[0]; + + wire [15: 0] s5_4 = I4 ? INIT[31:16] : INIT[15: 0]; + wire [ 7: 0] s5_3 = I3 ? s5_4[15: 8] : s5_4[ 7: 0]; + wire [ 3: 0] s5_2 = I2 ? s5_3[ 7: 4] : s5_3[ 3: 0]; + wire [ 1: 0] s5_1 = I1 ? s5_2[ 3: 2] : s5_2[ 1: 0]; + assign O5 = I0 ? s5_1[1] : s5_1[0]; +endmodule + +// This is a placeholder for ABC9 to extract the area/delay +// cost of 3-input LUTs and is not intended to be instantiated +(* abc9_lut=12 *) +module \$__ABC9_LUT7 (output O, input I0, I1, I2, I3, I4, I5, I6); +`ifndef __ICARUS__ +`ifdef IS_T16FFC + specify + (I0 => O) = 42 + 111 /* LUTMUX7.I1 */; + (I1 => O) = 39 + 111 /* LUTMUX7.I1 */; + (I2 => O) = 36 + 111 /* LUTMUX7.I1 */; + (I3 => O) = 33 + 111 /* LUTMUX7.I1 */; + (I4 => O) = 30 + 111 /* LUTMUX7.I1 */; + (I5 => O) = 28 + 111 /* LUTMUX7.I1 */; + (I6 => O) = 0 + 57 /* LUTMUX7.S */; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 185 + 210 /* LUTMUX7.I0 */; + (I1 => O) = 176 + 210 /* LUTMUX7.I0 */; + (I2 => O) = 165 + 210 /* LUTMUX7.I0 */; + (I3 => O) = 155 + 210 /* LUTMUX7.I0 */; + (I4 => O) = 144 + 210 /* LUTMUX7.I0 */; + (I5 => O) = 134 + 210 /* LUTMUX7.I0 */; + (I6 => O) = 0 + 188 /* LUTMUX7.S */; + endspecify +`endif +`endif +endmodule + +// This is a placeholder for ABC9 to extract the area/delay +// cost of 3-input LUTs and is not intended to be instantiated +(* abc9_lut=24 *) +module \$__ABC9_LUT8 (output O, input I0, I1, I2, I3, I4, I5, I6, I7); +`ifndef __ICARUS__ +`ifdef IS_T16FFC + specify + (I0 => O) = 42 + 111 /* LUTMUX7.I1 */ + 59 /* LUTMUX8.I0 */; + (I1 => O) = 39 + 111 /* LUTMUX7.I1 */ + 59 /* LUTMUX8.I0 */; + (I2 => O) = 36 + 111 /* LUTMUX7.I1 */ + 59 /* LUTMUX8.I0 */; + (I3 => O) = 33 + 111 /* LUTMUX7.I1 */ + 59 /* LUTMUX8.I0 */; + (I4 => O) = 30 + 111 /* LUTMUX7.I1 */ + 59 /* LUTMUX8.I0 */; + (I5 => O) = 28 + 111 /* LUTMUX7.I1 */ + 59 /* LUTMUX8.I0 */; + (I6 => O) = 0 + 57 /* LUTMUX7.S */ + 59 /* LUTMUX8.I0 */; + (I7 => O) = 0 + 0 + 60 /* LUTMUX8.S */; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 185 + 210 /* LUTMUX7.I0 */ + 155 /* LUTMUX8.I0 */; + (I1 => O) = 176 + 210 /* LUTMUX7.I0 */ + 155 /* LUTMUX8.I0 */; + (I2 => O) = 165 + 210 /* LUTMUX7.I0 */ + 155 /* LUTMUX8.I0 */; + (I3 => O) = 155 + 210 /* LUTMUX7.I0 */ + 155 /* LUTMUX8.I0 */; + (I4 => O) = 144 + 210 /* LUTMUX7.I0 */ + 155 /* LUTMUX8.I0 */; + (I5 => O) = 134 + 210 /* LUTMUX7.I0 */ + 155 /* LUTMUX8.I0 */; + (I6 => O) = 0 + 188 /* LUTMUX7.S */ + 155 /* LUTMUX8.I0 */; + (I7 => O) = 0 + 0 + 193 /* LUTMUX8.S */; + endspecify +`endif +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module LUTMUX7(output O, input I0, I1, S); + assign O = S ? I1 : I0; +`ifdef IS_T16FFC + specify + (I0 => O) = 95; + (I1 => O) = 111; + (S => O) = 57; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 210; + (I1 => O) = 182; + (S => O) = 188; + endspecify +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module LUTMUX8(output O, input I0, I1, S); + assign O = S ? I1 : I0; +`ifdef IS_T16FFC + specify + (I0 => O) = 59; + (I1 => O) = 59; + (S => O) = 60; + endspecify +`endif +`ifdef IS_T40LP + specify + (I0 => O) = 155; + (I1 => O) = 147; + (S => O) = 193; + endspecify +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module CRY4( + (* abc9_carry *) + output [3:0] CO, + output [3:0] O, + (* abc9_carry *) + input CI, + input CYINIT, + input [3:0] DI, S +); + assign O = S ^ {CO[2:0], CI | CYINIT}; + assign CO[0] = S[0] ? CI | CYINIT : DI[0]; + assign CO[1] = S[1] ? CO[0] : DI[1]; + assign CO[2] = S[2] ? CO[1] : DI[2]; + assign CO[3] = S[3] ? CO[2] : DI[3]; +`ifdef IS_T16FFC + specify + (CI => CO[0]) = 69; + (DI[0] => CO[0]) = 74; + (S[0] => CO[0]) = 76; + + (CI => CO[1]) = 76; + (DI[0] => CO[1]) = 109; + (DI[1] => CO[1]) = 69; + (S[0] => CO[1]) = 99; + (S[1] => CO[1]) = 59; + + (CI => CO[2]) = 144; + (DI[0] => CO[2]) = 155; + (DI[1] => CO[2]) = 126; + (DI[2] => CO[2]) = 63; + (S[0] => CO[2]) = 168; + (S[1] => CO[2]) = 117; + (S[2] => CO[2]) = 50; + + (CI => CO[3]) = 20; + (DI[0] => CO[3]) = 167; + (DI[1] => CO[3]) = 181; + (DI[2] => CO[3]) = 173; + (DI[3] => CO[3]) = 77; + (S[0] => CO[3]) = 185; + (S[1] => CO[3]) = 182; + (S[2] => CO[3]) = 159; + (S[3] => CO[3]) = 179; + + (CI => O[0]) = 50; + (S[0] => O[0]) = 82; + + (CI => O[1]) = 61; + (DI[0] => O[1]) = 117; + (S[0] => O[1]) = 132; + (S[1] => O[1]) = 49; + + (CI => O[2]) = 148; + (DI[0] => O[2]) = 196; + (DI[1] => O[2]) = 130; + (S[0] => O[2]) = 218; + (S[1] => O[2]) = 145; + (S[2] => O[2]) = 65; + + (CI => O[3]) = 132; + (DI[0] => O[3]) = 208; + (DI[1] => O[3]) = 148; + (DI[2] => O[3]) = 110; + (S[0] => O[3]) = 204; + (S[1] => O[3]) = 160; + (S[2] => O[3]) = 97; + (S[3] => O[3]) = 39; + endspecify +`endif +`ifdef IS_T40LP + specify + (CI => CO[0]) = 132; + (DI[0] => CO[0]) = 245; + (S[0] => CO[0]) = 218; + + (CI => CO[1]) = 183; + (DI[0] => CO[1]) = 261; + (DI[1] => CO[1]) = 325; + (S[0] => CO[1]) = 270; + (S[1] => CO[1]) = 199; + + (CI => CO[2]) = 218; + (DI[0] => CO[2]) = 284; + (DI[1] => CO[2]) = 345; + (DI[2] => CO[2]) = 242; + (S[0] => CO[2]) = 334; + (S[1] => CO[2]) = 271; + (S[2] => CO[2]) = 228; + + (CI => CO[3]) = 258; + (DI[0] => CO[3]) = 334; + (DI[1] => CO[3]) = 395; + (DI[2] => CO[3]) = 342; + (DI[3] => CO[3]) = 205; + (S[0] => CO[3]) = 382; + (S[1] => CO[3]) = 321; + (S[2] => CO[3]) = 324; + (S[3] => CO[3]) = 252; + + (CI => O[0]) = 123; + (S[0] => O[0]) = 150; + + (CI => O[1]) = 265; + (DI[0] => O[1]) = 331; + (S[0] => O[1]) = 292; + (S[1] => O[1]) = 151; + + (CI => O[2]) = 288; + (DI[0] => O[2]) = 371; + (DI[1] => O[2]) = 407; + (S[0] => O[2]) = 391; + (S[1] => O[2]) = 314; + (S[2] => O[2]) = 144; + + (CI => O[3]) = 313; + (DI[0] => O[3]) = 388; + (DI[1] => O[3]) = 432; + (DI[2] => O[3]) = 335; + (S[0] => O[3]) = 418; + (S[1] => O[3]) = 357; + (S[2] => O[3]) = 331; + (S[3] => O[3]) = 145; + endspecify +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module CRY4INIT( + (* abc9_carry *) + output CO, + (* abc9_carry *) + input CYINIT +); + assign CO = CYINIT; +`ifdef IS_T16FFC + specify + (CYINIT => CO) = 77; + endspecify +`endif +`ifdef IS_T40LP + specify + (CYINIT => CO) = 205; + endspecify +`endif +endmodule + +// Flip-flops. + +(* abc9_flop, lib_whitebox *) +module FFRE ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input D, + input R +); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @(posedge C) if (R) Q <= 1'b0; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D , posedge C, 65); + $setup(CE, posedge C, 63); + $setup(R , posedge C, 63); + if (R) (posedge C => (Q : 1'b0)) = 291; + if (!R && CE) (posedge C => (Q : D)) = 291; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D , posedge C, 144); + $setup(CE, posedge C, 412); + $setup(R , posedge C, 667); + if (R) (posedge C => (Q : 1'b0)) = 712; + if (!R && CE) (posedge C => (Q : D)) = 712; + endspecify +`endif +endmodule + +(* abc9_flop, lib_whitebox *) +module FFRE_N ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input D, + input R +); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @(negedge C) if (R) Q <= 1'b0; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D , negedge C, 65); + $setup(CE, negedge C, 63); + $setup(R , negedge C, 63); + if (R) (negedge C => (Q : 1'b0)) = 291; + if (!R && CE) (negedge C => (Q : D)) = 291; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D , negedge C, 144); + $setup(CE, negedge C, 412); + $setup(R , negedge C, 667); + if (R) (negedge C => (Q : 1'b0)) = 712; + if (!R && CE) (negedge C => (Q : D)) = 712; + endspecify +`endif +endmodule + +(* abc9_flop, lib_whitebox *) +module FFSE ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input D, + input S +); + parameter [0:0] INIT = 1'b1; + initial Q = INIT; + always @(posedge C) if (S) Q <= 1'b1; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D , posedge C, 65); + $setup(CE, posedge C, 63); + $setup(S , posedge C, 63); + if (S) (posedge C => (Q : 1'b1)) = 265; + if (!S && CE) (posedge C => (Q : D)) = 265; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D , posedge C, 144); + $setup(CE, posedge C, 412); + $setup(S , posedge C, 689); + if (S) (posedge C => (Q : 1'b1)) = 693; + if (!S && CE) (posedge C => (Q : D)) = 693; + endspecify +`endif +endmodule + +(* abc9_flop, lib_whitebox *) +module FFSE_N ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input D, + input S +); + parameter [0:0] INIT = 1'b1; + initial Q = INIT; + always @(negedge C) if (S) Q <= 1'b1; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D , negedge C, 65); + $setup(CE, negedge C, 63); + $setup(S , negedge C, 63); + if (S) (negedge C => (Q : 1'b1)) = 265; + if (!S && CE) (negedge C => (Q : D)) = 265; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D , negedge C, 144); + $setup(CE, negedge C, 412); + $setup(S , negedge C, 689); + if (S) (negedge C => (Q : 1'b1)) = 693; + if (!S && CE) (negedge C => (Q : D)) = 693; + endspecify +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module FFCE ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input CLR, + input D +); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @(posedge C, posedge CLR) if ( CLR) Q <= 1'b0; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D , posedge C, 65); + $setup(CE, posedge C, 64); + $setup(CLR, posedge C, 64); + if (!CLR && CE) (posedge C => (Q : D)) = 192; +`ifdef YOSYS + if (CLR) (CLR => Q) = 192; +`endif + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D , posedge C, 140); + $setup(CE, posedge C, 412); + $setup(CLR, posedge C, 6); + if (!CLR && CE) (posedge C => (Q : D)) = 55; +`ifdef YOSYS + if (CLR) (CLR => Q) = 55; +`endif + endspecify +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module FFCE_N ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input CLR, + input D +); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @(negedge C, posedge CLR) if (CLR) Q <= 1'b0; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D, negedge C, 65); + $setup(CE, negedge C, 64); + $setup(CLR, negedge C, 64); + if (!CLR && CE) (negedge C => (Q : D)) = 192; +`ifdef YOSYS + if (CLR) (CLR => Q) = 192; +`endif + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D, negedge C, 140); + $setup(CE, negedge C, 412); + $setup(CLR, negedge C, 6); + if (!CLR && CE) (negedge C => (Q : D)) = 55; +`ifdef YOSYS + if (CLR) (CLR => Q) = 55; +`endif + endspecify +`endif +endmodule + +module FFPE ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input PRE, + input D +); + parameter [0:0] INIT = 1'b1; + initial Q = INIT; + always @(posedge C, posedge PRE) if ( PRE) Q <= 1'b1; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D, posedge C, 65); + $setup(CE, posedge C, 65); + $setup(PRE, posedge C, 65); + if (!PRE && CE) (posedge C => (Q : D)) = 191; +`ifdef YOSYS + if (PRE) (PRE => Q) = 191; +`endif + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D, posedge C, 140); + $setup(CE, posedge C, 412); + $setup(PRE, posedge C, 6); + if (!PRE && CE) (posedge C => (Q : D)) = 55; +`ifdef YOSYS + if (PRE) (PRE => Q) = 55; +`endif + endspecify +`endif +endmodule + +module FFPE_N ( + output reg Q, + (* clkbuf_sink *) + input C, + input CE, + input PRE, + input D +); + parameter [0:0] INIT = 1'b1; + initial Q = INIT; + always @(negedge C, posedge PRE) if (PRE) Q <= 1'b1; else if (CE) Q <= D; +`ifdef IS_T16FFC + specify + $setup(D, negedge C, 64); + $setup(CE, negedge C, 64); + $setup(PRE, negedge C, 64); + if (!PRE && CE) (negedge C => (Q : D)) = 291; +`ifdef YOSYS + if (PRE) (PRE => Q) = 333; +`endif + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(D, negedge C, 89); + $setup(CE, negedge C, 89); + $setup(PRE, negedge C, 89); + if (!PRE && CE) (negedge C => (Q : D)) = 712; +`ifdef YOSYS + if (PRE) (PRE => Q) = 57; +`endif + endspecify +`endif +endmodule + +// LUTRAM. + +// Single port. + +(* abc9_box, lib_whitebox *) +module RAMS32X1 ( + output O, + input A0, A1, A2, A3, A4, + input D, + (* clkbuf_sink *) + input WCLK, + input WE +); + parameter [31:0] INIT = 32'h00000000; + wire [4:0] a = {A4, A3, A2, A1, A0}; + reg [31:0] mem = INIT; + assign O = mem[a]; + always @(posedge WCLK) if (WE) mem[a] <= D; +`ifdef IS_T16FFC + specify + $setup(A0, posedge WCLK, 62); + $setup(A1, posedge WCLK, 62); + $setup(A2, posedge WCLK, 62); + $setup(A3, posedge WCLK, 62); + $setup(A4, posedge WCLK, 62); + $setup(D, posedge WCLK, 62); + $setup(WE, posedge WCLK, 62); + (A0 => O) = 100; + (A1 => O) = 91; + (A2 => O) = 83; + (A3 => O) = 75; + (A4 => O) = 67; + (posedge WCLK => (O : D)) = 807; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(A0, posedge WCLK, 198); + $setup(A1, posedge WCLK, 198); + $setup(A2, posedge WCLK, 198); + $setup(A3, posedge WCLK, 198); + $setup(A4, posedge WCLK, 198); + $setup(D, posedge WCLK, 198); + $setup(WE, posedge WCLK, 198); + (A0 => O) = 211; + (A1 => O) = 202; + (A2 => O) = 193; + (A3 => O) = 185; + (A4 => O) = 176; + (posedge WCLK => (O : D)) = 1586; + endspecify +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module RAMS64X1 ( + output O, + input A0, A1, A2, A3, A4, A5, + input D, + (* clkbuf_sink *) + input WCLK, + input WE +); + parameter [63:0] INIT = 64'h0000000000000000; + wire [5:0] a = {A5, A4, A3, A2, A1, A0}; + reg [63:0] mem = INIT; + assign O = mem[a]; + always @(posedge WCLK) if (WE) mem[a] <= D; +`ifdef IS_T16FFC + specify + $setup(A0, posedge WCLK, 65); + $setup(A1, posedge WCLK, 65); + $setup(A2, posedge WCLK, 65); + $setup(A3, posedge WCLK, 65); + $setup(A4, posedge WCLK, 65); + $setup(A5, posedge WCLK, 65); + $setup(D, posedge WCLK, 65); + $setup(WE, posedge WCLK, 65); + (A0 => O) = 184; + (A1 => O) = 175; + (A2 => O) = 167; + (A3 => O) = 159; + (A4 => O) = 151; + (A5 => O) = 61; + (posedge WCLK => (O : D)) = 747; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(A0, posedge WCLK, 224); + $setup(A1, posedge WCLK, 224); + $setup(A2, posedge WCLK, 224); + $setup(A3, posedge WCLK, 224); + $setup(A4, posedge WCLK, 224); + $setup(A5, posedge WCLK, 224); + $setup(D, posedge WCLK, 224); + $setup(WE, posedge WCLK, 224); + (A0 => O) = 509; + (A1 => O) = 500; + (A2 => O) = 491; + (A3 => O) = 483; + (A4 => O) = 474; + (A5 => O) = 187; + (posedge WCLK => (O : D)) = 1730; + endspecify +`endif +endmodule + +// Dual port. + +(* abc9_box, lib_whitebox *) +module RAMD32X1 ( + output DPO, SPO, + input D, + (* clkbuf_sink *) + input WCLK, + input WE, + input A0, A1, A2, A3, A4, + input DPRA0, DPRA1, DPRA2, DPRA3, DPRA4 +); + parameter INIT = 32'h0; + wire [4:0] a = {A4, A3, A2, A1, A0}; + wire [4:0] dpra = {DPRA4, DPRA3, DPRA2, DPRA1, DPRA0}; + reg [31:0] mem = INIT; + assign SPO = mem[a]; + assign DPO = mem[dpra]; + always @(posedge WCLK) if (WE) mem[a] <= D; +`ifdef IS_T16FFC + specify + $setup(A0, posedge WCLK, 62); + $setup(A1, posedge WCLK, 62); + $setup(A2, posedge WCLK, 62); + $setup(A3, posedge WCLK, 62); + $setup(A4, posedge WCLK, 62); + $setup(D, posedge WCLK, 62); + $setup(WE, posedge WCLK, 62); + (A0 => SPO) = 100; + (A1 => SPO) = 91; + (A2 => SPO) = 83; + (A3 => SPO) = 75; + (A4 => SPO) = 67; + (DPRA0 => DPO) = 101; + (DPRA1 => DPO) = 92; + (DPRA2 => DPO) = 84; + (DPRA3 => DPO) = 76; + (DPRA4 => DPO) = 68; + (posedge WCLK => (SPO : D)) = 807; + (posedge WCLK => (DPO : D)) = 807; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(A0, posedge WCLK, 198); + $setup(A1, posedge WCLK, 198); + $setup(A2, posedge WCLK, 198); + $setup(A3, posedge WCLK, 198); + $setup(A4, posedge WCLK, 198); + $setup(D, posedge WCLK, 198); + $setup(WE, posedge WCLK, 198); + (A0 => SPO) = 211; + (A1 => SPO) = 202; + (A2 => SPO) = 193; + (A3 => SPO) = 185; + (A4 => SPO) = 176; + (DPRA0 => DPO) = 185; + (DPRA1 => DPO) = 176; + (DPRA2 => DPO) = 167; + (DPRA3 => DPO) = 159; + (DPRA4 => DPO) = 150; + (posedge WCLK => (SPO : D)) = 1586; + (posedge WCLK => (DPO : D)) = 1586; + endspecify +`endif +endmodule + +(* abc9_box, lib_whitebox *) +module RAMD64X1 ( + output DPO, SPO, + input D, + (* clkbuf_sink *) + input WCLK, + input WE, + input A0, A1, A2, A3, A4, A5, + input DPRA0, DPRA1, DPRA2, DPRA3, DPRA4, DPRA5 +); + parameter INIT = 64'h0; + wire [5:0] a = {A5, A4, A3, A2, A1, A0}; + wire [5:0] dpra = {DPRA5, DPRA4, DPRA3, DPRA2, DPRA1, DPRA0}; + reg [63:0] mem = INIT; + assign SPO = mem[a]; + assign DPO = mem[dpra]; + always @(posedge WCLK) if (WE) mem[a] <= D; +`ifdef IS_T16FFC + specify + $setup(A0, posedge WCLK, 65); + $setup(A1, posedge WCLK, 65); + $setup(A2, posedge WCLK, 65); + $setup(A3, posedge WCLK, 65); + $setup(A4, posedge WCLK, 65); + $setup(A5, posedge WCLK, 65); + $setup(D, posedge WCLK, 65); + $setup(WE, posedge WCLK, 65); + (A0 => SPO) = 184; + (A1 => SPO) = 175; + (A2 => SPO) = 167; + (A3 => SPO) = 159; + (A4 => SPO) = 151; + (A5 => SPO) = 61; + (DPRA0 => DPO) = 164; + (DPRA1 => DPO) = 155; + (DPRA2 => DPO) = 147; + (DPRA3 => DPO) = 139; + (DPRA4 => DPO) = 131; + (DPRA5 => DPO) = 64; + (posedge WCLK => (SPO : D)) = 761; + (posedge WCLK => (DPO : D)) = 733; + endspecify +`endif +`ifdef IS_T40LP + specify + $setup(A0, posedge WCLK, 224); + $setup(A1, posedge WCLK, 224); + $setup(A2, posedge WCLK, 224); + $setup(A3, posedge WCLK, 224); + $setup(A4, posedge WCLK, 224); + $setup(A5, posedge WCLK, 224); + $setup(D, posedge WCLK, 224); + $setup(WE, posedge WCLK, 224); + (A0 => SPO) = 509; + (A1 => SPO) = 500; + (A2 => SPO) = 491; + (A3 => SPO) = 483; + (A4 => SPO) = 474; + (A5 => SPO) = 187; + (DPRA0 => DPO) = 531; + (DPRA1 => DPO) = 522; + (DPRA2 => DPO) = 513; + (DPRA3 => DPO) = 505; + (DPRA4 => DPO) = 496; + (DPRA5 => DPO) = 199; + (posedge WCLK => (SPO : D)) = 1798; + (posedge WCLK => (DPO : D)) = 1807; + endspecify +`endif +endmodule + +// Shift registers. + +(* abc9_box, lib_whitebox *) +module SRG16E ( + output Q, + input A0, A1, A2, A3, CE, + (* clkbuf_sink *) + input CLK, + input D +); + parameter [15:0] INIT = 16'h0000; + + reg [15:0] r = INIT; + assign Q = r[{A3,A2,A1,A0}]; + always @(posedge CLK) if (CE) r <= { r[14:0], D }; + specify + $setup(D , posedge CLK, 173); + if (CE) (posedge CLK => (Q : D)) = 1472; + if (CE) (posedge CLK => (Q : 1'bx)) = 1472; + (A0 => Q) = 631; + (A1 => Q) = 472; + (A2 => Q) = 407; + (A3 => Q) = 238; + endspecify +endmodule + +// DSP + +(* abc9_box *) +module RBBDSP ( + output [21:0] AO_LOC, + output [21:0] BO_LOC, + output CE_O, + output [1:0] CO_LOC, + output [47:0] DO_LOC, + output [1:0] OPCODE_O, + output [47:0] P, + output [47:0] PO_LOC, + output RST_O, + + input [1:0] CI_LOC, + input [1:0] OPCODE, + input [1:0] OPCODE_I, + input [21:0] A, + input [21:0] AI_LOC, + input [21:0] B, + input [21:0] BI_LOC, + input [47:0] D, + input [47:0] DI_LOC, + input [47:0] PI_LOC, + input CE, + input CE_I, + (* clkbuf_sink *) + input CLK, + input CHIP_RST, + input RST_I, + input RST +); + +parameter AI_SEL_IN = 1'b0; +parameter [1:0] BC_CI = 2'b00; +parameter BI_SEL = 1'b0; +parameter BI_SEL_IN = 1'b0; +parameter CE_A = 1'b0; +parameter CE_ADD = 1'b0; +parameter CE_B = 1'b0; +parameter CE_C = 1'b0; +parameter CE_CRY = 1'b0; +parameter [1:0] CE_D = 2'b0; +parameter CE_M = 1'b0; +parameter CE_OPCODE = 1'b0; +parameter CE_PADD = 1'b0; +parameter CE_RST = 1'b1; +parameter CE_SEL = 1'b0; +parameter CE_SFT = 1'b0; +parameter [3:0] CI_SEL = 4'b0011; +parameter DI_SEL = 1'b0; +parameter DI_SEL_IN = 1'b0; +parameter OPCODE_SEL = 1'b0; +parameter [9:0] OP_ADD = 10'b0; +parameter OP_CPLX = 1'b0; +parameter [1:0] OP_MULT = 2'b11; +parameter [9:0] OP_PADD = 10'b0; +parameter [5:0] OP_SFT = 6'b0; +parameter [3:0] OP_X = 4'b1010; +parameter [3:0] OP_Y = 4'b0101; +parameter [3:0] OP_Z = 4'b0000; +parameter PO_LOC_SEL = 1'b1; +parameter PO_NWK_SEL = 1'b1; +parameter REG_A = 1'b0; +parameter REG_ADD = 1'b0; +parameter REG_B = 1'b0; +parameter REG_C = 1'b0; +parameter REG_CRY = 1'b0; +parameter [1:0] REG_D = 2'b0; +parameter REG_M = 1'b0; +parameter REG_OPCODE = 1'b0; +parameter REG_PADD = 1'b0; +parameter REG_SFT = 1'b0; +parameter RST_SEL = 1'b0; +parameter FF_SYNC_RST = 1'b0; + +specify + if (!REG_A) (A *> P) = 1000; + if (!REG_B) (B *> P) = 1000; + if (!REG_D) (D *> P) = 1000; +endspecify + +// Much of this functionality is TODO. + +assign P = $signed(A) * $signed(B); + +endmodule + +// Block RAM + +module RBRAM #( + parameter TARGET_NODE = "T40LP_Gen2.4", + parameter BRAM_MODE = "SDP_1024x40", + parameter QA_REG = 0, + parameter QB_REG = 0, + parameter CLKA_INV = 0, + parameter CLKB_INV = 0, + parameter DATA_WIDTH = 40, + parameter ADDR_WIDTH = 12, + parameter WE_WIDTH = 20, + parameter PERR_WIDTH = 4 +) ( + output [DATA_WIDTH-1:0] QA, + input [DATA_WIDTH-1:0] DA, + input CEA, + input [WE_WIDTH-1:0] WEA, + input [ADDR_WIDTH-1:0] AA, + (* clkbuf_sink *) + (* invertible_pin = "CLKA_INV" *) + input CLKA, + output [DATA_WIDTH-1:0] QB, + input [DATA_WIDTH-1:0] DB, + input CEB, + input [WE_WIDTH-1:0] WEB, + input [ADDR_WIDTH-1:0] AB, + (* clkbuf_sink *) + (* invertible_pin = "CLKB_INV" *) + input CLKB, + output reg [PERR_WIDTH-1:0] PERRA, + output reg [PERR_WIDTH-1:0] PERRB, + output SBEA, + output SBEB, + output MBEA, + output MBEB, + input SLP, + input PD +); + +// Timings simplified for now +specify + $setup(DA, posedge CLKA, 715); + $setup(CEA, posedge CLKA, 414); + $setup(AA, posedge CLKA, 624); + + $setup(DB, posedge CLKB, 744); + $setup(CEB, posedge CLKB, 350); + $setup(AB, posedge CLKB, 643); + + (posedge CLKA => (QA : DA)) = 2380; + (posedge CLKB => (QB : DB)) = 2289; +endspecify + +endmodule + +module RBRAM2 #( + parameter TARGET_NODE = "T16FFC_Gen2.4", + parameter BRAM_MODE = "SDP_2048x40", + parameter QA_REG = 0, + parameter QB_REG = 0, + parameter CLKA_INV = 0, + parameter CLKB_INV = 0, + parameter DATA_WIDTH = 40, + parameter ADDR_WIDTH = 13, + parameter WE_WIDTH = 20, + parameter PERR_WIDTH = 4 +) ( + output [DATA_WIDTH-1:0] QA, + input [DATA_WIDTH-1:0] DA, + input CEA, + input [WE_WIDTH-1:0] WEA, + input [ADDR_WIDTH-1:0] AA, + (* clkbuf_sink *) + (* invertible_pin = "CLKA_INV" *) + input CLKA, + output [DATA_WIDTH-1:0] QB, + input [DATA_WIDTH-1:0] DB, + input CEB, + input [WE_WIDTH-1:0] WEB, + input [ADDR_WIDTH-1:0] AB, + (* clkbuf_sink *) + (* invertible_pin = "CLKB_INV" *) + input CLKB, + output reg [PERR_WIDTH-1:0] PERRA, + output reg [PERR_WIDTH-1:0] PERRB, + output SBEA, + output SBEB, + output MBEA, + output MBEB, + input SLP, + input PD +); + +// Timings simplified for now +specify + $setup(DA, posedge CLKA, 2440); + $setup(CEA, posedge CLKA, 1668); + $setup(AA, posedge CLKA, 2289); + + $setup(DB, posedge CLKB, 2420); + $setup(CEB, posedge CLKB, 1805); + $setup(AB, posedge CLKB, 2170); + + (posedge CLKA => (QA : DA)) = 2227; + (posedge CLKB => (QB : DB)) = 2189; +endspecify + +endmodule diff --git a/techlibs/analogdevices/dsp_map.v b/techlibs/analogdevices/dsp_map.v new file mode 100644 index 00000000000..6f4b0822bb7 --- /dev/null +++ b/techlibs/analogdevices/dsp_map.v @@ -0,0 +1,60 @@ +module \$__MUL22X22 (input [21:0] A, input [21:0] B, output [43:0] Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 0; + parameter B_WIDTH = 0; + parameter Y_WIDTH = 0; + + wire [47:0] P_48; + RBBDSP #( + // Disable all registers + .AI_SEL_IN(1'b0), + .BC_CI(2'b00), + .BI_SEL(1'b0), + .BI_SEL_IN(1'b0), + .CE_A(1'b0), + .CE_ADD(1'b0), + .CE_B(1'b0), + .CE_C(1'b0), + .CE_CRY(1'b0), + .CE_D(2'b0), + .CE_M(1'b0), + .CE_OPCODE(1'b0), + .CE_PADD(1'b0), + .CE_RST(1'b1), + .CE_SEL(1'b0), + .CE_SFT(1'b0), + .CI_SEL(4'd3), + .DI_SEL(1'b0), + .DI_SEL_IN(1'b0), + .OPCODE_SEL(1'b0), + .OP_ADD(10'b0), + .OP_CPLX(1'b0), + .OP_MULT(2'b11), + .OP_PADD(10'b0000000000), + .OP_SFT(6'b000000), + .OP_X(4'b1010), + .OP_Y(4'b0101), + .OP_Z(4'b0000), + .PO_LOC_SEL(1'b1), + .PO_NWK_SEL(1'b1), + .REG_A(1'b0), + .REG_ADD(1'b0), + .REG_B(1'b0), + .REG_C(1'b0), + .REG_CRY(1'b0), + .REG_D(2'b0), + .REG_M(1'b0), + .REG_OPCODE(1'b0), + .REG_PADD(1'b0), + .REG_SFT(1'b0), + .RST_SEL(1'b0), + .FF_SYNC_RST(1'b0), + ) _TECHMAP_REPLACE_ ( + .P(P_48), + .A(A), + .B(B), + .D(48'b0) + ); + assign Y = P_48; +endmodule diff --git a/techlibs/analogdevices/ff_map.v b/techlibs/analogdevices/ff_map.v new file mode 100644 index 00000000000..0be742b49fe --- /dev/null +++ b/techlibs/analogdevices/ff_map.v @@ -0,0 +1,63 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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. + * + */ + +`ifndef _NO_FFS + +// Async reset, enable. + +module \$_DFFE_NP0P_ (input D, C, E, R, output Q); + FFCE_N #(.INIT(1'b0)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .CLR(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule +module \$_DFFE_PP0P_ (input D, C, E, R, output Q); + FFCE #(.INIT(1'b0)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .CLR(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +module \$_DFFE_NP1P_ (input D, C, E, R, output Q); + FFPE_N #(.INIT(1'b1)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .PRE(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule +module \$_DFFE_PP1P_ (input D, C, E, R, output Q); + FFPE #(.INIT(1'b1)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .PRE(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +// Sync reset, enable. + +module \$_SDFFE_NP0P_ (input D, C, E, R, output Q); + FFRE_N #(.INIT(1'b0)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .R(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule +module \$_SDFFE_PP0P_ (input D, C, E, R, output Q); + FFRE #(.INIT(1'b0)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .R(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +module \$_SDFFE_NP1P_ (input D, C, E, R, output Q); + FFSE_N #(.INIT(1'b1)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .S(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule +module \$_SDFFE_PP1P_ (input D, C, E, R, output Q); + FFSE #(.INIT(1'b1)) _TECHMAP_REPLACE_ (.D(D), .Q(Q), .C(C), .CE(E), .S(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +`endif + diff --git a/techlibs/analogdevices/lut_map.v b/techlibs/analogdevices/lut_map.v new file mode 100644 index 00000000000..832b5cbfbf3 --- /dev/null +++ b/techlibs/analogdevices/lut_map.v @@ -0,0 +1,79 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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. + * + */ + +// ============================================================================ +// LUT mapping + +`ifndef _NO_LUTS + +module \$lut (A, Y); + parameter WIDTH = 0; + parameter LUT = 0; + + (* force_downto *) + input [WIDTH-1:0] A; + output Y; + + generate + if (WIDTH == 1) begin + LUT1 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y), + .I0(A[0])); + end else + if (WIDTH == 2) begin + LUT2 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y), + .I0(A[0]), .I1(A[1])); + end else + if (WIDTH == 3) begin + LUT3 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y), + .I0(A[0]), .I1(A[1]), .I2(A[2])); + end else + if (WIDTH == 4) begin + LUT4 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y), + .I0(A[0]), .I1(A[1]), .I2(A[2]), + .I3(A[3])); + end else + if (WIDTH == 5) begin + LUT5 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y), + .I0(A[0]), .I1(A[1]), .I2(A[2]), + .I3(A[3]), .I4(A[4])); + end else + if (WIDTH == 6) begin + LUT6 #(.INIT(LUT)) _TECHMAP_REPLACE_ (.O(Y), + .I0(A[0]), .I1(A[1]), .I2(A[2]), + .I3(A[3]), .I4(A[4]), .I5(A[5])); + end else + if (WIDTH == 7) begin + wire f0, f1; + \$lut #(.LUT(LUT[ 63: 0]), .WIDTH(6)) lut0 (.A(A[5:0]), .Y(f0)); + \$lut #(.LUT(LUT[127:64]), .WIDTH(6)) lut1 (.A(A[5:0]), .Y(f1)); + LUTMUX7 mux7(.I0(f0), .I1(f1), .S(A[6]), .O(Y)); + end else + if (WIDTH == 8) begin + wire f0, f1; + \$lut #(.LUT(LUT[127: 0]), .WIDTH(7)) lut0 (.A(A[6:0]), .Y(f0)); + \$lut #(.LUT(LUT[255:128]), .WIDTH(7)) lut1 (.A(A[6:0]), .Y(f1)); + LUTMUX8 mux8 (.I0(f0), .I1(f1), .S(A[7]), .O(Y)); + end else begin + wire _TECHMAP_FAIL_ = 1; + end + endgenerate +endmodule + +`endif + diff --git a/techlibs/analogdevices/lutrams.txt b/techlibs/analogdevices/lutrams.txt new file mode 100644 index 00000000000..c391cdb43ef --- /dev/null +++ b/techlibs/analogdevices/lutrams.txt @@ -0,0 +1,20 @@ +ram distributed $__ANALOGDEVICES_LUTRAM_ { + option "SIZE" 32 abits 5; + option "SIZE" 64 abits 6; + width 1; + init no_undef; + prune_rom; + port arsw "RW" { + clock posedge; + } + option "MODE" "SP" { + option "SIZE" 32 cost 2; + option "SIZE" 64 cost 2; + } + option "MODE" "DP" { + option "SIZE" 32 cost 4; + option "SIZE" 64 cost 8; + port ar "R" { + } + } +} diff --git a/techlibs/analogdevices/lutrams_map.v b/techlibs/analogdevices/lutrams_map.v new file mode 100644 index 00000000000..18fa9351655 --- /dev/null +++ b/techlibs/analogdevices/lutrams_map.v @@ -0,0 +1,139 @@ +module $__ANALOGDEVICES_LUTRAM_ (...); + +parameter INIT = 0; +parameter OPTION_SIZE = 32; +parameter OPTION_MODE = "SP"; +parameter ABITS = 5; +parameter WIDTH = 1; + +output PORT_RW_RD_DATA; +input PORT_RW_WR_DATA; +input [ABITS-1:0] PORT_RW_ADDR; +input PORT_RW_WR_EN; +input PORT_RW_CLK; + +output PORT_R_RD_DATA; +input [ABITS-1:0] PORT_R_ADDR; + +generate + if (OPTION_MODE=="SP") + case(OPTION_SIZE) + 32: + RAMS32X1 + #( + .INIT(INIT) + ) + _TECHMAP_REPLACE_ + ( + .O(PORT_RW_RD_DATA), + .A0(PORT_RW_ADDR[0]), + .A1(PORT_RW_ADDR[1]), + .A2(PORT_RW_ADDR[2]), + .A3(PORT_RW_ADDR[3]), + .A4(PORT_RW_ADDR[4]), + .D(PORT_RW_WR_DATA), + .WCLK(PORT_RW_CLK), + .WE(PORT_RW_WR_EN) + ); + 64: + RAMS64X1 + #( + .INIT(INIT) + ) + _TECHMAP_REPLACE_ + ( + .O(PORT_RW_RD_DATA), + .A0(PORT_RW_ADDR[0]), + .A1(PORT_RW_ADDR[1]), + .A2(PORT_RW_ADDR[2]), + .A3(PORT_RW_ADDR[3]), + .A4(PORT_RW_ADDR[4]), + .A5(PORT_RW_ADDR[5]), + .D(PORT_RW_WR_DATA), + .WCLK(PORT_RW_CLK), + .WE(PORT_RW_WR_EN) + ); + default: + $error("invalid SIZE/MODE combination"); + endcase + else if (OPTION_MODE=="DP") + case (OPTION_SIZE) + 32: + RAMD32X1 + #( + .INIT(INIT) + ) + _TECHMAP_REPLACE_ + ( + .DPO(PORT_R_RD_DATA), + .SPO(PORT_RW_RD_DATA), + .A0(PORT_RW_ADDR[0]), + .A1(PORT_RW_ADDR[1]), + .A2(PORT_RW_ADDR[2]), + .A3(PORT_RW_ADDR[3]), + .A4(PORT_RW_ADDR[4]), + .D(PORT_RW_WR_DATA), + .DPRA0(PORT_R_ADDR[0]), + .DPRA1(PORT_R_ADDR[1]), + .DPRA2(PORT_R_ADDR[2]), + .DPRA3(PORT_R_ADDR[3]), + .DPRA4(PORT_R_ADDR[4]), + .WCLK(PORT_RW_CLK), + .WE(PORT_RW_WR_EN) + ); + 64: + RAMD64X1 + #( + .INIT(INIT) + ) + _TECHMAP_REPLACE_ + ( + .DPO(PORT_R_RD_DATA), + .SPO(PORT_RW_RD_DATA), + .A0(PORT_RW_ADDR[0]), + .A1(PORT_RW_ADDR[1]), + .A2(PORT_RW_ADDR[2]), + .A3(PORT_RW_ADDR[3]), + .A4(PORT_RW_ADDR[4]), + .A5(PORT_RW_ADDR[5]), + .D(PORT_RW_WR_DATA), + .DPRA0(PORT_R_ADDR[0]), + .DPRA1(PORT_R_ADDR[1]), + .DPRA2(PORT_R_ADDR[2]), + .DPRA3(PORT_R_ADDR[3]), + .DPRA4(PORT_R_ADDR[4]), + .DPRA5(PORT_R_ADDR[5]), + .WCLK(PORT_RW_CLK), + .WE(PORT_RW_WR_EN) + ); + default: + $error("invalid SIZE/MODE combination"); + endcase + else + wire _TECHMAP_FAIL_ = 1; +endgenerate + +endmodule + + +module $__ANALOGDEVICES_LUTRAM_DP_ (...); + +parameter INIT = 0; +parameter OPTION_SIZE = 32; +parameter ABITS = 5; +parameter WIDTH = 1; + +output PORT_RW_RD_DATA; +input PORT_RW_WR_DATA; +input [ABITS-1:0] PORT_RW_ADDR; +input PORT_RW_WR_EN; +input PORT_RW_CLK; + +output PORT_R_RD_DATA; +input [ABITS-1:0] PORT_R_ADDR; + +generate + +endgenerate + +endmodule diff --git a/techlibs/analogdevices/mux_map.v b/techlibs/analogdevices/mux_map.v new file mode 100644 index 00000000000..7fa45cb543b --- /dev/null +++ b/techlibs/analogdevices/mux_map.v @@ -0,0 +1,74 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * 2019 Eddie Hung + * + * 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. + * + */ + +// The purpose of these mapping rules is to allow preserve all (sufficiently +// wide) $shiftx cells during 'techmap' so that they can be mapped to hard +// resources, rather than being bit-blasted to gates during 'techmap' +// execution + +module \$shiftx (A, B, Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 1; + parameter B_WIDTH = 1; + parameter Y_WIDTH = 1; + + (* force_downto *) + input [A_WIDTH-1:0] A; + (* force_downto *) + input [B_WIDTH-1:0] B; + (* force_downto *) + output [Y_WIDTH-1:0] Y; + + parameter [B_WIDTH-1:0] _TECHMAP_CONSTMSK_B_ = 0; + parameter [B_WIDTH-1:0] _TECHMAP_CONSTVAL_B_ = 0; + + generate + if (B_SIGNED) begin + if (_TECHMAP_CONSTMSK_B_[B_WIDTH-1] && (_TECHMAP_CONSTVAL_B_[B_WIDTH-1] == 1'b0 || _TECHMAP_CONSTVAL_B_[B_WIDTH-1] === 1'bx)) + // Optimisation to remove B_SIGNED if sign bit of B is constant-0 + \$shiftx #( + .A_SIGNED(A_SIGNED), + .B_SIGNED(0), + .A_WIDTH(A_WIDTH), + .B_WIDTH(B_WIDTH-1'd1), + .Y_WIDTH(Y_WIDTH) + ) _TECHMAP_REPLACE_ ( + .A(A), .B(B[B_WIDTH-2:0]), .Y(Y) + ); + else + wire _TECHMAP_FAIL_ = 1; + end + else begin + if (((A_WIDTH + Y_WIDTH - 1) / Y_WIDTH) < `MIN_MUX_INPUTS) + wire _TECHMAP_FAIL_ = 1; + else + \$__ANALOGDEVICES_SHIFTX #( + .A_SIGNED(A_SIGNED), + .B_SIGNED(B_SIGNED), + .A_WIDTH(A_WIDTH), + .B_WIDTH(B_WIDTH), + .Y_WIDTH(Y_WIDTH) + ) _TECHMAP_REPLACE_ ( + .A(A), .B(B), .Y(Y) + ); + end + endgenerate +endmodule diff --git a/techlibs/analogdevices/retarget_map.v b/techlibs/analogdevices/retarget_map.v new file mode 100644 index 00000000000..a9712219ff5 --- /dev/null +++ b/techlibs/analogdevices/retarget_map.v @@ -0,0 +1,49 @@ +module FF (input C, D, output Q); +parameter INIT = 1'b0; +if (INIT === 1'b1) begin +FFPE _TECHMAP_REPLACE_ (.C(C), .D(D), .PRE(1'b0), .CE(1'b1), .Q(Q)); +end else begin +FFCE _TECHMAP_REPLACE_ (.C(C), .D(D), .CLR(1'b0), .CE(1'b1), .Q(Q)); +end +endmodule + +module FF_N (input C, D, output Q); +parameter INIT = 1'b0; +if (INIT === 1'b1) begin +FFPE_N _TECHMAP_REPLACE_ (.C(C), .D(D), .PRE(1'b0), .CE(1'b1), .Q(Q)); +end else begin +FFCE_N _TECHMAP_REPLACE_ (.C(C), .D(D), .CLR(1'b0), .CE(1'b1), .Q(Q)); +end +endmodule + +module FFC (input C, D, CLR, output Q); +FFCE _TECHMAP_REPLACE_ (.C(C), .D(D), .CLR(CLR), .CE(1'b1), .Q(Q)); +endmodule + +module FFC_N (input C, D, CLR, output Q); +FFCE_N _TECHMAP_REPLACE_ (.C(C), .D(D), .CLR(CLR), .CE(1'b1), .Q(Q)); +endmodule + +module FFP (input C, D, PRE, output Q); +FFPE _TECHMAP_REPLACE_ (.C(C), .D(D), .PRE(PRE), .CE(1'b1), .Q(Q)); +endmodule + +module FFP_N (input C, D, CLR, output Q); +FFPE_N _TECHMAP_REPLACE_ (.C(C), .D(D), .PRE(PRE), .CE(1'b1), .Q(Q)); +endmodule + +module FFR (input C, D, R, output Q); +FFRE _TECHMAP_REPLACE_ (.C(C), .D(D), .R(R), .CE(1'b1), .Q(Q)); +endmodule + +module FFR_N (input C, D, R, output Q); +FFRE_N _TECHMAP_REPLACE_ (.C(C), .D(D), .R(R), .CE(1'b1), .Q(Q)); +endmodule + +module FFS (input C, D, S, output Q); +FFSE _TECHMAP_REPLACE_ (.C(C), .D(D), .S(S), .CE(1'b1), .Q(Q)); +endmodule + +module FFS_N (input C, D, S, output Q); +FFSE_N _TECHMAP_REPLACE_ (.C(C), .D(D), .S(S), .CE(1'b1), .Q(Q)); +endmodule diff --git a/techlibs/analogdevices/synth_analogdevices.cc b/techlibs/analogdevices/synth_analogdevices.cc new file mode 100644 index 00000000000..00d2e18d69c --- /dev/null +++ b/techlibs/analogdevices/synth_analogdevices.cc @@ -0,0 +1,516 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * (C) 2019 Eddie Hung + * + * 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/celltypes.h" +#include "kernel/rtlil.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct SynthAnalogDevicesPass : public ScriptPass +{ + SynthAnalogDevicesPass() : ScriptPass("synth_analogdevices", "synthesis for Analog Devices FPGAs") { } + + void on_register() override + { + RTLIL::constpad["synth_analogdevices.abc9.W"] = "300"; // Number with which ABC will map a 6-input gate + // to one LUT6 (instead of a LUT5 + LUT2) + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" synth_analogdevices [options]\n"); + log("\n"); + log("This command runs synthesis for Analog Devices FPGAs. This command does not operate on\n"); + log("partly selected designs.\n"); + log("\n"); + log(" -top \n"); + log(" use the specified module as top module\n"); + log("\n"); + log(" -tech \n"); + log(" run synthesis for the specified ADI technology process\n"); + log(" currently only affects the type of BRAM used.\n"); + log(" supported values:\n"); + log(" - t40lp (RBRAM)\n"); + log(" - t16ffc (RBRAM2, default)\n"); + log("\n"); + log(" -edif \n"); + log(" write the design to the specified edif file. writing of an output file\n"); + log(" is omitted if this parameter is not specified.\n"); + log("\n"); + log(" -nobram\n"); + log(" do not use block RAM cells in output netlist\n"); + log("\n"); + log(" -nolutram\n"); + log(" do not use distributed RAM cells in output netlist\n"); + log("\n"); + log(" -nosrl\n"); + log(" do not use distributed SRL cells in output netlist\n"); + log("\n"); + log(" -nocarry\n"); + log(" do not use XORCY/MUXCY/CARRY4 cells in output netlist\n"); + log("\n"); + log(" -nowidelut\n"); + log(" do not use MUXF[7-8] resources to implement LUTs larger than native for\n"); + log(" the target\n"); + log("\n"); + log(" -nodsp\n"); + log(" do not use DSP48*s to implement multipliers and associated logic\n"); + log("\n"); + log(" -noiopad\n"); + log(" disable I/O buffer insertion (useful for hierarchical or \n"); + log(" out-of-context flows)\n"); + log("\n"); + log(" -noclkbuf\n"); + log(" disable automatic clock buffer insertion\n"); + log("\n"); + log(" -widemux \n"); + log(" enable inference of hard multiplexer resources (MUXF[78]) for muxes at\n"); + log(" or above this number of inputs (minimum value 2, recommended value >= 5)\n"); + log(" default: 0 (no inference)\n"); + log("\n"); + log(" -run :\n"); + log(" only run the commands between the labels (see below). an empty\n"); + log(" from label is synonymous to 'begin', and empty to label is\n"); + log(" synonymous to the end of the command list.\n"); + log("\n"); + log(" -noflatten\n"); + log(" do not flatten design before synthesis\n"); + log("\n"); + log(" -dff\n"); + log(" run 'abc'/'abc9' with -dff option\n"); + log("\n"); + log(" -retime\n"); + log(" run 'abc' with '-D 1' option to enable flip-flop retiming.\n"); + log(" implies -dff.\n"); + log("\n"); + log(" -noabc9\n"); + log(" disable use of new ABC9 flow\n"); + log("\n"); + log("\n"); + log("The following commands are executed by this synthesis command:\n"); + help_script(); + log("\n"); + } + + std::string top_opt, edif_file, json_file, tech, tech_param; + bool flatten, retime, noiopad, noclkbuf, nobram, nolutram, nosrl, nocarry, nowidelut, nodsp; + bool abc9, dff; + bool flatten_before_abc; + int widemux; + int widelut_size; + + void clear_flags() override + { + top_opt = "-auto-top"; + edif_file.clear(); + tech = "t16ffc"; + tech_param = " -D IS_T16FFC"; + flatten = true; + retime = false; + noiopad = false; + noclkbuf = false; + nocarry = false; + nobram = false; + nolutram = false; + nosrl = false; + nocarry = false; + nowidelut = false; + nodsp = false; + abc9 = true; + dff = false; + flatten_before_abc = false; + widemux = 0; + } + + void execute(std::vector args, RTLIL::Design *design) override + { + std::string run_from, run_to; + clear_flags(); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-top" && argidx+1 < args.size()) { + top_opt = "-top " + args[++argidx]; + continue; + } + if (args[argidx] == "-tech" && argidx+1 < args.size()) { + tech = args[++argidx]; + if (tech == "t16ffc") + tech_param = " -D IS_T16FFC"; + else if (tech == "t40lp") + tech_param = " -D IS_T40LP"; + continue; + } + if (args[argidx] == "-edif" && argidx+1 < args.size()) { + edif_file = args[++argidx]; + continue; + } + if (args[argidx] == "-run" && argidx+1 < args.size()) { + size_t pos = args[argidx+1].find(':'); + if (pos == std::string::npos) + break; + run_from = args[++argidx].substr(0, pos); + run_to = args[argidx].substr(pos+1); + continue; + } + if (args[argidx] == "-noflatten") { + flatten = false; + continue; + } + if (args[argidx] == "-flatten_before_abc") { + flatten_before_abc = true; + continue; + } + if (args[argidx] == "-retime") { + dff = true; + retime = true; + continue; + } + if (args[argidx] == "-nocarry") { + nocarry = true; + continue; + } + if (args[argidx] == "-nowidelut") { + nowidelut = true; + continue; + } + if (args[argidx] == "-iopad") { + continue; + } + if (args[argidx] == "-noiopad") { + noiopad = true; + continue; + } + if (args[argidx] == "-noclkbuf") { + noclkbuf = true; + continue; + } + if (args[argidx] == "-nocarry") { + nocarry = true; + continue; + } + if (args[argidx] == "-nobram") { + nobram = true; + continue; + } + if (args[argidx] == "-nolutram") { + nolutram = true; + continue; + } + if (args[argidx] == "-nosrl") { + nosrl = true; + continue; + } + if (args[argidx] == "-widemux" && argidx+1 < args.size()) { + widemux = atoi(args[++argidx].c_str()); + continue; + } + if (args[argidx] == "-noabc9") { + abc9 = false; + continue; + } + if (args[argidx] == "-nodsp") { + nodsp = true; + continue; + } + if (args[argidx] == "-dff") { + dff = true; + continue; + } + if (args[argidx] == "-json" && argidx+1 < args.size()) { + json_file = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, design); + + if (!(tech == "t16ffc" || tech == "t40lp")) + log_cmd_error("Invalid ADI -tech setting: '%s'.\n", tech); + + if (widemux != 0 && widemux < 2) + log_cmd_error("-widemux value must be 0 or >= 2.\n"); + + if (!design->full_selection()) + log_cmd_error("This command only operates on fully selected designs!\n"); + + if (abc9 && retime) + log_cmd_error("-retime option not currently compatible with -abc9!\n"); + + log_header(design, "Executing SYNTH_ANALOGDEVICES pass.\n"); + log_push(); + + run_script(design, run_from, run_to); + + log_pop(); + } + + void script() override + { + if (check_label("begin")) { + run(stringf("read_verilog -lib -specify %s +/analogdevices/cells_sim.v", tech_param)); + run(stringf("hierarchy -check %s", top_opt.c_str())); + } + + if (check_label("prepare")) { + run("proc"); + if (flatten || help_mode) + run("flatten", "(with '-flatten')"); + if (active_design) + active_design->scratchpad_unset("tribuf.added_something"); + run("tribuf -logic"); + if (noiopad && active_design && active_design->scratchpad_get_bool("tribuf.added_something")) + log_error("Tristate buffers are unsupported without the '-iopad' option.\n"); + run("deminout"); + run("opt_expr"); + run("opt_clean"); + run("check"); + run("opt -nodffe -nosdff"); + run("fsm"); + run("opt"); + if (help_mode) + run("wreduce [-keepdc]", "(option for '-widemux')"); + else + run("wreduce" + std::string(widemux > 0 ? " -keepdc" : "")); + run("peepopt"); + run("opt_clean"); + + if (widemux > 0 || help_mode) + run("muxpack", " ('-widemux' only)"); + + // xilinx_srl looks for $shiftx cells for identifying variable-length + // shift registers, so attempt to convert $pmux-es to this + // Also: wide multiplexer inference benefits from this too + if (!(nosrl && widemux == 0) || help_mode) { + run("pmux2shiftx", "(skip if '-nosrl' and '-widemux=0')"); + run("clean", " (skip if '-nosrl' and '-widemux=0')"); + } + } + + if (check_label("map_dsp", "(skip if '-nodsp')")) { + if (!nodsp || help_mode) { + run("memory_dff"); // xilinx_dsp will merge registers, reserve memory port registers first + // NB: Analog Devices multipliers are signed only + if (help_mode) + run("techmap -map +/mul2dsp.v -map +/analogdevices/{family}_dsp_map.v {options}"); + run("techmap -map +/mul2dsp.v -map +/analogdevices/dsp_map.v -D DSP_A_MAXWIDTH=22 -D DSP_B_MAXWIDTH=22 " + "-D DSP_A_MAXWIDTH_PARTIAL=18 " // Partial multipliers are intentionally + // limited to 18x18 in order to take + // advantage of the (PCOUT << 17) -> PCIN + // dedicated cascade chain capability + "-D DSP_A_MINWIDTH=2 -D DSP_B_MINWIDTH=2 " // Blocks Nx1 multipliers + "-D DSP_Y_MINWIDTH=9 " // UG901 suggests small multiplies are those 4x4 and smaller + "-D DSP_SIGNEDONLY=1 -D DSP_NAME=$__MUL22X22"); + + run("select a:mul2dsp"); + run("setattr -unset mul2dsp"); + run("opt_expr -fine"); + run("wreduce"); + run("select -clear"); + if (help_mode) + run("xilinx_dsp -family "); + else + run("xilinx_dsp -family xc7"); + run("chtype -set $mul t:$__soft_mul"); + } + } + + if (check_label("coarse")) { + run("techmap -map +/cmp2lut.v -map +/cmp2lcu.v -D LUT_WIDTH=6"); + run("alumacc"); + run("share"); + run("opt"); + run("memory -nomap"); + run("opt_clean"); + } + + if (check_label("map_memory")) { + std::string params = ""; + std::string lutrams_map = "+/analogdevices/lutrams_map.v"; + std::string brams_map = "+/analogdevices/brams_map.v"; + if (help_mode) { + params = " [...]"; + } else { + params += " -logic-cost-rom 0.015625"; + params += " -force-params"; + params += " -lib +/analogdevices/lutrams.txt"; + params += " -lib +/analogdevices/brams.txt"; + params += tech_param; + brams_map += tech_param; + if (nolutram) + params += " -no-auto-distributed"; + if (nobram) + params += " -no-auto-block"; + } + run("memory_libmap" + params); + run("techmap -map " + lutrams_map); + run("techmap -map " + brams_map); + } + + if (check_label("map_ffram")) { + if (widemux > 0) { + run("opt -fast -mux_bool -undriven -fine"); // Necessary to omit -mux_undef otherwise muxcover + // performs less efficiently + } else { + run("opt -fast -full"); + } + run("memory_map"); + } + + if (check_label("fine")) { + if (help_mode) { + run("simplemap t:$mux", "('-widemux' only)"); + run("muxcover ", "('-widemux' only)"); + } else if (widemux > 0) { + run("simplemap t:$mux"); + constexpr int cost_mux2 = 100; + std::string muxcover_args = stringf(" -nodecode -mux2=%d", cost_mux2); + switch (widemux) { + case 2: muxcover_args += stringf(" -mux4=%d -mux8=%d -mux16=%d", cost_mux2+1, cost_mux2+2, cost_mux2+3); break; + case 3: + case 4: muxcover_args += stringf(" -mux4=%d -mux8=%d -mux16=%d", cost_mux2*(widemux-1)-2, cost_mux2*(widemux-1)-1, cost_mux2*(widemux-1)); break; + case 5: + case 6: + case 7: + case 8: muxcover_args += stringf(" -mux8=%d -mux16=%d", cost_mux2*(widemux-1)-1, cost_mux2*(widemux-1)); break; + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + default: muxcover_args += stringf(" -mux16=%d", cost_mux2*(widemux-1)-1); break; + } + run("muxcover " + muxcover_args); + } + run("opt -full"); + + if (!nosrl || help_mode) + run("xilinx_srl -variable -minlen 3", "(skip if '-nosrl')"); + + std::string techmap_args = " -map +/techmap.v -D LUT_SIZE=6"; + if (help_mode) + techmap_args += " [-map +/analogdevices/mux_map.v]"; + else if (widemux > 0) + techmap_args += stringf(" -D MIN_MUX_INPUTS=%d -map +/analogdevices/mux_map.v", widemux); + if (!nocarry) { + techmap_args += " -map +/analogdevices/arith_map.v"; + } + run("techmap " + techmap_args); + run("opt -fast"); + } + + if (check_label("map_cells")) { + // Needs to be done before logic optimization, so that inverters (inserted + // here because of negative-polarity output enable) are handled. + if (help_mode || !noiopad) + run("iopadmap -bits -outpad OUTBUF I:O -inpad INBUF O:I A:top", "(skip if '-noiopad')"); + std::string techmap_args = "-map +/techmap.v -map +/analogdevices/cells_map.v"; + if (widemux > 0) + techmap_args += stringf(" -D MIN_MUX_INPUTS=%d", widemux); + run("techmap " + techmap_args); + run("clean"); + } + + if (check_label("map_ffs")) { + run("dfflegalize -cell $_DFFE_?P?P_ r -cell $_SDFFE_?P?P_ r"); + if (abc9 || help_mode) { + if (dff || help_mode) + run("zinit -all w:* t:$_SDFFE_*", "('-dff' only)"); + run("techmap -map +/analogdevices/ff_map.v", "('-abc9' only)"); + } + } + + if (check_label("map_luts")) { + run("opt_expr -mux_undef -noclkinv"); + if (flatten_before_abc) + run("flatten"); + if (help_mode) + run("abc -luts 2:2,3,6:5[,10,20] [-dff] [-D 1]", "(option for '-nowidelut', '-dff', '-retime')"); + else if (abc9) { + run("read_verilog -icells -lib -specify +/analogdevices/abc9_model.v"); + std::string abc9_opts; + std::string k = "synth_analogdevices.abc9.W"; + if (active_design && active_design->scratchpad.count(k)) + abc9_opts += stringf(" -W %s", active_design->scratchpad_get_string(k).c_str()); + else { + abc9_opts += stringf(" -W %s", RTLIL::constpad.at("synth_analogdevices.abc9.W").c_str()); + } + if (nowidelut) + abc9_opts += stringf(" -maxlut 6"); + if (dff) + abc9_opts += " -dff"; + run("abc9" + abc9_opts); + } + else { + std::string abc_opts; + if (nowidelut) + abc_opts += " -luts 2:2,3,6:5"; + else + abc_opts += " -luts 2:2,3,6:5,10,20"; + if (dff) + abc_opts += " -dff"; + if (retime) + abc_opts += " -D 1"; + run("abc -dress" + abc_opts); + } + run("clean"); + + if (help_mode || !abc9) + run("techmap -map +/analogdevices/ff_map.v", "(only if not '-abc9')"); + // This shregmap call infers fixed length shift registers after abc + // has performed any necessary retiming + if (!nosrl || help_mode) + run("xilinx_srl -fixed -minlen 3", "(skip if '-nosrl')"); + std::string techmap_args = "-map +/analogdevices/lut_map.v -map +/analogdevices/cells_map.v"; + techmap_args += " -D LUT_WIDTH=6"; + run("techmap " + techmap_args); + run("xilinx_dffopt"); + run("opt_lut_ins -tech analogdevices"); + } + + if (check_label("finalize")) { + run("clean"); + } + + if (check_label("check")) { + run("hierarchy -check"); + run("stat -tech analogdevices"); + run("check -noinit"); + run("blackbox =A:whitebox"); + } + + if (check_label("edif")) { + if (!edif_file.empty() || help_mode) { + run("delete t:$assert t:$scopeinfo"); + run(stringf("write_edif %s", edif_file.c_str())); + } + } + } +} SynthAnalogDevicesPass; + +PRIVATE_NAMESPACE_END diff --git a/techlibs/common/synth.cc b/techlibs/common/synth.cc index 0dbb7cbec7a..0623bf43daa 100644 --- a/techlibs/common/synth.cc +++ b/techlibs/common/synth.cc @@ -326,13 +326,13 @@ struct SynthPass : public ScriptPass { if ((!noabc && !flowmap) || help_mode) { #ifdef YOSYS_ENABLE_ABC if (help_mode) { - run(abc + " -fast", " (unless -noabc, unless -lut)"); - run(abc + " -fast -lut k", "(unless -noabc, if -lut)"); + run(abc, " (unless -noabc, unless -lut)"); + run(abc + " -lut k", "(unless -noabc, if -lut)"); } else { if (lut) - run(stringf("%s -fast -lut %d", abc, lut)); + run(stringf("%s -lut %d", abc, lut)); else - run(abc + " -fast"); + run(abc); } run("opt -fast", " (unless -noabc)"); #endif diff --git a/techlibs/fabulous/prims.v b/techlibs/fabulous/prims.v index 21dc5223d23..0dab9b8fd2a 100644 --- a/techlibs/fabulous/prims.v +++ b/techlibs/fabulous/prims.v @@ -149,6 +149,30 @@ module OutPass4_frame_config (input CLK, I0, I1, I2, I3); endmodule +(* blackbox, keep *) +module InPass4_frame_config_mux #( + parameter [3:0] O_reg = 0 +) ( + input CLK, + output O0, + output O1, + output O2, + output O3 +); +endmodule + +(* blackbox, keep *) +module OutPass4_frame_config_mux #( + parameter [3:0] I_reg = 0 +) ( + input I0, + input I1, + input I2, + input I3, + input CLK +); +endmodule + (* keep *) module IO_1_bidirectional_frame_config_pass (input CLK, T, I, output Q, O, (* iopad_external_pin *) inout PAD); assign PAD = T ? 1'bz : I; diff --git a/techlibs/gowin/Makefile.inc b/techlibs/gowin/Makefile.inc index 0744b1389df..be9adb8579a 100644 --- a/techlibs/gowin/Makefile.inc +++ b/techlibs/gowin/Makefile.inc @@ -3,6 +3,7 @@ OBJS += techlibs/gowin/synth_gowin.o $(eval $(call add_share_file,share/gowin,techlibs/gowin/cells_map.v)) $(eval $(call add_share_file,share/gowin,techlibs/gowin/cells_sim.v)) +$(eval $(call add_share_file,share/gowin,techlibs/gowin/cells_latch.v)) $(eval $(call add_share_file,share/gowin,techlibs/gowin/cells_xtra_gw1n.v)) $(eval $(call add_share_file,share/gowin,techlibs/gowin/cells_xtra_gw2a.v)) $(eval $(call add_share_file,share/gowin,techlibs/gowin/cells_xtra_gw5a.v)) diff --git a/techlibs/gowin/brams.txt b/techlibs/gowin/brams.txt index 6898c9bd9f5..1507d278186 100644 --- a/techlibs/gowin/brams.txt +++ b/techlibs/gowin/brams.txt @@ -51,11 +51,6 @@ ram block $__GOWIN_DP_ { portoption "WRITE_MODE" 1 { rdwr new; } - ifndef gw5a { - portoption "WRITE_MODE" 2 { - rdwr old; - } - } wrbe_separate; } } diff --git a/techlibs/gowin/brams_map.v b/techlibs/gowin/brams_map.v index 774896e79fa..6187eadacf2 100644 --- a/techlibs/gowin/brams_map.v +++ b/techlibs/gowin/brams_map.v @@ -205,7 +205,7 @@ output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; wire RSTA = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; wire RSTB = OPTION_RESET_MODE == "SYNC" ? PORT_B_RD_SRST : PORT_B_RD_ARST; -wire [13:0] ADA = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_B_WR_BE); +wire [13:0] ADA = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_A_WR_BE); wire [13:0] ADB = `addrbe(PORT_B_WIDTH, PORT_B_ADDR, PORT_B_WR_BE); generate diff --git a/techlibs/gowin/brams_map_gw5a.v b/techlibs/gowin/brams_map_gw5a.v index 547b0d1d142..8bc77bf4f9f 100644 --- a/techlibs/gowin/brams_map_gw5a.v +++ b/techlibs/gowin/brams_map_gw5a.v @@ -205,7 +205,7 @@ output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; wire RSTA = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; wire RSTB = OPTION_RESET_MODE == "SYNC" ? PORT_B_RD_SRST : PORT_B_RD_ARST; -wire [13:0] ADA = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_B_WR_BE); +wire [13:0] ADA = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_A_WR_BE); wire [13:0] ADB = `addrbe(PORT_B_WIDTH, PORT_B_ADDR, PORT_B_WR_BE); generate diff --git a/techlibs/gowin/cells_latch.v b/techlibs/gowin/cells_latch.v new file mode 100644 index 00000000000..b3d6c29f985 --- /dev/null +++ b/techlibs/gowin/cells_latch.v @@ -0,0 +1,37 @@ +`default_nettype none + +// DL D Latch with Positive Gate +module \$_DLATCH_P_ (input E, D, output Q); + DL _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(E)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +// DLN D Latch with Negative Gate +module \$_DLATCH_N_ (input E, D, output Q); + DLN _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(E)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +// DLC D Latch with Positive Gate and Asynchronous Clear +module \$_DLATCH_PP0_ (input E, R, D, output Q); + DLC _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(E), .CLEAR(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +// DLNC D Latch with Negative Gate and Asynchronous Clear +module \$_DLATCH_NP0_ (input E, R, D, output Q); + DLNC _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(E), .CLEAR(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +// DLP D Latch with Positive Gate and Asynchronous Preset +module \$_DLATCH_PP1_ (input E, R, D, output Q); + DLP _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(E), .PRESET(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule + +// DLNP D Latch with Negative Gate and Asynchronous Preset +module \$_DLATCH_NP1_ (input E, R, D, output Q); + DLNP _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(E), .PRESET(R)); + wire _TECHMAP_REMOVEINIT_Q_ = 1; +endmodule diff --git a/techlibs/gowin/cells_sim.v b/techlibs/gowin/cells_sim.v index 5e6083426c9..8d43038004b 100644 --- a/techlibs/gowin/cells_sim.v +++ b/techlibs/gowin/cells_sim.v @@ -540,6 +540,101 @@ module DFFNCE (output reg Q, input D, CLK, CE, CLEAR); end endmodule // DFFNCE (negative clock edge; asynchronous clear; clock enable) +// Latch sim cells +// Gate signal uses CLK port name to match the physical DFF BEL pin + +module DL (output reg Q, input D, CLK); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (CLK) Q <= D; +endmodule + +module DLN (output reg Q, input D, CLK); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (!CLK) Q <= D; +endmodule + +module DLE (output reg Q, input D, CLK, CE); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (CLK && CE) Q <= D; +endmodule + +module DLNE (output reg Q, input D, CLK, CE); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (!CLK && CE) Q <= D; +endmodule + +module DLC (output reg Q, input D, CLK, CLEAR); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (CLEAR) Q <= 1'b0; + else if (CLK) Q <= D; +endmodule + +module DLCE (output reg Q, input D, CLK, CE, CLEAR); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (CLEAR) Q <= 1'b0; + else if (CLK && CE) Q <= D; +endmodule + +module DLNC (output reg Q, input D, CLK, CLEAR); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (CLEAR) Q <= 1'b0; + else if (!CLK) Q <= D; +endmodule + +module DLNCE (output reg Q, input D, CLK, CE, CLEAR); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (CLEAR) Q <= 1'b0; + else if (!CLK && CE) Q <= D; +endmodule + +module DLP (output reg Q, input D, CLK, PRESET); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (PRESET) Q <= 1'b1; + else if (CLK) Q <= D; +endmodule + +module DLPE (output reg Q, input D, CLK, CE, PRESET); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (PRESET) Q <= 1'b1; + else if (CLK && CE) Q <= D; +endmodule + +module DLNP (output reg Q, input D, CLK, PRESET); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (PRESET) Q <= 1'b1; + else if (!CLK) Q <= D; +endmodule + +module DLNPE (output reg Q, input D, CLK, CE, PRESET); + parameter [0:0] INIT = 1'b0; + initial Q = INIT; + always @* + if (PRESET) Q <= 1'b1; + else if (!CLK && CE) Q <= D; +endmodule + // TODO add more DFF sim cells module VCC(output V); diff --git a/techlibs/gowin/synth_gowin.cc b/techlibs/gowin/synth_gowin.cc index cdc7d20f067..8abba1515cf 100644 --- a/techlibs/gowin/synth_gowin.cc +++ b/techlibs/gowin/synth_gowin.cc @@ -309,7 +309,7 @@ struct SynthGowinPass : public ScriptPass if (nolutram) args += " -no-auto-distributed"; } - run(stringf("memory_libmap -lib +/gowin/lutrams.txt -lib +/gowin/brams.txt -D %s", family) + args, "(-no-auto-block if -nobram, -no-auto-distributed if -nolutram)"); + run(stringf("memory_libmap -lib +/gowin/lutrams.txt -lib +/gowin/brams.txt%s", family == "gw5a" ? " -D gw5a" : "") + args, "(-no-auto-block if -nobram, -no-auto-distributed if -nolutram)"); run(stringf("techmap -map +/gowin/lutrams_map.v -map +/gowin/brams_map%s.v", family == "gw5a" ? "_gw5a" : "")); } @@ -340,17 +340,18 @@ struct SynthGowinPass : public ScriptPass run("opt_clean"); if (family == "gw5a") { if (strict_gw5a_dffs) { - run("dfflegalize -cell $_SDFFE_PP?P_ r -cell $_DFFE_PP?P_ r"); + run("dfflegalize -cell $_SDFFE_PP?P_ r -cell $_DFFE_PP?P_ r -cell $_DLATCH_?_ x -cell $_DLATCH_?P?_ x"); } else { - run("dfflegalize -cell $_DFF_?_ 0 -cell $_SDFFE_PP?P_ r -cell $_DFFE_PP?P_ r"); + run("dfflegalize -cell $_DFF_?_ 0 -cell $_SDFFE_PP?P_ r -cell $_DFFE_PP?P_ r -cell $_DLATCH_?_ x -cell $_DLATCH_?P?_ x"); } } else { if (nodffe) - run("dfflegalize -cell $_DFF_?_ 0 -cell $_SDFF_?P?_ r -cell $_DFF_?P?_ r"); + run("dfflegalize -cell $_DFF_?_ 0 -cell $_SDFF_?P?_ r -cell $_DFF_?P?_ r -cell $_DLATCH_?_ x -cell $_DLATCH_?P?_ x"); else - run("dfflegalize -cell $_DFF_?_ 0 -cell $_DFFE_?P_ 0 -cell $_SDFF_?P?_ r -cell $_SDFFE_?P?P_ r -cell $_DFF_?P?_ r -cell $_DFFE_?P?P_ r"); + run("dfflegalize -cell $_DFF_?_ 0 -cell $_DFFE_?P_ 0 -cell $_SDFF_?P?_ r -cell $_SDFFE_?P?P_ r -cell $_DFF_?P?_ r -cell $_DFFE_?P?P_ r -cell $_DLATCH_?_ x -cell $_DLATCH_?P?_ x"); } run("techmap -map +/gowin/cells_map.v"); + run("techmap -map +/gowin/cells_latch.v"); run("opt_expr -mux_undef"); run("simplemap"); } diff --git a/techlibs/quicklogic/synth_quicklogic.cc b/techlibs/quicklogic/synth_quicklogic.cc index ade6f944cef..1026dc233e1 100644 --- a/techlibs/quicklogic/synth_quicklogic.cc +++ b/techlibs/quicklogic/synth_quicklogic.cc @@ -236,6 +236,7 @@ struct SynthQuickLogicPass : public ScriptPass { run("ql_dsp_macc"); run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=20 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=11 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__QL_MUL20X18"); + run("chtype -set $mul t:$__soft_mul"); run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=10 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__QL_MUL10X9"); run("chtype -set $mul t:$__soft_mul"); diff --git a/tests/arch/analogdevices/.gitignore b/tests/arch/analogdevices/.gitignore new file mode 100644 index 00000000000..fb232f235f3 --- /dev/null +++ b/tests/arch/analogdevices/.gitignore @@ -0,0 +1 @@ +t_*.ys diff --git a/tests/arch/analogdevices/add_sub.ys b/tests/arch/analogdevices/add_sub.ys new file mode 100644 index 00000000000..2a297851c71 --- /dev/null +++ b/tests/arch/analogdevices/add_sub.ys @@ -0,0 +1,13 @@ +read_verilog ../common/add_sub.v +hierarchy -top top +proc +design -save orig + +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd top # Constrain all select calls below inside the top module +stat +select -assert-count 8 t:LUT2 +select -assert-count 2 t:CRY4 +select -assert-count 2 t:CRY4INIT +select -assert-none t:LUT2 t:CRY4 t:CRY4INIT %% t:* %D diff --git a/tests/arch/analogdevices/adffs.ys b/tests/arch/analogdevices/adffs.ys new file mode 100644 index 00000000000..9affcd52f34 --- /dev/null +++ b/tests/arch/analogdevices/adffs.ys @@ -0,0 +1,47 @@ +read_verilog ../common/adffs.v +design -save read + +hierarchy -top adff +proc +equiv_opt -async2sync -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd adff # Constrain all select calls below inside the top module +select -assert-count 1 t:FFCE + +select -assert-none t:FFCE %% t:* %D + + +design -load read +hierarchy -top adffn +proc +equiv_opt -async2sync -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd adffn # Constrain all select calls below inside the top module +select -assert-count 1 t:FFCE +select -assert-count 1 t:LUT1 + +select -assert-none t:FFCE t:LUT1 %% t:* %D + + +design -load read +hierarchy -top dffs +proc +equiv_opt -async2sync -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd dffs # Constrain all select calls below inside the top module +select -assert-count 1 t:FFRE +select -assert-count 1 t:LUT2 +stat +select -assert-none t:FFRE t:LUT2 %% t:* %D + + +design -load read +hierarchy -top ndffnr +proc +equiv_opt -async2sync -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd ndffnr # Constrain all select calls below inside the top module +select -assert-count 1 t:FFRE_N +select -assert-count 1 t:LUT1 + +select -assert-none t:FFRE_N t:LUT1 %% t:* %D diff --git a/tests/arch/analogdevices/asym_ram_sdp.ys b/tests/arch/analogdevices/asym_ram_sdp.ys new file mode 100644 index 00000000000..22ed5e01018 --- /dev/null +++ b/tests/arch/analogdevices/asym_ram_sdp.ys @@ -0,0 +1,50 @@ +# Memory bits <= 18K; Data width <= 36; Address width <= 14: -> RBRAM2 + +# w4b | r16b +design -reset +read_verilog asym_ram_sdp_read_wider.v +synth_analogdevices -top asym_ram_sdp_read_wider -noiopad +select -assert-count 1 t:RBRAM2 + +# w8b | r16b +design -reset +read_verilog asym_ram_sdp_read_wider.v +chparam -set WIDTHA 8 -set SIZEA 512 -set ADDRWIDTHA 9 asym_ram_sdp_read_wider +synth_analogdevices -top asym_ram_sdp_read_wider -noiopad +select -assert-count 1 t:RBRAM2 + +# w4b | r32b +design -reset +read_verilog asym_ram_sdp_read_wider.v +chparam -set WIDTHB 32 -set SIZEB 128 -set ADDRWIDTHB 7 asym_ram_sdp_read_wider +synth_analogdevices -top asym_ram_sdp_read_wider -noiopad +select -assert-count 2 t:RBRAM2 + +# w16b | r4b +design -reset +read_verilog asym_ram_sdp_write_wider.v +synth_analogdevices -top asym_ram_sdp_write_wider -noiopad +select -assert-count 1 t:RBRAM2 + +# w16b | r8b +design -reset +read_verilog asym_ram_sdp_write_wider.v +chparam -set WIDTHB 8 -set SIZEB 512 -set ADDRWIDTHB 9 asym_ram_sdp_read_wider +synth_analogdevices -top asym_ram_sdp_write_wider -noiopad +select -assert-count 1 t:RBRAM2 + +# w32b | r4b +design -reset +read_verilog asym_ram_sdp_write_wider.v +chparam -set WIDTHA 32 -set SIZEA 128 -set ADDRWIDTHA 7 asym_ram_sdp_read_wider +synth_analogdevices -top asym_ram_sdp_write_wider -noiopad +select -assert-count 1 t:RBRAM2 + +# w4b | r24b +design -reset +read_verilog asym_ram_sdp_read_wider.v +chparam -set SIZEA 768 +chparam -set WIDTHB 24 -set SIZEB 128 -set ADDRWIDTHB 7 asym_ram_sdp_read_wider +synth_analogdevices -top asym_ram_sdp_read_wider -noiopad +select -assert-count 2 t:RBRAM2 + diff --git a/tests/arch/analogdevices/asym_ram_sdp_read_wider.v b/tests/arch/analogdevices/asym_ram_sdp_read_wider.v new file mode 100644 index 00000000000..853ba62544b --- /dev/null +++ b/tests/arch/analogdevices/asym_ram_sdp_read_wider.v @@ -0,0 +1,73 @@ +// Asymmetric port RAM +// Read Wider than Write. Read Statement in loop +//asym_ram_sdp_read_wider.v +module asym_ram_sdp_read_wider (clkA, clkB, enaA, weA, enaB, addrA, addrB, diA, doB); + parameter WIDTHA = 4; + parameter SIZEA = 1024; + parameter ADDRWIDTHA = 10; + + parameter WIDTHB = 16; + parameter SIZEB = 256; + parameter ADDRWIDTHB = 8; + + input clkA; + input clkB; + input weA; + input enaA, enaB; + input [ADDRWIDTHA-1:0] addrA; + input [ADDRWIDTHB-1:0] addrB; + input [WIDTHA-1:0] diA; + output [WIDTHB-1:0] doB; + + `define max(a,b) {(a) > (b) ? (a) : (b)} + `define min(a,b) {(a) < (b) ? (a) : (b)} + + function integer log2; + input integer value; + reg [31:0] shifted; + integer res; + begin + if (value < 2) + log2 = value; + else + begin + shifted = value-1; + for (res=0; shifted>0; res=res+1) + shifted = shifted>>1; + log2 = res; + end + end + endfunction + + localparam maxSIZE = `max(SIZEA, SIZEB); + localparam maxWIDTH = `max(WIDTHA, WIDTHB); + localparam minWIDTH = `min(WIDTHA, WIDTHB); + + localparam RATIO = maxWIDTH / minWIDTH; + localparam log2RATIO = log2(RATIO); + + (* ram_style="block" *) + reg [minWIDTH-1:0] RAM [0:maxSIZE-1]; + reg [WIDTHB-1:0] readB; + + always @(posedge clkA) + begin + if (enaA) begin + if (weA) + RAM[addrA] <= diA; + end + end + + always @(posedge clkB) + begin : ramread + integer i; + reg [log2RATIO-1:0] lsbaddr; + if (enaB) begin + for (i = 0; i < RATIO; i = i+1) begin + lsbaddr = i; + readB[(i+1)*minWIDTH-1 -: minWIDTH] <= RAM[{addrB, lsbaddr}]; + end + end + end + assign doB = readB; +endmodule diff --git a/tests/arch/analogdevices/asym_ram_sdp_write_wider.v b/tests/arch/analogdevices/asym_ram_sdp_write_wider.v new file mode 100644 index 00000000000..abffa9fc4c2 --- /dev/null +++ b/tests/arch/analogdevices/asym_ram_sdp_write_wider.v @@ -0,0 +1,72 @@ +// Asymmetric port RAM +// Write wider than Read. Write Statement in a loop. +// asym_ram_sdp_write_wider.v +module asym_ram_sdp_write_wider (clkA, clkB, weA, enaA, enaB, addrA, addrB, diA, doB); + parameter WIDTHB = 4; + parameter SIZEB = 1024; + parameter ADDRWIDTHB = 10; + + parameter WIDTHA = 16; + parameter SIZEA = 256; + parameter ADDRWIDTHA = 8; + + input clkA; + input clkB; + input weA; + input enaA, enaB; + input [ADDRWIDTHA-1:0] addrA; + input [ADDRWIDTHB-1:0] addrB; + input [WIDTHA-1:0] diA; + output [WIDTHB-1:0] doB; + + `define max(a,b) {(a) > (b) ? (a) : (b)} + `define min(a,b) {(a) < (b) ? (a) : (b)} + + function integer log2; + input integer value; + reg [31:0] shifted; + integer res; + begin + if (value < 2) + log2 = value; + else + begin + shifted = value-1; + for (res=0; shifted>0; res=res+1) + shifted = shifted>>1; + log2 = res; + end + end + endfunction + + localparam maxSIZE = `max(SIZEA, SIZEB); + localparam maxWIDTH = `max(WIDTHA, WIDTHB); + localparam minWIDTH = `min(WIDTHA, WIDTHB); + + localparam RATIO = maxWIDTH / minWIDTH; + localparam log2RATIO = log2(RATIO); + + (* ram_style="block" *) + reg [minWIDTH-1:0] RAM [0:maxSIZE-1]; + reg [WIDTHB-1:0] readB; + + always @(posedge clkB) begin + if (enaB) begin + readB <= RAM[addrB]; + end + end + assign doB = readB; + + always @(posedge clkA) + begin : ramwrite + integer i; + reg [log2RATIO-1:0] lsbaddr; + for (i=0; i< RATIO; i= i+ 1) begin : write1 + lsbaddr = i; + if (enaA) begin + if (weA) + RAM[{addrA, lsbaddr}] <= diA[(i+1)*minWIDTH-1 -: minWIDTH]; + end + end + end +endmodule diff --git a/tests/arch/analogdevices/attributes_test.ys b/tests/arch/analogdevices/attributes_test.ys new file mode 100644 index 00000000000..03d6decff10 --- /dev/null +++ b/tests/arch/analogdevices/attributes_test.ys @@ -0,0 +1,39 @@ +# Check that blockram memory without parameters is not modified +read_verilog ../common/memory_attributes/attributes_test.v +hierarchy -top block_ram +synth_analogdevices -top block_ram -noiopad +cd block_ram # Constrain all select calls below inside the top module +# select -assert-count 1 t:RBRAM2 # This currently infers LUTRAM because BRAM is expensive. + +# Check that distributed memory without parameters is not modified +design -reset +read_verilog ../common/memory_attributes/attributes_test.v +hierarchy -top distributed_ram +synth_analogdevices -top distributed_ram -noiopad +cd distributed_ram # Constrain all select calls below inside the top module +select -assert-count 8 t:RAMS64X1 +select -assert-count 8 t:FFRE + +# Set ram_style distributed to blockram memory; will be implemented as distributed +design -reset +read_verilog ../common/memory_attributes/attributes_test.v +setattr -set ram_style "distributed" block_ram/m:* +synth_analogdevices -top block_ram -noiopad +cd block_ram # Constrain all select calls below inside the top module +select -assert-count 64 t:RAMS64X1 +select -assert-count 4 t:FFRE + +# Set synthesis, logic_block to blockram memory; will be implemented as distributed +design -reset +read_verilog ../common/memory_attributes/attributes_test.v +setattr -set logic_block 1 block_ram/m:* +synth_analogdevices -top block_ram -noiopad +cd block_ram # Constrain all select calls below inside the top module +select -assert-count 0 t:RBRAM2 + +# Set ram_style block to a distributed memory; will be implemented as blockram +design -reset +read_verilog ../common/memory_attributes/attributes_test.v +synth_analogdevices -top distributed_ram_manual -noiopad +cd distributed_ram_manual # Constrain all select calls below inside the top module +# select -assert-count 1 t:RBRAM2 # This gets implemented in logic instead diff --git a/tests/arch/analogdevices/blockram.ys b/tests/arch/analogdevices/blockram.ys new file mode 100644 index 00000000000..f6efa5ba886 --- /dev/null +++ b/tests/arch/analogdevices/blockram.ys @@ -0,0 +1,77 @@ +### TODO: Not running equivalence checking because BRAM models does not exists +### currently. Checking instance counts instead. +# Memory bits <= 18K; Data width <= 36; Address width <= 14: -> RBRAM2 +read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 12 -set DATA_WIDTH 1 sync_ram_sdp +setattr -set ram_style "block" sync_ram_sdp +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 1 t:RBRAM2 + +design -reset +read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 8 -set DATA_WIDTH 18 sync_ram_sdp +setattr -set ram_style "block" sync_ram_sdp +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 1 t:RBRAM2 + +design -reset +read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 14 -set DATA_WIDTH 1 sync_ram_sdp +setattr -set ram_style "block" sync_ram_sdp +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 1 t:RBRAM2 + +design -reset +read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 9 -set DATA_WIDTH 36 sync_ram_sdp +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 1 t:RBRAM2 + +# Anything memory bits < 1024 -> LUTRAM +design -reset +read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 8 -set DATA_WIDTH 2 sync_ram_sdp +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 0 t:RBRAM2 +select -assert-count 8 t:RAMD64X1 +select -assert-count 2 t:FFRE + +# More than 18K bits, data width <= 36 (TDP), and address width from 10 to 15b (non-cascaded) -> RAMB36E1 +design -reset +read_verilog ../common/blockram.v +chparam -set ADDRESS_WIDTH 10 -set DATA_WIDTH 36 sync_ram_sdp +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 1 t:RBRAM2 + + +### With parameters + +design -reset +read_verilog ../common/blockram.v +hierarchy -top sync_ram_sdp -chparam ADDRESS_WIDTH 12 -chparam DATA_WIDTH 1 +setattr -set ram_style "block" m:memory +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 1 t:RBRAM2 + +design -reset +read_verilog ../common/blockram.v +hierarchy -top sync_ram_sdp -chparam ADDRESS_WIDTH 12 -chparam DATA_WIDTH 1 +setattr -set logic_block 1 m:memory +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 0 t:RBRAM2 + +design -reset +read_verilog ../common/blockram.v +hierarchy -top sync_ram_sdp -chparam ADDRESS_WIDTH 8 -chparam DATA_WIDTH 1 +setattr -set ram_style "block" m:memory +synth_analogdevices -top sync_ram_sdp -noiopad +cd sync_ram_sdp +select -assert-count 1 t:RBRAM2 diff --git a/tests/arch/analogdevices/bug1460.ys b/tests/arch/analogdevices/bug1460.ys new file mode 100644 index 00000000000..66fb96a5bbb --- /dev/null +++ b/tests/arch/analogdevices/bug1460.ys @@ -0,0 +1,34 @@ +read_verilog < 1: + top_list.pop(0) + + # iterate over string substitutions + for top in top_list: + # limit number of tests per file to allow parallel make + if not f: + fn = f"t_mem{i}.ys" + f = open(fn, mode="w") + j = 0 + + # output yosys script test file + print( + blockram_template.format(param_str=param_str, top=top, tech=sim_test.tech, opts=" ".join(sim_test.opts)), + file=f + ) + for assertion in sim_test.assertions: + print(f"log ** CHECKING CELL COUNTS FOR TEST {top} WITH PARAMS{param_str} ON TECH {sim_test.tech}", file=f) + print(f"select {assertion}", file=f) + print("", file=f) + + # increment test counter + j += 1 + if j >= max_j: + f = f.close() + i += 1 + +if f: + f.close() diff --git a/tests/arch/analogdevices/mul.ys b/tests/arch/analogdevices/mul.ys new file mode 100644 index 00000000000..460bfeb68f3 --- /dev/null +++ b/tests/arch/analogdevices/mul.ys @@ -0,0 +1,9 @@ +read_verilog ../common/mul.v +hierarchy -top top +proc +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd top # Constrain all select calls below inside the top module + +select -assert-count 1 t:RBBDSP +select -assert-none t:RBBDSP %% t:* %D diff --git a/tests/arch/analogdevices/mul_unsigned.v b/tests/arch/analogdevices/mul_unsigned.v new file mode 100644 index 00000000000..e3713a64281 --- /dev/null +++ b/tests/arch/analogdevices/mul_unsigned.v @@ -0,0 +1,30 @@ +/* +Example from: https://www.xilinx.com/support/documentation/sw_manuals/xilinx2019_1/ug901-vivado-synthesis.pdf [p. 89]. +*/ + +// Unsigned 16x24-bit Multiplier +// 1 latency stage on operands +// 3 latency stage after the multiplication +// File: multipliers2.v +// +module mul_unsigned (clk, A, B, RES); +parameter WIDTHA = /*16*/ 6; +parameter WIDTHB = /*24*/ 9; +input clk; +input [WIDTHA-1:0] A; +input [WIDTHB-1:0] B; +output [WIDTHA+WIDTHB-1:0] RES; +reg [WIDTHA-1:0] rA; +reg [WIDTHB-1:0] rB; +reg [WIDTHA+WIDTHB-1:0] M [3:0]; +integer i; +always @(posedge clk) + begin + rA <= A; + rB <= B; + M[0] <= rA * rB; + for (i = 0; i < 3; i = i+1) + M[i+1] <= M[i]; + end +assign RES = M[3]; +endmodule diff --git a/tests/arch/analogdevices/mul_unsigned.ys b/tests/arch/analogdevices/mul_unsigned.ys new file mode 100644 index 00000000000..5bdbdaac4c2 --- /dev/null +++ b/tests/arch/analogdevices/mul_unsigned.ys @@ -0,0 +1,10 @@ +read_verilog mul_unsigned.v +hierarchy -top mul_unsigned +proc + +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd mul_unsigned # Constrain all select calls below inside the top module +select -assert-count 1 t:RBBDSP +select -assert-count 75 t:FFRE +select -assert-none t:RBBDSP t:FFRE %% t:* %D diff --git a/tests/arch/analogdevices/mux.ys b/tests/arch/analogdevices/mux.ys new file mode 100644 index 00000000000..375ce90f2b9 --- /dev/null +++ b/tests/arch/analogdevices/mux.ys @@ -0,0 +1,50 @@ +read_verilog ../common/mux.v +design -save read + +hierarchy -top mux2 +proc +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd mux2 # Constrain all select calls below inside the top module +select -assert-count 1 t:LUT3 + +select -assert-none t:LUT3 %% t:* %D + + +design -load read +hierarchy -top mux4 +proc +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd mux4 # Constrain all select calls below inside the top module +select -assert-count 1 t:LUT6 + +select -assert-none t:LUT6 %% t:* %D + + +design -load read +hierarchy -top mux8 +proc +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd mux8 # Constrain all select calls below inside the top module +select -assert-count 1 t:LUT3 +select -assert-count 2 t:LUT6 + +select -assert-none t:LUT3 t:LUT6 %% t:* %D + + +design -load read +hierarchy -top mux16 +proc +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd mux16 # Constrain all select calls below inside the top module +select -assert-max 2 t:LUT3 +select -assert-max 2 t:LUT4 +select -assert-min 4 t:LUT6 +select -assert-max 7 t:LUT6 +select -assert-max 2 t:LUTMUX7 +dump + +select -assert-none t:LUT6 t:LUT4 t:LUT3 t:LUTMUX7 %% t:* %D diff --git a/tests/arch/analogdevices/opt_lut_ins.ys b/tests/arch/analogdevices/opt_lut_ins.ys new file mode 100644 index 00000000000..9723ee651ff --- /dev/null +++ b/tests/arch/analogdevices/opt_lut_ins.ys @@ -0,0 +1,26 @@ +read_rtlil << EOF + +module \top + + wire width 4 input 1 \A + + wire output 2 \O + + cell \LUT4 $0 + parameter \INIT 16'1111110011000000 + connect \I0 \A [0] + connect \I1 \A [1] + connect \I2 \A [2] + connect \I3 \A [3] + connect \O \O + end +end + +EOF + +read_verilog -lib +/analogdevices/cells_sim.v +equiv_opt -assert -map +/analogdevices/cells_sim.v opt_lut_ins -tech analogdevices + +design -load postopt + +select -assert-count 1 t:LUT3 diff --git a/tests/arch/analogdevices/run-test.sh b/tests/arch/analogdevices/run-test.sh new file mode 100755 index 00000000000..9b5e2f7f4eb --- /dev/null +++ b/tests/arch/analogdevices/run-test.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eu +python3 mem_gen.py +source ../../gen-tests-makefile.sh +generate_mk --yosys-scripts --bash diff --git a/tests/arch/analogdevices/shifter.ys b/tests/arch/analogdevices/shifter.ys new file mode 100644 index 00000000000..3cd67cb9340 --- /dev/null +++ b/tests/arch/analogdevices/shifter.ys @@ -0,0 +1,10 @@ +read_verilog ../common/shifter.v +hierarchy -top top +proc +flatten +equiv_opt -assert -map +/analogdevices/cells_sim.v synth_analogdevices -noiopad # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd top # Constrain all select calls below inside the top module + +select -assert-count 8 t:FFRE +select -assert-none t:FFRE %% t:* %D diff --git a/tests/arch/common/blockram.v b/tests/arch/common/blockram.v index 4a9d45a6b4a..4358a4655e6 100644 --- a/tests/arch/common/blockram.v +++ b/tests/arch/common/blockram.v @@ -22,6 +22,30 @@ module sync_ram_sp #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10) endmodule // sync_ram_sp +module sync_ram_sp_nochange #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10) + (input wire write_enable, clk, + input wire [DATA_WIDTH-1:0] data_in, + input wire [ADDRESS_WIDTH-1:0] address_in, + output wire [DATA_WIDTH-1:0] data_out); + + localparam WORD = (DATA_WIDTH-1); + localparam DEPTH = (2**ADDRESS_WIDTH-1); + + reg [WORD:0] data_out_r; + reg [WORD:0] memory [0:DEPTH]; + + always @(posedge clk) begin + if (write_enable) + memory[address_in] <= data_in; + else + data_out_r <= memory[address_in]; + end + + assign data_out = data_out_r; + +endmodule // sync_ram_sp_nochange + + module sync_ram_sdp #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10) (input wire clk, write_enable, input wire [DATA_WIDTH-1:0] data_in, @@ -112,6 +136,62 @@ module sync_ram_sdp_wrr #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10, SHIFT_VAL=1) endmodule // sync_ram_sdp_wrr +module double_sync_ram_sp #(parameter DATA_WIDTH=8, ADDRESS_WIDTH=10, USE_TDP=0) +( + input wire write_enable_a, clk_a, + input wire [DATA_WIDTH-1:0] data_in_a, + input wire [ADDRESS_WIDTH-1:0] address_in_a, + output wire [DATA_WIDTH-1:0] data_out_a, + + input wire write_enable_b, clk_b, + input wire [DATA_WIDTH-1:0] data_in_b, + input wire [ADDRESS_WIDTH-1:0] address_in_b, + output wire [DATA_WIDTH-1:0] data_out_b +); + + generate + if (USE_TDP) begin + + sync_ram_tdp #( + .DATA_WIDTH(DATA_WIDTH), + .ADDRESS_WIDTH(ADDRESS_WIDTH+1) + ) ram ( + .clk_a(clk_a), .clk_b(clk_b), + .write_enable_a(write_enable_a), .write_enable_b(write_enable_b), + .write_data_a(data_in_a), .write_data_b(data_in_b), + .addr_a({1'b0, address_in_a}), .addr_b({1'b1, address_in_b}), + .read_data_a(data_out_a), .read_data_b(data_out_b) + ); + + end else begin + + sync_ram_sp #( + .DATA_WIDTH(DATA_WIDTH), + .ADDRESS_WIDTH(ADDRESS_WIDTH) + ) a_ram ( + .write_enable(write_enable_a), + .clk(clk_a), + .data_in(data_in_a), + .address_in(address_in_a), + .data_out(data_out_a) + ); + + sync_ram_sp #( + .DATA_WIDTH(DATA_WIDTH), + .ADDRESS_WIDTH(ADDRESS_WIDTH) + ) b_ram ( + .write_enable(write_enable_b), + .clk(clk_b), + .data_in(data_in_b), + .address_in(address_in_b), + .data_out(data_out_b) + ); + end + endgenerate + +endmodule // double_sync_ram_sp + + module double_sync_ram_sdp #(parameter DATA_WIDTH_A=8, ADDRESS_WIDTH_A=10, DATA_WIDTH_B=8, ADDRESS_WIDTH_B=10) ( input wire write_enable_a, clk_a, diff --git a/tests/arch/ecp5/opt_lut_ins.ys b/tests/arch/ecp5/opt_lut_ins.ys index 7aae10d05a4..9587bd41f8a 100644 --- a/tests/arch/ecp5/opt_lut_ins.ys +++ b/tests/arch/ecp5/opt_lut_ins.ys @@ -23,7 +23,7 @@ EOF read_verilog -lib +/ecp5/cells_sim.v -equiv_opt -nocells -assert -map +/ecp5/cells_sim.v opt_lut_ins -tech ecp5 +equiv_opt -nocells -assert -map +/ecp5/cells_sim.v opt_lut_ins -tech lattice design -load postopt diff --git a/tests/arch/gowin/bug5688.ys b/tests/arch/gowin/bug5688.ys new file mode 100644 index 00000000000..39019c4d6fe --- /dev/null +++ b/tests/arch/gowin/bug5688.ys @@ -0,0 +1,31 @@ +read_verilog << EOT +`default_nettype none + +module top ( + input wire clk, + input wire [9:0] rd_addr, + output reg [15:0] rd_data, + input wire [9:0] wr_addr, + input wire [15:0] wr_data, + input wire wr_en +); + + (* ram_style = "block" *) reg [15:0] mem [0:1023]; + + // Read port — separate always block + always @(posedge clk) begin + rd_data <= mem[rd_addr]; + end + + // Write port — separate always block + always @(posedge clk) begin + if (wr_en) + mem[wr_addr] <= wr_data; + end + +endmodule + +EOT +synth_gowin -top top +splitnets +select -assert-any top/mem.0.0 %ci*:+DPX9B[ADA]:+DFF:+IBUF i:wr_en %i diff --git a/tests/arch/gowin/latches.ys b/tests/arch/gowin/latches.ys new file mode 100644 index 00000000000..6a05d2357de --- /dev/null +++ b/tests/arch/gowin/latches.ys @@ -0,0 +1,25 @@ +read_verilog ../common/latches.v +design -save read + +hierarchy -top latchp +proc +equiv_opt -async2sync -assert -map +/gowin/cells_sim.v synth_gowin # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd latchp # Constrain all select calls below inside the top module +select -assert-count 1 t:DL +select -assert-count 3 t:IBUF +select -assert-count 1 t:OBUF + +select -assert-none t:DL t:IBUF t:OBUF %% t:* %D + +design -load read +hierarchy -top latchn +proc +equiv_opt -async2sync -assert -map +/gowin/cells_sim.v synth_gowin # equivalency check +design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) +cd latchn # Constrain all select calls below inside the top module +select -assert-count 1 t:DLN +select -assert-count 3 t:IBUF +select -assert-count 1 t:OBUF + +select -assert-none t:DLN t:IBUF t:OBUF %% t:* %D diff --git a/tests/arch/ice40/fsm.ys b/tests/arch/ice40/fsm.ys index e3b74620232..b01d34bba31 100644 --- a/tests/arch/ice40/fsm.ys +++ b/tests/arch/ice40/fsm.ys @@ -12,5 +12,5 @@ cd fsm # Constrain all select calls below inside the top module select -assert-count 4 t:SB_DFF select -assert-count 2 t:SB_DFFESR -select -assert-max 15 t:SB_LUT4 +select -assert-max 16 t:SB_LUT4 select -assert-none t:SB_DFFESR t:SB_DFF t:SB_LUT4 %% t:* %D diff --git a/tests/arch/ice40/ice40_dsp_const.ys b/tests/arch/ice40/ice40_dsp_const.ys index c9c76a1acba..735f945a1e0 100644 --- a/tests/arch/ice40/ice40_dsp_const.ys +++ b/tests/arch/ice40/ice40_dsp_const.ys @@ -74,6 +74,7 @@ EOT techmap -wb -D EQUIV -autoproc -map +/ice40/cells_sim.v +async2sync equiv_make top ref equiv select -assert-any -module equiv t:$equiv equiv_induct diff --git a/tests/opt/opt_clean_init_const.ys b/tests/opt/opt_clean_init_const.ys new file mode 100644 index 00000000000..1b3d5db63e0 --- /dev/null +++ b/tests/opt/opt_clean_init_const.ys @@ -0,0 +1,9 @@ +read_rtlil << EOT +module \top + attribute \init 1'0 + wire \w + + connect \w 1'0 +end +EOT +opt_clean diff --git a/tests/opt/opt_hier.tcl b/tests/opt/opt_hier.tcl index 65d8f9809a2..d006759f56b 100644 --- a/tests/opt/opt_hier.tcl +++ b/tests/opt/opt_hier.tcl @@ -27,7 +27,7 @@ foreach fn [glob opt_hier_*.v] { design -copy-from gate -as gate A:top yosys rename -hide equiv_make gold gate equiv - equiv_induct equiv + equiv_induct -ignore-unknown-cells equiv equiv_status -assert equiv log -pop diff --git a/tests/pyosys/test_dict.py b/tests/pyosys/test_dict.py index 717fed8eafb..59342391c83 100644 --- a/tests/pyosys/test_dict.py +++ b/tests/pyosys/test_dict.py @@ -33,12 +33,3 @@ before = len(repr_test) print(repr_test.popitem()) assert before - 1 == len(repr_test) - -# test noncomparable -## if ys.CellType ever gets an == operator just disable this section -uncomparable_value = ys.Globals.yosys_celltypes.cell_types[ys.IdString("$not")] - -x = ys.IdstringToCelltypeDict({ ys.IdString("\\a"): uncomparable_value}) -y = ys.IdstringToCelltypeDict({ ys.IdString("\\a"): uncomparable_value}) - -assert x != y # not comparable diff --git a/tests/svtypes/array_assign.sv b/tests/svtypes/array_assign.sv new file mode 100644 index 00000000000..a5ca6363ccf --- /dev/null +++ b/tests/svtypes/array_assign.sv @@ -0,0 +1,87 @@ +// Test for array-to-array assignment and ternary expressions + +`define STRINGIFY(x) `"x`" +`define STATIC_ASSERT(x) if(!(x)) $error({"assert failed: ", `STRINGIFY(x)}) + +module top; + // Test 1: Basic array ternary with continuous assignment + reg [7:0] a1[4]; + reg [7:0] b1[4]; + wire [7:0] out1[4]; + wire sel1; + assign out1 = sel1 ? a1 : b1; + `STATIC_ASSERT($bits(out1) == 32); + + // Test 2: Non-zero base index + reg [7:0] a2[3:6]; + reg [7:0] b2[3:6]; + wire [7:0] out2[3:6]; + wire sel2; + assign out2 = sel2 ? a2 : b2; + `STATIC_ASSERT($bits(out2) == 32); + + // Test 3: Single-bit elements + reg a3[8]; + reg b3[8]; + wire out3[8]; + wire sel3; + assign out3 = sel3 ? a3 : b3; + `STATIC_ASSERT($bits(out3) == 8); + + // Test 4: Multi-dimensional array ternary + reg [7:0] a4[2][3]; + reg [7:0] b4[2][3]; + wire [7:0] out4[2][3]; + wire sel4; + assign out4 = sel4 ? a4 : b4; + `STATIC_ASSERT($bits(out4) == 48); + + // Test 5: Direct array assignment (continuous) + reg [7:0] a5[4]; + wire [7:0] b5[4]; + assign b5 = a5; + `STATIC_ASSERT($bits(b5) == 32); + + // Test 6: Multi-dimensional direct assignment (continuous) + reg [7:0] a6[2][3]; + wire [7:0] b6[2][3]; + assign b6 = a6; + `STATIC_ASSERT($bits(b6) == 48); + + // Test 7: Procedural direct assignment with different unpacked index ranges + // Covers the AST_BLOCK expansion path for AST_ASSIGN_EQ. + logic pa [1:0][1:0]; + logic pb [1:0][0:1]; + always_comb begin + pa[0][0] = 1'b0; + pa[0][1] = 1'b1; + pa[1][0] = 1'b1; + pa[1][1] = 1'b1; + + pb = pa; + + assert(pb[0][1] == 1'b0); + assert(pb[0][0] == 1'b1); + assert(pb[1][1] == 1'b1); + assert(pb[1][0] == 1'b1); + end + + // Test 8: Procedural ternary assignment on arrays + // Covers the AST_BLOCK expansion path for ternary RHS. + logic pt_a [1:0]; + logic pt_b [1:0]; + logic pt_o [1:0]; + logic pt_sel; + always_comb begin + pt_a[0] = 1'b0; + pt_a[1] = 1'b1; + pt_b[0] = 1'b1; + pt_b[1] = 1'b0; + pt_sel = 1'b1; + + pt_o = pt_sel ? pt_a : pt_b; + + assert(pt_o[0] == 1'b0); + assert(pt_o[1] == 1'b1); + end +endmodule diff --git a/tests/techmap/abc9-nonbox-loop-with-box.ys b/tests/techmap/abc9-nonbox-loop-with-box.ys new file mode 100644 index 00000000000..2b7a551bc51 --- /dev/null +++ b/tests/techmap/abc9-nonbox-loop-with-box.ys @@ -0,0 +1,19 @@ +read_verilog -icells -specify < o) = 1; +endspecify +endmodule + +module top(input i, output o); + wire a, b, c, z; + $_AND_ a0(.A(b), .B(i), .Y(a)); + $_AND_ b0(.A(a), .B(c), .Y(b)); + $_AND_ c0(.A(b), .B(i), .Y(c)); + box1 u_box(.i(i), .o(z)); + assign o = c ^ z; +endmodule +EOT + +abc9 -lut 4 diff --git a/tests/techmap/abc9.ys b/tests/techmap/abc9.ys index 2140dde2672..c7a97ec4f8a 100644 --- a/tests/techmap/abc9.ys +++ b/tests/techmap/abc9.ys @@ -17,7 +17,8 @@ design -save gold abc9 -lut 4 design -load gold -abc9 -lut 4 -fast +scratchpad -copy abc9.script.default.fast abc9.script +abc9 -lut 4 design -load gold scratchpad -copy abc9.script.default.area abc9.script diff --git a/tests/tools/rtlil-fuzz-grammar.json b/tests/tools/rtlil-fuzz-grammar.json index c27b160f418..96af9bde3f3 100644 --- a/tests/tools/rtlil-fuzz-grammar.json +++ b/tests/tools/rtlil-fuzz-grammar.json @@ -8,7 +8,7 @@ "end\n" ] ], - "": [ [ " wire width ", "", " ", "", " ", "", "\n" ] ], + "": [ [ "", " wire width ", "", " ", "", " ", "", "\n" ] ], "": [ [ "1" ], [ "2" ], [ "3" ], [ "4" ], [ "32" ], [ "128" ] ], "": [ [ "input ", "" ], [ "output ", "" ], [ "inout ", "" ], [] ], "": [ @@ -71,6 +71,7 @@ " end\n" ] ], + "": [ [ " attribute \\init ", "", "\n" ] ], "": [ [ "\\wire_a" ], [ "\\wire_b" ], [ "\\wire_c" ], [ "\\wire_d" ], [ "\\wire_e" ], [ "\\wire_f" ], [ "\\wire_g" ], [ "\\wire_h" ], [ "\\wire_i" ], [ "\\wire_j" ] ], "": [ [ "\\cell_a" ], [ "\\cell_b" ], [ "\\cell_c" ], [ "\\cell_d" ], [ "\\cell_e" ], [ "\\cell_f" ], [ "\\cell_g" ], [ "\\cell_h" ], [ "\\cell_i" ], [ "\\cell_j" ] ], "": [ [ "\\bb1" ], [ "\\bb2" ] ], @@ -97,6 +98,7 @@ "": [ [ " connect ", "", " ", "", "\n" ] ], "": [ [ ], [ "", "" ] ], + "": [ [ ], [ "", "" ] ], "": [ [ ], [ "", "" ] ], "": [ [ ], [ "", "" ] ], "": [ [ ], [ "", "" ] ], diff --git a/tests/unit/Makefile b/tests/unit/Makefile index 1a436461d8c..004d7c0a7ef 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile @@ -4,10 +4,10 @@ UNAME_S := $(shell uname -s) GTEST_PREFIX := $(shell brew --prefix googletest 2>/dev/null) ifeq ($(GTEST_PREFIX),) GTEST_CXXFLAGS := - GTEST_LDFLAGS := -lgtest -lgtest_main + GTEST_LDFLAGS := -lgtest -lgmock -lgtest_main else GTEST_CXXFLAGS := -I$(GTEST_PREFIX)/include - GTEST_LDFLAGS := -L$(GTEST_PREFIX)/lib -lgtest -lgtest_main + GTEST_LDFLAGS := -L$(GTEST_PREFIX)/lib -lgtest -lgmock -lgtest_main endif ifeq ($(UNAME_S),Darwin) diff --git a/tests/unit/kernel/cellTypesTest.cc b/tests/unit/kernel/cellTypesTest.cc new file mode 100644 index 00000000000..f2c044df4f7 --- /dev/null +++ b/tests/unit/kernel/cellTypesTest.cc @@ -0,0 +1,87 @@ +#include +#include "kernel/yosys.h" +#include "kernel/yosys_common.h" +#include "kernel/celltypes.h" +#include "kernel/newcelltypes.h" + +#include + +YOSYS_NAMESPACE_BEGIN + +TEST(CellTypesTest, basic) +{ + yosys_setup(); + log_files.push_back(stdout); + CellTypes older; + NewCellTypes newer; + older.setup(nullptr); + newer.setup(nullptr); + older.setup_type(ID(bleh), {ID::G}, {ID::H, ID::I}, false, true); + newer.setup_type(ID(bleh), {ID::G}, {ID::H, ID::I}, false, true); + EXPECT_EQ(older.cell_known(ID(aaaaa)), newer.cell_known(ID(aaaaa))); + EXPECT_EQ(older.cell_known(ID($and)), newer.cell_known(ID($and))); + auto check_port = [&](auto type, auto port) { + EXPECT_EQ(older.cell_port_dir(type, port), newer.cell_port_dir(type, port)); + EXPECT_EQ(older.cell_input(type, port), newer.cell_input(type, port)); + EXPECT_EQ(older.cell_output(type, port), newer.cell_output(type, port)); + }; + + // ground truth + const pool expected_ff_types = { + 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_), + }; + + for (size_t i = 0; i < static_cast(RTLIL::StaticId::STATIC_ID_END); i++) { + IdString type; + type.index_ = i; + EXPECT_EQ(older.cell_known(type), newer.cell_known(type)); + if (older.cell_evaluable(type) != newer.cell_evaluable(type)) + std::cout << type.str() << "\n"; + EXPECT_EQ(older.cell_evaluable(type), newer.cell_evaluable(type)); + for (auto port : StaticCellTypes::builder.cells.data()->inputs.ports) + check_port(type, port); + for (auto port : StaticCellTypes::builder.cells.data()->outputs.ports) + check_port(type, port); + EXPECT_EQ(expected_ff_types.count(type) > 0, StaticCellTypes::categories.is_ff(type)); + } + yosys_shutdown(); +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/modindexTest.cc b/tests/unit/kernel/modindexTest.cc new file mode 100644 index 00000000000..1921c9a9374 --- /dev/null +++ b/tests/unit/kernel/modindexTest.cc @@ -0,0 +1,47 @@ +#include + +#include "kernel/modtools.h" +#include "kernel/rtlil.h" + +YOSYS_NAMESPACE_BEGIN + +TEST(ModIndexSwapTest, has) +{ + Design* d = new Design; + Module* m = d->addModule("$m"); + Wire* o = m->addWire("$o", 2); + o->port_input = true; + Wire* i = m->addWire("$i", 2); + i->port_input = true; + m->fixup_ports(); + m->addNot("$not", i, o); + auto mi = ModIndex(m); + mi.reload_module(); + for (auto [sb, info] : mi.database) { + EXPECT_TRUE(mi.database.find(sb) != mi.database.end()); + } + m->swap_names(i, o); + for (auto [sb, info] : mi.database) { + EXPECT_TRUE(mi.database.find(sb) != mi.database.end()); + } +} + +TEST(ModIndexDeleteTest, has) +{ + if (log_files.empty()) log_files.emplace_back(stdout); + Design* d = new Design; + Module* m = d->addModule("$m"); + Wire* w = m->addWire("$w"); + Wire* o = m->addWire("$o"); + o->port_output = true; + m->fixup_ports(); + Cell* not_ = m->addNotGate("$not", w, o); + auto mi = ModIndex(m); + mi.reload_module(); + mi.dump_db(); + Wire* a = m->addWire("\\a"); + not_->setPort(ID::A, a); + EXPECT_TRUE(mi.ok()); +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/sigspecRemove2Test.cc b/tests/unit/kernel/sigspecRemove2Test.cc new file mode 100644 index 00000000000..a6789c774ba --- /dev/null +++ b/tests/unit/kernel/sigspecRemove2Test.cc @@ -0,0 +1,333 @@ +#include + +#include "kernel/rtlil.h" + +YOSYS_NAMESPACE_BEGIN + +// Test fixture with helper functions +class SigSpecRemove2Test : public ::testing::Test { +protected: + Design* d; + Module* m; + + void SetUp() override { + d = new Design; + m = d->addModule("$test"); + } + + void TearDown() override { + delete d; + } + + // Create n wires with given width + std::vector createWires(int count, int width = 4) { + std::vector wires; + for (int i = 0; i < count; i++) { + Wire* w = m->addWire(stringf("$w%d", i), width); + wires.push_back(w); + } + return wires; + } + + // Append all wires to a SigSpec + SigSpec wiresAsSigSpec(const std::vector& wires) { + SigSpec sig; + for (auto w : wires) + sig.append(w); + return sig; + } + + // Create a SigSpec of constants + SigSpec constsAsSigSpec(int count, int width = 4) { + SigSpec sig; + for (int i = 0; i < count; i++) + sig.append(Const(i, width)); + return sig; + } + + // Convert wires to pool of SigBits + pool wiresToPool(const std::vector& wires) { + pool pool; + for (auto w : wires) + for (auto &bit : SigSpec(w)) + pool.insert(bit); + return pool; + } + + // Convert wires to set of SigBits + std::set wiresToSet(const std::vector& wires) { + std::set set; + for (auto w : wires) + for (auto &bit : SigSpec(w)) + set.insert(bit); + return set; + } +}; + +TEST_F(SigSpecRemove2Test, WithSigSpecPattern) +{ + auto wires = createWires(4); + SigSpec sig = wiresAsSigSpec(wires); + SigSpec pattern(wires[1]); // Remove w2 + SigSpec other = constsAsSigSpec(4); + + EXPECT_EQ(sig.size(), 16); + sig.remove2(pattern, &other); + EXPECT_EQ(sig.size(), 12); + EXPECT_EQ(other.size(), 12); + + // Verify correct wires remain (w1, w3, w4) + SigSpec expected; + expected.append(wires[0]); + expected.append(wires[2]); + expected.append(wires[3]); + EXPECT_EQ(sig, expected); +} + +TEST_F(SigSpecRemove2Test, WithPoolPattern) +{ + auto wires = createWires(3); + SigSpec sig = wiresAsSigSpec(wires); + auto pattern = wiresToPool({wires[1]}); + SigSpec other = constsAsSigSpec(3); + + EXPECT_EQ(sig.size(), 12); + sig.remove2(pattern, &other); + EXPECT_EQ(sig.size(), 8); + EXPECT_EQ(other.size(), 8); + + // Verify correct wires remain (w0, w2) + SigSpec expected; + expected.append(wires[0]); + expected.append(wires[2]); + EXPECT_EQ(sig, expected); +} + +TEST_F(SigSpecRemove2Test, WithSetPattern) +{ + auto wires = createWires(3); + SigSpec sig = wiresAsSigSpec(wires); + auto pattern = wiresToSet({wires[1]}); + SigSpec other = constsAsSigSpec(3); + + EXPECT_EQ(sig.size(), 12); + sig.remove2(pattern, &other); + EXPECT_EQ(sig.size(), 8); + EXPECT_EQ(other.size(), 8); + + // Verify correct wires remain (w0, w2) + SigSpec expected; + expected.append(wires[0]); + expected.append(wires[2]); + EXPECT_EQ(sig, expected); +} + +TEST_F(SigSpecRemove2Test, ManyElements) +{ + const int num_wires = 100; + auto wires = createWires(num_wires); + SigSpec sig = wiresAsSigSpec(wires); + + // Remove every other wire + std::vector to_remove; + for (int i = 0; i < num_wires; i += 2) + to_remove.push_back(wires[i]); + auto pattern = wiresToPool(to_remove); + + EXPECT_EQ(sig.size(), num_wires * 4); + sig.remove2(pattern, nullptr); + EXPECT_EQ(sig.size(), (num_wires / 2) * 4); + + // Verify odd-indexed wires remain (w1, w3, w5, ..., w99) + SigSpec expected; + for (int i = 1; i < num_wires; i += 2) + expected.append(wires[i]); + EXPECT_EQ(sig, expected); +} + +// Test remove2 with very large dataset to check scaling +TEST_F(SigSpecRemove2Test, VeryLargeScalingTest) +{ + const int num_wires = 50000; + auto wires = createWires(num_wires); + SigSpec sig = wiresAsSigSpec(wires); + SigSpec other = constsAsSigSpec(num_wires); + + // Create pattern with many chunks (one per wire) + SigSpec pattern; + for (int i = 0; i < num_wires; i += 2) + pattern.append(wires[i]); + + EXPECT_EQ(sig.size(), num_wires * 4); + sig.remove2(pattern, &other); + EXPECT_EQ(sig.size(), (num_wires / 2) * 4); + EXPECT_EQ(other.size(), (num_wires / 2) * 4); + + // Spot-check: odd-indexed wires should remain at expected positions + for (int i = 0; i < num_wires / 2; i++) { + EXPECT_EQ(sig[i * 4 + 0].wire, wires[i * 2 + 1]); + EXPECT_EQ(sig[i * 4 + 1].wire, wires[i * 2 + 1]); + EXPECT_EQ(sig[i * 4 + 2].wire, wires[i * 2 + 1]); + EXPECT_EQ(sig[i * 4 + 3].wire, wires[i * 2 + 1]); + } +} + +// Test multiple sequential removals (simulates removeSignalFromCaseTree) +TEST_F(SigSpecRemove2Test, MultipleSequentialRemovals) +{ + const int num_wires = 512; + auto wires = createWires(num_wires); + + // Create many actions, each with one wire + std::vector actions; + for (auto w : wires) + actions.push_back(SigSpec(w)); + + // Remove half the wires from all actions + for (int i = 0; i < num_wires / 2; i++) { + SigSpec pattern(wires[i]); + for (auto &action : actions) + if (action.size() > 0) + action.remove2(pattern, nullptr); + } + + // Verify correct actions were cleared + for (int i = 0; i < num_wires; i++) { + EXPECT_EQ(actions[i].size(), i < num_wires / 2 ? 0 : 4); + } + + // Verify remaining actions contain the correct wires + for (int i = num_wires / 2; i < num_wires; i++) { + SigSpec expected(wires[i]); + EXPECT_EQ(actions[i], expected); + } +} + +// Test remove2 with very large dataset to check scaling +TEST_F(SigSpecRemove2Test, PoolOverloadLargeDataset) +{ + const int num_wires = 50000; + auto wires = createWires(num_wires, 1); + SigSpec sig = wiresAsSigSpec(wires); + SigSpec other = constsAsSigSpec(num_wires, 1); + + // Remove half + pool pattern; + for (int i = 0; i < num_wires; i += 2) + pattern.insert(SigBit(wires[i], 0)); + + EXPECT_EQ(sig.size(), num_wires); + sig.remove2(pattern, &other); + EXPECT_EQ(sig.size(), num_wires / 2); + EXPECT_EQ(other.size(), num_wires / 2); + + // Spot-check: odd-indexed wires should remain + for (int i = 0; i < num_wires / 2; i++) { + EXPECT_EQ(sig[i].wire, wires[i * 2 + 1]); + } +} + +// Test set overload (same perf characteristics as pool) +TEST_F(SigSpecRemove2Test, SetOverloadLargeDataset) +{ + const int num_wires = 50000; + auto wires = createWires(num_wires, 1); + SigSpec sig = wiresAsSigSpec(wires); + SigSpec other = constsAsSigSpec(num_wires, 1); + + // Remove half + std::set pattern; + for (int i = 0; i < num_wires; i += 2) + pattern.insert(SigBit(wires[i], 0)); + + EXPECT_EQ(sig.size(), num_wires); + sig.remove2(pattern, &other); + EXPECT_EQ(sig.size(), num_wires / 2); + EXPECT_EQ(other.size(), num_wires / 2); + + // Spot-check: odd-indexed wires should remain + for (int i = 0; i < num_wires / 2; i++) { + EXPECT_EQ(sig[i].wire, wires[i * 2 + 1]); + } +} + +// Worst case: remove almost all elements +TEST_F(SigSpecRemove2Test, RemoveAlmostAllElements) +{ + const int num_wires = 10000; + auto wires = createWires(num_wires, 1); + SigSpec sig = wiresAsSigSpec(wires); + + // Remove all but last + pool pattern; + for (int i = 0; i < num_wires - 1; i++) + pattern.insert(SigBit(wires[i], 0)); + + EXPECT_EQ(sig.size(), num_wires); + sig.remove2(pattern, nullptr); + EXPECT_EQ(sig.size(), 1); + EXPECT_EQ(sig[0].wire, wires[num_wires - 1]); +} + +TEST_F(SigSpecRemove2Test, EmptyPattern) +{ + auto wires = createWires(1); + SigSpec sig(wires[0]); + pool empty_pattern; + + EXPECT_EQ(sig.size(), 4); + sig.remove2(empty_pattern, nullptr); + EXPECT_EQ(sig.size(), 4); + + // Verify the wire is unchanged + SigSpec expected(wires[0]); + EXPECT_EQ(sig, expected); +} + +// Test that NULL wire bits (constants) are not removed +TEST_F(SigSpecRemove2Test, NullWireBitsStay) +{ + auto wires = createWires(1); + SigSpec sig; + sig.append(wires[0]); + sig.append(Const(15, 4)); + + // Try to remove the constants + pool pattern; + SigSpec const_spec(Const(15, 4)); + for (auto &bit : const_spec) + pattern.insert(bit); + + EXPECT_EQ(sig.size(), 8); + sig.remove2(pattern, nullptr); + EXPECT_EQ(sig.size(), 8); // Constants stay + + // Verify original content is preserved + SigSpec expected; + expected.append(wires[0]); + expected.append(Const(15, 4)); + EXPECT_EQ(sig, expected); +} + +TEST_F(SigSpecRemove2Test, PartialBitRemoval) +{ + Wire* w = m->addWire("$w1", 8); + SigSpec sig(w); + + // Remove bits 2-5 + pool pattern; + for (int i = 2; i < 6; i++) + pattern.insert(SigBit(w, i)); + + EXPECT_EQ(sig.size(), 8); + sig.remove2(pattern, nullptr); + EXPECT_EQ(sig.size(), 4); + + // Verify only bits 0,1,6,7 remain + EXPECT_EQ(sig[0], SigBit(w, 0)); + EXPECT_EQ(sig[1], SigBit(w, 1)); + EXPECT_EQ(sig[2], SigBit(w, 6)); + EXPECT_EQ(sig[3], SigBit(w, 7)); +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/threadingTest.cc b/tests/unit/kernel/threadingTest.cc new file mode 100644 index 00000000000..cbab4d118eb --- /dev/null +++ b/tests/unit/kernel/threadingTest.cc @@ -0,0 +1,442 @@ +#include +#include +#include "kernel/threading.h" + +YOSYS_NAMESPACE_BEGIN + +class ThreadingTest : public testing::Test { +protected: + ThreadingTest() { + if (log_files.empty()) + log_files.emplace_back(stdout); + } +}; + +TEST_F(ThreadingTest, ParallelDispatchThreadPoolCreate) { + // Test creating a pool with 0 threads (treated as 1) + ParallelDispatchThreadPool pool0(0); + EXPECT_EQ(pool0.num_threads(), 1); + + // Test creating a pool with 1 thread + ParallelDispatchThreadPool pool1(1); + EXPECT_EQ(pool1.num_threads(), 1); + + // Test creating a pool with 2 threads + ParallelDispatchThreadPool pool2(2); + // YOSYS_MAX_THREADS or system configuration could mean we + // decide to only use one thread. + EXPECT_GE(pool2.num_threads(), 1); + EXPECT_LE(pool2.num_threads(), 2); +} + +TEST_F(ThreadingTest, ParallelDispatchThreadPoolRunSimple) { + ParallelDispatchThreadPool pool(2); + + std::atomic counter{0}; + pool.run([&counter](const ParallelDispatchThreadPool::RunCtx &) { + counter.fetch_add(1, std::memory_order_relaxed); + }); + + EXPECT_EQ(counter.load(), pool.num_threads()); +} + +TEST_F(ThreadingTest, ParallelDispatchThreadPoolRunMultiple) { + ParallelDispatchThreadPool pool(2); + + std::atomic counter{0}; + // Run multiple times to verify the pool can be reused + for (int i = 0; i < 5; ++i) + pool.run([&counter](const ParallelDispatchThreadPool::RunCtx &) { + counter.fetch_add(1, std::memory_order_relaxed); + }); + + EXPECT_EQ(counter.load(), pool.num_threads() * 5); +} + +TEST_F(ThreadingTest, ParallelDispatchThreadPoolRunCtxThreadNums) { + ParallelDispatchThreadPool pool(4); + + std::vector thread_nums(pool.num_threads(), -1); + pool.run([&thread_nums](const ParallelDispatchThreadPool::RunCtx &ctx) { + thread_nums[ctx.thread_num] = ctx.thread_num; + }); + + // Every thread should have recorded its own thread number + for (int i = 0; i < pool.num_threads(); ++i) + EXPECT_EQ(thread_nums[i], i); +} + +TEST_F(ThreadingTest, ParallelDispatchThreadPoolItemRange) { + ParallelDispatchThreadPool pool(3); + + const int num_items = 100; + std::vector> item_counts(num_items); + for (std::atomic &c : item_counts) + c.store(0); + + pool.run([&item_counts](const ParallelDispatchThreadPool::RunCtx &ctx) { + for (int i : ctx.item_range(num_items)) + item_counts[i].fetch_add(1); + }); + + // Each item should have been processed exactly once + for (int i = 0; i < num_items; ++i) + EXPECT_EQ(item_counts[i].load(), 1); +} + +TEST_F(ThreadingTest, ParallelDispatchThreadPoolSubpool) { + ParallelDispatchThreadPool pool(4); + + // Subpool limited to 2 threads + ParallelDispatchThreadPool::Subpool subpool(pool, 2); + EXPECT_LE(subpool.num_threads(), 2); + + std::atomic counter{0}; + subpool.run([&counter](const ParallelDispatchThreadPool::RunCtx &) { + counter.fetch_add(1, std::memory_order_relaxed); + }); + + EXPECT_EQ(counter.load(), subpool.num_threads()); +} + +TEST_F(ThreadingTest, IntRangeIteration) { + IntRange range{3, 7}; + std::vector values; + for (int i : range) + values.push_back(i); + EXPECT_THAT(values, testing::ElementsAre(3, 4, 5, 6)); +} + +TEST_F(ThreadingTest, IntRangeEmpty) { + IntRange range{5, 5}; + for (int _ : range) + FAIL(); +} + +TEST_F(ThreadingTest, ItemRangeForWorker) { + EXPECT_EQ(item_range_for_worker(10, 0, 3), (IntRange{0, 4})); + EXPECT_EQ(item_range_for_worker(10, 1, 3), (IntRange{4, 7})); + EXPECT_EQ(item_range_for_worker(10, 2, 3), (IntRange{7, 10})); +} + +TEST_F(ThreadingTest, ItemRangeForWorkerZeroThreads) { + EXPECT_EQ(item_range_for_worker(10, 0, 0), (IntRange{0, 10})); +} + +TEST_F(ThreadingTest, ShardedVectorBasic) { + ParallelDispatchThreadPool pool(2); + ShardedVector vec(pool); + pool.run([&vec](const ParallelDispatchThreadPool::RunCtx &ctx) { + vec.insert(ctx, ctx.thread_num * 10); + vec.insert(ctx, ctx.thread_num * 10 + 1); + }); + + EXPECT_FALSE(vec.empty()); + + // Count elements + std::vector elements; + for (int v : vec) { + elements.push_back(v); + } + + if (pool.num_threads() == 2) + EXPECT_THAT(elements, testing::ElementsAre(0, 1, 10, 11)); + else + EXPECT_THAT(elements, testing::ElementsAre(0, 1)); +} + +TEST_F(ThreadingTest, MonotonicFlagBasic) { + MonotonicFlag flag; + EXPECT_FALSE(flag.load()); + flag.set(); + EXPECT_TRUE(flag.load()); + flag.set(); + EXPECT_TRUE(flag.load()); +} + +TEST_F(ThreadingTest, MonotonicFlagSetAndReturnOld) { + MonotonicFlag flag; + EXPECT_FALSE(flag.set_and_return_old()); + EXPECT_TRUE(flag.load()); + EXPECT_TRUE(flag.set_and_return_old()); +} + +TEST_F(ThreadingTest, ConcurrentQueueBasic) { + ConcurrentQueue queue; + queue.push_back(1); + queue.push_back(2); + queue.push_back(3); + + auto v1 = queue.pop_front(); + auto v2 = queue.pop_front(); + auto v3 = queue.pop_front(); + + ASSERT_TRUE(v1.has_value()); + ASSERT_TRUE(v2.has_value()); + ASSERT_TRUE(v3.has_value()); + EXPECT_EQ(*v1, 1); + EXPECT_EQ(*v2, 2); + EXPECT_EQ(*v3, 3); +} + +TEST_F(ThreadingTest, ConcurrentQueueTryPopEmpty) { + ConcurrentQueue queue; + auto v = queue.try_pop_front(); + EXPECT_FALSE(v.has_value()); +} + +TEST_F(ThreadingTest, ConcurrentQueueClose) { + ConcurrentQueue queue; + queue.push_back(42); + queue.close(); + + // Can still pop existing elements + auto v1 = queue.pop_front(); + ASSERT_TRUE(v1.has_value()); + EXPECT_EQ(*v1, 42); + + // After close and empty, pop_front returns nullopt + auto v2 = queue.pop_front(); + EXPECT_FALSE(v2.has_value()); +} + +TEST_F(ThreadingTest, ThreadPoolCreate) { + // pool_size of 0 means no worker threads + ThreadPool pool0(0, [](int) {}); + EXPECT_EQ(pool0.num_threads(), 0); + + // pool_size of 1 means 1 worker thread + std::atomic counter{0}; + { + ThreadPool pool1(1, [&counter](int thread_num) { + EXPECT_EQ(thread_num, 0); + counter.fetch_add(1); + }); + } +#ifdef YOSYS_ENABLE_THREADS + EXPECT_EQ(counter.load(), 1); +#else + EXPECT_EQ(counter.load(), 0); +#endif +} + +TEST_F(ThreadingTest, ThreadPoolMultipleThreads) { + std::atomic counter{0}; + { + ThreadPool pool(2, [&counter](int) { + counter.fetch_add(1); + }); + EXPECT_LE(pool.num_threads(), 2); + } +#ifdef YOSYS_ENABLE_THREADS + EXPECT_GE(counter.load(), 1); + EXPECT_LE(counter.load(), 2); +#else + EXPECT_EQ(counter.load(), 0); +#endif +} + +// Helper types for ShardedHashtable tests +struct IntValue { + using Accumulated = IntValue; + int value; + operator int() const { return value; } +}; + +struct IntValueEquality { + bool operator()(int a, int b) const { return a == b; } +}; + +TEST_F(ThreadingTest, ShardedHashtableBasic) { + ParallelDispatchThreadPool pool(1); + + using HashSet = ShardedHashtable>; + HashSet::Builder builder(pool); + + // Insert some values + pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) { + builder.insert(ctx, {{10}, 10}); + builder.insert(ctx, {{20}, 20}); + builder.insert(ctx, {{30}, 30}); + }); + + // Process + pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) { + builder.process(ctx); + }); + + // Build and lookup + HashSet set(builder); + const IntValue *found10 = set.find({{10}, 10}); + const IntValue *found20 = set.find({{20}, 20}); + const IntValue *found99 = set.find({{99}, 99}); + + ASSERT_NE(found10, nullptr); + ASSERT_NE(found20, nullptr); + EXPECT_EQ(found99, nullptr); + EXPECT_EQ(*found10, 10); + EXPECT_EQ(*found20, 20); +} + +TEST_F(ThreadingTest, ShardedHashtableParallelInsert) { + ParallelDispatchThreadPool pool(3); + + using HashSet = ShardedHashtable>; + HashSet::Builder builder(pool); + + // Insert values from multiple threads + pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) { + for (int i = 0; i < 10; ++i) { + int val = ctx.thread_num * 100 + i; + builder.insert(ctx, {{val}, static_cast(val)}); + } + }); + + pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) { + builder.process(ctx); + }); + + HashSet set(builder); + + // Verify all values can be found + for (int t = 0; t < pool.num_threads(); ++t) { + for (int i = 0; i < 10; ++i) { + int val = t * 100 + i; + const IntValue *found = set.find({{val}, static_cast(val)}); + ASSERT_NE(found, nullptr) << "Value " << val << " not found"; + EXPECT_EQ(*found, val); + } + } +} + +// Helper types for ShardedHashtable tests +struct IntDictValue { + using Accumulated = IntDictValue; + int key; + int value; + bool operator==(const IntDictValue &other) const { return key == other.key && value == other.value; } + bool operator!=(const IntDictValue &other) const { return !(*this == other); } +}; + +struct IntDictKeyEquality { + bool operator()(const IntDictValue &a, const IntDictValue &b) const { return a.key == b.key; } +}; + +// Collision handler that sums values +struct SumCollisionHandler { + void operator()(IntDictValue &existing, IntDictValue &incoming) const { + existing.value += incoming.value; + } +}; + +TEST_F(ThreadingTest, ShardedHashtableCollision) { + ParallelDispatchThreadPool pool(1); + + using HashSet = ShardedHashtable; + HashSet::Builder builder(pool); + + // Insert duplicate keys with same hash - duplicates should collapse + pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) { + builder.insert(ctx, {{5, 10}, 5}); + builder.insert(ctx, {{5, 12}, 5}); // Duplicate key/hash + builder.insert(ctx, {{5, 14}, 5}); // Another duplicate + }); + + pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) { + builder.process(ctx); + }); + + HashSet set(builder); + const IntDictValue *found = set.find({{5, 0}, 5}); + ASSERT_NE(found, nullptr); + // With default collision handler, first value is kept + EXPECT_EQ(*found, (IntDictValue{5, 36})); +} + +TEST_F(ThreadingTest, ShardedHashtableEmpty) { + ParallelDispatchThreadPool pool(1); + + using HashSet = ShardedHashtable>; + HashSet::Builder builder(pool); + + // Don't insert anything, just process + pool.run([&builder](const ParallelDispatchThreadPool::RunCtx &ctx) { + builder.process(ctx); + }); + + HashSet set(builder); + const IntValue *found = set.find({{42}, 42}); + EXPECT_EQ(found, nullptr); +} + +TEST_F(ThreadingTest, ConcurrentWorkQueueSingleThread) { + ConcurrentWorkQueue queue(1, 10); // 1 thread, batch size 10 + EXPECT_EQ(queue.num_threads(), 1); + + ThreadIndex thread{0}; + + // Push some items (less than batch size) + for (int i = 0; i < 5; ++i) + queue.push(thread, i); + + // Pop should return those items + std::vector batch = queue.pop_batch(thread); + EXPECT_THAT(batch, testing::UnorderedElementsAre(0, 1, 2, 3, 4)); + + // Next pop should return empty (all threads "waiting") + std::vector empty_batch = queue.pop_batch(thread); + EXPECT_TRUE(empty_batch.empty()); +} + +TEST_F(ThreadingTest, ConcurrentWorkQueueBatching) { + ConcurrentWorkQueue queue(1, 3); // batch size 3 + ThreadIndex thread{0}; + + queue.push(thread, 10); + queue.push(thread, 20); + queue.push(thread, 30); + queue.push(thread, 40); + queue.push(thread, 50); + + std::vector popped; + while (true) { + std::vector batch = queue.pop_batch(thread); + if (batch.empty()) + break; + popped.insert(popped.end(), batch.begin(), batch.end()); + } + EXPECT_THAT(popped, testing::UnorderedElementsAre(10, 20, 30, 40, 50)); +} + +TEST_F(ThreadingTest, ConcurrentWorkQueueParallel) { + ParallelDispatchThreadPool pool(2); + if (pool.num_threads() < 2) { + // Skip test if we don't have multiple threads + return; + } + + ConcurrentWorkQueue queue(2, 3); + std::atomic sum{0}; + + pool.run([&queue, &sum](const ParallelDispatchThreadPool::RunCtx &ctx) { + // Each thread pushes some work + for (int i = 0; i < 10; ++i) + queue.push(ctx, ctx.thread_num * 100 + i); + + // Each thread processes work until done + while (true) { + std::vector batch = queue.pop_batch(ctx); + if (batch.empty()) + break; + for (int v : batch) + sum.fetch_add(v); + } + }); + + // Thread 0 pushes: 0+1+2+...+9 = 45 + // Thread 1 pushes: 100+101+...+109 = 1045 + // Total = 45 + 1045 = 1090 + EXPECT_EQ(sum.load(), 1090); +} + +YOSYS_NAMESPACE_END diff --git a/tests/various/const_shift_empty_arg.ys b/tests/various/const_shift_empty_arg.ys new file mode 100644 index 00000000000..4073e198d3b --- /dev/null +++ b/tests/various/const_shift_empty_arg.ys @@ -0,0 +1,49 @@ +# Regression test for #5684: const_shift_worker must not crash when arg1 is +# empty. + +read_json << EOF +{ + "modules": { + "sshl": { + "cells": { + "sshlCell": { + "connections": { + "A": [], + "B": [3], + "Y": [1] + }, + "parameters": { + "A_SIGNED": "1", + "A_WIDTH": "0", + "B_SIGNED": "0", + "B_WIDTH": "1", + "Y_WIDTH": "1" + }, + "port_directions": { + "A": "input", + "B": "input", + "Y": "output" + }, + "type": "$sshl" + } + }, + "ports": { + "A": { + "bits": [], + "direction": "input" + }, + "B": { + "bits": [3], + "direction": "input" + }, + "Y": { + "bits": [1], + "direction": "output" + } + } + } + } +} +EOF + +eval -set B 0 -show Y sshl diff --git a/tests/various/setundef_selection.ys b/tests/various/setundef_selection.ys new file mode 100644 index 00000000000..c49cc06da7f --- /dev/null +++ b/tests/various/setundef_selection.ys @@ -0,0 +1,32 @@ +# Test that setundef -zero respects wire selection: only selected wire is changed +read_verilog <> 4) & 1'b1)); +endmodule +EOF +proc +async2sync +select -assert-none t:$dff t:$dlatch %% +sat -verify -prove-asserts -show-all + +# automatic in a clocked block — only the explicitly registered +# output (result) must get a DFF; the automatic temp must not. +design -reset +read_verilog -sv <