diff --git a/.editorconfig b/.editorconfig index f5444d81a..bbe9c3107 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,11 @@ insert_final_newline = true indent_style = space indent_size = 2 trim_trailing_whitespace = false + +[*.rst] +indent_style = space +indent_size = 3 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.gitcommit b/.gitcommit index 46b7856fb..6828f88dc 100644 --- a/.gitcommit +++ b/.gitcommit @@ -1 +1 @@ -$Format:%h$ +$Format:%H$ diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 27cfd09b7..f754d16c7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -6,15 +6,14 @@ body: attributes: value: > - If you have a general question, please ask it in the [Discussions](https://github.com/YosysHQ/yosys/discussions) area - or join our [IRC Channel](https://web.libera.chat/#yosys) or [Community Slack](https://join.slack.com/t/yosyshq/shared_invite/zt-1aopkns2q-EiQ97BeQDt_pwvE41sGSuA). + If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/). If you have a feature request, please fill out the appropriate issue form, this form is for bugs and/or regressions. Please contact [YosysHQ GmbH](https://www.yosyshq.com/) if you need - commercial support for Yosys. + commercial support or work done for Yosys. - type: input id: yosys_version @@ -34,6 +33,7 @@ body: - macOS - Windows - BSD + - WebAssembly multiple: true validations: required: true @@ -42,7 +42,7 @@ body: attributes: value: > When providing steps to reproduce the issue, please ensure that the issue - is reproducible in the current git master of Yosys. Also ensure to + is reproducible in the current git main of Yosys. Also ensure to provide all necessary source files needed. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bef410a3c..c758bf1f6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,8 @@ contact_links: - - name: Discussions - url: https://github.com/YosysHQ/yosys/discussions - about: "Have a question? Ask it on our discussions page!" - - name: Community Slack - url: https://join.slack.com/t/yosyshq/shared_invite/zt-1aopkns2q-EiQ97BeQDt_pwvE41sGSuA - about: "Yosys Community Slack" + - name: Discourse + url: https://yosyshq.discourse.group + about: "Have a question? Ask it on our Discourse group!" - name: IRC Channel url: https://web.libera.chat/#yosys about: "#yosys on irc.libera.chat" - + diff --git a/.github/ISSUE_TEMPLATE/docs_report.yml b/.github/ISSUE_TEMPLATE/docs_report.yml new file mode 100644 index 000000000..c3ad2f7cc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs_report.yml @@ -0,0 +1,54 @@ +name: Documentation Report +description: Report a problem with the Yosys documentation +labels: ["pending-verification"] +body: + - type: markdown + attributes: + value: > + + If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/). + + + If you have found a bug in Yosys, or in building the documentation, + please fill out the Bug Report issue form, this form is for problems + with the live documentation on [Read the + Docs](https://yosyshq.readthedocs.io/projects/yosys/). Please only + report problems that appear on the latest version of the documentation. + + + Please contact [YosysHQ GmbH](https://www.yosyshq.com/) if you need + commercial support for Yosys. + + - type: input + id: docs_url + attributes: + label: Link to page + description: "Please provide a link to the page where the problem was found." + placeholder: "e.g. https://yosyshq.readthedocs.io/projects/yosys/" + validations: + required: true + + - type: input + id: build_number + attributes: + label: Build number + description: "If possible, please provide the latest build number from https://readthedocs.org/projects/yosys/builds/." + placeholder: "e.g. Build #24078236" + validations: + required: false + + - type: textarea + id: problem + attributes: + label: Issue + description: "Please describe what is incorrect, invalid, or missing." + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected + description: "If applicable, please describe what should appear instead." + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c521b5296..49d86f341 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,18 +1,17 @@ name: Feature Request -description: "Submit a feature request for Yosys" +description: "Submit a feature request for Yosys" labels: ["feature-request"] body: - type: markdown attributes: value: > - If you have a general question, please ask it in the [Discussions](https://github.com/YosysHQ/yosys/discussions) area - or join our [IRC Channel](https://web.libera.chat/#yosys) or [Community Slack](https://join.slack.com/t/yosyshq/shared_invite/zt-1aopkns2q-EiQ97BeQDt_pwvE41sGSuA). - + If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/). + If you have a bug report, please fill out the appropriate issue form, this form is for feature requests. - + Please contact [YosysHQ GmbH](https://www.yosyshq.com/) if you need commercial support or work done for Yosys. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..d8d929f3f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +_If your work is part of a larger effort, please discuss your general plans on [Discourse](https://yosyshq.discourse.group/) first to align your vision with maintainers._ + +_What are the reasons/motivation for this change?_ + +_Explain how this is achieved._ + +_Make sure your change comes with tests. If not possible, share how a reviewer might evaluate it._ + +_These template prompts can be deleted when you're done responding to them._ \ No newline at end of file diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml new file mode 100644 index 000000000..60fe481e7 --- /dev/null +++ b/.github/actions/setup-build-env/action.yml @@ -0,0 +1,91 @@ +name: Build environment setup +description: Configure build env for Yosys builds + +inputs: + runs-on: + required: true + type: string + get-build-deps: + description: 'Install Yosys build dependencies' + default: false + required: false + type: boolean + get-docs-deps: + description: 'Install Yosys docs dependencies' + default: false + required: false + type: boolean + get-test-deps: + description: 'Install Yosys test dependencies' + default: false + required: false + type: boolean + get-iverilog: + description: 'Install iverilog' + default: false + required: false + type: boolean + +runs: + using: composite + steps: + # if updating common/build/docs dependencies, make sure to update README.md + # and docs/source/getting_started/installation.rst to match. + - name: Linux common dependencies + if: runner.os == 'Linux' + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 + with: + packages: gawk git make python3 + version: ${{ inputs.runs-on }}-commonys + + - name: Linux build dependencies + if: runner.os == 'Linux' && inputs.get-build-deps == 'true' + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 + with: + packages: bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev + version: ${{ inputs.runs-on }}-buildys + + - name: Linux docs dependencies + if: runner.os == 'Linux' && inputs.get-docs-deps == 'true' + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 + with: + packages: graphviz xdot + version: ${{ inputs.runs-on }}-docsys + + # if updating test dependencies, make sure to update + # docs/source/yosys_internals/extending_yosys/test_suites.rst to match. + - name: Linux test dependencies + if: runner.os == 'Linux' && inputs.get-test-deps == 'true' + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 + with: + packages: libgtest-dev + version: ${{ inputs.runs-on }}-testys + + - name: Install macOS Dependencies + if: runner.os == 'macOS' + shell: bash + run: | + brew bundle + + - name: Linux runtime environment + if: runner.os == 'Linux' + shell: bash + run: | + echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH + echo "procs=$(nproc)" >> $GITHUB_ENV + + - name: macOS runtime environment + if: runner.os == 'macOS' + shell: bash + run: | + echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH + echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH + echo "$(brew --prefix bison)/bin" >> $GITHUB_PATH + echo "$(brew --prefix flex)/bin" >> $GITHUB_PATH + echo "procs=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV + + - name: Setup iverilog + if: inputs.get-iverilog == 'true' + uses: ./.github/actions/setup-iverilog + with: + runs-on: ${{ inputs.runs-on }} diff --git a/.github/actions/setup-iverilog/action.yml b/.github/actions/setup-iverilog/action.yml new file mode 100644 index 000000000..0acb582e3 --- /dev/null +++ b/.github/actions/setup-iverilog/action.yml @@ -0,0 +1,70 @@ +name: iverilog setup +description: Cached build and install of iverilog + +inputs: + runs-on: + required: true + type: string + +runs: + using: composite + steps: + - name: iverilog Linux deps + if: steps.restore-iverilog.outputs.cache-hit != 'true' && runner.os == 'Linux' + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 + with: + packages: autoconf gperf make gcc g++ bison flex libbz2-dev + version: ${{ inputs.runs-on }}-iverilog + + - name: iverilog macOS deps + if: steps.restore-iverilog.outputs.cache-hit != 'true' && runner.os == 'macOS' + shell: bash + run: | + brew install autoconf + + - name: Get iverilog + id: get-iverilog + shell: bash + run: | + git clone https://github.com/steveicarus/iverilog.git + cd iverilog + echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Get vcd2fst + shell: bash + run: | + git clone https://github.com/mmicko/libwave.git + mkdir -p ${{ github.workspace }}/.local/ + cd libwave + cmake . -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/.local + make -j$procs + make install + + - uses: actions/cache/restore@v4 + id: restore-iverilog + with: + path: .local/ + key: ${{ inputs.runs-on }}-${{ steps.get-iverilog.outputs.IVERILOG_GIT }} + + - name: Build iverilog + if: steps.restore-iverilog.outputs.cache-hit != 'true' + shell: bash + run: | + mkdir -p ${{ github.workspace }}/.local/ + cd iverilog + autoconf + CC=gcc CXX=g++ ./configure --prefix=${{ github.workspace }}/.local + make -j$procs + make install + + - name: Check iverilog + shell: bash + run: | + iverilog -V + + - uses: actions/cache/save@v4 + id: save-iverilog + if: steps.restore-iverilog.outputs.cache-hit != 'true' + with: + path: .local/ + key: ${{ steps.restore-iverilog.outputs.cache-primary-key }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4a77d2234..4bca5a8a5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -10,11 +10,17 @@ jobs: name: Analyze runs-on: ubuntu-latest steps: - - name: Install deps - run: sudo apt-get install bison flex libreadline-dev tcl-dev libffi-dev - - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + + - name: Setup environment + uses: ./.github/actions/setup-build-env + with: + runs-on: ubuntu-latest + get-build-deps: true - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/emcc.yml b/.github/workflows/emcc.yml deleted file mode 100644 index 7c6409c1b..000000000 --- a/.github/workflows/emcc.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Emscripten Build - -on: [push, pull_request] - -jobs: - emcc: - runs-on: ubuntu-latest - steps: - - uses: mymindstorm/setup-emsdk@v14 - - uses: actions/checkout@v4 - - name: Build - run: | - make config-emcc - make YOSYS_VER=latest - - uses: actions/upload-artifact@v4 - with: - name: yosysjs - path: yosysjs-latest.zip diff --git a/.github/workflows/extra-builds.yml b/.github/workflows/extra-builds.yml new file mode 100644 index 000000000..5d0ac72d4 --- /dev/null +++ b/.github/workflows/extra-builds.yml @@ -0,0 +1,127 @@ +name: Test extra build flows + +on: + # always test main + push: + branches: + - main + merge_group: + # test PRs + pull_request: + # allow triggering tests, ignores skip check + workflow_dispatch: + +jobs: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + 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' }} + + vs-prep: + name: Prepare Visual Studio build + runs-on: ubuntu-latest + needs: [pre_job] + if: needs.pre_job.outputs.should_skip != 'true' + steps: + - uses: actions/checkout@v4 + 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 + with: + name: vcxsrc + path: yosys-win32-vcxsrc-latest.zip + + vs-build: + name: Visual Studio build + runs-on: windows-latest + needs: [vs-prep, pre_job] + if: needs.pre_job.outputs.should_skip != 'true' + steps: + - uses: actions/download-artifact@v4 + with: + name: vcxsrc + path: . + - name: unzip + run: unzip yosys-win32-vcxsrc-latest.zip + - name: setup-msbuild + uses: microsoft/setup-msbuild@v2 + - name: MSBuild + working-directory: yosys-win32-vcxsrc-latest + run: msbuild YosysVS.sln /p:PlatformToolset=v142 /p:Configuration=Release /p:WindowsTargetPlatformVersion=10.0.26100.0 + + wasi-build: + name: WASI build + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + - name: Build + run: | + WASI_SDK=wasi-sdk-27.0-x86_64-linux + WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-x86_64-linux.tar.gz + if ! [ -d ${WASI_SDK} ]; then curl -L ${WASI_SDK_URL} | tar xzf -; fi + + FLEX_VER=2.6.4 + FLEX=flex-${FLEX_VER} + FLEX_URL=https://github.com/westes/flex/releases/download/v${FLEX_VER}/${FLEX}.tar.gz + if ! [ -d ${FLEX} ]; then curl -L ${FLEX_URL} | tar xzf -; fi + + mkdir -p flex-build + (cd flex-build && + ../${FLEX}/configure --prefix=$(pwd)/../flex-prefix && + make && + make install) + + mkdir -p build + cat > build/Makefile.conf <> $GITHUB_OUTPUT + + 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' }} + 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: Build Yosys + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_HELP_SOURCE := 1" >> Makefile.conf + make -j$procs + + - name: Prepare docs + shell: bash + run: + make docs/prep -j$procs TARGETS= EXTRA_TARGETS= + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: cmd-ref-${{ github.sha }} + path: | + docs/source/generated + docs/source/_images + docs/source/code_examples + + - name: Install doc prereqs + shell: bash + run: | + make docs/reqs + + - name: Test build docs + shell: bash + run: | + make -C docs html -j$procs TARGETS= EXTRA_TARGETS= + + - name: Trigger RTDs build + if: ${{ needs.check_docs_rebuild.outputs.docs_export == 'true' }} + uses: dfm/rtds-action@v1.1.0 + with: + webhook_url: ${{ secrets.RTDS_WEBHOOK_URL }} + webhook_token: ${{ secrets.RTDS_WEBHOOK_TOKEN }} + commit_ref: ${{ github.ref }} diff --git a/.github/workflows/source-vendor.yml b/.github/workflows/source-vendor.yml new file mode 100644 index 000000000..dc9480ef6 --- /dev/null +++ b/.github/workflows/source-vendor.yml @@ -0,0 +1,34 @@ +name: Create source archive with vendored dependencies + +on: [push, workflow_dispatch] + +jobs: + vendor-sources: + runs-on: ubuntu-latest + steps: + - name: Checkout repository with submodules + uses: actions/checkout@v4 + with: + submodules: 'recursive' + persist-credentials: false + + - name: Create clean tarball + run: | + git archive --format=tar HEAD -o yosys-src-vendored.tar + git submodule foreach ' + git archive --format=tar --prefix="${sm_path}/" HEAD --output=${toplevel}/vendor-${name}.tar + ' + + # 2008 bug https://lists.gnu.org/archive/html/bug-tar/2008-08/msg00002.html + for file in vendor-*.tar; do + tar --concatenate --file=yosys-src-vendored.tar "$file" + done + + gzip yosys-src-vendored.tar + + - name: Store tarball artifact + uses: actions/upload-artifact@v4 + with: + name: vendored-sources + path: yosys-src-vendored.tar.gz + retention-days: 1 diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml new file mode 100644 index 000000000..ab6eb3148 --- /dev/null +++ b/.github/workflows/test-build.yml @@ -0,0 +1,266 @@ +name: Build and run tests + +on: + # always test main + push: + branches: + - main + merge_group: + # test PRs + pull_request: + # allow triggering tests, ignores skip check + workflow_dispatch: + +jobs: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + 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' }} + + pre_docs_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5 + with: + # don't run on readme changes + paths_ignore: '["**/README.md"]' + # cancel previous builds if a new commit is pushed + # but never cancel main + cancel_others: ${{ github.ref != 'refs/heads/main' }} + + build-yosys: + name: Reusable build + runs-on: ${{ matrix.os }} + # pre_job is a subset of pre_docs_job, so we can always build for pre_docs_job + needs: pre_docs_job + if: needs.pre_docs_job.outputs.should_skip != 'true' + env: + CC: clang + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + fail-fast: false + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + + - name: Setup environment + uses: ./.github/actions/setup-build-env + with: + runs-on: ${{ matrix.os }} + get-build-deps: true + + - name: Build + shell: bash + run: | + mkdir build + cd build + make -f ../Makefile config-$CC + make -f ../Makefile -j$procs + + - name: Log yosys-config output + run: | + ./yosys-config || true + + - name: Compress build + shell: bash + run: | + cd build + tar -cvf ../build.tar share/ yosys yosys-* + + - name: Store build artifact + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.os }} + path: build.tar + retention-days: 1 + + test-yosys: + name: Run tests + runs-on: ${{ matrix.os }} + needs: [build-yosys, pre_job] + if: needs.pre_job.outputs.should_skip != 'true' + env: + CC: clang + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + fail-fast: false + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup environment + uses: ./.github/actions/setup-build-env + with: + runs-on: ${{ matrix.os }} + get-test-deps: true + get-iverilog: true + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: build-${{ matrix.os }} + + - name: Uncompress build + shell: bash + run: + tar -xvf build.tar + + - name: Log yosys-config output + run: | + ./yosys-config || true + + - name: Run tests + shell: bash + run: | + make -j$procs test TARGETS= EXTRA_TARGETS= CONFIG=$CC + + - name: Report errors + if: ${{ failure() }} + shell: bash + run: | + find tests/**/*.err -print -exec cat {} \; + + test-cells: + name: Run test_cell + runs-on: ${{ matrix.os }} + needs: [build-yosys, pre_job] + if: needs.pre_job.outputs.should_skip != 'true' + env: + CC: clang + strategy: + matrix: + os: [ubuntu-latest] + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup environment + uses: ./.github/actions/setup-build-env + with: + runs-on: ${{ matrix.os }} + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: build-${{ matrix.os }} + + - name: Uncompress build + shell: bash + run: + tar -xvf build.tar + + - name: test_cell + shell: bash + run: | + ./yosys -p 'test_cell -n 20 -s 1 all' + ./yosys -p 'test_cell -n 20 -s 1 -nosat -aigmap $pow $pmux' + ./yosys -p 'test_cell -n 20 -s 1 -nosat -aigmap $eqx $nex $bweqx' + ./yosys -p 'test_cell -n 20 -s 1 -aigmap $buf' + + test-docs: + name: Run docs tests + runs-on: ${{ matrix.os }} + needs: [build-yosys, pre_docs_job] + if: needs.pre_docs_job.outputs.should_skip != 'true' + env: + CC: clang + strategy: + matrix: + os: [ubuntu-latest] + fail-fast: false + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup environment + uses: ./.github/actions/setup-build-env + with: + runs-on: ${{ matrix.os }} + get-build-deps: true + get-docs-deps: true + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: build-${{ matrix.os }} + + - name: Uncompress build + shell: bash + run: + tar -xvf build.tar + + - name: Log yosys-config output + run: | + ./yosys-config || true + + - name: Run tests + shell: bash + run: | + make -C docs test -j$procs + + test-docs-build: + 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' }} + strategy: + matrix: + docs-target: [html, latexpdf] + fail-fast: false + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + + - name: Runtime environment + run: | + echo "procs=$(nproc)" >> $GITHUB_ENV + + - name: Build Yosys + run: | + make config-clang + echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_HELP_SOURCE := 1" >> Makefile.conf + make -j$procs + + - name: Install doc prereqs + shell: bash + run: | + make docs/reqs + + - name: Build docs + shell: bash + run: | + make docs DOC_TARGET=${{ matrix.docs-target }} -j$procs + + - name: Store docs build artifact + uses: actions/upload-artifact@v4 + with: + name: docs-build-${{ matrix.docs-target }} + path: docs/build/ + retention-days: 7 diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml new file mode 100644 index 000000000..000d1c400 --- /dev/null +++ b/.github/workflows/test-compile.yml @@ -0,0 +1,92 @@ +name: Compiler testing + +on: + # always test main + push: + branches: + - main + merge_group: + # test PRs + pull_request: + # allow triggering tests, ignores skip check + workflow_dispatch: + +jobs: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + 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' }} + + test-compile: + runs-on: ${{ matrix.os }} + needs: pre_job + if: 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' }} + strategy: + matrix: + os: + - ubuntu-latest + compiler: + # oldest supported + - 'clang-10' + - 'gcc-10' + # newest, make sure to update maximum standard step to match + - 'clang-19' + - 'gcc-14' + include: + # macOS x86 + - os: macos-15-intel + compiler: 'clang-19' + # macOS arm + - os: macos-latest + compiler: 'clang-19' + fail-fast: false + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + + - name: Setup environment + uses: ./.github/actions/setup-build-env + with: + runs-on: ${{ matrix.os }} + get-build-deps: true + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + + - name: Tool versions + shell: bash + run: | + $CC --version + $CXX --version + + # minimum standard + - name: Build C++17 + shell: bash + run: | + make config-$CC_SHORT + make -j$procs CXXSTD=c++17 compile-only + + # maximum standard, only on newest compilers + - name: Build C++20 + if: ${{ matrix.compiler == 'clang-19' || matrix.compiler == 'gcc-14' }} + shell: bash + run: | + make config-$CC_SHORT + make -j$procs CXXSTD=c++20 compile-only diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml deleted file mode 100644 index b3a6bd846..000000000 --- a/.github/workflows/test-linux.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Build and run tests (Linux) - -on: [push, pull_request] - -jobs: - test-linux: - runs-on: ${{ matrix.os.id }} - strategy: - matrix: - os: - - { id: ubuntu-20.04, name: focal } - compiler: - - 'clang-12' - - 'gcc-11' - cpp_std: - - 'c++11' - - 'c++14' - - 'c++17' - - 'c++20' - include: - # Limit the older compilers to C++11 mode - - os: { id: ubuntu-20.04, name: focal } - compiler: 'clang-11' - cpp_std: 'c++11' - - os: { id: ubuntu-20.04, name: focal } - compiler: 'gcc-10' - cpp_std: 'c++11' - fail-fast: false - steps: - - name: Install Dependencies - shell: bash - run: | - sudo apt-get update - sudo apt-get install gperf build-essential bison flex libreadline-dev gawk tcl-dev libffi-dev git graphviz xdot pkg-config python python3 libboost-system-dev libboost-python-dev libboost-filesystem-dev zlib1g-dev - - - name: Setup GCC - if: startsWith(matrix.compiler, 'gcc') - shell: bash - run: | - CXX=${CC/#gcc/g++} - sudo apt-add-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get install $CC $CXX - echo "CC=$CC" >> $GITHUB_ENV - echo "CXX=$CXX" >> $GITHUB_ENV - echo "CXXFLAGS=-Wp,-D_GLIBCXX_ASSERTIONS" >> $GITHUB_ENV - env: - CC: ${{ matrix.compiler }} - - - name: Setup Clang - if: startsWith(matrix.compiler, 'clang') - shell: bash - run: | - wget https://apt.llvm.org/llvm-snapshot.gpg.key - sudo apt-key add llvm-snapshot.gpg.key - rm llvm-snapshot.gpg.key - sudo apt-add-repository "deb https://apt.llvm.org/${{ matrix.os.name }}/ llvm-toolchain-${{ matrix.os.name }} main" - sudo apt-get update - CXX=${CC/#clang/clang++} - sudo apt-get install $CC $CXX - echo "CC=$CC" >> $GITHUB_ENV - echo "CXX=$CXX" >> $GITHUB_ENV - env: - CC: ${{ matrix.compiler }} - - - name: Runtime environment - shell: bash - env: - WORKSPACE: ${{ github.workspace }} - run: | - echo "GITHUB_WORKSPACE=`pwd`" >> $GITHUB_ENV - echo "$GITHUB_WORKSPACE/.local/bin" >> $GITHUB_PATH - echo "procs=$(nproc)" >> $GITHUB_ENV - - - name: Tool versions - shell: bash - run: | - $CC --version - $CXX --version - - - name: Checkout Yosys - uses: actions/checkout@v4 - - - name: Get iverilog - shell: bash - run: | - git clone https://github.com/steveicarus/iverilog.git - cd iverilog - echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - - - name: Cache iverilog - id: cache-iverilog - uses: actions/cache@v4 - with: - path: .local/ - key: ${{ matrix.os.id }}-${{ env.IVERILOG_GIT }} - - - name: Build iverilog - if: steps.cache-iverilog.outputs.cache-hit != 'true' - shell: bash - run: | - mkdir -p $GITHUB_WORKSPACE/.local/ - cd iverilog - autoconf - CC=gcc CXX=g++ ./configure --prefix=$GITHUB_WORKSPACE/.local - make -j${{ env.procs }} - make install - - - name: Build yosys - shell: bash - run: | - make config-${CC%%-*} - make -j${{ env.procs }} CXXSTD=${{ matrix.cpp_std }} CC=$CC CXX=$CC LD=$CC - - - name: Run tests - if: (matrix.cpp_std == 'c++11') && (matrix.compiler == 'gcc-11') - shell: bash - run: | - make -j${{ env.procs }} test CXXSTD=${{ matrix.cpp_std }} CC=$CC CXX=$CC LD=$CC - - - name: Log yosys-config output - run: | - ./yosys-config || true diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml deleted file mode 100644 index 9b806a580..000000000 --- a/.github/workflows/test-macos.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Build and run tests (macOS) - -on: [push, pull_request] - -jobs: - test-macos: - runs-on: ${{ matrix.os.id }} - strategy: - matrix: - os: - - { id: macos-13, name: 'Ventura' } - cpp_std: - - 'c++11' - - 'c++17' - fail-fast: false - steps: - - name: Install Dependencies - run: | - brew install bison flex gawk libffi pkg-config bash - - - name: Runtime environment - shell: bash - env: - WORKSPACE: ${{ github.workspace }} - run: | - echo "GITHUB_WORKSPACE=`pwd`" >> $GITHUB_ENV - echo "$GITHUB_WORKSPACE/.local/bin" >> $GITHUB_PATH - echo "$(brew --prefix bison)/bin" >> $GITHUB_PATH - echo "$(brew --prefix flex)/bin" >> $GITHUB_PATH - echo "procs=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV - - - name: Tool versions - shell: bash - run: | - cc --version - - - name: Checkout Yosys - uses: actions/checkout@v4 - - - name: Get iverilog - shell: bash - run: | - git clone https://github.com/steveicarus/iverilog.git - cd iverilog - echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - - - name: Cache iverilog - id: cache-iverilog - uses: actions/cache@v4 - with: - path: .local/ - key: ${{ matrix.os.id }}-${{ env.IVERILOG_GIT }} - - - name: Build iverilog - if: steps.cache-iverilog.outputs.cache-hit != 'true' - shell: bash - run: | - mkdir -p $GITHUB_WORKSPACE/.local/ - cd iverilog - autoconf - CC=gcc CXX=g++ ./configure --prefix=$GITHUB_WORKSPACE/.local/ - make -j${{ env.procs }} - make install - - - name: Build yosys - shell: bash - run: | - make config-clang - make -j${{ env.procs }} CXXSTD=${{ matrix.cpp_std }} CC=cc CXX=cc LD=cc - - - name: Run tests - if: matrix.cpp_std == 'c++11' - shell: bash - run: | - make -j${{ env.procs }} test CXXSTD=${{ matrix.cpp_std }} CC=cc CXX=cc LD=cc diff --git a/.github/workflows/test-sanitizers.yml b/.github/workflows/test-sanitizers.yml new file mode 100644 index 000000000..11a339cd3 --- /dev/null +++ b/.github/workflows/test-sanitizers.yml @@ -0,0 +1,79 @@ +name: Check clang sanitizers + +on: + # always test main + push: + branches: + - main + merge_group: + # ignore PRs due to time needed + # allow triggering tests, ignores skip check + workflow_dispatch: + +jobs: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5 + with: + # don't run on documentation changes + paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]' + + run_san: + name: Build and run tests + runs-on: ${{ matrix.os }} + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + env: + CC: clang + ASAN_OPTIONS: halt_on_error=1 + UBSAN_OPTIONS: halt_on_error=1 + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + sanitizer: ['undefined,address'] + fail-fast: false + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + submodules: true + persist-credentials: false + + - name: Setup environment + uses: ./.github/actions/setup-build-env + with: + runs-on: ${{ matrix.os }} + get-build-deps: true + get-test-deps: true + get-iverilog: true + + - name: Build + shell: bash + run: | + make config-$CC + echo 'SANITIZER = ${{ matrix.sanitizer }}' >> Makefile.conf + make -j$procs + + - name: Log yosys-config output + run: | + ./yosys-config || true + + - name: Run tests + shell: bash + run: | + make -j$procs test TARGETS= EXTRA_TARGETS= + + - name: Report errors + if: ${{ failure() }} + shell: bash + run: | + find tests/**/*.err -print -exec cat {} \; + + - name: Run unit tests + shell: bash + run: | + make -j$procs unit-test ENABLE_LIBYOSYS=1 diff --git a/.github/workflows/test-verific.yml b/.github/workflows/test-verific.yml new file mode 100644 index 000000000..adc6f59d8 --- /dev/null +++ b/.github/workflows/test-verific.yml @@ -0,0 +1,125 @@ +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 + workflow_dispatch: + +jobs: + pre-job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + 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' }} + + test-verific: + needs: pre-job + if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }} + 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: Build Yosys + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_FUNCTIONAL_TESTS := 1" >> Makefile.conf + make -j$procs ENABLE_LTO=1 + + - name: Install Yosys + run: | + make install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX= + + - name: Checkout SBY + uses: actions/checkout@v4 + with: + repository: 'YosysHQ/sby' + path: 'sby' + persist-credentials: false + + - name: Build SBY + run: | + make -C sby install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX= + + - name: Run Yosys tests + run: | + make -j$procs test + + - name: Run Verific specific Yosys tests + run: | + make -C tests/sva + cd tests/svtypes && bash run-test.sh + + - name: Run SBY tests + if: ${{ github.ref == 'refs/heads/main' }} + run: | + make -C sby run_ci + + - name: Run unit tests + shell: bash + run: | + make -j$procs unit-test ENABLE_LTO=1 ENABLE_LIBYOSYS=1 + + test-pyosys: + needs: pre-job + if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }} + runs-on: [self-hosted, linux, x64, fast] + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + persist-credentials: false + submodules: true + - name: Install UV + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + - name: Runtime environment + run: | + echo "procs=$(nproc)" >> $GITHUB_ENV + echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH + + - name: Build pyosys + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_PYOSYS := 1" >> Makefile.conf + echo "PYTHON_DESTDIR := /usr/lib/python3/site-packages" >> Makefile.conf + make -j$procs + + - name: Install pyosys + run: | + make install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX= + + - name: Run pyosys tests + run: | + export PYTHONPATH=${GITHUB_WORKSPACE}/.local/usr/lib/python3/site-packages:$PYTHONPATH + python3 tests/pyosys/run_tests.py diff --git a/.github/workflows/update-flake-lock.yml b/.github/workflows/update-flake-lock.yml new file mode 100644 index 000000000..b32498baf --- /dev/null +++ b/.github/workflows/update-flake-lock.yml @@ -0,0 +1,25 @@ +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 index 47a7fe1a3..78d34db46 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -7,20 +7,20 @@ on: 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: Take repository - id: repo - run: echo "message=$GITHUB_REPOSITORY" >> $GITHUB_OUTPUT - name: Bump version - if: "!contains(steps.log.outputs.message, 'Bump version') && contains(steps.repo.outputs.message, 'YosysHQ/yosys')" + if: ${{ !contains(steps.log.outputs.message, 'Bump version') }} run: | make bumpversion git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -28,7 +28,7 @@ jobs: 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') && contains(steps.repo.outputs.message, 'YosysHQ/yosys')" + 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/vs.yml b/.github/workflows/vs.yml deleted file mode 100644 index 799c5f259..000000000 --- a/.github/workflows/vs.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Visual Studio Build - -on: [push, pull_request] - -jobs: - yosys-vcxsrc: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Build - run: make vcxsrc YOSYS_VER=latest - - uses: actions/upload-artifact@v4 - with: - name: vcxsrc - path: yosys-win32-vcxsrc-latest.zip - - build: - runs-on: windows-2019 - needs: yosys-vcxsrc - steps: - - uses: actions/download-artifact@v4 - with: - name: vcxsrc - path: . - - name: unzip - run: unzip yosys-win32-vcxsrc-latest.zip - - name: setup-msbuild - uses: microsoft/setup-msbuild@v1 - - name: MSBuild - working-directory: yosys-win32-vcxsrc-latest - run: msbuild YosysVS.sln /p:PlatformToolset=v142 /p:Configuration=Release /p:WindowsTargetPlatformVersion=10.0.17763.0 diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml deleted file mode 100644 index d6d200d92..000000000 --- a/.github/workflows/wasi.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: WASI Build - -on: [push, pull_request] - -jobs: - wasi: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Build - run: | - WASI_SDK=wasi-sdk-19.0 - WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-19/wasi-sdk-19.0-linux.tar.gz - if ! [ -d ${WASI_SDK} ]; then curl -L ${WASI_SDK_URL} | tar xzf -; fi - - mkdir -p build - cat > build/Makefile.conf <> $GITHUB_ENV + - if: ${{ matrix.os.family == 'windows' }} + name: "[Windows] Flex/Bison" + run: | + choco install winflexbison3 + - if: ${{ matrix.os.family == 'macos' && matrix.os.archs == 'arm64' }} + name: "[macOS/arm64] Install Python 3.8 (see: https://cibuildwheel.pypa.io/en/stable/faq/#macos-building-cpython-38-wheels-on-arm64)" + uses: actions/setup-python@v5 + with: + python-version: 3.8 + - name: Build wheels + uses: pypa/cibuildwheel@v2.21.1 + env: + # * APIs not supported by PyPy + # * Musllinux disabled because it increases build time from 48m to ~3h + CIBW_SKIP: > + pp* + *musllinux* + CIBW_ARCHS: ${{ matrix.os.archs }} + CIBW_BUILD_VERBOSITY: "1" + # manylinux2014 (default) does not have a modern enough C++ compiler for Yosys + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28 + CIBW_BEFORE_ALL: bash ./.github/workflows/wheels/cibw_before_all.sh + CIBW_ENVIRONMENT: > + OPTFLAGS=-O3 + PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig + PATH="$PWD/bison/src:$PATH" + CIBW_ENVIRONMENT_MACOS: > + OPTFLAGS=-O3 + PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig + MACOSX_DEPLOYMENT_TARGET=11 + makeFlags='CONFIG=clang' + 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 + with: + name: python-wheels-${{ matrix.os.runner }} + path: ./wheelhouse/*.whl + upload_wheels: + name: Upload Wheels + if: (github.repository == 'YosysHQ/Yosys') && (github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + # Specifying a GitHub environment is optional, but strongly encouraged + environment: pypi + permissions: + # IMPORTANT: this permission is mandatory for Trusted Publishing + id-token: write + needs: build_wheels + steps: + - uses: actions/download-artifact@v4 + with: + path: "." + pattern: python-wheels-* + merge-multiple: true + - run: | + ls + mkdir -p ./dist + mv *.whl ./dist + - name: Publish + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/wheels/_run_cibw_linux.py b/.github/workflows/wheels/_run_cibw_linux.py new file mode 100644 index 000000000..1e8a0f497 --- /dev/null +++ b/.github/workflows/wheels/_run_cibw_linux.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 Efabless Corporation +# +# 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. +""" +This runs the cibuildwheel step from the wheels workflow locally. +""" + +import os +import yaml +import platform +import subprocess +from pathlib import Path + +__yosys_root__ = Path(__file__).absolute().parents[3] + +for source in ["ffi", "bison"]: + if not (__yosys_root__ / source).is_dir(): + print( + "You need to download ffi and bison in a similar manner to wheels.yml first." + ) + exit(-1) + +with open(__yosys_root__ / ".github" / "workflows" / "wheels.yml") as f: + workflow = yaml.safe_load(f) + +env = os.environ.copy() + +steps = workflow["jobs"]["build_wheels"]["steps"] +cibw_step = None +for step in steps: + if (step.get("uses") or "").startswith("pypa/cibuildwheel"): + cibw_step = step + break + +for key, value in cibw_step["env"].items(): + if key.endswith("WIN") or key.endswith("MAC"): + continue + env[key] = value + +env["CIBW_ARCHS"] = os.getenv("CIBW_ARCHS", platform.machine()) +subprocess.check_call(["cibuildwheel"], env=env) diff --git a/.github/workflows/wheels/cibw_before_all.sh b/.github/workflows/wheels/cibw_before_all.sh new file mode 100644 index 000000000..1aef650d7 --- /dev/null +++ b/.github/workflows/wheels/cibw_before_all.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e -x + +# Build-time dependencies +## Linux Docker Images +if command -v yum &> /dev/null; then + yum install -y flex # manylinux's bison versions are hopelessly out of date +fi + +if command -v apk &> /dev/null; then + apk add flex bison +fi + +if ! printf '%s\n' '%require "3.8"' '%%' 'start: ;' | bison -o /dev/null /dev/stdin ; then + ( + set -e -x + cd bison + ./configure + make clean + make install -j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu) + ) +fi + +## macOS/Windows -- installed in GitHub Action itself, not container + +# Runtime Dependencies +## Build Static FFI (platform-dependent but not Python version dependent) +( + set -e -x + cd ffi + ## Ultimate libyosys.so will be shared, so we need fPIC for the static libraries + CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=$PWD/pfx + make clean + make install -j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu) + ## Forces static library to be used in all situations + sed -i.bak 's@-L${toolexeclibdir} -lffi@${toolexeclibdir}/libffi.a@' ./pfx/lib/pkgconfig/libffi.pc +) diff --git a/.github/workflows/wheels/cibw_before_build.sh b/.github/workflows/wheels/cibw_before_build.sh new file mode 100644 index 000000000..1ce96b291 --- /dev/null +++ b/.github/workflows/wheels/cibw_before_build.sh @@ -0,0 +1,13 @@ +set -e +set -x + +# Don't use Python objects from previous compiles +make clean-py + +# DEBUG: show python3 and python3-config outputs +if [ "$(uname)" != "Linux" ]; then + # https://github.com/pypa/cibuildwheel/issues/2021 + ln -s $(dirname $(readlink -f $(which python3)))/python3-config $(dirname $(which python3))/python3-config +fi +python3 --version +python3-config --includes diff --git a/.gitignore b/.gitignore index 6e4ffea42..a8b04ac45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,29 @@ +## user config +/Makefile.conf + +## homebrew +/Brewfile.lock.json + +## build artifacts +/.git-abc-submodule-hash +# compiler intermediate files *.o *.d *.dwo -.*.swp *.gch *.gcda *.gcno -*~ -__pycache__ -/.cproject -/.project -/.settings -/qtcreator.files -/qtcreator.includes -/qtcreator.config -/qtcreator.creator -/qtcreator.creator.user -/coverage.info -/coverage_html -/Makefile.conf -/abc -/viz.js +*.so.dSYM/ + +## test artifacts +**/run-test.mk +*.err +*.log +*.tmp + +# compiler output files +/kernel/version_*.cc +/share /yosys /yosys.exe /yosys.js @@ -35,14 +39,50 @@ __pycache__ /yosys-witness-script.py /yosys-filterlib /yosys-filterlib.exe -/kernel/*.pyh -/kernel/python_wrappers.cc -/kernel/version_*.cc -/share /yosys-win32-mxebin-* /yosys-win32-vcxsrc-* /yosysjs-* /libyosys.so + +# build directories /tests/unit/bintest/ /tests/unit/objtest/ /tests/ystests +/build +/result +/dist + +# pyosys +/kernel/*.pyh +/kernel/python_wrappers.cc +/ffi +/bison +/venv +/*.whl +/*.egg-info + +# yosysjs dependency +/viz.js + +# other +/coverage.info +/coverage_html + + +# these really belong in global gitignore since they're not specific to this project but rather to user tool choice +# but too many people don't have a global gitignore configured: +# https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer +__pycache__ +*~ +.*.swp +/.cache +/.vscode +/.cproject +/.project +/.settings +/qtcreator.files +/qtcreator.includes +/qtcreator.config +/qtcreator.creator +/qtcreator.creator.user +/compile_commands.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..9f18be11e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "abc"] + path = abc + url = https://github.com/YosysHQ/abc +# Don't use paths as names to avoid git archive problems +[submodule "cxxopts"] + path = libs/cxxopts + url = https://github.com/jarro2783/cxxopts diff --git a/.mailmap b/.mailmap index 78afe1b6c..2d9f424d4 100644 --- a/.mailmap +++ b/.mailmap @@ -1,6 +1,6 @@ -Marcelina Kościelnicka -Marcelina Kościelnicka -Marcelina Kościelnicka +Wanda Phinode +Wanda Phinode +Wanda Phinode Claire Xenia Wolf Claire Xenia Wolf Claire Xenia Wolf diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..4a04219de --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +# .readthedocs.yaml +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: '3.12' + +formats: + - pdf + +sphinx: + configuration: docs/source/conf.py + fail_on_warning: true + +python: + install: + - requirements: docs/source/requirements.txt diff --git a/Brewfile b/Brewfile index b50c70a4b..917f1bdb4 100644 --- a/Brewfile +++ b/Brewfile @@ -6,7 +6,9 @@ brew "git" brew "graphviz" brew "pkg-config" brew "python3" -brew "tcl-tk" +brew "uv" brew "xdot" brew "bash" -brew 'boost-python3' +brew "llvm@20" +brew "lld" +brew "googletest" diff --git a/CHANGELOG b/CHANGELOG index ed91d5e7c..73c1606da 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,9 +2,353 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.38 .. Yosys 0.39-dev +Yosys 0.61 .. Yosys 0.62-dev -------------------------- +Yosys 0.60 .. Yosys 0.61 +-------------------------- + * Various + - Removed "cover" pass for coverage tracking. + - Avoid merging formal properties with "opt_merge" pass. + - Parallelize "opt_merge" pass. + + * New commands and options + - Added "design_equal" pass to support fuzz-test comparison. + - Added "lut2bmux" pass to convert $lut to $bmux. + - Added "-legalize" option to "read_rtlil" pass to prevent + semantic errors. + +Yosys 0.59 .. Yosys 0.60 +-------------------------- + * Various + - read_verilog: suport unsized parameters. + - Added static library compile option. + + * New commands and options + - Added "sdc" pass for reading SDC files. + - Added experimental "sdc_expand" and "opensta" for OpenSTA integration. + - Added "icell_liberty" pass for used internal cells. + +Yosys 0.58 .. Yosys 0.59 +-------------------------- + * Various + - Pyosys is rewritten using pybind11. + - alumacc: merge independent of sign. + - write_btor: Include $assert and $assume cells in -ywmap output. + - RTLIL parser rewritten for efficiency. + - Wildcards enabled for Liberty file consuming. + - timeest: Add top ports launching/sampling. + + * New commands and options + - Added "-apply_derived_type" option to "box_derive" pass. + - Added "-publish_icells" option to "chtype" pass. + - Added "-width" option to "sim" pass. + - Added "sort" pass for sorting the design objects. + - Merged "synth_ecp5" and "synth_nexus" into "synth_lattice" pass. + - Added "-strict-gw5a-dffs" and "-setundef" options to "synth_gowin" pass. + +Yosys 0.57 .. Yosys 0.58 +-------------------------- + * Various + - Run ABC passes in parallel. + - Extending support for buffer normalization. + - Overhaul of logging APIs. + - read_blif: Represent sequential elements with gate cells. + - Support multiple lib files in abc9_exe. + + * New commands and options + - Added "-wireshape" option to "show" command to allow + control the shape of wire nodes. + - Added "-relativeshare" option to "read_verilog", "synth" + and "techmap" pass for synthesis reproducibility testing. + - "write_rtlil" pass no longer sorts design, added "-sort" + option to match old behavior + - Added "-sva-continue-on-err" to "verific" pass to allow + processing designs that includes unsupported SVA. + +Yosys 0.56 .. Yosys 0.57 +-------------------------- + * New commands and options + - Added "-initstates" option to "abstract" pass. + - Added "-set-assumes" option to "equiv_induct" + and "equiv_simple" passes. + - Added "-always" option to "raise_error" pass. + - Added "-hierarchy" option to "stat" pass. + - Added "-noflatten" option to "synth_quicklogic" pass. + + * Various + - smtbmc: Support skipping steps in cover mode. + - write_btor: support $buf. + - read_verilog: support package import. + +Yosys 0.55 .. Yosys 0.56 +-------------------------- + * New commands and options + - Added "-unescape" option to "rename" pass. + - Added "-assert2cover" option to "chformal" pass. + - Added "linecoverage" pass to generate lcov report from selection. + - Added "opt_hier" pass to enable hierarchical optimization. + - Added "-hieropt" option to "synth" pass. + - Added "-expect-return", "-err-grep" and "-suffix" options + to "bugpoint" pass. + - Added "raise_error" dev pass. + + * Various + - Added groups to command reference documentation. + - Added bugpoint guide to documentation. + - verific: correctly reset Verific flags after import. + +Yosys 0.54 .. Yosys 0.55 +-------------------------- + * Various + - read_verilog: Implemented SystemVerilog unique/priority if. + - "attrmap" pass is able to alter memory attributes. + - verific: Support SVA followed-by operator in cover mode. + +Yosys 0.53 .. Yosys 0.54 +-------------------------- + * New commands and options + - Added "-genlib" option to "abc_new" and "abc9_exe" passes. + - Added "-verbose" and "-quiet" options to "libcache" pass. + - Added "-no-sort" option to "write_aiger" pass. + + * Various + - Added "muldiv_c" peepopt. + - Accept (and ignore) SystemVerilog unique/priority if. + - "read_verilog" copy inout ports in and out of functions/tasks. + - Enable single-bit vector wires in RTLIL. + + * Xilinx support + - Single-port URAM mapping to support memories 2048 x 144b + +Yosys 0.52 .. Yosys 0.53 +-------------------------- + * New commands and options + - Added "constmap" pass for technology mapping of coarse constant value. + - Added "timeest" pass to estimate the critical path in clock domain. + - Added "-blackbox" option to "cutpoint" pass to cut all instances of + blackboxes. + - Added "-noscopeinfo" option to "cutpoint" pass. + - Added "-nocleanup" option to "flatten" pass to prevent removal of + unused submodules. + - Added "-declockgate" option to "formalff" pass that turns clock + gating into clock enables. + + * Various + - Added "$scopeinfo" cells to preserve information during "cutpoint" pass. + - Added dataflow tracking documentation. + - share: Restrict activation patterns to potentially relevant signal. + - liberty: More robust parsing. + - verific: bit blast RAM if using mem2reg attribute. + +Yosys 0.51 .. Yosys 0.52 +-------------------------- + * New commands and options + - Added "-pattern-limit" option to "share" pass to limit analysis effort. + - Added "libcache" pass to control caching of technology library + data parsed from liberty files. + - Added "read_verilog_file_list" to parse verilog file list. + + * Various + - Added $macc_v2 cell. + - Improve lexer performance and zlib support for "read_liberty". + - opt_expr: optimize pow of 2 cells. + + +Yosys 0.50 .. Yosys 0.51 +-------------------------- + * New commands and options + - Added "abstract" pass to allow reducing and never increasing + the constraints on a circuit's behavior in a formal verification setting. + + * Various + - "splitcells" pass now splits "aldff" cells. + - FunctionalIR documentation + + * QuickLogic support + - Added IOFF inference for qlf_k6n10f + + * Intel support + - Fixed RAM and DSP support. + - Overall performance improvement for "synth_intel". + +Yosys 0.49 .. Yosys 0.50 +-------------------------- + * Various + - "write_verilog" emits "$check" cell names as labels. + +Yosys 0.48 .. Yosys 0.49 +-------------------------- + * Various + - "$scopeinfo" cells are now part of JSON export by default. + - Added option to specify hierarchical separator for "flatten". + - Improved "wreduce" to handle more cases of operator size reduction. + - Updated hashing interface, see docs/source/yosys_internals/hashing.rst + for breaking API changes. + + * New commands and options + - Added "-noscopeinfo" option to "json" and "write_json" pass. + +Yosys 0.47 .. Yosys 0.48 +-------------------------- + * Various + - Removed "read_ilang" deprecated pass. + - Enhanced boxing features in the experimental "abc_new" command. + - Added new Tcl methods for design inspection. + - Added clock enable inference to "dfflibmap". + - Added a Han-Carlson and Sklansky option for $lcu mapping. + + * New commands and options + - Added "-nopeepopt" option to "clk2fflogic" pass. + - Added "-liberty" and "-dont_use" options to "clockgate" pass. + - Added "-ignore_buses" option to "read_liberty" pass. + - Added "-dont_map" option to "techmap" pass. + - Added "-selected" option to "write_json" pass. + - Added "wrapcell" command for creating wrapper modules + around selected cells. + - Added "portarcs" command for deriving propagation timing arcs. + - Added "setenv" command for setting environment variables. + + * Gowin support + - Added "-family" option to "synth_gowin" pass. + - Cell definitions split by family. + + * Verific support + - Improved blackbox support. + +Yosys 0.46 .. Yosys 0.47 +-------------------------- + * Various + - Added cxxopts library for handling command line arguments. + - Added docs generation from cells help output. + + * New commands and options + - Added "-json" option to "synth_xilinx" pass. + - Added "-derive_luts" option to "cellmatch" pass. + - Added "t:@" syntax to "select" pass. + - Added "-list-mod" option to "select" pass. + - Removed deprecated "qwp" pass. + + * Verific support + - Initial state handling for VHDL assertions. + +Yosys 0.45 .. Yosys 0.46 +-------------------------- + * Various + - Added new "functional backend" infrastructure with three example + backends (C++, SMTLIB and Rosette). + - Added new coarse-grain buffer cell type "$buf" to RTLIL. + - Added "-y" command line option to execute a Python script with + libyosys available as a built-in module. + - Added support for casting to type in Verilog frontend. + + * New commands and options + - Added "clockgate" pass for automatic clock gating cell insertion. + - Added "bufnorm" experimental pass to convert design into + buffered-normalized form. + - Added experimental "aiger2" and "xaiger2" backends, and an + experimental "abc_new" command + - Added "-force-detailed-loop-check" option to "check" pass. + - Added "-unit_delay" option to "read_liberty" pass. + + * Verific support + - Added left and right bound properties to wires when using + specific VHDL types. + +Yosys 0.44 .. Yosys 0.45 +-------------------------- + * Various + - Added cell types help messages. + + * New back-ends + - Added initial NG-Ultra support. ( synth_nanoxplore ) + +Yosys 0.43 .. Yosys 0.44 +-------------------------- + * Various + - Added ENABLE_LTO compile option to enable link time + optimizations. + - Build support for Haiku OS. + + * New commands and options + - Added "keep_hierarchy" pass to add attribute with + same name to modules based on cost. + - Added options "-noopt","-bloat" and "-check_cost" to + "test_cell" pass. + + * New back-ends + - Added initial PolarFire support. ( synth_microchip ) + +Yosys 0.42 .. Yosys 0.43 +-------------------------- + * Various + - C++ compiler with C++17 support is required. + - Support for IO liberty files for verification. + - Limit padding from shiftadd for "peepopt" pass. + + * Verific support + - Support building Yosys with various Verific library + configurations. Can be built now without YosysHQ + specific patch and extension library. + +Yosys 0.41 .. Yosys 0.42 +-------------------------- + * New commands and options + - Added "box_derive" pass to derive box modules. + - Added option "assert-mod-count" to "select" pass. + - Added option "-header","-push" and "-pop" to "log" pass. + * Intel support + - Dropped Quartus support in "synth_intel_alm" pass. + +Yosys 0.40 .. Yosys 0.41 +-------------------------- + * New commands and options + - Added "cellmatch" pass for picking out standard cells automatically. + + * Various + - Extended the experimental incremental JSON API to allow arbitrary + smtlib subexpressions. + - Added support for using ABCs library merging when providing multiple + liberty files. + + * Verific support + - Expose library name as module attribute. + +Yosys 0.39 .. Yosys 0.40 +-------------------------- + * New commands and options + - Added option "-vhdl2019" to "read" and "verific" pass. + + * Various + - Major documentation overhaul. + - Added port statistics to "stat" command. + - Added new formatting features to cxxrtl backend. + + * Verific support + - Added better support for VHDL constants import. + - Added support for VHDL 2009. + +Yosys 0.38 .. Yosys 0.39 +-------------------------- + * New commands and options + - Added option "-extra-map" to "synth" pass. + - Added option "-dont_use" to "dfflibmap" pass. + - Added option "-href" to "show" command. + - Added option "-noscopeinfo" to "flatten" pass. + - Added option "-scopename" to "flatten" pass. + + * SystemVerilog + - Added support for packed multidimensional arrays. + + * Various + - Added "$scopeinfo" cells to preserve information about + the hierarchy during flattening. + - Added sequential area output to "stat -liberty". + - Added ability to record/replay diagnostics in cxxrtl backend. + + * Verific support + - Added attributes to module instantiation. + Yosys 0.37 .. Yosys 0.38 -------------------------- * New commands and options diff --git a/CODEOWNERS b/CODEOWNERS index a33a9a68c..4617c39bb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -10,6 +10,7 @@ # PATH (can use glob) USERNAME(S) +CODEOWNERS @nakengelhardt passes/cmds/scratchpad.cc @nakengelhardt frontends/rpc/ @whitequark backends/cxxrtl/ @whitequark @@ -18,7 +19,9 @@ passes/techmap/flowmap.cc @whitequark passes/opt/opt_lut.cc @whitequark passes/techmap/abc9*.cc @eddiehung @Ravenslofty backends/aiger/xaiger.cc @eddiehung - +docs/ @KrystalDelusion +docs/source/using_yosys/synthesis/abc.rst @KrystalDelusion @Ravenslofty +.github/workflows/*.yml @mmicko ## External Contributors # Only users with write permission to the repository get review @@ -27,15 +30,16 @@ backends/aiger/xaiger.cc @eddiehung # These still override previous lines, so be careful not to # accidentally disable any of the above rules. -frontends/verilog/ @zachjs -frontends/ast/ @zachjs +frontends/verilog/ @widlarizer +frontends/ast/ @widlarizer techlibs/intel_alm/ @Ravenslofty techlibs/gowin/ @pepijndevos techlibs/gatemate/ @pu-cc # pyosys -misc/*.py @btut +pyosys/* @donn +setup.py @donn backends/firrtl @ucbjrl @azidar diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..ad2910630 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at contact@yosyshq.com and/or +claire@clairexen.net. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..403292b0b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,70 @@ +# Introduction + +Thanks for thinking about contributing to the Yosys project. If this is your +first time contributing to an open source project, please take a look at the +following guide: +https://opensource.guide/how-to-contribute/#orienting-yourself-to-a-new-project. + +Information about the Yosys coding style is available on our Read the Docs: +https://yosys.readthedocs.io/en/latest/yosys_internals/extending_yosys/contributing.html. + +# Using the issue tracker + +The [issue tracker](https://github.com/YosysHQ/yosys/issues) is used for +tracking bugs or other problems with Yosys or its documentation. It is also the +place to go for requesting new features. +When [creating a new issue](https://github.com/YosysHQ/yosys/issues/new/choose), +we have a few templates available. Please make use of these! It will make it +much easier for someone to respond and help. + +### Bug reports + +Before you submit an issue, please check out the [how-to guide for +`bugpoint`](https://yosys.readthedocs.io/en/latest/using_yosys/bugpoint.html). +This guide will take you through the process of using the [`bugpoint` +command](https://yosys.readthedocs.io/en/latest/cmd/bugpoint.html) in Yosys to +produce a [minimal, complete and verifiable +example](https://stackoverflow.com/help/minimal-reproducible-example) (MVCE). +Providing an MVCE with your bug report drastically increases the likelihood that +someone will be able to help resolve your issue. + + +# Using pull requests + +If you are working on something to add to Yosys, or fix something that isn't +working quite right, make a [PR](https://github.com/YosysHQ/yosys/pulls)! An +open PR, even as a draft, tells everyone that you're working on it and they +don't have to. It can also be a useful way to solicit feedback on in-progress +changes. See below to find the best way to [ask us +questions](#asking-questions). + +In general, all changes to the code are done as a PR, with [Continuous +Integration (CI)](https://github.com/YosysHQ/yosys/actions) tools that +automatically run the full suite of tests compiling and running Yosys. Please +make use of this! If you're adding a feature: add a test! Not only does it +verify that your feature is working as expected, but it can also be a handy way +for people to see how the feature is used. If you're fixing a bug: add a test! +If you can, do this first; it's okay if the test starts off failing - you +already know there is a bug. CI also helps to make sure that your changes still +work under a range of compilers, settings, and targets. + + +### Labels + +We use [labels](https://github.com/YosysHQ/yosys/labels) to help categorise +issues and PRs. If a label seems relevant to your work, please do add it; this +also includes the labels beggining with 'status-'. The 'merge-' labels are used +by maintainers for tracking and communicating which PRs are ready and pending +merge; please do not use these labels if you are not a maintainer. + + +# Asking questions + +If you have a question about how to use Yosys, please ask on our [Discourse forum](https://yosyshq.discourse.group/) or in our [discussions +page](https://github.com/YosysHQ/yosys/discussions). +The Discourse is also a great place to ask questions about developing or +contributing to Yosys. + +We have open [dev 'jour fixe' (JF) meetings](https://docs.google.com/document/d/1SapA6QAsJcsgwsdKJDgnGR2mr97pJjV4eeXg_TVJhRU/edit?usp=sharing) where developers from YosysHQ and the +community come together to discuss open issues and PRs. This is also a good +place to talk to us about how to implement larger PRs. diff --git a/COPYING b/COPYING index e8b123be2..a3ca45b42 100644 --- a/COPYING +++ b/COPYING @@ -1,6 +1,6 @@ ISC License -Copyright (C) 2012 - 2020 Claire Xenia Wolf +Copyright (C) 2012 - 2026 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 diff --git a/Dockerfile b/Dockerfile index 549c73c97..9806696e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN apt-get update -qq \ && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ ca-certificates \ clang \ + lld \ curl \ libffi-dev \ libreadline-dev \ diff --git a/Makefile b/Makefile index 81715ded5..775cf8828 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,8 @@ -CONFIG := clang +CONFIG := none +# CONFIG := clang # CONFIG := gcc -# CONFIG := afl-gcc -# CONFIG := emcc # CONFIG := wasi -# CONFIG := mxe # CONFIG := msys2-32 # CONFIG := msys2-64 @@ -17,32 +15,42 @@ ENABLE_READLINE := 1 ENABLE_EDITLINE := 0 ENABLE_GHDL := 0 ENABLE_VERIFIC := 0 +ENABLE_VERIFIC_SYSTEMVERILOG := 1 +ENABLE_VERIFIC_VHDL := 1 +ENABLE_VERIFIC_HIER_TREE := 1 +ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0 ENABLE_VERIFIC_EDIF := 0 ENABLE_VERIFIC_LIBERTY := 0 -DISABLE_VERIFIC_EXTENSIONS := 0 -DISABLE_VERIFIC_VHDL := 0 -ENABLE_COVER := 1 ENABLE_LIBYOSYS := 0 +ENABLE_LIBYOSYS_STATIC := 0 ENABLE_ZLIB := 1 +ENABLE_HELP_SOURCE := 0 # python wrappers ENABLE_PYOSYS := 0 +PYOSYS_USE_UV := 1 # other configuration flags ENABLE_GCOV := 0 ENABLE_GPROF := 0 ENABLE_DEBUG := 0 -ENABLE_NDEBUG := 0 +ENABLE_LTO := 0 ENABLE_CCACHE := 0 # sccache is not always a drop-in replacement for ccache in practice ENABLE_SCCACHE := 0 +ENABLE_FUNCTIONAL_TESTS := 0 LINK_CURSES := 0 LINK_TERMCAP := 0 LINK_ABC := 0 # Needed for environments that can't run executables (i.e. emscripten, wasm) DISABLE_SPAWN := 0 # Needed for environments that don't have proper thread support (i.e. emscripten, wasm--for now) +ENABLE_THREADS := 1 +ifeq ($(ENABLE_THREADS),1) DISABLE_ABC_THREADS := 0 +else +DISABLE_ABC_THREADS := 1 +endif # clang sanitizers SANITIZER = @@ -51,6 +59,11 @@ SANITIZER = # SANITIZER = undefined # SANITIZER = cfi +# Prefer using ENABLE_DEBUG over setting these +OPT_LEVEL := -O3 +GCC_LTO := +CLANG_LTO := -flto=thin + PROGRAM_PREFIX := OS := $(shell uname -s) @@ -82,26 +95,31 @@ TARGETS = $(PROGRAM_PREFIX)yosys$(EXE) $(PROGRAM_PREFIX)yosys-config PRETTY = 1 SMALL = 0 -# Unit test -UNITESTPATH := tests/unit - all: top-all YOSYS_SRC := $(dir $(firstword $(MAKEFILE_LIST))) VPATH := $(YOSYS_SRC) -CXXSTD ?= c++11 +# Unit test +UNITESTPATH := $(YOSYS_SRC)/tests/unit + +export CXXSTD ?= c++17 CXXFLAGS := $(CXXFLAGS) -Wall -Wextra -ggdb -I. -I"$(YOSYS_SRC)" -MD -MP -D_YOSYS_ -fPIC -I$(PREFIX)/include LIBS := $(LIBS) -lstdc++ -lm PLUGIN_LINKFLAGS := PLUGIN_LIBS := EXE_LINKFLAGS := +EXE_LIBS := ifeq ($(OS), MINGW) EXE_LINKFLAGS := -Wl,--export-all-symbols -Wl,--out-implib,libyosys_exe.a PLUGIN_LINKFLAGS += -L"$(LIBDIR)" PLUGIN_LIBS := -lyosys_exe endif +ifeq ($(ENABLE_HELP_SOURCE),1) +CXXFLAGS += -DYOSYS_ENABLE_HELP_SOURCE +endif + PKG_CONFIG ?= pkg-config SED ?= sed BISON ?= bison @@ -110,17 +128,14 @@ AWK ?= awk ifeq ($(OS), Darwin) PLUGIN_LINKFLAGS += -undefined dynamic_lookup +LINKFLAGS += -rdynamic # homebrew search paths ifneq ($(shell :; command -v brew),) BREW_PREFIX := $(shell brew --prefix)/opt $(info $$BREW_PREFIX is [${BREW_PREFIX}]) -ifeq ($(ENABLE_PYOSYS),1) -CXXFLAGS += -I$(BREW_PREFIX)/boost/include/boost -LINKFLAGS += -L$(BREW_PREFIX)/boost/lib -endif -CXXFLAGS += -I$(BREW_PREFIX)/readline/include -LINKFLAGS += -L$(BREW_PREFIX)/readline/lib +CXXFLAGS += -I$(BREW_PREFIX)/readline/include -I$(BREW_PREFIX)/flex/include +LINKFLAGS += -L$(BREW_PREFIX)/readline/lib -L$(BREW_PREFIX)/flex/lib PKG_CONFIG_PATH := $(BREW_PREFIX)/libffi/lib/pkgconfig:$(PKG_CONFIG_PATH) PKG_CONFIG_PATH := $(BREW_PREFIX)/tcl-tk/lib/pkgconfig:$(PKG_CONFIG_PATH) export PATH := $(BREW_PREFIX)/bison/bin:$(BREW_PREFIX)/gettext/bin:$(BREW_PREFIX)/flex/bin:$(PATH) @@ -141,33 +156,38 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.38+129 +ifeq ($(OS), Haiku) +# Allow usage of non-posix vasprintf, mkstemps functions +CXXFLAGS += -D_DEFAULT_SOURCE +endif + +YOSYS_VER := 0.61+21 +YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) +YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) +YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) +CXXFLAGS += -DYOSYS_VER=\\"$(YOSYS_VER)\\" \ + -DYOSYS_MAJOR=$(YOSYS_MAJOR) \ + -DYOSYS_MINOR=$(YOSYS_MINOR) \ + -DYOSYS_COMMIT=$(YOSYS_COMMIT) # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo # will have this file in its unexpanded form tough, in which case we fall # back to calling git directly. TARBALL_GIT_REV := $(shell cat $(YOSYS_SRC)/.gitcommit) -ifeq ($(TARBALL_GIT_REV),$$Format:%h$$) +ifneq ($(findstring Format:,$(TARBALL_GIT_REV)),) GIT_REV := $(shell GIT_DIR=$(YOSYS_SRC)/.git git rev-parse --short=9 HEAD || echo UNKNOWN) +GIT_DIRTY := $(shell GIT_DIR=$(YOSYS_SRC)/.git git diff --exit-code --quiet 2>/dev/null; if [ $$? -ne 0 ]; then echo "-dirty"; fi) else GIT_REV := $(TARBALL_GIT_REV) +GIT_DIRTY := "" endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 543faed.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 5ae48ee.. | wc -l`/;" Makefile -# set 'ABCREV = default' to use abc/ as it is -# -# Note: If you do ABC development, make sure that 'abc' in this directory -# is just a symlink to your actual ABC working directory, as 'make mrproper' -# will remove the 'abc' directory and you do not want to accidentally -# delete your work on ABC.. -ABCREV = 0cd90d0 -ABCPULL = 1 -ABCURL ?= https://github.com/YosysHQ/abc ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) # set ABCEXTERNAL = to use an external ABC instance @@ -187,17 +207,17 @@ endif include Makefile.conf endif -PYTHON_EXECUTABLE := $(shell if python3 -c ""; then echo "python3"; else echo "python"; fi) +PYTHON_EXECUTABLE ?= $(shell if python3 -c ""; then echo "python3"; else echo "python"; fi) ifeq ($(ENABLE_PYOSYS),1) PYTHON_VERSION_TESTCODE := "import sys;t='{v[0]}.{v[1]}'.format(v=list(sys.version_info[:2]));print(t)" PYTHON_VERSION := $(shell $(PYTHON_EXECUTABLE) -c ""$(PYTHON_VERSION_TESTCODE)"") PYTHON_MAJOR_VERSION := $(shell echo $(PYTHON_VERSION) | cut -f1 -d.) -ENABLE_PYTHON_CONFIG_EMBED ?= $(shell $(PYTHON_EXECUTABLE)-config --embed --libs > /dev/null && echo 1) -ifeq ($(ENABLE_PYTHON_CONFIG_EMBED),1) -PYTHON_CONFIG := $(PYTHON_EXECUTABLE)-config --embed -else PYTHON_CONFIG := $(PYTHON_EXECUTABLE)-config +PYTHON_CONFIG_FOR_EXE := $(PYTHON_CONFIG) +PYTHON_CONFIG_EMBED_AVAILABLE ?= $(shell $(PYTHON_EXECUTABLE)-config --embed --libs > /dev/null && echo 1) +ifeq ($(PYTHON_CONFIG_EMBED_AVAILABLE),1) +PYTHON_CONFIG_FOR_EXE := $(PYTHON_CONFIG) --embed endif PYTHON_DESTDIR := $(shell $(PYTHON_EXECUTABLE) -c "import site; print(site.getsitepackages()[-1]);") @@ -214,18 +234,22 @@ ifeq ($(OS), OpenBSD) ABC_ARCHFLAGS += "-DABC_NO_RLIMIT" endif +# This gets overridden later. +LTOFLAGS := $(GCC_LTO) + ifeq ($(CONFIG),clang) CXX = clang++ -CXXFLAGS += -std=$(CXXSTD) -Os -ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -Wno-c++11-narrowing $(ABC_ARCHFLAGS)" +CXXFLAGS += -std=$(CXXSTD) $(OPT_LEVEL) +ifeq ($(ENABLE_LTO),1) +LINKFLAGS += -fuse-ld=lld +endif +ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H $(ABC_ARCHFLAGS)" +LTOFLAGS := $(CLANG_LTO) ifneq ($(SANITIZER),) $(info [Clang Sanitizer] $(SANITIZER)) CXXFLAGS += -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=$(SANITIZER) LINKFLAGS += -g -fsanitize=$(SANITIZER) -ifneq ($(findstring address,$(SANITIZER)),) -ENABLE_COVER := 0 -endif ifneq ($(findstring memory,$(SANITIZER)),) CXXFLAGS += -fPIE -fsanitize-memory-track-origins LINKFLAGS += -fPIE -fsanitize-memory-track-origins @@ -233,91 +257,42 @@ endif ifneq ($(findstring cfi,$(SANITIZER)),) CXXFLAGS += -flto LINKFLAGS += -flto +LTOFLAGS = endif endif else ifeq ($(CONFIG),gcc) CXX = g++ -CXXFLAGS += -std=$(CXXSTD) -Os +CXXFLAGS += -std=$(CXXSTD) $(OPT_LEVEL) ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H $(ABC_ARCHFLAGS)" else ifeq ($(CONFIG),gcc-static) LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -static LIBS := $(filter-out -lrt,$(LIBS)) CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) -CXXFLAGS += -std=$(CXXSTD) -Os +CXXFLAGS += -std=$(CXXSTD) $(OPT_LEVEL) ABCMKARGS = CC="$(CC)" CXX="$(CXX)" LD="$(CXX)" ABC_USE_LIBSTDCXX=1 LIBS="-lm -lpthread -static" OPTFLAGS="-O" \ ARCHFLAGS="-DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING=1 -Wno-unused-but-set-variable $(ARCHFLAGS)" ABC_USE_NO_READLINE=1 ifeq ($(DISABLE_ABC_THREADS),1) ABCMKARGS += "ABC_USE_NO_PTHREADS=1" endif -else ifeq ($(CONFIG),afl-gcc) -CXX = AFL_QUIET=1 AFL_HARDEN=1 afl-gcc -CXXFLAGS += -std=$(CXXSTD) -Os -ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H" - -else ifeq ($(CONFIG),cygwin) -CXX = g++ -CXXFLAGS += -std=gnu++11 -Os -ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H" - -else ifeq ($(CONFIG),emcc) -CXX = emcc -CXXFLAGS := -std=$(CXXSTD) $(filter-out -fPIC -ggdb,$(CXXFLAGS)) -ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DABC_MEMALIGN=8 -Wno-c++11-narrowing" -EMCC_CXXFLAGS := -Os -Wno-warn-absolute-paths -EMCC_LINKFLAGS := --embed-file share -EMCC_LINKFLAGS += -s NO_EXIT_RUNTIME=1 -EMCC_LINKFLAGS += -s EXPORTED_FUNCTIONS="['_main','_run','_prompt','_errmsg','_memset']" -EMCC_LINKFLAGS += -s TOTAL_MEMORY=134217728 -EMCC_LINKFLAGS += -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -# https://github.com/kripken/emscripten/blob/master/src/settings.js -CXXFLAGS += $(EMCC_CXXFLAGS) -LINKFLAGS += $(EMCC_LINKFLAGS) -LIBS = -EXE = .js - -DISABLE_SPAWN := 1 - -TARGETS := $(filter-out $(PROGRAM_PREFIX)yosys-config,$(TARGETS)) -EXTRA_TARGETS += yosysjs-$(YOSYS_VER).zip - -ifeq ($(ENABLE_ABC),1) -LINK_ABC := 1 -DISABLE_ABC_THREADS := 1 -endif - -viz.js: - wget -O viz.js.part https://github.com/mdaines/viz.js/releases/download/0.0.3/viz.js - mv viz.js.part viz.js - -yosysjs-$(YOSYS_VER).zip: yosys.js viz.js misc/yosysjs/* - rm -rf yosysjs-$(YOSYS_VER) yosysjs-$(YOSYS_VER).zip - mkdir -p yosysjs-$(YOSYS_VER) - cp viz.js misc/yosysjs/* yosys.js yosys.wasm yosysjs-$(YOSYS_VER)/ - zip -r yosysjs-$(YOSYS_VER).zip yosysjs-$(YOSYS_VER) - -yosys.html: misc/yosys.html - $(P) cp misc/yosys.html yosys.html - else ifeq ($(CONFIG),wasi) ifeq ($(WASI_SDK),) CXX = clang++ AR = llvm-ar RANLIB = llvm-ranlib -WASIFLAGS := -target wasm32-wasi --sysroot $(WASI_SYSROOT) $(WASIFLAGS) +WASIFLAGS := -target wasm32-wasi $(WASIFLAGS) else CXX = $(WASI_SDK)/bin/clang++ AR = $(WASI_SDK)/bin/ar RANLIB = $(WASI_SDK)/bin/ranlib -WASIFLAGS := --sysroot $(WASI_SDK)/share/wasi-sysroot $(WASIFLAGS) endif -CXXFLAGS := $(WASIFLAGS) -std=$(CXXSTD) -Os -D_WASI_EMULATED_PROCESS_CLOCKS $(filter-out -fPIC,$(CXXFLAGS)) +CXXFLAGS := $(WASIFLAGS) -std=$(CXXSTD) $(OPT_LEVEL) -D_WASI_EMULATED_PROCESS_CLOCKS $(filter-out -fPIC,$(CXXFLAGS)) LINKFLAGS := $(WASIFLAGS) -Wl,-z,stack-size=1048576 $(filter-out -rdynamic,$(LINKFLAGS)) LIBS := -lwasi-emulated-process-clocks $(filter-out -lrt,$(LIBS)) ABCMKARGS += AR="$(AR)" RANLIB="$(RANLIB)" -ABCMKARGS += ARCHFLAGS="$(WASIFLAGS) -D_WASI_EMULATED_PROCESS_CLOCKS -DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING -DABC_NO_RLIMIT -Wno-c++11-narrowing" +ABCMKARGS += ARCHFLAGS="$(WASIFLAGS) -D_WASI_EMULATED_PROCESS_CLOCKS -DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING -DABC_NO_RLIMIT" ABCMKARGS += OPTFLAGS="-Os" EXE = .wasm @@ -325,72 +300,76 @@ DISABLE_SPAWN := 1 ifeq ($(ENABLE_ABC),1) LINK_ABC := 1 +ENABLE_THREADS := 0 DISABLE_ABC_THREADS := 1 endif -else ifeq ($(CONFIG),mxe) -PKG_CONFIG = /usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-pkg-config -CXX = /usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-g++ -CXXFLAGS += -std=$(CXXSTD) -Os -D_POSIX_SOURCE -DYOSYS_MXE_HACKS -Wno-attributes -CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) -LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -s -LIBS := $(filter-out -lrt,$(LIBS)) -ABCMKARGS += ARCHFLAGS="-DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" -# TODO: Try to solve pthread linking issue in more appropriate way -ABCMKARGS += LIBS="lib/x86/pthreadVC2.lib -s" LINKFLAGS="-Wl,--allow-multiple-definition" ABC_USE_NO_READLINE=1 CC="/usr/local/src/mxe/usr/bin/i686-w64-mingw32.static-gcc" -EXE = .exe - else ifeq ($(CONFIG),msys2-32) CXX = i686-w64-mingw32-g++ -CXXFLAGS += -std=$(CXXSTD) -Os -D_POSIX_SOURCE -DYOSYS_WIN32_UNIX_DIR +CXXFLAGS += -std=$(CXXSTD) $(OPT_LEVEL) -D_POSIX_SOURCE -DYOSYS_WIN32_UNIX_DIR CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -s LIBS := $(filter-out -lrt,$(LIBS)) -ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" +ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DWIN32 -DHAVE_STRUCT_TIMESPEC -fpermissive -w" ABCMKARGS += LIBS="-lpthread -lshlwapi -s" ABC_USE_NO_READLINE=0 CC="i686-w64-mingw32-gcc" CXX="$(CXX)" EXE = .exe else ifeq ($(CONFIG),msys2-64) CXX = x86_64-w64-mingw32-g++ -CXXFLAGS += -std=$(CXXSTD) -Os -D_POSIX_SOURCE -DYOSYS_WIN32_UNIX_DIR +CXXFLAGS += -std=$(CXXSTD) $(OPT_LEVEL) -D_POSIX_SOURCE -DYOSYS_WIN32_UNIX_DIR CXXFLAGS := $(filter-out -fPIC,$(CXXFLAGS)) LINKFLAGS := $(filter-out -rdynamic,$(LINKFLAGS)) -s LIBS := $(filter-out -lrt,$(LIBS)) -ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DHAVE_STRUCT_TIMESPEC -fpermissive -w" +ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H -DWIN32_NO_DLL -DWIN32 -DHAVE_STRUCT_TIMESPEC -fpermissive -w" ABCMKARGS += LIBS="-lpthread -lshlwapi -s" ABC_USE_NO_READLINE=0 CC="x86_64-w64-mingw32-gcc" CXX="$(CXX)" EXE = .exe -else ifneq ($(CONFIG),none) -$(error Invalid CONFIG setting '$(CONFIG)'. Valid values: clang, gcc, emcc, mxe, msys2-32, msys2-64) +else ifeq ($(CONFIG),none) +CXXFLAGS += -std=$(CXXSTD) $(OPT_LEVEL) +ABCMKARGS += ARCHFLAGS="-DABC_USE_STDINT_H $(ABC_ARCHFLAGS)" +LTOFLAGS = + +else +$(error Invalid CONFIG setting '$(CONFIG)'. Valid values: clang, gcc, msys2-32, msys2-64, none) +endif + + +ifeq ($(ENABLE_LTO),1) +CXXFLAGS += $(LTOFLAGS) +LINKFLAGS += $(LTOFLAGS) endif ifeq ($(ENABLE_LIBYOSYS),1) TARGETS += libyosys.so +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) +TARGETS += libyosys.a endif +endif + +PY_WRAPPER_FILE = pyosys/wrappers + +# running make clean on just those and then recompiling saves a lot of +# time when running cibuildwheel +PYTHON_OBJECTS = pyosys/wrappers.o kernel/drivers.o kernel/yosys.o passes/cmds/plugin.o ifeq ($(ENABLE_PYOSYS),1) -# Detect name of boost_python library. Some distros use boost_python-py, other boost_python, some only use the major version number, some a concatenation of major and minor version numbers -CHECK_BOOST_PYTHON = (echo "int main(int argc, char ** argv) {return 0;}" | $(CXX) -xc -o /dev/null $(shell $(PYTHON_CONFIG) --ldflags) -l$(1) - > /dev/null 2>&1 && echo "-l$(1)") -BOOST_PYTHON_LIB ?= $(shell \ - $(call CHECK_BOOST_PYTHON,boost_python-py$(subst .,,$(PYTHON_VERSION))) || \ - $(call CHECK_BOOST_PYTHON,boost_python-py$(PYTHON_MAJOR_VERSION)) || \ - $(call CHECK_BOOST_PYTHON,boost_python$(subst .,,$(PYTHON_VERSION))) || \ - $(call CHECK_BOOST_PYTHON,boost_python$(PYTHON_MAJOR_VERSION)) \ -) +# python-config --ldflags includes -l and -L, but LINKFLAGS is only -L -ifeq ($(BOOST_PYTHON_LIB),) -$(error BOOST_PYTHON_LIB could not be detected. Please define manually) +UV_ENV := +ifeq ($(PYOSYS_USE_UV),1) +UV_ENV := uv run --no-project --with 'pybind11>3,<4' --with 'cxxheaderparser' endif -LIBS += $(shell $(PYTHON_CONFIG) --libs) $(BOOST_PYTHON_LIB) -lboost_system -lboost_filesystem -# python-config --ldflags includes LIBS for some reason LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags)) -CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DWITH_PYTHON +LIBS += $(shell $(PYTHON_CONFIG) --libs) +EXE_LIBS += $(filter-out $(LIBS),$(shell $(PYTHON_CONFIG_FOR_EXE) --libs)) +PYBIND11_INCLUDE ?= $(shell $(UV_ENV) $(PYTHON_EXECUTABLE) -m pybind11 --includes) +CXXFLAGS += -I$(PYBIND11_INCLUDE) -DYOSYS_ENABLE_PYTHON +CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DYOSYS_ENABLE_PYTHON -PY_WRAPPER_FILE = kernel/python_wrappers OBJS += $(PY_WRAPPER_FILE).o -PY_GEN_SCRIPT= py_wrap_generator -PY_WRAP_INCLUDES := $(shell python$(PYTHON_VERSION) -c "from misc import $(PY_GEN_SCRIPT); $(PY_GEN_SCRIPT).print_includes()") +PY_GEN_SCRIPT = $(YOSYS_SRC)/pyosys/generator.py +PY_WRAP_INCLUDES := $(shell $(UV_ENV) $(PYTHON_EXECUTABLE) $(PY_GEN_SCRIPT) --print-includes) endif # ENABLE_PYOSYS ifeq ($(ENABLE_READLINE),1) @@ -407,22 +386,22 @@ ifeq ($(LINK_TERMCAP),1) LIBS += -ltermcap ABCMKARGS += "ABC_READLINE_LIBRARIES=-lreadline -ltermcap" endif -ifeq ($(CONFIG),mxe) -LIBS += -ltermcap -endif else ifeq ($(ENABLE_EDITLINE),1) CXXFLAGS += -DYOSYS_ENABLE_EDITLINE -LIBS += -ledit -ltinfo -lbsd -else -ABCMKARGS += "ABC_USE_NO_READLINE=1" +LIBS += -ledit endif +ABCMKARGS += "ABC_USE_NO_READLINE=1" endif ifeq ($(DISABLE_ABC_THREADS),1) ABCMKARGS += "ABC_USE_NO_PTHREADS=1" endif +ifeq ($(LINK_ABC),1) +ABCMKARGS += "ABC_USE_PIC=1" +endif + ifeq ($(DISABLE_SPAWN),1) CXXFLAGS += -DYOSYS_DISABLE_SPAWN endif @@ -459,12 +438,10 @@ TCL_INCLUDE ?= /usr/include/$(TCL_VERSION) TCL_LIBS ?= -l$(TCL_VERSION) endif -ifeq ($(CONFIG),mxe) -CXXFLAGS += -DYOSYS_ENABLE_TCL -LIBS += -ltcl86 -lwsock32 -lws2_32 -lnetapi32 -lz -luserenv -else CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --cflags tcl || echo -I$(TCL_INCLUDE)) -DYOSYS_ENABLE_TCL LIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --libs tcl || echo $(TCL_LIBS)) +ifneq (,$(findstring TCL_WITH_EXTERNAL_TOMMATH,$(CXXFLAGS))) +LIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --libs libtommath || echo) endif endif @@ -478,16 +455,14 @@ CXXFLAGS += -pg LINKFLAGS += -pg endif -ifeq ($(ENABLE_NDEBUG),1) -CXXFLAGS := -O3 -DNDEBUG $(filter-out -Os -ggdb,$(CXXFLAGS)) +ifeq ($(ENABLE_DEBUG),1) +CXXFLAGS := -Og -DDEBUG $(filter-out $(OPT_LEVEL),$(CXXFLAGS)) +STRIP := endif -ifeq ($(ENABLE_DEBUG),1) -ifeq ($(CONFIG),clang) -CXXFLAGS := -O0 -DDEBUG $(filter-out -Os,$(CXXFLAGS)) -else -CXXFLAGS := -Og -DDEBUG $(filter-out -Os,$(CXXFLAGS)) -endif +ifeq ($(ENABLE_THREADS),1) +CXXFLAGS += -DYOSYS_ENABLE_THREADS +LIBS += -lpthread endif ifeq ($(ENABLE_ABC),1) @@ -499,7 +474,10 @@ LIBS += -lpthread endif else ifeq ($(ABCEXTERNAL),) -TARGETS += $(PROGRAM_PREFIX)yosys-abc$(EXE) +TARGETS := $(PROGRAM_PREFIX)yosys-abc$(EXE) $(TARGETS) +endif +ifeq ($(DISABLE_SPAWN),1) +$(error ENABLE_ABC=1 requires either LINK_ABC=1 or DISABLE_SPAWN=0) endif endif endif @@ -515,8 +493,24 @@ endif LIBS_VERIFIC = ifeq ($(ENABLE_VERIFIC),1) VERIFIC_DIR ?= /usr/local/src/verific_lib -VERIFIC_COMPONENTS ?= verilog database util containers hier_tree -ifneq ($(DISABLE_VERIFIC_VHDL),1) +VERIFIC_COMPONENTS ?= database util containers +ifeq ($(ENABLE_VERIFIC_HIER_TREE),1) +VERIFIC_COMPONENTS += hier_tree +CXXFLAGS += -DVERIFIC_HIER_TREE_SUPPORT +else +ifneq ($(wildcard $(VERIFIC_DIR)/hier_tree),) +VERIFIC_COMPONENTS += hier_tree +endif +endif +ifeq ($(ENABLE_VERIFIC_SYSTEMVERILOG),1) +VERIFIC_COMPONENTS += verilog +CXXFLAGS += -DVERIFIC_SYSTEMVERILOG_SUPPORT +else +ifneq ($(wildcard $(VERIFIC_DIR)/verilog),) +VERIFIC_COMPONENTS += verilog +endif +endif +ifeq ($(ENABLE_VERIFIC_VHDL),1) VERIFIC_COMPONENTS += vhdl CXXFLAGS += -DVERIFIC_VHDL_SUPPORT else @@ -532,9 +526,17 @@ ifeq ($(ENABLE_VERIFIC_LIBERTY),1) VERIFIC_COMPONENTS += synlib CXXFLAGS += -DVERIFIC_LIBERTY_SUPPORT endif -ifneq ($(DISABLE_VERIFIC_EXTENSIONS),1) +ifeq ($(ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS),1) VERIFIC_COMPONENTS += extensions CXXFLAGS += -DYOSYSHQ_VERIFIC_EXTENSIONS +else +# YosysHQ flavor of Verific always needs extensions linked +# if disabled it will just not be invoked but parts +# are required for it to initialize properly +ifneq ($(wildcard $(VERIFIC_DIR)/extensions),) +VERIFIC_COMPONENTS += extensions +OBJS += kernel/log_compat.o +endif endif CXXFLAGS += $(patsubst %,-I$(VERIFIC_DIR)/%,$(VERIFIC_COMPONENTS)) -DYOSYS_ENABLE_VERIFIC ifeq ($(OS), Darwin) @@ -544,11 +546,6 @@ LIBS_VERIFIC += -Wl,--whole-archive $(patsubst %,$(VERIFIC_DIR)/%/*-linux.a,$(VE endif endif - -ifeq ($(ENABLE_COVER),1) -CXXFLAGS += -DYOSYS_ENABLE_COVER -endif - ifeq ($(ENABLE_CCACHE),1) CXX := ccache $(CXX) else @@ -564,6 +561,13 @@ $(subst //,/,$(1)/$(notdir $(2))): $(2) $$(Q) cp "$(YOSYS_SRC)"/$(2) $(subst //,/,$(1)/$(notdir $(2))) endef +define add_share_file_and_rename +EXTRA_TARGETS += $(subst //,/,$(1)/$(3)) +$(subst //,/,$(1)/$(3)): $(2) + $$(P) mkdir -p $(1) + $$(Q) cp "$(YOSYS_SRC)"/$(2) $(subst //,/,$(1)/$(3)) +endef + define add_gen_share_file EXTRA_TARGETS += $(subst //,/,$(1)/$(notdir $(2))) $(subst //,/,$(1)/$(notdir $(2))): $(2) @@ -596,12 +600,14 @@ S = endif $(eval $(call add_include_file,kernel/binding.h)) +$(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/consteval.h)) $(eval $(call add_include_file,kernel/constids.inc)) $(eval $(call add_include_file,kernel/cost.h)) +$(eval $(call add_include_file,kernel/drivertools.h)) $(eval $(call add_include_file,kernel/ff.h)) $(eval $(call add_include_file,kernel/ffinit.h)) $(eval $(call add_include_file,kernel/ffmerge.h)) @@ -609,7 +615,9 @@ $(eval $(call add_include_file,kernel/fmt.h)) ifeq ($(ENABLE_ZLIB),1) $(eval $(call add_include_file,kernel/fstdata.h)) endif +$(eval $(call add_include_file,kernel/gzip.h)) $(eval $(call add_include_file,kernel/hashlib.h)) +$(eval $(call add_include_file,kernel/io.h)) $(eval $(call add_include_file,kernel/json.h)) $(eval $(call add_include_file,kernel/log.h)) $(eval $(call add_include_file,kernel/macc.h)) @@ -620,10 +628,13 @@ $(eval $(call add_include_file,kernel/register.h)) $(eval $(call add_include_file,kernel/rtlil.h)) $(eval $(call add_include_file,kernel/satgen.h)) $(eval $(call add_include_file,kernel/scopeinfo.h)) +$(eval $(call add_include_file,kernel/sexpr.h)) $(eval $(call add_include_file,kernel/sigtools.h)) +$(eval $(call add_include_file,kernel/threading.h)) $(eval $(call add_include_file,kernel/timinginfo.h)) $(eval $(call add_include_file,kernel/utils.h)) $(eval $(call add_include_file,kernel/yosys.h)) +$(eval $(call add_include_file,kernel/yosys_common.h)) $(eval $(call add_include_file,kernel/yw.h)) $(eval $(call add_include_file,libs/ezsat/ezsat.h)) $(eval $(call add_include_file,libs/ezsat/ezminisat.h)) @@ -633,14 +644,19 @@ endif $(eval $(call add_include_file,libs/sha1/sha1.h)) $(eval $(call add_include_file,libs/json11/json11.hpp)) $(eval $(call add_include_file,passes/fsm/fsmdata.h)) -$(eval $(call add_include_file,frontends/ast/ast.h)) -$(eval $(call add_include_file,frontends/ast/ast_binding.h)) +$(eval $(call add_include_file,passes/techmap/libparse.h)) $(eval $(call add_include_file,frontends/blif/blifparse.h)) $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) -OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o -OBJS += kernel/binding.o -OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o +OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o kernel/io.o kernel/gzip.o +OBJS += kernel/rtlil_bufnorm.o +OBJS += kernel/log_help.o +ifeq ($(ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS),1) +OBJS += kernel/log_compat.o +endif +OBJS += kernel/binding.o kernel/tclapi.o +OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o +OBJS += kernel/drivertools.o kernel/functional.o kernel/threading.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o endif @@ -679,6 +695,9 @@ OBJS += libs/fst/fastlz.o OBJS += libs/fst/lz4.o endif +techlibs/%_pm.h: passes/pmgen/pmgen.py techlibs/%.pmg + $(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p $(notdir $*) $(filter-out $<,$^) + ifneq ($(SMALL),1) OBJS += libs/subcircuit/subcircuit.o @@ -702,7 +721,6 @@ OBJS += passes/hierarchy/hierarchy.o OBJS += passes/cmds/select.o OBJS += passes/cmds/show.o OBJS += passes/cmds/stat.o -OBJS += passes/cmds/cover.o OBJS += passes/cmds/design.o OBJS += passes/cmds/plugin.o @@ -730,20 +748,31 @@ top-all: $(TARGETS) $(EXTRA_TARGETS) @echo " Build successful." @echo "" -ifeq ($(CONFIG),emcc) -yosys.js: $(filter-out yosysjs-$(YOSYS_VER).zip,$(EXTRA_TARGETS)) -endif +.PHONY: compile-only +compile-only: $(OBJS) $(GENFILES) $(EXTRA_TARGETS) + @echo "" + @echo " Compile successful." + @echo "" + +.PHONY: share +share: $(EXTRA_TARGETS) + @echo "" + @echo " Share directory created." + @echo "" $(PROGRAM_PREFIX)yosys$(EXE): $(OBJS) - $(P) $(CXX) -o $(PROGRAM_PREFIX)yosys$(EXE) $(EXE_LINKFLAGS) $(LINKFLAGS) $(OBJS) $(LIBS) $(LIBS_VERIFIC) + $(P) $(CXX) -o $(PROGRAM_PREFIX)yosys$(EXE) $(EXE_LINKFLAGS) $(LINKFLAGS) $(OBJS) $(EXE_LIBS) $(LIBS) $(LIBS_VERIFIC) libyosys.so: $(filter-out kernel/driver.o,$(OBJS)) ifeq ($(OS), Darwin) - $(P) $(CXX) -o libyosys.so -shared -Wl,-install_name,$(LIBDIR)/libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) + $(P) $(CXX) -o libyosys.so -shared -undefined dynamic_lookup -Wl,-install_name,libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) else - $(P) $(CXX) -o libyosys.so -shared -Wl,-soname,$(LIBDIR)/libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) + $(P) $(CXX) -o libyosys.so -shared -Wl,-soname,libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) endif +libyosys.a: $(filter-out kernel/driver.o,$(OBJS)) + $(P) $(AR) rcs $@ $^ + %.o: %.cc $(Q) mkdir -p $(dir $@) $(P) $(CXX) -o $@ -c $(CPPFLAGS) $(CXXFLAGS) $< @@ -753,21 +782,22 @@ endif $(P) cat $< | grep -E -v "#[ ]*(include|error)" | $(CXX) $(CXXFLAGS) -x c++ -o $@ -E -P - ifeq ($(ENABLE_PYOSYS),1) -$(PY_WRAPPER_FILE).cc: misc/$(PY_GEN_SCRIPT).py $(PY_WRAP_INCLUDES) +$(PY_WRAPPER_FILE).cc: $(PY_GEN_SCRIPT) pyosys/wrappers_tpl.cc $(PY_WRAP_INCLUDES) pyosys/hashlib.h $(Q) mkdir -p $(dir $@) - $(P) python$(PYTHON_VERSION) -c "from misc import $(PY_GEN_SCRIPT); $(PY_GEN_SCRIPT).gen_wrappers(\"$(PY_WRAPPER_FILE).cc\")" + $(P) $(UV_ENV) $(PYTHON_EXECUTABLE) $(PY_GEN_SCRIPT) $(PY_WRAPPER_FILE).cc endif %.o: %.cpp $(Q) mkdir -p $(dir $@) $(P) $(CXX) -o $@ -c $(CPPFLAGS) $(CXXFLAGS) $< -YOSYS_VER_STR := Yosys $(YOSYS_VER) (git sha1 $(GIT_REV), $(notdir $(CXX)) $(shell \ - $(CXX) --version | tr ' ()' '\n' | grep '^[0-9]' | head -n1) $(filter -f% -m% -O% -DNDEBUG,$(CXXFLAGS))) +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 := Yosys $(YOSYS_VER) (git sha1 $(YOSYS_GIT_STR), $(YOSYS_COMPILER)) kernel/version_$(GIT_REV).cc: $(YOSYS_SRC)/Makefile $(P) rm -f kernel/version_*.o kernel/version_*.d kernel/version_*.cc - $(Q) mkdir -p kernel && echo "namespace Yosys { extern const char *yosys_version_str; const char *yosys_version_str=\"$(YOSYS_VER_STR)\"; }" > kernel/version_$(GIT_REV).cc + $(Q) mkdir -p kernel && echo "namespace Yosys { extern const char *yosys_version_str; const char *yosys_version_str=\"$(YOSYS_VER_STR)\"; const char *yosys_git_hash_str=\"$(YOSYS_GIT_STR)\"; }" > kernel/version_$(GIT_REV).cc ifeq ($(ENABLE_VERIFIC),1) CXXFLAGS_NOVERIFIC = $(foreach v,$(CXXFLAGS),$(if $(findstring $(VERIFIC_DIR),$(v)),,$(v))) @@ -777,47 +807,71 @@ CXXFLAGS_NOVERIFIC = $(CXXFLAGS) LIBS_NOVERIFIC = $(LIBS) endif -$(PROGRAM_PREFIX)yosys-config: misc/yosys-config.in +$(PROGRAM_PREFIX)yosys-config: misc/yosys-config.in $(YOSYS_SRC)/Makefile $(P) $(SED) -e 's#@CXXFLAGS@#$(subst -Ilibs/dlfcn-win32,,$(subst -I. -I"$(YOSYS_SRC)",-I"$(DATDIR)/include",$(strip $(CXXFLAGS_NOVERIFIC))))#;' \ -e 's#@CXX@#$(strip $(CXX))#;' -e 's#@LINKFLAGS@#$(strip $(LINKFLAGS) $(PLUGIN_LINKFLAGS))#;' -e 's#@LIBS@#$(strip $(LIBS_NOVERIFIC) $(PLUGIN_LIBS))#;' \ -e 's#@BINDIR@#$(strip $(BINDIR))#;' -e 's#@DATDIR@#$(strip $(DATDIR))#;' < $< > $(PROGRAM_PREFIX)yosys-config $(Q) chmod +x $(PROGRAM_PREFIX)yosys-config -abc/abc-$(ABCREV)$(EXE) abc/libabc-$(ABCREV).a: +.PHONY: check-git-abc + +check-git-abc: + @if [ ! -d "$(YOSYS_SRC)/abc" ] && git -C "$(YOSYS_SRC)" status >/dev/null 2>&1; then \ + echo "Error: The 'abc' directory does not exist."; \ + echo "Initialize the submodule: Run 'git submodule update --init' to set up 'abc' as a submodule."; \ + exit 1; \ + elif git -C "$(YOSYS_SRC)" submodule status abc 2>/dev/null | grep -q '^ '; then \ + exit 0; \ + elif [ -f "$(YOSYS_SRC)/abc/.gitcommit" ] && ! grep -q '\$$Format:%[hH]\$$' "$(YOSYS_SRC)/abc/.gitcommit"; then \ + echo "'abc' comes from a tarball. Continuing."; \ + exit 0; \ + elif git -C "$(YOSYS_SRC)" submodule status abc 2>/dev/null | grep -q '^+'; then \ + echo "'abc' submodule does not match expected commit."; \ + echo "Run 'git submodule update' to check out the correct version."; \ + echo "Note: If testing a different version of abc, call 'git commit abc' in the Yosys source directory to update the expected commit."; \ + exit 1; \ + elif git -C "$(YOSYS_SRC)" submodule status abc 2>/dev/null | grep -q '^U'; then \ + echo "'abc' submodule has merge conflicts."; \ + echo "Please resolve merge conflicts before continuing."; \ + exit 1; \ + elif [ -f "$(YOSYS_SRC)/abc/.gitcommit" ] && grep -q '\$$Format:%[hH]\$$' "$(YOSYS_SRC)/abc/.gitcommit"; then \ + echo "Error: 'abc' is not configured as a git submodule."; \ + echo "To resolve this:"; \ + echo "1. Back up your changes: Save any modifications from the 'abc' directory to another location."; \ + echo "2. Remove the existing 'abc' directory: Delete the 'abc' directory and all its contents."; \ + echo "3. Initialize the submodule: Run 'git submodule update --init' to set up 'abc' as a submodule."; \ + echo "4. Reapply your changes: Move your saved changes back to the 'abc' directory, if necessary."; \ + exit 1; \ + elif ! git -C "$(YOSYS_SRC)" status >/dev/null 2>&1; then \ + echo "$(realpath $(YOSYS_SRC)) is not configured as a git repository, and 'abc' folder is missing."; \ + echo "If you already have ABC, set 'ABCEXTERNAL' make variable to point to ABC executable."; \ + echo "Otherwise, download release archive 'yosys.tar.gz' from https://github.com/YosysHQ/yosys/releases."; \ + echo " ('Source code' archive does not contain submodules.)"; \ + exit 1; \ + else \ + echo "Initialize the submodule: Run 'git submodule update --init' to set up 'abc' as a submodule."; \ + exit 1; \ + fi + +.git-abc-submodule-hash: FORCE + @new=$$(cd abc 2>/dev/null && git rev-parse HEAD 2>/dev/null || echo none); \ + old=$$(cat .git-abc-submodule-hash 2>/dev/null || echo none); \ + if [ "$$new" != "$$old" ]; then \ + echo "$$new" > .git-abc-submodule-hash; \ + fi + +abc/abc$(EXE) abc/libabc.a: .git-abc-submodule-hash | check-git-abc + @if [ "$$(cd abc 2>/dev/null && git rev-parse HEAD 2>/dev/null)" != "$$(cat ../.git-abc-submodule-hash 2>/dev/null || echo none)" ]; then \ + rm -f abc/abc$(EXE); \ + fi $(P) -ifneq ($(ABCREV),default) - $(Q) if test -d abc/.hg; then \ - echo 'REEBE: NOP qverpgbel vf n ut jbexvat pbcl! Erzbir nop/ naq er-eha "znxr".' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; false; \ - fi - $(Q) if test -d abc && test -d abc/.git && ! git -C abc diff-index --quiet HEAD; then \ - echo 'REEBE: NOP pbagnvaf ybpny zbqvsvpngvbaf! Frg NOPERI=qrsnhyg va Lbflf Znxrsvyr!' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; false; \ - fi - $(Q) if test -d abc && ! test -d abc/.git && ! test "`cat abc/.gitcommit | cut -c1-7`" = "$(ABCREV)"; then \ - echo 'REEBE: Qbjaybnqrq NOP irefvbaf qbrf abg zngpu! Qbjaybnq sebz:' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; echo $(ABCURL)/archive/$(ABCREV).tar.gz; false; \ - fi -# set a variable so the test fails if git fails to run - when comparing outputs directly, empty string would match empty string - $(Q) if test -d abc && ! test -d abc/.git && test "`cat abc/.gitcommit | cut -c1-7`" = "$(ABCREV)"; then \ - echo "Compiling local copy of ABC"; \ - elif ! (cd abc 2> /dev/null && rev="`git rev-parse $(ABCREV)`" && test "`git rev-parse HEAD`" = "$$rev"); then \ - test $(ABCPULL) -ne 0 || { echo 'REEBE: NOP abg hc gb qngr naq NOPCHYY frg gb 0 va Znxrsvyr!' | tr 'A-Za-z' 'N-ZA-Mn-za-m'; exit 1; }; \ - echo "Pulling ABC from $(ABCURL):"; set -x; \ - test -d abc || git clone $(ABCURL) abc; \ - cd abc && $(MAKE) DEP= clean && git fetch $(ABCURL) && git checkout $(ABCREV); \ - fi -endif - $(Q) rm -f abc/abc-[0-9a-f]* - $(Q) $(MAKE) -C abc $(S) $(ABCMKARGS) $(if $(filter %.a,$@),PROG="abc-$(ABCREV)",PROG="abc-$(ABCREV)$(EXE)") MSG_PREFIX="$(eval P_OFFSET = 5)$(call P_SHOW)$(eval P_OFFSET = 10) ABC: " $(if $(filter %.a,$@),libabc-$(ABCREV).a) + $(Q) mkdir -p abc && $(MAKE) -C $(PROGRAM_PREFIX)abc -f "$(realpath $(YOSYS_SRC)/abc/Makefile)" ABCSRC="$(realpath $(YOSYS_SRC)/abc/)" $(S) $(ABCMKARGS) $(if $(filter %.a,$@),PROG="abc",PROG="abc$(EXE)") MSG_PREFIX="$(eval P_OFFSET = 5)$(call P_SHOW)$(eval P_OFFSET = 10) ABC: " $(if $(filter %.a,$@),libabc.a) -ifeq ($(ABCREV),default) -.PHONY: abc/abc-$(ABCREV)$(EXE) -.PHONY: abc/libabc-$(ABCREV).a -endif +$(PROGRAM_PREFIX)yosys-abc$(EXE): abc/abc$(EXE) + $(P) cp $< $(PROGRAM_PREFIX)yosys-abc$(EXE) -$(PROGRAM_PREFIX)yosys-abc$(EXE): abc/abc-$(ABCREV)$(EXE) - $(P) cp abc/abc-$(ABCREV)$(EXE) $(PROGRAM_PREFIX)yosys-abc$(EXE) - -$(PROGRAM_PREFIX)yosys-libabc.a: abc/libabc-$(ABCREV).a - $(P) cp abc/libabc-$(ABCREV).a $(PROGRAM_PREFIX)yosys-libabc.a +$(PROGRAM_PREFIX)yosys-libabc.a: abc/libabc.a + $(P) cp $< $(PROGRAM_PREFIX)yosys-libabc.a ifneq ($(SEED),) SEEDOPT="-S $(SEED)" @@ -831,66 +885,106 @@ else ABCOPT="" endif -# When YOSYS_NOVERIFIC is set as a make variable, also export it to the -# enviornment, so that `YOSYS_NOVERIFIC=1 make test` _and_ -# `make test YOSYS_NOVERIFIC=1` will run with verific disabled. -ifeq ($(YOSYS_NOVERIFIC),1) -export YOSYS_NOVERIFIC +# Tests that generate .mk with tests/gen-tests-makefile.sh +MK_TEST_DIRS = +MK_TEST_DIRS += tests/arch/anlogic +MK_TEST_DIRS += tests/arch/ecp5 +MK_TEST_DIRS += tests/arch/efinix +MK_TEST_DIRS += tests/arch/gatemate +MK_TEST_DIRS += tests/arch/gowin +MK_TEST_DIRS += tests/arch/ice40 +MK_TEST_DIRS += tests/arch/intel_alm +MK_TEST_DIRS += tests/arch/machxo2 +MK_TEST_DIRS += tests/arch/microchip +MK_TEST_DIRS += tests/arch/nanoxplore +MK_TEST_DIRS += tests/arch/nexus +MK_TEST_DIRS += tests/arch/quicklogic/pp3 +MK_TEST_DIRS += tests/arch/quicklogic/qlf_k6n10f +MK_TEST_DIRS += tests/arch/xilinx +MK_TEST_DIRS += tests/bugpoint +MK_TEST_DIRS += tests/opt +MK_TEST_DIRS += tests/sat +MK_TEST_DIRS += tests/sdc +MK_TEST_DIRS += tests/sim +MK_TEST_DIRS += tests/svtypes +MK_TEST_DIRS += tests/techmap +MK_TEST_DIRS += tests/various +MK_TEST_DIRS += tests/rtlil +ifeq ($(ENABLE_VERIFIC),1) +ifneq ($(YOSYS_NOVERIFIC),1) +MK_TEST_DIRS += tests/verific +endif +endif +MK_TEST_DIRS += tests/verilog + +# Tests that don't generate .mk +SH_TEST_DIRS = +SH_TEST_DIRS += tests/simple +SH_TEST_DIRS += tests/simple_abc9 +SH_TEST_DIRS += tests/hana +SH_TEST_DIRS += tests/asicworld +# SH_TEST_DIRS += tests/realmath +SH_TEST_DIRS += tests/share +SH_TEST_DIRS += tests/opt_share +SH_TEST_DIRS += tests/fsm +SH_TEST_DIRS += tests/memlib +SH_TEST_DIRS += tests/bram +SH_TEST_DIRS += tests/svinterfaces +SH_TEST_DIRS += tests/xprop +SH_TEST_DIRS += tests/select +SH_TEST_DIRS += tests/peepopt +SH_TEST_DIRS += tests/proc +SH_TEST_DIRS += tests/blif +SH_TEST_DIRS += tests/arch +SH_TEST_DIRS += tests/rpc +SH_TEST_DIRS += tests/memfile +SH_TEST_DIRS += tests/fmt +SH_TEST_DIRS += tests/cxxrtl +SH_TEST_DIRS += tests/liberty +ifeq ($(ENABLE_FUNCTIONAL_TESTS),1) +SH_TEST_DIRS += tests/functional endif -test: $(TARGETS) $(EXTRA_TARGETS) -ifeq ($(ENABLE_VERIFIC),1) -ifeq ($(YOSYS_NOVERIFIC),1) - @echo - @echo "Running tests without verific support due to YOSYS_NOVERIFIC=1" - @echo -else - +cd tests/verific && bash run-test.sh $(SEEDOPT) -endif -endif - +cd tests/simple && bash run-test.sh $(SEEDOPT) - +cd tests/simple_abc9 && bash run-test.sh $(SEEDOPT) - +cd tests/hana && bash run-test.sh $(SEEDOPT) - +cd tests/asicworld && bash run-test.sh $(SEEDOPT) - # +cd tests/realmath && bash run-test.sh $(SEEDOPT) - +cd tests/share && bash run-test.sh $(SEEDOPT) - +cd tests/opt_share && bash run-test.sh $(SEEDOPT) - +cd tests/fsm && bash run-test.sh $(SEEDOPT) - +cd tests/techmap && bash run-test.sh - +cd tests/memories && bash run-test.sh $(ABCOPT) $(SEEDOPT) - +cd tests/memlib && bash run-test.sh $(SEEDOPT) - +cd tests/bram && bash run-test.sh $(SEEDOPT) - +cd tests/various && bash run-test.sh - +cd tests/select && bash run-test.sh - +cd tests/sat && bash run-test.sh - +cd tests/sim && bash run-test.sh - +cd tests/svinterfaces && bash run-test.sh $(SEEDOPT) - +cd tests/svtypes && bash run-test.sh $(SEEDOPT) - +cd tests/proc && bash run-test.sh - +cd tests/blif && bash run-test.sh - +cd tests/opt && bash run-test.sh - +cd tests/aiger && bash run-test.sh $(ABCOPT) - +cd tests/arch && bash run-test.sh - +cd tests/arch/ice40 && bash run-test.sh $(SEEDOPT) - +cd tests/arch/xilinx && bash run-test.sh $(SEEDOPT) - +cd tests/arch/ecp5 && bash run-test.sh $(SEEDOPT) - +cd tests/arch/machxo2 && bash run-test.sh $(SEEDOPT) - +cd tests/arch/efinix && bash run-test.sh $(SEEDOPT) - +cd tests/arch/anlogic && bash run-test.sh $(SEEDOPT) - +cd tests/arch/gowin && bash run-test.sh $(SEEDOPT) - +cd tests/arch/intel_alm && bash run-test.sh $(SEEDOPT) - +cd tests/arch/nexus && bash run-test.sh $(SEEDOPT) - +cd tests/arch/quicklogic/pp3 && bash run-test.sh $(SEEDOPT) - +cd tests/arch/quicklogic/qlf_k6n10f && bash run-test.sh $(SEEDOPT) - +cd tests/arch/gatemate && bash run-test.sh $(SEEDOPT) - +cd tests/rpc && bash run-test.sh - +cd tests/memfile && bash run-test.sh - +cd tests/verilog && bash run-test.sh - +cd tests/xprop && bash run-test.sh $(SEEDOPT) - +cd tests/fmt && bash run-test.sh - +cd tests/cxxrtl && bash run-test.sh +# Tests that don't generate .mk and need special args +SH_ABC_TEST_DIRS = +SH_ABC_TEST_DIRS += tests/memories +SH_ABC_TEST_DIRS += tests/aiger +SH_ABC_TEST_DIRS += tests/alumacc + +# seed-tests/ is a dummy string, not a directory +.PHONY: seed-tests +seed-tests: $(SH_TEST_DIRS:%=seed-tests/%) +.PHONY: seed-tests/% +seed-tests/%: %/run-test.sh $(TARGETS) $(EXTRA_TARGETS) + +cd $* && bash run-test.sh $(SEEDOPT) + +@echo "...passed tests in $*" + +# abcopt-tests/ is a dummy string, not a directory +.PHONY: abcopt-tests +abcopt-tests: $(SH_ABC_TEST_DIRS:%=abcopt-tests/%) +abcopt-tests/%: %/run-test.sh $(TARGETS) $(EXTRA_TARGETS) + +cd $* && bash run-test.sh $(ABCOPT) $(SEEDOPT) + +@echo "...passed tests in $*" + +# makefile-tests/ is a dummy string, not a directory +.PHONY: makefile-tests +makefile-tests: $(MK_TEST_DIRS:%=makefile-tests/%) +# this target actually emits .mk files +%.mk: + +cd $(dir $*) && bash run-test.sh +# this one spawns submake on each +makefile-tests/%: %/run-test.mk $(TARGETS) $(EXTRA_TARGETS) + $(MAKE) -C $* -f run-test.mk + +@echo "...passed tests in $*" + +test: makefile-tests abcopt-tests seed-tests @echo "" @echo " Passed \"make test\"." +ifeq ($(ENABLE_VERIFIC),1) +ifeq ($(YOSYS_NOVERIFIC),1) + @echo " Ran tests without verific support due to YOSYS_NOVERIFIC=1." +endif +endif @echo "" VALGRIND ?= valgrind --error-exitcode=1 --leak-check=full --show-reachable=yes --errors-for-leak-kinds=all @@ -917,34 +1011,49 @@ ystests: $(TARGETS) $(EXTRA_TARGETS) # Unit test unit-test: libyosys.so - @$(MAKE) -C $(UNITESTPATH) CXX="$(CXX)" CPPFLAGS="$(CPPFLAGS)" \ - CXXFLAGS="$(CXXFLAGS)" LIBS="$(LIBS)" ROOTPATH="$(CURDIR)" + @$(MAKE) -C $(UNITESTPATH) CXX="$(CXX)" CC="$(CC)" CPPFLAGS="$(CPPFLAGS)" \ + CXXFLAGS="$(CXXFLAGS)" LINKFLAGS="$(LINKFLAGS)" LIBS="$(LIBS)" ROOTPATH="$(CURDIR)" clean-unit-test: @$(MAKE) -C $(UNITESTPATH) clean +install-dev: $(PROGRAM_PREFIX)yosys-config share + $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(BINDIR) + $(INSTALL_SUDO) cp $(PROGRAM_PREFIX)yosys-config $(DESTDIR)$(BINDIR) + $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(DATDIR) + $(INSTALL_SUDO) cp -r share/. $(DESTDIR)$(DATDIR)/. + install: $(TARGETS) $(EXTRA_TARGETS) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(BINDIR) - $(INSTALL_SUDO) cp $(filter-out libyosys.so,$(TARGETS)) $(DESTDIR)$(BINDIR) + $(INSTALL_SUDO) cp $(filter-out libyosys.so libyosys.a,$(TARGETS)) $(DESTDIR)$(BINDIR) ifneq ($(filter $(PROGRAM_PREFIX)yosys,$(TARGETS)),) - $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys + if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys; fi endif ifneq ($(filter $(PROGRAM_PREFIX)yosys-abc,$(TARGETS)),) - $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-abc + if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-abc; fi endif ifneq ($(filter $(PROGRAM_PREFIX)yosys-filterlib,$(TARGETS)),) - $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-filterlib + if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-filterlib; fi endif $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(DATDIR) $(INSTALL_SUDO) cp -r share/. $(DESTDIR)$(DATDIR)/. ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(LIBDIR) $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(LIBDIR)/ - $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(LIBDIR)/libyosys.so + if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(LIBDIR)/libyosys.so; fi +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) + $(INSTALL_SUDO) cp libyosys.a $(DESTDIR)$(LIBDIR)/ +endif ifeq ($(ENABLE_PYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys + $(INSTALL_SUDO) cp $(YOSYS_SRC)/pyosys/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so - $(INSTALL_SUDO) cp misc/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/ + $(INSTALL_SUDO) cp -r share $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys +ifeq ($(ENABLE_ABC),1) +ifeq ($(ABCEXTERNAL),) + $(INSTALL_SUDO) cp $(PROGRAM_PREFIX)yosys-abc$(EXE) $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/yosys-abc$(EXE) +endif +endif endif endif ifeq ($(ENABLE_PLUGINS),1) @@ -959,6 +1068,9 @@ uninstall: $(INSTALL_SUDO) rm -rvf $(DESTDIR)$(DATDIR) ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) rm -vf $(DESTDIR)$(LIBDIR)/libyosys.so +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) + $(INSTALL_SUDO) rm -vf $(DESTDIR)$(LIBDIR)/libyosys.a +endif ifeq ($(ENABLE_PYOSYS),1) $(INSTALL_SUDO) rm -vf $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so $(INSTALL_SUDO) rm -vf $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py @@ -966,28 +1078,77 @@ ifeq ($(ENABLE_PYOSYS),1) endif endif -# also others, but so long as it doesn't fail this is enough to know we tried -docs/source/cmd/abc.rst: $(TARGETS) $(EXTRA_TARGETS) - mkdir -p docs/source/cmd - ./$(PROGRAM_PREFIX)yosys -p 'help -write-rst-command-reference-manual' +docs/source/generated/cmds.json: docs/source/generated $(TARGETS) $(EXTRA_TARGETS) + $(Q) ./$(PROGRAM_PREFIX)yosys -p 'help -dump-cmds-json $@' -PHONY: docs/gen_images docs/guidelines -docs/gen_images: - $(Q) $(MAKE) -C docs/images all +docs/source/generated/cells.json: docs/source/generated $(TARGETS) $(EXTRA_TARGETS) + $(Q) ./$(PROGRAM_PREFIX)yosys -p 'help -dump-cells-json $@' -DOCS_GUIDELINE_FILES := GettingStarted CodingStyle -docs/guidelines: - $(Q) mkdir -p docs/source/temp - $(Q) cp -f $(addprefix guidelines/,$(DOCS_GUIDELINE_FILES)) docs/source/temp +docs/source/generated/%.cc: backends/%.cc + $(Q) mkdir -p $(@D) + $(Q) cp $< $@ + +# diff returns exit code 1 if the files are different, but it's not an error +docs/source/generated/functional/rosette.diff: backends/functional/smtlib.cc backends/functional/smtlib_rosette.cc + $(Q) mkdir -p $(@D) + $(Q) diff -U 20 $^ > $@ || exit 0 + +PHONY: docs/gen/functional_ir +docs/gen/functional_ir: docs/source/generated/functional/smtlib.cc docs/source/generated/functional/rosette.diff + +docs/source/generated/%.log: docs/source/generated $(TARGETS) $(EXTRA_TARGETS) + $(Q) ./$(PROGRAM_PREFIX)yosys -qQT -h '$*' -l $@ + +docs/source/generated/chformal.cc: passes/cmds/chformal.cc docs/source/generated + $(Q) cp $< $@ + +PHONY: docs/gen/chformal +docs/gen/chformal: docs/source/generated/chformal.log docs/source/generated/chformal.cc + +PHONY: docs/gen docs/usage docs/reqs +docs/gen: $(TARGETS) + $(Q) $(MAKE) -C docs gen + +docs/source/generated: + $(Q) mkdir -p docs/source/generated + +# some commands return an error and print the usage text to stderr +define DOC_USAGE_STDERR +docs/source/generated/$(1): $(TARGETS) docs/source/generated FORCE + -$(Q) ./$(PROGRAM_PREFIX)$(1) --help 2> $$@ +endef +DOCS_USAGE_STDERR := yosys-filterlib + +# The in-tree ABC (yosys-abc) is only built when ABCEXTERNAL is not set. +ifeq ($(ABCEXTERNAL),) +DOCS_USAGE_STDERR += yosys-abc +endif + +$(foreach usage,$(DOCS_USAGE_STDERR),$(eval $(call DOC_USAGE_STDERR,$(usage)))) + +# others print to stdout +define DOC_USAGE_STDOUT +docs/source/generated/$(1): $(TARGETS) docs/source/generated + $(Q) ./$(PROGRAM_PREFIX)$(1) --help > $$@ || rm $$@ +endef +DOCS_USAGE_STDOUT := yosys yosys-smtbmc yosys-witness yosys-config +$(foreach usage,$(DOCS_USAGE_STDOUT),$(eval $(call DOC_USAGE_STDOUT,$(usage)))) + +docs/usage: $(addprefix docs/source/generated/,$(DOCS_USAGE_STDOUT) $(DOCS_USAGE_STDERR)) + +docs/reqs: + $(Q) $(MAKE) -C docs reqs + +.PHONY: docs/prep +docs/prep: docs/source/generated/cells.json docs/source/generated/cmds.json docs/gen docs/usage docs/gen/functional_ir docs/gen/chformal DOC_TARGET ?= html -docs: docs/source/cmd/abc.rst docs/gen_images docs/guidelines +docs: docs/prep $(Q) $(MAKE) -C docs $(DOC_TARGET) -clean: +clean: clean-py clean-unit-test rm -rf share - rm -rf kernel/*.pyh - rm -f $(OBJS) $(GENFILES) $(TARGETS) $(EXTRA_TARGETS) $(EXTRA_OBJS) $(PY_WRAP_INCLUDES) $(PY_WRAPPER_FILE).cc + rm -f $(OBJS) $(GENFILES) $(TARGETS) $(EXTRA_TARGETS) $(EXTRA_OBJS) rm -f kernel/version_*.o kernel/version_*.cc rm -f libs/*/*.d frontends/*/*.d passes/*/*.d backends/*/*.d kernel/*.d techlibs/*/*.d rm -rf tests/asicworld/*.out tests/asicworld/*.log @@ -999,13 +1160,21 @@ clean: rm -rf vloghtb/Makefile vloghtb/refdat vloghtb/rtl vloghtb/scripts vloghtb/spec vloghtb/check_yosys vloghtb/vloghammer_tb.tar.bz2 vloghtb/temp vloghtb/log_test_* rm -f tests/svinterfaces/*.log_stdout tests/svinterfaces/*.log_stderr tests/svinterfaces/dut_result.txt tests/svinterfaces/reference_result.txt tests/svinterfaces/a.out tests/svinterfaces/*_syn.v tests/svinterfaces/*.diff rm -f tests/tools/cmp_tbdata - $(MAKE) -C docs clean - $(MAKE) -C docs/images clean - rm -rf docs/source/cmd docs/util/__pycache__ + rm -f $(addsuffix /run-test.mk,$(MK_TEST_DIRS)) + -$(MAKE) -C $(YOSYS_SRC)/docs clean + rm -rf docs/util/__pycache__ + rm -f libyosys.so + +clean-py: + rm -f $(PY_WRAPPER_FILE).inc.cc $(PY_WRAPPER_FILE).cc + rm -f $(PYTHON_OBJECTS) + rm -f *.whl + rm -f libyosys.so libyosys.a + rm -rf kernel/*.pyh clean-abc: - $(MAKE) -C abc DEP= clean - rm -f $(PROGRAM_PREFIX)yosys-abc$(EXE) $(PROGRAM_PREFIX)yosys-libabc.a abc/abc-[0-9a-f]* abc/libabc-[0-9a-f]*.a + $(MAKE) -C $(YOSYS_SRC)/abc DEP= clean + rm -f $(PROGRAM_PREFIX)yosys-abc$(EXE) $(PROGRAM_PREFIX)yosys-libabc.a abc/abc-[0-9a-f]* abc/libabc-[0-9a-f]*.a .git-abc-submodule-hash mrproper: clean git clean -xdf @@ -1016,36 +1185,37 @@ coverage: lcov --capture -d . --no-external -o coverage.info genhtml coverage.info --output-directory coverage_html +clean_coverage: + find . -name "*.gcda" -type f -delete + +FUNC_KERNEL := functional.cc functional.h sexpr.cc sexpr.h compute_graph.h +FUNC_INCLUDES := $(addprefix --include *,functional/* $(FUNC_KERNEL)) +coverage_functional: + rm -rf coverage.info coverage_html + lcov --capture -d backends/functional -d kernel $(FUNC_INCLUDES) --no-external -o coverage.info + genhtml coverage.info --output-directory coverage_html + qtcreator: + echo "$(CXXFLAGS)" | grep -o '\-D[^ ]*' | tr ' ' '\n' | sed 's/-D/#define /' | sed 's/=/ /'> qtcreator.config { for file in $(basename $(OBJS)); do \ for prefix in cc y l; do if [ -f $${file}.$${prefix} ]; then echo $$file.$${prefix}; fi; done \ done; find backends frontends kernel libs passes -type f \( -name '*.h' -o -name '*.hh' \); } > qtcreator.files { echo .; find backends frontends kernel libs passes -type f \( -name '*.h' -o -name '*.hh' \) -printf '%h\n' | sort -u; } > qtcreator.includes - touch qtcreator.config qtcreator.creator + touch qtcreator.creator -vcxsrc: $(GENFILES) $(EXTRA_TARGETS) - rm -rf yosys-win32-vcxsrc-$(YOSYS_VER){,.zip} +VCX_DIR_NAME := yosys-win32-vcxsrc-$(YOSYS_VER) +vcxsrc: $(GENFILES) $(EXTRA_TARGETS) kernel/version_$(GIT_REV).cc + rm -rf $(VCX_DIR_NAME){,.zip} + cp -f kernel/version_$(GIT_REV).cc kernel/version.cc set -e; for f in `ls $(filter %.cc %.cpp,$(GENFILES)) $(addsuffix .cc,$(basename $(OBJS))) $(addsuffix .cpp,$(basename $(OBJS))) 2> /dev/null`; do \ - echo "Analyse: $$f" >&2; cpp -std=c++11 -MM -I. -D_YOSYS_ $$f; done | sed 's,.*:,,; s,//*,/,g; s,/[^/]*/\.\./,/,g; y, \\,\n\n,;' | grep '^[^/]' | sort -u | grep -v kernel/version_ > srcfiles.txt - bash misc/create_vcxsrc.sh yosys-win32-vcxsrc $(YOSYS_VER) $(GIT_REV) - echo "namespace Yosys { extern const char *yosys_version_str; const char *yosys_version_str=\"Yosys (Version Information Unavailable)\"; }" > kernel/version.cc - zip yosys-win32-vcxsrc-$(YOSYS_VER)/genfiles.zip $(GENFILES) kernel/version.cc - zip -r yosys-win32-vcxsrc-$(YOSYS_VER).zip yosys-win32-vcxsrc-$(YOSYS_VER)/ + echo "Analyse: $$f" >&2; cpp -std=c++17 -MM -I. -D_YOSYS_ $$f; done | sed 's,.*:,,; s,//*,/,g; s,/[^/]*/\.\./,/,g; y, \\,\n\n,;' | grep '^[^/]' | sort -u | grep -v kernel/version_ > srcfiles.txt + echo "libs/fst/fst_win_unistd.h" >> srcfiles.txt + echo "kernel/version.cc" >> srcfiles.txt + bash misc/create_vcxsrc.sh $(VCX_DIR_NAME) $(YOSYS_VER) + zip $(VCX_DIR_NAME)/genfiles.zip $(GENFILES) kernel/version.cc + zip -r $(VCX_DIR_NAME).zip $(VCX_DIR_NAME)/ rm -f srcfiles.txt kernel/version.cc -ifeq ($(CONFIG),mxe) -mxebin: $(TARGETS) $(EXTRA_TARGETS) - rm -rf yosys-win32-mxebin-$(YOSYS_VER){,.zip} - mkdir -p yosys-win32-mxebin-$(YOSYS_VER) - cp -r $(PROGRAM_PREFIX)yosys.exe share/ yosys-win32-mxebin-$(YOSYS_VER)/ -ifeq ($(ENABLE_ABC),1) - cp -r $(PROGRAM_PREFIX)yosys-abc.exe abc/lib/x86/pthreadVC2.dll yosys-win32-mxebin-$(YOSYS_VER)/ -endif - echo -en 'This is Yosys $(YOSYS_VER) for Win32.\r\n' > yosys-win32-mxebin-$(YOSYS_VER)/readme.txt - echo -en 'Documentation at https://yosyshq.net/yosys/.\r\n' >> yosys-win32-mxebin-$(YOSYS_VER)/readme.txt - zip -r yosys-win32-mxebin-$(YOSYS_VER).zip yosys-win32-mxebin-$(YOSYS_VER)/ -endif - config-clean: clean rm -f Makefile.conf @@ -1061,17 +1231,6 @@ config-gcc-static: clean echo 'ENABLE_READLINE := 0' >> Makefile.conf echo 'ENABLE_TCL := 0' >> Makefile.conf -config-afl-gcc: clean - echo 'CONFIG := afl-gcc' > Makefile.conf - -config-emcc: clean - echo 'CONFIG := emcc' > Makefile.conf - echo 'ENABLE_TCL := 0' >> Makefile.conf - echo 'ENABLE_ABC := 0' >> Makefile.conf - echo 'ENABLE_PLUGINS := 0' >> Makefile.conf - echo 'ENABLE_READLINE := 0' >> Makefile.conf - echo 'ENABLE_ZLIB := 0' >> Makefile.conf - config-wasi: clean echo 'CONFIG := wasi' > Makefile.conf echo 'ENABLE_TCL := 0' >> Makefile.conf @@ -1080,10 +1239,6 @@ config-wasi: clean echo 'ENABLE_READLINE := 0' >> Makefile.conf echo 'ENABLE_ZLIB := 0' >> Makefile.conf -config-mxe: clean - echo 'CONFIG := mxe' > Makefile.conf - echo 'ENABLE_PLUGINS := 0' >> Makefile.conf - config-msys2-32: clean echo 'CONFIG := msys2-32' > Makefile.conf echo "PREFIX := $(MINGW_PREFIX)" >> Makefile.conf @@ -1092,9 +1247,6 @@ config-msys2-64: clean echo 'CONFIG := msys2-64' > Makefile.conf echo "PREFIX := $(MINGW_PREFIX)" >> Makefile.conf -config-cygwin: clean - echo 'CONFIG := cygwin' > Makefile.conf - config-gcov: clean echo 'CONFIG := gcc' > Makefile.conf echo 'ENABLE_GCOV := 1' >> Makefile.conf @@ -1113,8 +1265,8 @@ echo-yosys-ver: echo-git-rev: @echo "$(GIT_REV)" -echo-abc-rev: - @echo "$(ABCREV)" +echo-cxx: + @echo "$(CXX)" -include libs/*/*.d -include frontends/*/*.d @@ -1123,5 +1275,7 @@ echo-abc-rev: -include kernel/*.d -include techlibs/*/*.d -.PHONY: all top-all abc test install install-abc docs clean mrproper qtcreator coverage vcxsrc mxebin -.PHONY: config-clean config-clang config-gcc config-gcc-static config-afl-gcc config-gprof config-sudo +FORCE: + +.PHONY: all top-all abc test install-dev install install-abc docs clean mrproper qtcreator coverage vcxsrc +.PHONY: config-clean config-clang config-gcc config-gcc-static config-gprof config-sudo diff --git a/README.md b/README.md index 660bd5c6d..427d59c9e 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,3 @@ -``` -yosys -- Yosys Open SYnthesis Suite - -Copyright (C) 2012 - 2024 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. -``` - - yosys – Yosys Open SYnthesis Suite =================================== @@ -33,6 +14,10 @@ Yosys is free software licensed under the ISC license (a GPL compatible license that is similar in terms to the MIT license or the 2-clause BSD license). +Third-party software distributed alongside this software +is licensed under compatible licenses. +Please refer to `abc` and `libs` subdirectories for their license terms. + Web Site and Other Resources ============================ @@ -40,17 +25,20 @@ Web Site and Other Resources More information and documentation can be found on the Yosys web site: - https://yosyshq.net/yosys/ -The "Documentation" page on the web site contains links to more resources, -including a manual that even describes some of the Yosys internals: -- https://yosyshq.net/yosys/documentation.html +If you have any Yosys-related questions, please post them on the Discourse group: +- https://yosyshq.discourse.group -The directory `guidelines` contains additional information -for people interested in using the Yosys C++ APIs. +Documentation from this repository is automatically built and available on Read +the Docs: +- https://yosyshq.readthedocs.io/projects/yosys -Users interested in formal verification might want to use the formal verification -front-end for Yosys, SymbiYosys: -- https://symbiyosys.readthedocs.io/en/latest/ -- https://github.com/YosysHQ/SymbiYosys +Users interested in formal verification might want to use the formal +verification front-end for Yosys, SBY: +- https://yosyshq.readthedocs.io/projects/sby/ +- https://github.com/YosysHQ/sby + +The Yosys blog has news and articles from users: +- https://blog.yosyshq.com Installation @@ -68,51 +56,54 @@ For more information about the difference between Tabby CAD Suite and the OSS CA Many Linux distributions also provide Yosys binaries, some more up to date than others. Check with your package manager! + Building from Source ==================== -You need a C++ compiler with C++11 support (up-to-date CLANG or GCC is +For more details, and instructions for other platforms, check [building from +source](https://yosyshq.readthedocs.io/projects/yosys/en/latest/getting_started/installation.html#building-from-source) +on Read the Docs. + +When cloning Yosys, some required libraries are included as git submodules. Make +sure to call e.g. + + $ git clone --recurse-submodules https://github.com/YosysHQ/yosys.git + +or + + $ git clone https://github.com/YosysHQ/yosys.git + $ cd yosys + $ git submodule update --init --recursive + +You need a C++ compiler with C++17 support (up-to-date CLANG or GCC is recommended) and some standard tools such as GNU Flex, GNU Bison, and GNU Make. TCL, readline and libffi are optional (see ``ENABLE_*`` settings in Makefile). Xdot (graphviz) is used by the ``show`` command in yosys to display schematics. -For example on Ubuntu Linux 16.04 LTS the following commands will install all +For example on Ubuntu Linux 22.04 LTS the following commands will install all prerequisites for building yosys: - $ sudo apt-get install build-essential clang bison flex \ - libreadline-dev gawk tcl-dev libffi-dev git \ - graphviz xdot pkg-config python3 libboost-system-dev \ - libboost-python-dev libboost-filesystem-dev zlib1g-dev + $ sudo apt-get install gawk git make python3 lld bison clang flex \ + libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev \ + graphviz xdot + $ curl -LsSf https://astral.sh/uv/install.sh | sh -Similarily, on Mac OS X Homebrew can be used to install dependencies (from within cloned yosys repository): - - $ brew tap Homebrew/bundle && brew bundle - -or MacPorts: - - $ sudo port install bison flex readline gawk libffi \ - git graphviz pkgconfig python36 boost zlib tcl - -On FreeBSD use the following command to install all prerequisites: - - # pkg install bison flex readline gawk libffi\ - git graphviz pkgconf python3 python36 tcl-wrapper boost-libs - -On FreeBSD system use gmake instead of make. To run tests use: - % MAKE=gmake CC=cc gmake test - -For Cygwin use the following command to install all prerequisites, or select these additional packages: - - setup-x86_64.exe -q --packages=bison,flex,gcc-core,gcc-g++,git,libffi-devel,libreadline-devel,make,pkg-config,python3,tcl-devel,boost-build,zlib-devel - -To configure the build system to use a specific compiler, use one of +The environment variable `CXX` can be used to control the C++ compiler used, or +run one of the following to override it: $ make config-clang $ make config-gcc -For other compilers and build configurations it might be -necessary to make some changes to the config section of the -Makefile. +The Makefile has many variables influencing the build process. These can be +adjusted by modifying the Makefile.conf file which is created at the `make +config-...` step (see above), or they can be set by passing an option to the +make command directly: + + $ make CXX=$CXX + +For other compilers and build configurations it might be necessary to make some +changes to the config section of the Makefile. It's also an alternative way to +set the make variables mentioned above. $ vi Makefile # ..or.. $ vi Makefile.conf @@ -122,10 +113,9 @@ To build Yosys simply type 'make' in this directory. $ make $ sudo make install -Note that this also downloads, builds and installs ABC (using yosys-abc -as executable name). - -Tests are located in the tests subdirectory and can be executed using the test target. Note that you need gawk as well as a recent version of iverilog (i.e. build from git). Then, execute tests via: +Tests are located in the tests subdirectory and can be executed using the test +target. Note that you need gawk as well as a recent version of iverilog (i.e. +build from git). Then, execute tests via: $ make test @@ -136,6 +126,7 @@ To use a separate (out-of-tree) build directory, provide a path to the Makefile. Out-of-tree builds require a clean source tree. + Getting Started =============== @@ -240,365 +231,14 @@ The command ``prep`` provides a good default word-level synthesis script, as used in SMT-based formal verification. -Unsupported Verilog-2005 Features -================================= - -The following Verilog-2005 features are not supported by -Yosys and there are currently no plans to add support -for them: - -- Non-synthesizable language features as defined in - IEC 62142(E):2005 / IEEE Std. 1364.1(E):2002 - -- The ``tri``, ``triand`` and ``trior`` net types - -- The ``config`` and ``disable`` keywords and library map files - - -Verilog Attributes and non-standard features -============================================ - -- The ``full_case`` attribute on case statements is supported - (also the non-standard ``// synopsys full_case`` directive) - -- The ``parallel_case`` attribute on case statements is supported - (also the non-standard ``// synopsys parallel_case`` directive) - -- The ``// synopsys translate_off`` and ``// synopsys translate_on`` - directives are also supported (but the use of ``` `ifdef .. `endif ``` - is strongly recommended instead). - -- The ``nomem2reg`` attribute on modules or arrays prohibits the - automatic early conversion of arrays to separate registers. This - is potentially dangerous. Usually the front-end has good reasons - for converting an array to a list of registers. Prohibiting this - step will likely result in incorrect synthesis results. - -- The ``mem2reg`` attribute on modules or arrays forces the early - conversion of arrays to separate registers. - -- The ``nomeminit`` attribute on modules or arrays prohibits the - creation of initialized memories. This effectively puts ``mem2reg`` - on all memories that are written to in an ``initial`` block and - are not ROMs. - -- The ``nolatches`` attribute on modules or always-blocks - prohibits the generation of logic-loops for latches. Instead - all not explicitly assigned values default to x-bits. This does - not affect clocked storage elements such as flip-flops. - -- The ``nosync`` attribute on registers prohibits the generation of a - storage element. The register itself will always have all bits set - to 'x' (undefined). The variable may only be used as blocking assigned - temporary variable within an always block. This is mostly used internally - by Yosys to synthesize Verilog functions and access arrays. - -- The ``nowrshmsk`` attribute on a register prohibits the generation of - shift-and-mask type circuits for writing to bit slices of that register. - -- The ``onehot`` attribute on wires mark them as one-hot state register. This - is used for example for memory port sharing and set by the fsm_map pass. - -- The ``blackbox`` attribute on modules is used to mark empty stub modules - that have the same ports as the real thing but do not contain information - on the internal configuration. This modules are only used by the synthesis - passes to identify input and output ports of cells. The Verilog backend - also does not output blackbox modules on default. ``read_verilog``, unless - called with ``-noblackbox`` will automatically set the blackbox attribute - on any empty module it reads. - -- The ``noblackbox`` attribute set on an empty module prevents ``read_verilog`` - from automatically setting the blackbox attribute on the module. - -- The ``whitebox`` attribute on modules triggers the same behavior as - ``blackbox``, but is for whitebox modules, i.e. library modules that - contain a behavioral model of the cell type. - -- The ``lib_whitebox`` attribute overwrites ``whitebox`` when ``read_verilog`` - is run in `-lib` mode. Otherwise it's automatically removed. - -- The ``dynports`` attribute is used by the Verilog front-end to mark modules - that have ports with a width that depends on a parameter. - -- The ``hdlname`` attribute is used by some passes to document the original - (HDL) name of a module when renaming a module. It should contain a single - name, or, when describing a hierarchical name in a flattened design, multiple - names separated by a single space character. - -- The ``keep`` attribute on cells and wires is used to mark objects that should - never be removed by the optimizer. This is used for example for cells that - have hidden connections that are not part of the netlist, such as IO pads. - Setting the ``keep`` attribute on a module has the same effect as setting it - on all instances of the module. - -- The ``keep_hierarchy`` attribute on cells and modules keeps the ``flatten`` - command from flattening the indicated cells and modules. - -- The ``init`` attribute on wires is set by the frontend when a register is - initialized "FPGA-style" with ``reg foo = val``. It can be used during - synthesis to add the necessary reset logic. - -- The ``top`` attribute on a module marks this module as the top of the - design hierarchy. The ``hierarchy`` command sets this attribute when called - with ``-top``. Other commands, such as ``flatten`` and various backends - use this attribute to determine the top module. - -- The ``src`` attribute is set on cells and wires created by to the string - ``:`` by the HDL front-end and is then carried - through the synthesis. When entities are combined, a new |-separated - string is created that contains all the string from the original entities. - -- The ``defaultvalue`` attribute is used to store default values for - module inputs. The attribute is attached to the input wire by the HDL - front-end when the input is declared with a default value. - -- The ``parameter`` and ``localparam`` attributes are used to mark wires - that represent module parameters or localparams (when the HDL front-end - is run in ``-pwires`` mode). - -- Wires marked with the ``hierconn`` attribute are connected to wires with the - same name (format ``cell_name.identifier``) when they are imported from - sub-modules by ``flatten``. - -- The ``clkbuf_driver`` attribute can be set on an output port of a blackbox - module to mark it as a clock buffer output, and thus prevent ``clkbufmap`` - from inserting another clock buffer on a net driven by such output. - -- The ``clkbuf_sink`` attribute can be set on an input port of a module to - request clock buffer insertion by the ``clkbufmap`` pass. - -- The ``clkbuf_inv`` attribute can be set on an output port of a module - with the value set to the name of an input port of that module. When - the ``clkbufmap`` would otherwise insert a clock buffer on this output, - it will instead try inserting the clock buffer on the input port (this - is used to implement clock inverter cells that clock buffer insertion - will "see through"). - -- The ``clkbuf_inhibit`` is the default attribute to set on a wire to prevent - automatic clock buffer insertion by ``clkbufmap``. This behaviour can be - overridden by providing a custom selection to ``clkbufmap``. - -- The ``invertible_pin`` attribute can be set on a port to mark it as - invertible via a cell parameter. The name of the inversion parameter - is specified as the value of this attribute. The value of the inversion - parameter must be of the same width as the port, with 1 indicating - an inverted bit and 0 indicating a non-inverted bit. - -- The ``iopad_external_pin`` attribute on a blackbox module's port marks - it as the external-facing pin of an I/O pad, and prevents ``iopadmap`` - from inserting another pad cell on it. - -- The module attribute ``abc9_lut`` is an integer attribute indicating to - `abc9` that this module describes a LUT with an area cost of this value, and - propagation delays described using `specify` statements. - -- The module attribute ``abc9_box`` is a boolean specifying a black/white-box - definition, with propagation delays described using `specify` statements, for - use by `abc9`. - -- The port attribute ``abc9_carry`` marks the carry-in (if an input port) and - carry-out (if output port) ports of a box. This information is necessary for - `abc9` to preserve the integrity of carry-chains. Specifying this attribute - onto a bus port will affect only its most significant bit. - -- The module attribute ``abc9_flop`` is a boolean marking the module as a - flip-flop. This allows `abc9` to analyse its contents in order to perform - sequential synthesis. - -- The frontend sets attributes ``always_comb``, ``always_latch`` and - ``always_ff`` on processes derived from SystemVerilog style always blocks - according to the type of the always. These are checked for correctness in - ``proc_dlatch``. - -- The cell attribute ``wildcard_port_conns`` represents wildcard port - connections (SystemVerilog ``.*``). These are resolved to concrete - connections to matching wires in ``hierarchy``. - -- In addition to the ``(* ... *)`` attribute syntax, Yosys supports - the non-standard ``{* ... *}`` attribute syntax to set default attributes - for everything that comes after the ``{* ... *}`` statement. (Reset - by adding an empty ``{* *}`` statement.) - -- In module parameter and port declarations, and cell port and parameter - lists, a trailing comma is ignored. This simplifies writing Verilog code - generators a bit in some cases. - -- Modules can be declared with ``module mod_name(...);`` (with three dots - instead of a list of module ports). With this syntax it is sufficient - to simply declare a module port as 'input' or 'output' in the module - body. - -- When defining a macro with `define, all text between triple double quotes - is interpreted as macro body, even if it contains unescaped newlines. The - triple double quotes are removed from the macro body. For example: - - `define MY_MACRO(a, b) """ - assign a = 23; - assign b = 42; - """ - -- The attribute ``via_celltype`` can be used to implement a Verilog task or - function by instantiating the specified cell type. The value is the name - of the cell type to use. For functions the name of the output port can - be specified by appending it to the cell type separated by a whitespace. - The body of the task or function is unused in this case and can be used - to specify a behavioral model of the cell type for simulation. For example: - - module my_add3(A, B, C, Y); - parameter WIDTH = 8; - input [WIDTH-1:0] A, B, C; - output [WIDTH-1:0] Y; - ... - endmodule - - module top; - ... - (* via_celltype = "my_add3 Y" *) - (* via_celltype_defparam_WIDTH = 32 *) - function [31:0] add3; - input [31:0] A, B, C; - begin - add3 = A + B + C; - end - endfunction - ... - endmodule - -- The ``wiretype`` attribute is added by the verilog parser for wires of a - typedef'd type to indicate the type identifier. - -- Various ``enum_value_{value}`` attributes are added to wires of an enumerated type - to give a map of possible enum items to their values. - -- The ``enum_base_type`` attribute is added to enum items to indicate which - enum they belong to (enums -- anonymous and otherwise -- are - automatically named with an auto-incrementing counter). Note that enums - are currently not strongly typed. - -- A limited subset of DPI-C functions is supported. The plugin mechanism - (see ``help plugin``) can be used to load .so files with implementations - of DPI-C routines. As a non-standard extension it is possible to specify - a plugin alias using the ``:`` syntax. For example: - - module dpitest; - import "DPI-C" function foo:round = real my_round (real); - parameter real r = my_round(12.345); - endmodule - - $ yosys -p 'plugin -a foo -i /lib/libm.so; read_verilog dpitest.v' - -- Sized constants (the syntax ``'s?[bodh]``) support constant - expressions as ````. If the expression is not a simple identifier, it - must be put in parentheses. Examples: ``WIDTH'd42``, ``(4+2)'b101010`` - -- The system tasks ``$finish``, ``$stop`` and ``$display`` are supported in - initial blocks in an unconditional context (only if/case statements on - expressions over parameters and constant values are allowed). The intended - use for this is synthesis-time DRC. - -- There is limited support for converting ``specify`` .. ``endspecify`` - statements to special ``$specify2``, ``$specify3``, and ``$specrule`` cells, - for use in blackboxes and whiteboxes. Use ``read_verilog -specify`` to - enable this functionality. (By default these blocks are ignored.) - -- The ``reprocess_after`` internal attribute is used by the Verilog frontend to - mark cells with bindings which might depend on the specified instantiated - module. Modules with such cells will be reprocessed during the ``hierarchy`` - pass once the referenced module definition(s) become available. - -- The ``smtlib2_module`` attribute can be set on a blackbox module to specify a - formal model directly using SMT-LIB 2. For such a module, the - ``smtlib2_comb_expr`` attribute can be used on output ports to define their - value using an SMT-LIB 2 expression. For example: - - (* blackbox *) - (* smtlib2_module *) - module submod(a, b); - input [7:0] a; - (* smtlib2_comb_expr = "(bvnot a)" *) - output [7:0] b; - endmodule - -Non-standard or SystemVerilog features for formal verification -============================================================== - -- Support for ``assert``, ``assume``, ``restrict``, and ``cover`` is enabled - when ``read_verilog`` is called with ``-formal``. - -- The system task ``$initstate`` evaluates to 1 in the initial state and - to 0 otherwise. - -- The system function ``$anyconst`` evaluates to any constant value. This is - equivalent to declaring a reg as ``rand const``, but also works outside - of checkers. (Yosys also supports ``rand const`` outside checkers.) - -- The system function ``$anyseq`` evaluates to any value, possibly a different - value in each cycle. This is equivalent to declaring a reg as ``rand``, - but also works outside of checkers. (Yosys also supports ``rand`` - variables outside checkers.) - -- The system functions ``$allconst`` and ``$allseq`` can be used to construct - formal exist-forall problems. Assumptions only hold if the trace satisfies - the assumption for all ``$allconst/$allseq`` values. For assertions and cover - statements it is sufficient if just one ``$allconst/$allseq`` value triggers - the property (similar to ``$anyconst/$anyseq``). - -- Wires/registers declared using the ``anyconst/anyseq/allconst/allseq`` attribute - (for example ``(* anyconst *) reg [7:0] foobar;``) will behave as if driven - by a ``$anyconst/$anyseq/$allconst/$allseq`` function. - -- The SystemVerilog tasks ``$past``, ``$stable``, ``$rose`` and ``$fell`` are - supported in any clocked block. - -- The syntax ``@($global_clock)`` can be used to create FFs that have no - explicit clock input (``$ff`` cells). The same can be achieved by using - ``@(posedge )`` or ``@(negedge )`` when ```` - is marked with the ``(* gclk *)`` Verilog attribute. - - -Supported features from SystemVerilog -===================================== - -When ``read_verilog`` is called with ``-sv``, it accepts some language features -from SystemVerilog: - -- The ``assert`` statement from SystemVerilog is supported in its most basic - form. In module context: ``assert property ();`` and within an - always block: ``assert();``. It is transformed to an ``$assert`` cell. - -- The ``assume``, ``restrict``, and ``cover`` statements from SystemVerilog are - also supported. The same limitations as with the ``assert`` statement apply. - -- The keywords ``always_comb``, ``always_ff`` and ``always_latch``, ``logic`` - and ``bit`` are supported. - -- Declaring free variables with ``rand`` and ``rand const`` is supported. - -- Checkers without a port list that do not need to be instantiated (but instead - behave like a named block) are supported. - -- SystemVerilog packages are supported. Once a SystemVerilog file is read - into a design with ``read_verilog``, all its packages are available to - SystemVerilog files being read into the same design afterwards. - -- typedefs are supported (including inside packages) - - type casts are currently not supported - -- enums are supported (including inside packages) - - but are currently not strongly typed - -- packed structs and unions are supported - - arrays of packed structs/unions are currently not supported - - structure literals are currently not supported - -- multidimensional arrays are supported - - array assignment of unpacked arrays is currently not supported - - array literals are currently not supported - -- SystemVerilog interfaces (SVIs) are supported. Modports for specifying whether - ports are inputs or outputs are supported. - -- Assignments within expressions are supported. +Additional information +====================== + +The ``read_verilog`` command, used by default when calling ``read`` with Verilog +source input, does not perform syntax checking. You should instead lint your +source with another tool such as +[Verilator](https://www.veripool.org/verilator/) first, e.g. by calling +``verilator --lint-only``. Building the documentation @@ -608,12 +248,24 @@ Note that there is no need to build the manual if you just want to read it. Simply visit https://yosys.readthedocs.io/en/latest/ instead. In addition to those packages listed above for building Yosys from source, the -following are used for building the website: +following are used for building the website: - $ sudo apt-get install pdf2svg faketime + $ sudo apt install pdf2svg faketime + +Or for MacOS, using homebrew: + + $ brew install pdf2svg libfaketime PDFLaTeX, included with most LaTeX distributions, is also needed during the -build process for the website. +build process for the website. Or, run the following: + + $ sudo apt install texlive-latex-base texlive-latex-extra latexmk + +Or for MacOS, using homebrew: + + $ brew install basictex + $ sudo tlmgr update --self + $ sudo tlmgr install collection-latexextra latexmk tex-gyre The Python package, Sphinx, is needed along with those listed in `docs/source/requirements.txt`: @@ -622,5 +274,13 @@ The Python package, Sphinx, is needed along with those listed in From the root of the repository, run `make docs`. This will build/rebuild yosys as necessary before generating the website documentation from the yosys help -commands. To build for pdf instead of html, call +commands. To build for pdf instead of html, call `make docs DOC_TARGET=latexpdf`. + +It is recommended to use the `ENABLE_HELP_SOURCE` make option for Yosys builds +that will be used to build the documentation. This option enables source +location tracking for passes and improves the command reference through grouping +related commands and allowing for the documentation to link to the corresponding +source files. Without this, a warning will be raised during the Sphinx build +about `Found commands assigned to group unknown` and `make docs` is configured +to fail on warnings by default. diff --git a/abc b/abc new file mode 160000 index 000000000..799ba6322 --- /dev/null +++ b/abc @@ -0,0 +1 @@ +Subproject commit 799ba632239b2a4db2bacda81de4e6efdc486b0c diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc index f2cb5d9bc..95f4c19e2 100644 --- a/backends/aiger/aiger.cc +++ b/backends/aiger/aiger.cc @@ -132,7 +132,7 @@ struct AigerWriter return a; } - AigerWriter(Module *module, bool zinit_mode, bool imode, bool omode, bool bmode, bool lmode) : module(module), zinit_mode(zinit_mode), sigmap(module) + AigerWriter(Module *module, bool no_sort, bool zinit_mode, bool imode, bool omode, bool bmode, bool lmode) : module(module), zinit_mode(zinit_mode), sigmap(module) { pool undriven_bits; pool unused_bits; @@ -152,6 +152,37 @@ struct AigerWriter if (wire->port_input) sigmap.add(wire); + // handle ports + // provided the input_bits and output_bits don't get sorted they + // will be returned in reverse order, so add them in reverse to + // match + for (auto riter = module->ports.rbegin(); riter != module->ports.rend(); ++riter) { + auto *wire = module->wire(*riter); + for (int i = 0; i < GetSize(wire); i++) + { + SigBit wirebit(wire, i); + SigBit bit = sigmap(wirebit); + + if (bit.wire == nullptr) { + if (wire->port_output) { + aig_map[wirebit] = (bit == State::S1) ? 1 : 0; + output_bits.insert(wirebit); + } + continue; + } + + if (wire->port_input) + input_bits.insert(bit); + + if (wire->port_output) { + if (bit != wirebit) + alias_map[wirebit] = bit; + output_bits.insert(wirebit); + } + } + } + + // handle wires for (auto wire : module->wires()) { if (wire->attributes.count(ID::init)) { @@ -167,25 +198,13 @@ struct AigerWriter SigBit wirebit(wire, i); SigBit bit = sigmap(wirebit); - if (bit.wire == nullptr) { - if (wire->port_output) { - aig_map[wirebit] = (bit == State::S1) ? 1 : 0; - output_bits.insert(wirebit); - } + if (bit.wire == nullptr) + continue; + if (wire->port_input || wire->port_output) continue; - } undriven_bits.insert(bit); unused_bits.insert(bit); - - if (wire->port_input) - input_bits.insert(bit); - - if (wire->port_output) { - if (bit != wirebit) - alias_map[wirebit] = bit; - output_bits.insert(wirebit); - } } if (wire->width == 1) { @@ -200,12 +219,6 @@ struct AigerWriter } } - for (auto bit : input_bits) - undriven_bits.erase(bit); - - for (auto bit : output_bits) - unused_bits.erase(bit); - for (auto cell : module->cells()) { if (cell->type == ID($_NOT_)) @@ -343,8 +356,11 @@ struct AigerWriter } init_map.sort(); - input_bits.sort(); - output_bits.sort(); + // we are relying here on unsorted pools iterating last-in-first-out + if (!no_sort) { + input_bits.sort(); + output_bits.sort(); + } not_map.sort(); ff_map.sort(); and_map.sort(); @@ -662,8 +678,7 @@ struct AigerWriter f << std::endl; } } - - f << stringf("c\nGenerated by %s\n", yosys_version_str); + f << stringf("c\nGenerated by %s\n", yosys_maybe_version()); } void write_map(std::ostream &f, bool verbose_map, bool no_startoffset) @@ -698,7 +713,7 @@ struct AigerWriter } if (wire->port_output) { - int o = ordered_outputs.at(sig[i]); + int o = ordered_outputs.at(SigSpec(wire, i)); output_lines[o] += stringf("output %d %d %s\n", o, index, log_id(wire)); } @@ -746,7 +761,7 @@ struct AigerWriter { json.begin_object(); json.entry("version", "Yosys Witness Aiger map"); - json.entry("gennerator", yosys_version_str); + json.entry("gennerator", yosys_maybe_version()); json.entry("latch_count", aig_l); json.entry("input_count", aig_i); @@ -902,6 +917,9 @@ struct AigerBackend : public Backend { log(" -symbols\n"); log(" include a symbol table in the generated AIGER file\n"); log("\n"); + log(" -no-sort\n"); + log(" don't sort input/output ports\n"); + log("\n"); log(" -map \n"); log(" write an extra file with port and latch symbols\n"); log("\n"); @@ -926,6 +944,7 @@ struct AigerBackend : public Backend { bool zinit_mode = false; bool miter_mode = false; bool symbols_mode = false; + bool no_sort = false; bool verbose_map = false; bool imode = false; bool omode = false; @@ -956,6 +975,10 @@ struct AigerBackend : public Backend { symbols_mode = true; continue; } + if (args[argidx] == "-no-sort") { + no_sort = true; + continue; + } if (map_filename.empty() && args[argidx] == "-map" && argidx+1 < args.size()) { map_filename = args[++argidx]; continue; @@ -1009,7 +1032,7 @@ struct AigerBackend : public Backend { if (!top_module->memories.empty()) log_error("Found unmapped memories in module %s: unmapped memories are not supported in AIGER backend!\n", log_id(top_module)); - AigerWriter writer(top_module, zinit_mode, imode, omode, bmode, lmode); + AigerWriter writer(top_module, no_sort, zinit_mode, imode, omode, bmode, lmode); writer.write_aiger(*f, ascii_mode, miter_mode, symbols_mode); if (!map_filename.empty()) { @@ -1017,7 +1040,7 @@ struct AigerBackend : public Backend { std::ofstream mapf; mapf.open(map_filename.c_str(), std::ofstream::trunc); if (mapf.fail()) - log_error("Can't open file `%s' for writing: %s\n", map_filename.c_str(), strerror(errno)); + log_error("Can't open file `%s' for writing: %s\n", map_filename, strerror(errno)); writer.write_map(mapf, verbose_map, no_startoffset); } @@ -1028,7 +1051,7 @@ struct AigerBackend : public Backend { PrettyJson json; if (!json.write_to_file(yw_map_filename)) - log_error("Can't open file `%s' for writing: %s\n", yw_map_filename.c_str(), strerror(errno)); + log_error("Can't open file `%s' for writing: %s\n", yw_map_filename, strerror(errno)); writer.write_ywmap(json); } } diff --git a/backends/aiger/xaiger.cc b/backends/aiger/xaiger.cc index 68c2ff52f..988bc558b 100644 --- a/backends/aiger/xaiger.cc +++ b/backends/aiger/xaiger.cc @@ -18,32 +18,6 @@ * */ -// https://stackoverflow.com/a/46137633 -#ifdef _MSC_VER -#include -#define bswap32 _byteswap_ulong -#elif defined(__APPLE__) -#include -#define bswap32 OSSwapInt32 -#elif defined(__GNUC__) -#define bswap32 __builtin_bswap32 -#else -#include -inline static uint32_t bswap32(uint32_t x) -{ - // https://stackoverflow.com/a/27796212 - register uint32_t value = number_to_be_reversed; - uint8_t lolo = (value >> 0) & 0xFF; - uint8_t lohi = (value >> 8) & 0xFF; - uint8_t hilo = (value >> 16) & 0xFF; - uint8_t hihi = (value >> 24) & 0xFF; - return (hihi << 24) - | (hilo << 16) - | (lohi << 8) - | (lolo << 0); -} -#endif - #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/utils.h" @@ -52,16 +26,6 @@ inline static uint32_t bswap32(uint32_t x) USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -inline int32_t to_big_endian(int32_t i32) { -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - return bswap32(i32); -#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - return i32; -#else -#error "Unknown endianness" -#endif -} - void aiger_encode(std::ostream &f, int x) { log_assert(x >= 0); @@ -89,6 +53,8 @@ struct XAigerWriter dict arrival_times; vector> aig_gates; + vector bit2aig_stack; + int next_loop_check = 1024; vector aig_outputs; int aig_m = 0, aig_i = 0, aig_l = 0, aig_o = 0, aig_a = 0; @@ -112,6 +78,24 @@ struct XAigerWriter return it->second; } + if (GetSize(bit2aig_stack)== next_loop_check) { + for (int i = 0; i < next_loop_check; ++i) + { + SigBit report_bit = bit2aig_stack[i]; + if (report_bit != bit) + continue; + for (int j = i; j < next_loop_check; ++j) { + report_bit = bit2aig_stack[j]; + if (report_bit.is_wire() && report_bit.wire->name.isPublic()) + break; + } + log_error("Found combinatorial logic loop while processing signal %s.\n", log_signal(report_bit)); + } + next_loop_check *= 2; + } + + bit2aig_stack.push_back(bit); + // NB: Cannot use iterator returned from aig_map.insert() // since this function is called recursively @@ -129,6 +113,8 @@ struct XAigerWriter a = bit2aig(alias_map.at(bit)); } + bit2aig_stack.pop_back(); + if (bit == State::Sx || bit == State::Sz) { log_debug("Design contains 'x' or 'z' bits. Treating as 1'b0.\n"); a = aig_map.at(State::S0); @@ -537,9 +523,12 @@ struct XAigerWriter f << "c"; - auto write_buffer = [](std::stringstream &buffer, int i32) { - int32_t i32_be = to_big_endian(i32); - buffer.write(reinterpret_cast(&i32_be), sizeof(i32_be)); + auto write_buffer = [](std::ostream &buffer, unsigned int u32) { + typedef unsigned char uchar; + unsigned char u32_be[4] = { + (uchar) (u32 >> 24), (uchar) (u32 >> 16), (uchar) (u32 >> 8), (uchar) u32 + }; + buffer.write((char *) u32_be, sizeof(u32_be)); }; std::stringstream h_buffer; auto write_h_buffer = std::bind(write_buffer, std::ref(h_buffer), std::placeholders::_1); @@ -640,14 +629,12 @@ struct XAigerWriter f << "r"; std::string buffer_str = r_buffer.str(); - int32_t buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); f << "s"; buffer_str = s_buffer.str(); - buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); RTLIL::Design *holes_design; @@ -664,22 +651,19 @@ struct XAigerWriter f << "a"; std::string buffer_str = a_buffer.str(); - int32_t buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); } } f << "h"; std::string buffer_str = h_buffer.str(); - int32_t buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); f << "i"; buffer_str = i_buffer.str(); - buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); //f << "o"; //buffer_str = o_buffer.str(); @@ -687,7 +671,7 @@ struct XAigerWriter //f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); //f.write(buffer_str.data(), buffer_str.size()); - f << stringf("Generated by %s\n", yosys_version_str); + f << stringf("Generated by %s\n", yosys_maybe_version()); design->scratchpad_set_int("write_xaiger.num_ands", and_map.size()); design->scratchpad_set_int("write_xaiger.num_wires", aig_map.size()); @@ -804,7 +788,7 @@ struct XAigerBackend : public Backend { std::ofstream mapf; mapf.open(map_filename.c_str(), std::ofstream::trunc); if (mapf.fail()) - log_error("Can't open file `%s' for writing: %s\n", map_filename.c_str(), strerror(errno)); + log_error("Can't open file `%s' for writing: %s\n", map_filename, strerror(errno)); writer.write_map(mapf); } } diff --git a/backends/aiger2/Makefile.inc b/backends/aiger2/Makefile.inc new file mode 100644 index 000000000..494b8d6c6 --- /dev/null +++ b/backends/aiger2/Makefile.inc @@ -0,0 +1 @@ +OBJS += backends/aiger2/aiger.o diff --git a/backends/aiger2/aiger.cc b/backends/aiger2/aiger.cc new file mode 100644 index 000000000..babc29826 --- /dev/null +++ b/backends/aiger2/aiger.cc @@ -0,0 +1,1488 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) Martin Povišer + * + * 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. + * + */ + +// TODOs: +// - gracefully handling inout ports (an error message probably) +// - undriven wires +// - zero-width operands + +#include "kernel/register.h" +#include "kernel/celltypes.h" +#include "kernel/rtlil.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +#define BITWISE_OPS ID($buf), ID($not), ID($mux), ID($and), ID($or), ID($xor), ID($xnor), ID($fa), \ + ID($bwmux) + +#define REDUCE_OPS ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool) + +#define LOGIC_OPS ID($logic_and), ID($logic_or), ID($logic_not) + +#define GATE_OPS ID($_BUF_), ID($_NOT_), ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), \ + ID($_XOR_), ID($_XNOR_), ID($_ANDNOT_), ID($_ORNOT_), ID($_MUX_), ID($_NMUX_), \ + ID($_AOI3_), ID($_OAI3_), ID($_AOI4_), ID($_OAI4_) + +#define CMP_OPS ID($eq), ID($ne), ID($lt), ID($le), ID($ge), ID($gt) + +// 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*/ + +template +struct Index { + struct HierCursor; + struct ModuleInfo { + Module *module; + int len; + dict windices; + dict suboffsets; + pool found_blackboxes; + + bool indexing = false; + bool indexed = false; + }; + + dict modules; + + int index_wires(ModuleInfo &info, RTLIL::Module *m) + { + int sum = 0; + for (auto w : m->wires()) { + info.windices[w] = sum; + sum += w->width; + } + return sum; + } + + bool flatten = false; + bool inline_whiteboxes = false; + bool allow_blackboxes = false; + + int index_module(RTLIL::Module *m) + { + ModuleInfo &info = modules[m]; + + if (info.indexed) + return info.len; + + if (info.indexing && !info.indexed) + log_error("Hierarchy error\n"); + + info.module = m; + 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))) + continue; + + Module *submodule = m->design->module(cell->type); + + if (submodule && flatten && + !submodule->get_bool_attribute(ID::keep_hierarchy) && + !submodule->get_blackbox_attribute(inline_whiteboxes)) { + info.suboffsets[cell] = pos; + pos += index_module(submodule); + } else { + if (allow_blackboxes) { + 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 + // can't bail at this point. If they are hit by a traversal + // (which can only really happen with $tribuf not + // $connect), we can still detect this as an error later. + if (cell->type == ID($connect) || (cell->type == ID($tribuf) && cell->has_attribute(ID(aiger2_zbuf)))) + continue; + if (!submodule || submodule->get_blackbox_attribute()) + log_error("Unsupported cell type: %s (%s in %s)\n", + log_id(cell->type), log_id(cell), log_id(m)); + } + } + } + + info.len = pos; + return info.len; + } + + Design *design; + Module *top; + ModuleInfo *top_minfo; + std::vector lits; + + Index() + { + } + + void setup(RTLIL::Module *top) + { + design = top->design; + this->top = top; + + modules.reserve(top->design->modules().size()); + int nlits = index_module(top); + log_debug("allocating for %d literals\n", nlits); + lits.resize(nlits, Writer::EMPTY_LIT); + top_minfo = &modules.at(top); + } + + bool const_folding = true; + bool strashing = false; + dict, Lit> cache; + + Lit AND(Lit a, Lit b) + { + if (const_folding) { + if (a == CFALSE || b == CFALSE) + return CFALSE; + if (a == CTRUE) + return b; + if (b == CTRUE) + return a; + } + + if (!strashing) { + return (static_cast(this))->emit_gate(a, b); + } else { + if (a < b) std::swap(a, b); + auto pair = std::make_pair(a, b); + + if (!cache.count(pair)) { + Lit nl = (static_cast(this))->emit_gate(a, b); + cache[pair] = nl; + return nl; + } else { + return cache.at(pair); + } + } + } + + Lit NOT(Lit lit) + { + return Writer::negate(lit); + } + + Lit OR(Lit a, Lit b) + { + return NOT(AND(NOT(a), NOT(b))); + } + + Lit MUX(Lit a, Lit b, Lit s) + { + if (const_folding) { + if (a == b) + return a; + if (s == CFALSE) + return a; + if (s == CTRUE) + return b; + } + + return OR(AND(a, NOT(s)), AND(b, s)); + } + + Lit XOR(Lit a, Lit b) + { + return OR(AND(a, NOT(b)), AND(NOT(a), b)); + } + + Lit XNOR(Lit a, Lit b) + { + return NOT(OR(AND(a, NOT(b)), AND(NOT(a), b))); + } + + Lit CARRY(Lit a, Lit b, Lit c) + { + if (const_folding) { + if (c == CTRUE) { + return OR(a, b); + } else if (c == CFALSE) { + return AND(a, b); + } + } + return OR(AND(a, b), AND(c, OR(a, b))); + } + + Lit REDUCE(std::vector lits, bool op_xor=false) + { + std::vector next; + while (lits.size() > 1) { + next.clear(); + for (int i = 0; i < (int) lits.size(); i += 2) { + if (i + 1 >= (int) lits.size()) { + next.push_back(lits[i]); + } else { + Lit a = lits[i], b = lits[i + 1]; + next.push_back(op_xor ? XOR(a, b) : AND(a, b)); + } + } + next.swap(lits); + } + + if (lits.empty()) + return op_xor ? CFALSE : CTRUE; + else + return lits.front(); + } + + Lit impl_op(HierCursor &cursor, Cell *cell, IdString oport, int obit) + { + if (cell->type.in(REDUCE_OPS, LOGIC_OPS, CMP_OPS) && obit != 0) { + return CFALSE; + } else if (cell->type.in(CMP_OPS)) { + SigSpec aport = cell->getPort(ID::A); + bool asigned = cell->getParam(ID::A_SIGNED).as_bool(); + SigSpec bport = cell->getPort(ID::B); + bool bsigned = cell->getParam(ID::B_SIGNED).as_bool(); + + int width = std::max(aport.size(), bport.size()) + 1; + aport.extend_u0(width, asigned); + bport.extend_u0(width, bsigned); + + if (cell->type.in(ID($eq), ID($ne))) { + int carry = CTRUE; + for (int i = 0; i < width; i++) { + Lit a = visit(cursor, aport[i]); + Lit b = visit(cursor, bport[i]); + carry = AND(carry, XNOR(a, b)); + } + return (cell->type == ID($eq)) ? carry : /* $ne */ NOT(carry); + } 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; + Lit a = Writer::EMPTY_LIT; + Lit b = Writer::EMPTY_LIT; + // TODO: this might not be the most economic structure; revisit at a later date + for (int i = 0; i < width; i++) { + a = visit(cursor, aport[i]); + b = visit(cursor, bport[i]); + if (i != width - 1) + carry = CARRY(a, NOT(b), carry); + } + return XOR(carry, XNOR(a, b)); + } else { + log_abort(); + } + } else if (cell->type.in(REDUCE_OPS, ID($logic_not))) { + SigSpec inport = cell->getPort(ID::A); + + std::vector lits; + for (int i = 0; i < inport.size(); i++) { + Lit lit = visit(cursor, inport[i]); + if (cell->type.in(ID($reduce_and), ID($reduce_xor), ID($reduce_xnor))) { + lits.push_back(lit); + } else if (cell->type.in(ID($reduce_or), ID($reduce_bool), ID($logic_not))) { + lits.push_back(NOT(lit)); + } else { + log_abort(); + } + } + + Lit acc = REDUCE(lits, cell->type.in(ID($reduce_xor), ID($reduce_xnor))); + + if (!cell->type.in(ID($reduce_xnor), ID($reduce_or), ID($reduce_bool))) + return acc; + else + return NOT(acc); + } else if (cell->type.in(ID($logic_and), ID($logic_or))) { + SigSpec aport = cell->getPort(ID::A); + SigSpec bport = cell->getPort(ID::B); + + log_assert(aport.size() > 0 && bport.size() > 0); // TODO + + Lit a = visit(cursor, aport[0]); + for (int i = 1; i < aport.size(); i++) { + Lit l = visit(cursor, aport[i]); + a = OR(a, l); + } + + Lit b = visit(cursor, bport[0]); + for (int i = 1; i < bport.size(); i++) { + Lit l = visit(cursor, bport[i]); + b = OR(b, l); + } + + if (cell->type == ID($logic_and)) + return AND(a, b); + else if (cell->type == ID($logic_or)) + return OR(a, b); + else + log_abort(); + } else if (cell->type.in(BITWISE_OPS, GATE_OPS, ID($pos))) { + SigSpec aport = cell->getPort(ID::A); + Lit a; + if (obit < aport.size()) { + a = visit(cursor, aport[obit]); + } else { + if (cell->getParam(ID::A_SIGNED).as_bool()) + a = visit(cursor, aport.msb()); + else + a = CFALSE; + } + + if (cell->type.in(ID($buf), ID($pos), ID($_BUF_))) { + return a; + } else if (cell->type.in(ID($not), ID($_NOT_))) { + return NOT(a); + } else { + SigSpec bport = cell->getPort(ID::B); + Lit b; + if (obit < bport.size()) { + b = visit(cursor, bport[obit]); + } else { + if (cell->getParam(ID::B_SIGNED).as_bool()) + b = visit(cursor, bport.msb()); + else + b = CFALSE; + } + + if (cell->type.in(ID($and), ID($_AND_))) { + return AND(a, b); + } else if (cell->type.in(ID($_NAND_))) { + return NOT(AND(a, b)); + } else if (cell->type.in(ID($or), ID($_OR_))) { + return OR(a, b); + } else if (cell->type.in(ID($_NOR_))) { + return NOT(OR(a, b)); + } else if (cell->type.in(ID($xor), ID($_XOR_))) { + return XOR(a, b); + } else if (cell->type.in(ID($xnor), ID($_XNOR_))) { + return NOT(XOR(a, b)); + } else if (cell->type.in(ID($_ANDNOT_))) { + return AND(a, NOT(b)); + } else if (cell->type.in(ID($_ORNOT_))) { + return OR(a, NOT(b)); + } else if (cell->type.in(ID($mux), ID($_MUX_))) { + Lit s = visit(cursor, cell->getPort(ID::S)); + return MUX(a, b, s); + } else if (cell->type.in(ID($bwmux))) { + Lit s = visit(cursor, cell->getPort(ID::S)[obit]); + return MUX(a, b, s); + } else if (cell->type.in(ID($_NMUX_))) { + Lit s = visit(cursor, cell->getPort(ID::S)[obit]); + return NOT(MUX(a, b, s)); + } else if (cell->type.in(ID($fa))) { + Lit c = visit(cursor, cell->getPort(ID::C)[obit]); + Lit ab = XOR(a, b); + if (oport == ID::Y) { + return XOR(ab, c); + } else /* oport == ID::X */ { + return OR(AND(a, b), AND(c, ab)); + } + } else if (cell->type.in(ID($_AOI3_), ID($_OAI3_), ID($_AOI4_), ID($_OAI4_))) { + Lit c, d; + + c = visit(cursor, cell->getPort(ID::C)[obit]); + if (/* 4 input types */ cell->type.in(ID($_AOI4_), ID($_OAI4_))) + d = visit(cursor, cell->getPort(ID::D)[obit]); + else + d = cell->type == ID($_AOI3_) ? CTRUE : CFALSE; + + if (/* aoi */ cell->type.in(ID($_AOI3_), ID($_AOI4_))) + return NOT(OR(AND(a, b), AND(c, d))); + else + return NOT(AND(OR(a, b), OR(c, d))); + } else { + log_abort(); + } + } + } else if (cell->type == ID($pmux)) { + SigSpec aport = cell->getPort(ID::A); + SigSpec bport = cell->getPort(ID::B); + SigSpec sport = cell->getPort(ID::S); + int width = aport.size(); + + Lit a = visit(cursor, aport[obit]); + + std::vector bar, sels; + for (int i = 0; i < sport.size(); i++) { + Lit s = visit(cursor, sport[i]); + Lit b = visit(cursor, bport[width * i + obit]); + bar.push_back(NOT(AND(s, b))); + sels.push_back(NOT(s)); + } + + return OR(AND(REDUCE(sels), a), NOT(REDUCE(bar))); + } else if (cell->type == ID($bmux)) { + SigSpec aport = cell->getPort(ID::A); + SigSpec sport = cell->getPort(ID::S); + int width = cell->getParam(ID::WIDTH).as_int(); + + std::vector data; + for (int i = obit; i < aport.size(); i += width) + data.push_back(visit(cursor, aport[i])); + + std::vector next; + for (int i = 0; i < sport.size(); i++) { + Lit s = visit(cursor, sport[i]); + next.clear(); + for (int j = 0; j < (int) data.size(); j += 2) + next.push_back(MUX(data[j], data[j + 1], s)); + data.swap(next); + } + log_assert(data.size() == 1); + return data[0]; + } else { + log_abort(); + } + } + + struct HierCursor { + typedef std::pair Level; + std::vector levels; + int instance_offset = 0; + + HierCursor() + { + } + + ModuleInfo &leaf_minfo(Index &index) + { + if (levels.empty()) + return *index.top_minfo; + else + return levels.back().first; + } + + Module *leaf_module(Index &index) + { + return leaf_minfo(index).module; + } + + int bitwire_index(Index &index, SigBit bit) + { + log_assert(bit.wire != nullptr); + return instance_offset + leaf_minfo(index).windices[bit.wire] + bit.offset; + } + + Cell *exit(Index &index) + { + log_assert(!levels.empty()); + Cell *instance = levels.back().second; + + levels.pop_back(); + instance_offset -= leaf_minfo(index).suboffsets.at(instance); + + // return the instance we just exited + return instance; + } + + Module *enter(Index &index, Cell *cell) + { + Design *design = index.design; + auto &minfo = leaf_minfo(index); + if (!minfo.suboffsets.count(cell)) + log_error("Reached unsupport cell %s (%s in %s)\n", log_id(cell->type), log_id(cell), log_id(cell->module)); + Module *def = design->module(cell->type); + log_assert(def); + levels.push_back(Level(index.modules.at(def), cell)); + instance_offset += minfo.suboffsets.at(cell); + + // return the module definition we just entered + return def; + } + + bool is_top() + { + return levels.empty(); + } + + std::string path() + { + std::string ret; + bool first = true; + for (auto pair : levels) { + if (!first) + ret += "."; + if (!pair.second) + ret += RTLIL::unescape_id(pair.first.module->name); + else + ret += RTLIL::unescape_id(pair.second->name); + first = false; + } + return ret; + } + + int hash() const + { + int hash = 0; + for (auto pair : levels) + hash += (uintptr_t) pair.second; + return hash; + } + + bool operator==(const HierCursor &other) const + { + if (levels.size() != other.levels.size()) + return false; + + for (int i = 0; i < levels.size(); i++) + if (levels[i].second != other.levels[i].second) + return false; + + return true; + } + }; + + bool visit_hook(int, HierCursor&, SigBit) + { + return false; + } + + Lit visit(HierCursor &cursor, SigBit bit) + { + if (!bit.wire) { + if (bit == State::S1) + return CTRUE; + else if (bit == State::S0) + return CFALSE; + else if (bit == State::Sx) + return CFALSE; + else + log_error("Unhandled state %s\n", log_signal(bit)); + } + + int idx = cursor.bitwire_index(*this, bit); + if (lits[idx] != Writer::EMPTY_LIT) { + // literal already assigned + return lits[idx]; + } + + // provide means for the derived class to override + // the visit behavior + if ((static_cast(this))->visit_hook(idx, cursor, bit)) { + return lits[idx]; + } + + Lit ret; + if (!bit.wire->port_input || bit.wire->port_output) { + // an output of a cell + Cell *driver = bit.wire->driverCell(); + + if (driver->type.in(KNOWN_OPS)) { + ret = impl_op(cursor, driver, bit.wire->driverPort(), bit.offset); + } else { + Module *def = cursor.enter(*this, driver); + { + IdString portname = bit.wire->driverPort(); + Wire *w = def->wire(portname); + if (!w) + log_error("Output port %s on instance %s of %s doesn't exist\n", + log_id(portname), log_id(driver), log_id(def)); + if (bit.offset >= w->width) + log_error("Bit position %d of output port %s on instance %s of %s is out of range (port has width %d)\n", + bit.offset, log_id(portname), log_id(driver), log_id(def), w->width); + ret = visit(cursor, SigBit(w, bit.offset)); + } + cursor.exit(*this); + } + } else { + // a module input: we cannot be the top module, otherwise + // the branch for pre-existing literals would have been taken + log_assert(!cursor.is_top()); + + // step into the upper module + Cell *instance = cursor.exit(*this); + { + IdString portname = bit.wire->name; + if (!instance->hasPort(portname)) + log_error("Input port %s on instance %s of %s unconnected\n", + log_id(portname), log_id(instance), log_id(instance->type)); + auto &port = instance->getPort(portname); + if (bit.offset >= port.size()) + log_error("Bit %d of input port %s on instance %s of %s unconnected\n", + bit.offset, log_id(portname), log_id(instance), log_id(instance->type)); + ret = visit(cursor, port[bit.offset]); + } + cursor.enter(*this, instance); + } + + lits[idx] = ret; + return ret; + } + + Lit &pi_literal(SigBit bit, HierCursor *cursor=nullptr) + { + log_assert(bit.wire); + + if (!cursor) { + log_assert(bit.wire->module == top); + log_assert(bit.wire->port_input && !bit.wire->port_output); + return lits[top_minfo->windices[bit.wire] + bit.offset]; + } else { + log_assert(bit.wire->module == cursor->leaf_module(*this)); + return lits[cursor->bitwire_index(*this, bit)]; + } + } + + Lit eval_po(SigBit bit, HierCursor *cursor=nullptr) + { + Lit ret; + if (!cursor) { + HierCursor cursor_; + ret = visit(cursor_, bit); + log_assert(cursor_.is_top()); + log_assert(cursor_.instance_offset == 0); + } else { + ret = visit(*cursor, bit); + } + return ret; + } + + void visit_hierarchy(std::function f, + HierCursor &cursor) + { + f(cursor); + + ModuleInfo &minfo = cursor.leaf_minfo(*this); + for (auto cell : minfo.module->cells()) { + if (minfo.suboffsets.count(cell)) { + cursor.enter(*this, cell); + visit_hierarchy(f, cursor); + cursor.exit(*this); + } + } + } + + void visit_hierarchy(std::function f) + { + HierCursor cursor; + visit_hierarchy(f, cursor); + } +}; + +struct AigerWriter : Index { + typedef unsigned int Lit; + + const static constexpr Lit EMPTY_LIT = std::numeric_limits::max(); + + static Lit negate(Lit lit) { + return lit ^ 1; + } + + std::ostream *f; + Lit lit_counter; + int ninputs, nlatches, noutputs, nands; + + void encode(int delta) + { + log_assert(delta >= 0); + unsigned int x = delta; + while (x & ~0x7f) { + f->put((x & 0x7f) | 0x80); + x = x >> 7; + } + f->put(x); + } + + Lit emit_gate(Lit a, Lit b) + { + Lit out = lit_counter; + nands++; + lit_counter += 2; + + if (a < b) std::swap(a, b); + encode(out - a); + encode(a - b); + return out; + } + + void reset_counters() + { + lit_counter = 2; + ninputs = nlatches = noutputs = nands = 0; + } + + void write_header() { + log_assert(lit_counter == (Lit) (ninputs + nlatches + nands) * 2 + 2); + + char buf[128]; + snprintf(buf, sizeof(buf) - 1, "aig %08d %08d %08d %08d %08d\n", + ninputs + nlatches + nands, ninputs, nlatches, noutputs, nands); + f->write(buf, strlen(buf)); + } + + void write(std::ostream *f) { + reset_counters(); + + auto file_start = f->tellp(); + + // populate inputs + std::vector inputs; + for (auto id : top->ports) { + Wire *w = top->wire(id); + log_assert(w); + if (w->port_input && !w->port_output) + for (int i = 0; i < w->width; i++) { + pi_literal(SigBit(w, i)) = lit_counter; + inputs.push_back(SigBit(w, i)); + lit_counter += 2; + ninputs++; + } + } + + this->f = f; + // start with the header + write_header(); + // insert padding where output literals will go (once known) + for (auto id : top->ports) { + Wire *w = top->wire(id); + log_assert(w); + if (w->port_output) { + for (auto bit : SigSpec(w)) { + (void) bit; + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", 0); + f->write(buf, strlen(buf)); + noutputs++; + } + } + } + auto data_start = f->tellp(); + + // now the guts + std::vector> outputs; + for (auto w : top->wires()) + if (w->port_output) { + for (auto bit : SigSpec(w)) + outputs.push_back({bit, eval_po(bit)}); + } + auto data_end = f->tellp(); + + // revisit header and the list of outputs + f->seekp(file_start); + write_header(); + for (auto pair : outputs) { + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", pair.second); + f->write(buf, strlen(buf)); + } + // double check we arrived at the same offset for the + // main data section + log_assert(data_start == f->tellp()); + + f->seekp(data_end); + int i = 0; + for (auto pair : outputs) { + if (SigSpec(pair.first).is_wire()) { + char buf[32]; + snprintf(buf, sizeof(buf) - 1, "o%d ", i); + f->write(buf, strlen(buf)); + std::string name = RTLIL::unescape_id(pair.first.wire->name); + f->write(name.data(), name.size()); + f->put('\n'); + } + i++; + } + i = 0; + for (auto bit : inputs) { + if (SigSpec(bit).is_wire()) { + char buf[32]; + snprintf(buf, sizeof(buf) - 1, "i%d ", i); + f->write(buf, strlen(buf)); + std::string name = RTLIL::unescape_id(bit.wire->name); + f->write(name.data(), name.size()); + f->put('\n'); + } + i++; + } + } +}; + +struct XAigerAnalysis : Index { + const static constexpr int EMPTY_LIT = -1; + + XAigerAnalysis() + { + allow_blackboxes = true; + + // Disable const folding and strashing as literal values are not unique + const_folding = false; + strashing = false; + } + + static int negate(int lit) + { + return lit; + } + + int emit_gate(int a, int b) + { + return max(a, b) + 1; + } + + pool seen; + + bool visit_hook(int idx, HierCursor &cursor, SigBit bit) + { + log_assert(cursor.is_top()); // TOOD: fix analyzer to work with hierarchy + + if (bit.wire->port_input && !bit.wire->port_output) + return false; + + Cell *driver = bit.wire->driverCell(); + Module *mod = design->module(driver->type); + if (!mod || !mod->has_attribute(ID::abc9_box_id)) + return false; + + int max = 1; + for (auto wire : mod->wires()) { + if (wire->port_input && !wire->port_output) { + SigSpec port = driver->getPort(wire->name); + for (int i = 0; i < std::min(wire->width, port.size()); i++) { + int ilevel = visit(cursor, port[i]); + max = std::max(max, ilevel + 1); + } + } + } + lits[idx] = max; + + if (!seen.count(driver)) + seen.insert(driver); + + return true; + } + + void analyze(Module *top) + { + setup(top); + + for (auto id : top->ports) { + Wire *w = top->wire(id); + log_assert(w); + if (w->port_input && !w->port_output) + for (int i = 0; i < w->width; i++) + pi_literal(SigBit(w, i)) = 0; + } + + HierCursor cursor; + for (auto box : top_minfo->found_blackboxes) { + Module *def = design->module(box->type); + if (!(def && def->has_attribute(ID::abc9_box_id))) + for (auto &conn : box->connections_) + if (box->port_dir(conn.first) != RTLIL::PD_INPUT) + for (auto bit : conn.second) + pi_literal(bit, &cursor) = 0; + } + + for (auto w : top->wires()) + if (w->port_output) { + for (auto bit : SigSpec(w)) + (void) eval_po(bit); + } + + for (auto box : top_minfo->found_blackboxes) { + Module *def = design->module(box->type); + if (!(def && def->has_attribute(ID::abc9_box_id))) + for (auto &conn : box->connections_) + if (box->port_dir(conn.first) == RTLIL::PD_INPUT) + for (auto bit : conn.second) + (void) eval_po(bit); + } + } +}; + +struct XAigerWriter : AigerWriter { + XAigerWriter() + { + allow_blackboxes = true; + } + + bool mapping_prep = false; + pool keep_wires; + std::ofstream map_file; + + typedef std::pair HierBit; + 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. + int proper_pos_counter = 0; + + pool driven_by_opaque_box; + + void ensure_pi(SigBit bit, HierCursor cursor={}, + bool box_port=false) + { + Lit &lit = pi_literal(bit, &cursor); + if (lit == EMPTY_LIT) { + lit = lit_counter; + pis.push_back(std::make_pair(bit, cursor)); + lit_counter += 2; + if (map_file.is_open() && !box_port) { + log_assert(cursor.is_top()); // TODO + driven_by_opaque_box.insert(bit); + map_file << "pi " << pis.size() - 1 << " " << bit.offset + << " " << bit.wire->name.c_str() << "\n"; + } + } else { + log_assert(!box_port); + } + } + + bool is_pi(SigBit bit, HierCursor cursor={}) + { + return pi_literal(bit, &cursor) != EMPTY_LIT; + } + + void pad_pi() + { + pis.push_back(std::make_pair(RTLIL::Sx, HierCursor{})); + lit_counter += 2; + } + + void append_opaque_box_ports(Cell *box, HierCursor &cursor, bool inputs) + { + for (auto &conn : box->connections_) { + bool is_input = box->port_dir(conn.first) == RTLIL::PD_INPUT; + + if (is_input && inputs) { + int bitp = 0; + for (auto bit : conn.second) { + if (!bit.wire) { + bitp++; + continue; + } + + // Inputs to opaque boxes are proper POs as far as abc is concerned + if (map_file.is_open()) { + log_assert(cursor.is_top()); + map_file << "pseudopo " << proper_pos_counter << " " << bitp + << " " << box->name.c_str() + << " " << conn.first.c_str() << "\n"; + } + proper_pos_counter++; + pos.push_back(std::make_pair(bit, cursor)); + + if (mapping_prep) + conn.second[bitp] = RTLIL::Sx; + + bitp++; + } + } else if (!is_input && !inputs) { + for (auto &bit : conn.second) { + if (!bit.wire || (bit.wire->port_input && !bit.wire->port_output)) + log_error("Bad connection %s/%s ~ %s\n", log_id(box), log_id(conn.first), log_signal(conn.second)); + + + ensure_pi(bit, cursor); + keep_wires.insert(bit.wire); + } + } + } + } + + RTLIL::Module *holes_module; + + std::stringstream h_buffer; + static void write_be32(std::ostream &buffer, uint32_t u32) + { + typedef unsigned char uchar; + unsigned char u32_be[4] = { + (uchar) (u32 >> 24), (uchar) (u32 >> 16), (uchar) (u32 >> 8), (uchar) u32 + }; + buffer.write((char *) u32_be, sizeof(u32_be)); + } + + void prep_boxes(int pending_pos_num) + { + XAigerAnalysis analysis; + log_debug("preforming analysis on '%s'\n", log_id(top)); + analysis.analyze(top); + log_debug("analysis on '%s' done\n", log_id(top)); + + // boxes which have timing data, maybe a whitebox model + std::vector> nonopaque_boxes; + // boxes which are fully opaque + std::vector> opaque_boxes; + + log_debug("found boxes:\n"); + visit_hierarchy([&](HierCursor &cursor) { + auto &minfo = cursor.leaf_minfo(*this); + + for (auto box : minfo.found_blackboxes) { + log_debug(" - %s.%s (type %s): ", cursor.path(), + RTLIL::unescape_id(box->name), + log_id(box->type)); + + Module *box_module = design->module(box->type), *box_derived; + + if (box_module && !box->parameters.empty()) { + // TODO: This is potentially costly even if a cached derivation exists + box_derived = design->module(box_module->derive(design, box->parameters)); + log_assert(box_derived); + } else { + box_derived = box_module; + } + + if (box_derived && box_derived->has_attribute(ID::abc9_box_id)) { + // This is an ABC9 box, we have timing data, maybe even a whitebox model + // These need to go last in the AIGER port list. + nonopaque_boxes.push_back(std::make_tuple(cursor, box, box_derived)); + log_debug("non-opaque\n"); + } else { + opaque_boxes.push_back(std::make_tuple(cursor, box, box_derived)); + log_debug("opaque\n"); + } + } + }); + + for (auto [cursor, box, def] : opaque_boxes) + append_opaque_box_ports(box, cursor, false); + + holes_module = design->addModule(NEW_ID); + std::vector holes_pis; + int boxes_ci_num = 0, boxes_co_num = 0; + + int box_seq = 0; + + std::vector boxes_order(analysis.seen.begin(), analysis.seen.end()); + std::reverse(boxes_order.begin(), boxes_order.end()); + + nonopaque_boxes.clear(); + for (auto box : boxes_order) { + HierCursor cursor; + Module *def = design->module(box->type); + nonopaque_boxes.push_back(std::make_tuple(cursor, box, def)); + } + + for (auto [cursor, box, def] : nonopaque_boxes) { + // use `def->name` not `box->type` as we want the derived type + Cell *holes_wb = holes_module->addCell(NEW_ID, def->name); + int holes_pi_idx = 0; + + if (map_file.is_open()) { + log_assert(cursor.is_top()); + map_file << "box " << box_seq << " " << box->name.c_str() << "\n"; + } + box_seq++; + + for (auto port_id : def->ports) { + Wire *port = def->wire(port_id); + log_assert(port); + + SigSpec conn = box->hasPort(port_id) ? box->getPort(port_id) : SigSpec{}; + + if (port->port_input && !port->port_output) { + // primary + for (int i = 0; i < port->width; i++) { + SigBit bit; + if (i < conn.size()) { + bit = conn[i]; + } else { + // FIXME: hierarchical path + log_warning("connection on port %s[%d] of instance %s (type %s) missing, using 1'bx\n", + log_id(port_id), i, log_id(box), log_id(box->type)); + bit = RTLIL::Sx; + } + + // Nonopaque box inputs come first and are not part of + // the PO numbering used by the mapping file. + pos.push_back(std::make_pair(bit, cursor)); + } + boxes_co_num += port->width; + + if (mapping_prep && !conn.empty()) + box->setPort(port_id, SigSpec(RTLIL::Sx, conn.size())); + + // holes + SigSpec in_conn; + for (int i = 0; i < port->width; i++) { + while (holes_pi_idx >= (int) holes_pis.size()) { + Wire *w = holes_module->addWire(NEW_ID, 1); + w->port_input = true; + holes_module->ports.push_back(w->name); + holes_pis.push_back(w); + } + in_conn.append(holes_pis[holes_pi_idx]); + holes_pi_idx++; + } + holes_wb->setPort(port_id, in_conn); + } else if (port->port_output) { + // primary + for (int i = 0; i < port->width; i++) { + SigBit bit; + if (i < conn.size() && conn[i].is_wire()) { + bit = conn[i]; + } else { + // FIXME: hierarchical path + log_warning("connection on port %s[%d] of instance %s (type %s) missing\n", + log_id(port_id), i, log_id(box), log_id(box->type)); + pad_pi(); + continue; + } + + ensure_pi(bit, cursor, true); + keep_wires.insert(bit.wire); + } + boxes_ci_num += port->width; + + // holes + Wire *w = holes_module->addWire(NEW_ID, port->width); + w->port_output = true; + holes_module->ports.push_back(w->name); + holes_wb->setPort(port_id, w); + } else { + log_error("Ambiguous port direction on %s/%s\n", + log_id(box->type), log_id(port_id)); + } + } + } + + for (auto [cursor, box, def] : opaque_boxes) + append_opaque_box_ports(box, cursor, true); + + write_be32(h_buffer, 1); + write_be32(h_buffer, pis.size()); + log_debug("ciNum = %zu\n", pis.size()); + write_be32(h_buffer, pending_pos_num + pos.size()); + log_debug("coNum = %zu\n", pending_pos_num + pos.size()); + write_be32(h_buffer, pis.size() - boxes_ci_num); + log_debug("piNum = %zu\n", pis.size() - boxes_ci_num); + write_be32(h_buffer, pending_pos_num + pos.size() - boxes_co_num); + log_debug("poNum = %zu\n", pending_pos_num + pos.size() - boxes_co_num); + write_be32(h_buffer, nonopaque_boxes.size()); + + box_seq = 0; + for (auto [cursor, box, def] : nonopaque_boxes) { + int box_ci_num = 0, box_co_num = 0; + for (auto port_id : def->ports) { + Wire *port = def->wire(port_id); + log_assert(port); + if (port->port_input && !port->port_output) { + box_co_num += port->width; + } else if (port->port_output) { + box_ci_num += port->width; + } else { + log_abort(); + } + } + + write_be32(h_buffer, box_co_num); + write_be32(h_buffer, box_ci_num); + write_be32(h_buffer, def->attributes.at(ID::abc9_box_id).as_int()); + write_be32(h_buffer, box_seq++); + } + } + + void clear_boxes() + { + design->remove(holes_module); + } + + void write(std::ostream *f) { + reset_counters(); + + for (auto w : top->wires()) + if (w->port_input && !w->port_output) + for (int i = 0; i < w->width; i++) + ensure_pi(SigBit(w, i)); + + int proper_po_num = 0; + for (auto w : top->wires()) + if (w->port_output) + proper_po_num += w->width; + + prep_boxes(proper_po_num); + for (auto w : top->wires()) + if (w->port_output) + for (int i = 0; i < w->width; i++) { + // When a module output is directly driven by an opaque box, we + // don't emit it to the mapping file to aid re-integration, but we + // do emit a proper PO. + if (map_file.is_open() && !driven_by_opaque_box.count(SigBit(w, i))) { + map_file << "po " << proper_pos_counter << " " << i + << " " << w->name.c_str() << "\n"; + } + proper_pos_counter++; + pos.push_back(std::make_pair(SigBit(w, i), HierCursor{})); + } + + this->f = f; + // start with the header + ninputs = pis.size(); + noutputs = pos.size(); + write_header(); + // insert padding where output literals will go (once known) + for (auto _ : pos) { + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", 0); + f->write(buf, strlen(buf)); + } + auto data_start = f->tellp(); + + // now the guts + std::vector outlits; + for (auto &pair : pos) + outlits.push_back(eval_po(pair.first, &pair.second)); + + // revisit header and the list of outputs + f->seekp(0); + ninputs = pis.size(); + noutputs = pos.size(); + write_header(); + for (auto lit : outlits) { + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", lit); + f->write(buf, strlen(buf)); + } + // double check we arrived at the same offset for the + // main data section + log_assert(data_start == f->tellp()); + + // extensions + f->seekp(0, std::ios::end); + + f->put('c'); + + // insert empty 'r' and 's' sections (abc crashes if we provide 'a' without those) + f->put('r'); + write_be32(*f, 4); + write_be32(*f, 0); + f->put('s'); + write_be32(*f, 4); + write_be32(*f, 0); + + f->put('h'); + // TODO: get rid of std::string copy + std::string h_buffer_str = h_buffer.str(); + write_be32(*f, h_buffer_str.size()); + f->write(h_buffer_str.data(), h_buffer_str.size()); + +#if 1 + f->put('a'); + write_be32(*f, 0); // size to be filled later + auto holes_aiger_start = f->tellp(); + { + AigerWriter holes_writer; + holes_writer.flatten = true; + holes_writer.inline_whiteboxes = true; + holes_writer.setup(holes_module); + holes_writer.write(f); + } + auto holes_aiger_size = f->tellp() - holes_aiger_start; + f->seekp(holes_aiger_start, std::ios::beg); + f->seekp(-4, std::ios::cur); + write_be32(*f, holes_aiger_size); +#endif + f->seekp(0, std::ios::end); + + if (mapping_prep) { + std::vector to_remove_cells; + for (auto cell : top->cells()) + if (!top_minfo->found_blackboxes.count(cell)) + to_remove_cells.push_back(cell); + for (auto cell : to_remove_cells) + top->remove(cell); + pool to_remove; + for (auto wire : top->wires()) + if (!wire->port_input && !wire->port_output && !keep_wires.count(wire)) + to_remove.insert(wire); + top->remove(to_remove); + } + + clear_boxes(); + } +}; + +struct Aiger2Backend : Backend { + Aiger2Backend() : Backend("aiger2", "(experimental) write design to AIGER file") + { + experimental(); + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_aiger2 [options] [filename]\n"); + log("\n"); + log("Write the selected module to an AIGER file.\n"); + log("\n"); + log(" -strash\n"); + 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("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) { + log("\n "); + col = 0; + } + col += pair.first.size() + 2; + log("%s, ", log_id(pair.first)); + } + 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) { + log("\n "); + col = 0; + } + col += pair.first.size() + 2; + log("%s, ", log_id(pair.first)); + } + log("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, Design *design) override + { + log_header(design, "Executing AIGER2 backend.\n"); + + size_t argidx; + AigerWriter writer; + writer.const_folding = true; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-strash") + writer.strashing = true; + else if (args[argidx] == "-flatten") + writer.flatten = true; + else + break; + } + extra_args(f, filename, args, argidx); + + Module *top = design->top_module(); + + if (!top || !design->selected_whole_module(top)) + log_cmd_error("No top module selected\n"); + + design->bufNormalize(true); + writer.setup(top); + writer.write(f); + + // we are leaving the sacred land, un-bufnormalize + // (if not, this will lead to bugs: the buf-normalized + // flag must not be kept on past the code that can work + // with it) + design->bufNormalize(false); + } +} Aiger2Backend; + +struct XAiger2Backend : Backend { + XAiger2Backend() : Backend("xaiger2", "(experimental) write module to XAIGER file") + { + experimental(); + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_xaiger2 [options] [filename]\n"); + log("\n"); + log("Write the selected module to a XAIGER file including the 'h' and 'a' extensions\n"); + log("with box information for ABC.\n"); + log("\n"); + log(" -strash\n"); + 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("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, Design *design) override + { + log_header(design, "Executing XAIGER2 backend.\n"); + + size_t argidx; + XAigerWriter writer; + std::string map_filename; + writer.const_folding = true; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-strash") + writer.strashing = true; + else if (args[argidx] == "-flatten") + writer.flatten = true; + else if (args[argidx] == "-mapping_prep") + writer.mapping_prep = true; + else if (args[argidx] == "-map2" && argidx + 1 < args.size()) + map_filename = args[++argidx]; + else + break; + } + extra_args(f, filename, args, argidx); + + Module *top = design->top_module(); + + if (!top || !design->selected_whole_module(top)) + log_cmd_error("No top module selected\n"); + + if (!map_filename.empty()) { + writer.map_file.open(map_filename); + if (!writer.map_file) + log_cmd_error("Failed to open '%s' for writing\n", map_filename); + } + + design->bufNormalize(true); + writer.setup(top); + writer.write(f); + + // we are leaving the sacred land, un-bufnormalize + // (if not, this will lead to bugs: the buf-normalized + // flag must not be kept on past the code that can work + // with it) + design->bufNormalize(false); + } +} XAiger2Backend; + +PRIVATE_NAMESPACE_END diff --git a/backends/blif/blif.cc b/backends/blif/blif.cc index 788b7f951..ab7861802 100644 --- a/backends/blif/blif.cc +++ b/backends/blif/blif.cc @@ -157,14 +157,14 @@ struct BlifDumper f << stringf("%c", ch); f << stringf("\"\n"); } else - f << stringf("%s\n", param.second.as_string().c_str()); + f << stringf("%s\n", param.second.as_string()); } } void dump() { f << stringf("\n"); - f << stringf(".model %s\n", str(module->name).c_str()); + f << stringf(".model %s\n", str(module->name)); std::map inputs, outputs; @@ -179,7 +179,7 @@ struct BlifDumper for (auto &it : inputs) { RTLIL::Wire *wire = it.second; for (int i = 0; i < wire->width; i++) - f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str()); + f << stringf(" %s", str(RTLIL::SigSpec(wire, i))); } f << stringf("\n"); @@ -187,7 +187,7 @@ struct BlifDumper for (auto &it : outputs) { RTLIL::Wire *wire = it.second; for (int i = 0; i < wire->width; i++) - f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str()); + f << stringf(" %s", str(RTLIL::SigSpec(wire, i))); } f << stringf("\n"); @@ -200,7 +200,7 @@ struct BlifDumper if (!config->impltf_mode) { if (!config->false_type.empty()) { if (config->false_type == "+") - f << stringf(".names %s\n", config->false_out.c_str()); + f << stringf(".names %s\n", config->false_out); else if (config->false_type != "-") f << stringf(".%s %s %s=$false\n", subckt_or_gate(config->false_type), config->false_type.c_str(), config->false_out.c_str()); @@ -208,7 +208,7 @@ struct BlifDumper f << stringf(".names $false\n"); if (!config->true_type.empty()) { if (config->true_type == "+") - f << stringf(".names %s\n1\n", config->true_out.c_str()); + f << stringf(".names %s\n1\n", config->true_out); else if (config->true_type != "-") f << stringf(".%s %s %s=$true\n", subckt_or_gate(config->true_type), config->true_type.c_str(), config->true_out.c_str()); @@ -216,7 +216,7 @@ struct BlifDumper f << stringf(".names $true\n1\n"); if (!config->undef_type.empty()) { if (config->undef_type == "+") - f << stringf(".names %s\n", config->undef_out.c_str()); + f << stringf(".names %s\n", config->undef_out); else if (config->undef_type != "-") f << stringf(".%s %s %s=$undef\n", subckt_or_gate(config->undef_type), config->undef_type.c_str(), config->undef_out.c_str()); @@ -331,31 +331,31 @@ struct BlifDumper } if (!config->icells_mode && cell->type == ID($_FF_)) { - f << stringf(".latch %s %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + f << stringf(".latch %s %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DFF_N_)) { - f << stringf(".latch %s %s fe %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + f << stringf(".latch %s %s fe %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)), str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DFF_P_)) { - f << stringf(".latch %s %s re %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + f << stringf(".latch %s %s re %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)), str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DLATCH_N_)) { - f << stringf(".latch %s %s al %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + f << stringf(".latch %s %s al %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)), str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_DLATCH_P_)) { - f << stringf(".latch %s %s ah %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(), + f << stringf(".latch %s %s ah %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)), str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str()); goto internal_cell; } @@ -366,10 +366,10 @@ struct BlifDumper auto width = cell->parameters.at(ID::WIDTH).as_int(); log_assert(inputs.size() == width); for (int i = width-1; i >= 0; i--) - f << stringf(" %s", str(inputs.extract(i, 1)).c_str()); + f << stringf(" %s", str(inputs.extract(i, 1))); auto &output = cell->getPort(ID::Y); log_assert(output.size() == 1); - f << stringf(" %s", str(output).c_str()); + f << stringf(" %s", str(output)); f << stringf("\n"); RTLIL::SigSpec mask = cell->parameters.at(ID::LUT); for (int i = 0; i < (1 << width); i++) @@ -387,15 +387,15 @@ struct BlifDumper auto &inputs = cell->getPort(ID::A); auto width = cell->parameters.at(ID::WIDTH).as_int(); auto depth = cell->parameters.at(ID::DEPTH).as_int(); - vector table = cell->parameters.at(ID::TABLE).bits; + vector table = cell->parameters.at(ID::TABLE).to_bits(); while (GetSize(table) < 2*width*depth) table.push_back(State::S0); log_assert(inputs.size() == width); for (int i = 0; i < width; i++) - f << stringf(" %s", str(inputs.extract(i, 1)).c_str()); + f << stringf(" %s", str(inputs.extract(i, 1))); auto &output = cell->getPort(ID::Y); log_assert(output.size() == 1); - f << stringf(" %s", str(output).c_str()); + f << stringf(" %s", str(output)); f << stringf("\n"); for (int i = 0; i < depth; i++) { for (int j = 0; j < width; j++) { @@ -410,11 +410,11 @@ struct BlifDumper goto internal_cell; } - f << stringf(".%s %s", subckt_or_gate(cell->type.str()), str(cell->type).c_str()); + f << stringf(".%s %s", subckt_or_gate(cell->type.str()), str(cell->type)); for (auto &conn : cell->connections()) { if (conn.second.size() == 1) { - f << stringf(" %s=%s", str(conn.first).c_str(), str(conn.second[0]).c_str()); + f << stringf(" %s=%s", str(conn.first), str(conn.second[0])); continue; } @@ -423,11 +423,11 @@ struct BlifDumper if (w == nullptr) { for (int i = 0; i < GetSize(conn.second); i++) - f << stringf(" %s[%d]=%s", str(conn.first).c_str(), i, str(conn.second[i]).c_str()); + f << stringf(" %s[%d]=%s", str(conn.first), i, str(conn.second[i])); } else { for (int i = 0; i < std::min(GetSize(conn.second), GetSize(w)); i++) { SigBit sig(w, i); - f << stringf(" %s[%d]=%s", str(conn.first).c_str(), sig.wire->upto ? + f << stringf(" %s[%d]=%s", str(conn.first), sig.wire->upto ? sig.wire->start_offset+sig.wire->width-sig.offset-1 : sig.wire->start_offset+sig.offset, str(conn.second[i]).c_str()); } @@ -436,7 +436,7 @@ struct BlifDumper f << stringf("\n"); if (config->cname_mode) - f << stringf(".cname %s\n", str(cell->name).c_str()); + f << stringf(".cname %s\n", str(cell->name)); if (config->attr_mode) dump_params(".attr", cell->attributes); if (config->param_mode) @@ -445,7 +445,7 @@ struct BlifDumper if (0) { internal_cell: if (config->iname_mode) - f << stringf(".cname %s\n", str(cell->name).c_str()); + f << stringf(".cname %s\n", str(cell->name)); if (config->iattr_mode) dump_params(".attr", cell->attributes); } @@ -461,12 +461,12 @@ struct BlifDumper continue; if (config->conn_mode) - f << stringf(".conn %s %s\n", str(rhs_bit).c_str(), str(lhs_bit).c_str()); + f << stringf(".conn %s %s\n", str(rhs_bit), str(lhs_bit)); else if (!config->buf_type.empty()) - f << stringf(".%s %s %s=%s %s=%s\n", subckt_or_gate(config->buf_type), config->buf_type.c_str(), + f << stringf(".%s %s %s=%s %s=%s\n", subckt_or_gate(config->buf_type), config->buf_type, config->buf_in.c_str(), str(rhs_bit).c_str(), config->buf_out.c_str(), str(lhs_bit).c_str()); else - f << stringf(".names %s %s\n1 1\n", str(rhs_bit).c_str(), str(lhs_bit).c_str()); + f << stringf(".names %s %s\n1 1\n", str(rhs_bit), str(lhs_bit)); } f << stringf(".end\n"); @@ -649,7 +649,7 @@ struct BlifBackend : public Backend { if (module->get_bool_attribute(ID::top)) top_module_name = module->name.str(); - *f << stringf("# Generated by %s\n", yosys_version_str); + *f << stringf("# Generated by %s\n", yosys_maybe_version()); std::vector mod_list; @@ -674,7 +674,7 @@ struct BlifBackend : public Backend { } if (!top_module_name.empty()) - log_error("Can't find top module `%s'!\n", top_module_name.c_str()); + log_error("Can't find top module `%s'!\n", top_module_name); for (auto module : mod_list) BlifDumper::dump(*f, module, design, config); diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc index 9cfd967e5..ca7cf8a7f 100644 --- a/backends/btor/btor.cc +++ b/backends/btor/btor.cc @@ -30,6 +30,7 @@ #include "kernel/mem.h" #include "kernel/json.h" #include "kernel/yw.h" +#include "kernel/utils.h" #include USING_YOSYS_NAMESPACE @@ -97,24 +98,22 @@ struct BtorWorker vector ywmap_states; dict ywmap_clock_bits; dict ywmap_clock_inputs; + vector ywmap_asserts; + vector ywmap_assumes; PrettyJson ywmap_json; - void btorf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3)) + template + void btorf(FmtString...> fmt, const Args &... args) { - va_list ap; - va_start(ap, fmt); - f << indent << vstringf(fmt, ap); - va_end(ap); + f << indent << fmt.format(args...); } - void infof(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3)) + template + void infof(FmtString...> fmt, const Args &... args) { - va_list ap; - va_start(ap, fmt); - info_lines.push_back(vstringf(fmt, ap)); - va_end(ap); + info_lines.push_back(fmt.format(args...)); } template @@ -128,7 +127,7 @@ struct BtorWorker std::replace(src.begin(), src.end(), ' ', '_'); if (srcsymbols.count(src) || module->count_id("\\" + src)) { for (int i = 1;; i++) { - string s = stringf("%s-%d", src.c_str(), i); + string s = stringf("%s-%d", src, i); if (!srcsymbols.count(s) && !module->count_id("\\" + s)) { src = s; break; @@ -191,7 +190,7 @@ struct BtorWorker void btorf_push(const string &id) { if (verbose) { - f << indent << stringf(" ; begin %s\n", id.c_str()); + f << indent << stringf(" ; begin %s\n", id); indent += " "; } } @@ -200,7 +199,7 @@ struct BtorWorker { if (verbose) { indent = indent.substr(4); - f << indent << stringf(" ; end %s\n", id.c_str()); + f << indent << stringf(" ; end %s\n", id); } } @@ -245,7 +244,7 @@ struct BtorWorker string cell_list; for (auto c : cell_recursion_guard) cell_list += stringf("\n %s", log_id(c)); - log_error("Found topological loop while processing cell %s. Active cells:%s\n", log_id(cell), cell_list.c_str()); + log_error("Found topological loop while processing cell %s. Active cells:%s\n", log_id(cell), cell_list); } cell_recursion_guard.insert(cell); @@ -321,12 +320,12 @@ struct BtorWorker btorf("%d slt %d %d %d\n", nid_b_ltz, sid_bit, nid_b, nid_zero); nid = next_nid++; - btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_b_ltz, nid_l, nid_r, getinfo(cell).c_str()); + btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_b_ltz, nid_l, nid_r, getinfo(cell)); } else { nid = next_nid++; - btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %s %d %d %d%s\n", nid, btor_op, sid, nid_a, nid_b, getinfo(cell)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -367,7 +366,7 @@ struct BtorWorker int sid = get_bv_sid(width); int nid = next_nid++; - btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op, sid, nid_a, nid_b, getinfo(cell)); SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -393,12 +392,12 @@ struct BtorWorker if (cell->type == ID($_ANDNOT_)) { btorf("%d not %d %d\n", nid1, sid, nid_b); - btorf("%d and %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str()); + btorf("%d and %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell)); } if (cell->type == ID($_ORNOT_)) { btorf("%d not %d %d\n", nid1, sid, nid_b); - btorf("%d or %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str()); + btorf("%d or %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -420,13 +419,13 @@ struct BtorWorker if (cell->type == ID($_OAI3_)) { btorf("%d or %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d and %d %d %d\n", nid2, sid, nid1, nid_c); - btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell)); } if (cell->type == ID($_AOI3_)) { btorf("%d and %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d or %d %d %d\n", nid2, sid, nid1, nid_c); - btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -451,14 +450,14 @@ struct BtorWorker btorf("%d or %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d or %d %d %d\n", nid2, sid, nid_c, nid_d); btorf("%d and %d %d %d\n", nid3, sid, nid1, nid2); - btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell)); } if (cell->type == ID($_AOI4_)) { btorf("%d and %d %d %d\n", nid1, sid, nid_a, nid_b); btorf("%d and %d %d %d\n", nid2, sid, nid_c, nid_d); btorf("%d or %d %d %d\n", nid3, sid, nid1, nid2); - btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -490,9 +489,9 @@ struct BtorWorker int nid = next_nid++; if (cell->type.in(ID($lt), ID($le), ID($ge), ID($gt))) { - btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op, sid, nid_a, nid_b, getinfo(cell)); } else { - btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %s %d %d %d%s\n", nid, btor_op, sid, nid_a, nid_b, getinfo(cell)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -508,7 +507,7 @@ struct BtorWorker goto okay; } - if (cell->type.in(ID($not), ID($neg), ID($_NOT_), ID($pos))) + if (cell->type.in(ID($not), ID($neg), ID($_NOT_), ID($pos), ID($buf), ID($_BUF_))) { string btor_op; if (cell->type.in(ID($not), ID($_NOT_))) btor_op = "not"; @@ -520,14 +519,14 @@ struct BtorWorker int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); SigSpec sig = sigmap(cell->getPort(ID::Y)); - // the $pos cell just passes through, all other cells need an actual operation applied + // the $pos/$buf cells just pass through, all other cells need an actual operation applied int nid = nid_a; - if (cell->type != ID($pos)) + if (!cell->type.in(ID($pos), ID($buf), ID($_BUF_))) { log_assert(!btor_op.empty()); int sid = get_bv_sid(width); nid = next_nid++; - btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell)); } if (GetSize(sig) < width) { @@ -567,9 +566,9 @@ struct BtorWorker int nid = next_nid++; if (btor_op != "not") - btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str()); + btorf("%d %s %d %d %d%s\n", nid, btor_op, sid, nid_a, nid_b, getinfo(cell)); else - btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell)); SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -600,11 +599,11 @@ struct BtorWorker if (cell->type == ID($reduce_xnor)) { int nid2 = next_nid++; - btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell)); btorf("%d not %d %d\n", nid2, sid, nid); nid = nid2; } else { - btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str()); + btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); @@ -639,9 +638,9 @@ struct BtorWorker int tmp = nid; nid = next_nid++; btorf("%d ite %d %d %d %d\n", tmp, sid, nid_s, nid_b, nid_a); - btorf("%d not %d %d%s\n", nid, sid, tmp, getinfo(cell).c_str()); + btorf("%d not %d %d%s\n", nid, sid, tmp, getinfo(cell)); } else { - btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_s, nid_b, nid_a, getinfo(cell).c_str()); + btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_s, nid_b, nid_a, getinfo(cell)); } add_nid_sig(nid, sig_y); @@ -664,7 +663,7 @@ struct BtorWorker int nid_s = get_sig_nid(sig_s.extract(i)); int nid2 = next_nid++; if (i == GetSize(sig_s)-1) - btorf("%d ite %d %d %d %d%s\n", nid2, sid, nid_s, nid_b, nid, getinfo(cell).c_str()); + btorf("%d ite %d %d %d %d%s\n", nid2, sid, nid_s, nid_b, nid, getinfo(cell)); else btorf("%d ite %d %d %d %d\n", nid2, sid, nid_s, nid_b, nid); nid = nid2; @@ -708,12 +707,13 @@ struct BtorWorker } } - Const initval; + Const::Builder initval_bits(GetSize(sig_q)); for (int i = 0; i < GetSize(sig_q); i++) if (initbits.count(sig_q[i])) - initval.bits.push_back(initbits.at(sig_q[i]) ? State::S1 : State::S0); + initval_bits.push_back(initbits.at(sig_q[i]) ? State::S1 : State::S0); else - initval.bits.push_back(State::Sx); + initval_bits.push_back(State::Sx); + Const initval = initval_bits.build(); int nid_init_val = -1; @@ -752,7 +752,7 @@ struct BtorWorker int sid = get_bv_sid(GetSize(sig_y)); int nid = next_nid++; - btorf("%d state %d%s\n", nid, sid, getinfo(cell).c_str()); + btorf("%d state %d%s\n", nid, sid, getinfo(cell)); ywmap_state(sig_y); @@ -775,7 +775,7 @@ struct BtorWorker int one_nid = get_sig_nid(State::S1); int zero_nid = get_sig_nid(State::S0); initstate_nid = next_nid++; - btorf("%d state %d%s\n", initstate_nid, sid, getinfo(cell).c_str()); + btorf("%d state %d%s\n", initstate_nid, sid, getinfo(cell)); btorf("%d init %d %d %d\n", next_nid++, sid, initstate_nid, one_nid); btorf("%d next %d %d %d\n", next_nid++, sid, initstate_nid, zero_nid); @@ -832,7 +832,10 @@ struct BtorWorker } } - if (constword) + // If not fully defined, undef bits should be able to take a + // different value for each address so we can't initialise from + // one value (and btor2parser doesn't like it) + if (constword && firstword.is_fully_def()) { if (verbose) btorf("; initval = %s\n", log_signal(firstword)); @@ -1039,15 +1042,16 @@ struct BtorWorker { if (bit.wire == nullptr) { - Const c(bit.data); - - while (i+GetSize(c) < GetSize(sig) && sig[i+GetSize(c)].wire == nullptr) - c.bits.push_back(sig[i+GetSize(c)].data); + Const::Builder c_bits; + c_bits.push_back(bit.data); + while (i + GetSize(c_bits) < GetSize(sig) && sig[i + GetSize(c_bits)].wire == nullptr) + c_bits.push_back(sig[i + GetSize(c_bits)].data); + Const c = c_bits.build(); if (consts.count(c) == 0) { int sid = get_bv_sid(GetSize(c)); int nid = next_nid++; - btorf("%d const %d %s\n", nid, sid, c.as_string().c_str()); + btorf("%d const %d %s\n", nid, sid, c.as_string()); consts[c] = nid; nid_width[nid] = GetSize(c); } @@ -1077,6 +1081,7 @@ struct BtorWorker btorf("%d input %d\n", nid, sid); ywmap_input(s); nid_width[nid] = GetSize(s); + add_nid_sig(nid, s); for (int j = 0; j < GetSize(s); j++) nidbits.push_back(make_pair(nid, j)); @@ -1210,7 +1215,7 @@ struct BtorWorker int sid = get_bv_sid(GetSize(sig)); int nid = next_nid++; - btorf("%d input %d%s\n", nid, sid, getinfo(wire).c_str()); + btorf("%d input %d%s\n", nid, sid, getinfo(wire)); ywmap_input(wire); add_nid_sig(nid, sig); @@ -1255,7 +1260,7 @@ struct BtorWorker btorf_push(stringf("output %s", log_id(wire))); int nid = get_sig_nid(wire); - btorf("%d output %d%s\n", next_nid++, nid, getinfo(wire).c_str()); + btorf("%d output %d%s\n", next_nid++, nid, getinfo(wire)); btorf_pop(stringf("output %s", log_id(wire))); } @@ -1277,6 +1282,8 @@ struct BtorWorker btorf("%d or %d %d %d\n", nid_a_or_not_en, sid, nid_a, nid_not_en); btorf("%d constraint %d\n", nid, nid_a_or_not_en); + if (ywmap_json.active()) ywmap_assumes.emplace_back(cell); + btorf_pop(log_id(cell)); } @@ -1297,10 +1304,12 @@ struct BtorWorker bad_properties.push_back(nid_en_and_not_a); } else { if (cover_mode) { - infof("bad %d%s\n", nid_en_and_not_a, getinfo(cell, true).c_str()); + infof("bad %d%s\n", nid_en_and_not_a, getinfo(cell, true)); } else { int nid = next_nid++; - btorf("%d bad %d%s\n", nid, nid_en_and_not_a, getinfo(cell, true).c_str()); + btorf("%d bad %d%s\n", nid, nid_en_and_not_a, getinfo(cell, true)); + + if (ywmap_json.active()) ywmap_asserts.emplace_back(cell); } } @@ -1322,7 +1331,7 @@ struct BtorWorker bad_properties.push_back(nid_en_and_a); } else { int nid = next_nid++; - btorf("%d bad %d%s\n", nid, nid_en_and_a, getinfo(cell, true).c_str()); + btorf("%d bad %d%s\n", nid, nid_en_and_a, getinfo(cell, true)); } btorf_pop(log_id(cell)); @@ -1343,7 +1352,7 @@ struct BtorWorker continue; int this_nid = next_nid++; - btorf("%d uext %d %d %d%s\n", this_nid, sid, nid, 0, getinfo(wire).c_str()); + btorf("%d uext %d %d %d%s\n", this_nid, sid, nid, 0, getinfo(wire)); if (info_clocks.count(nid)) info_clocks[this_nid] |= info_clocks[nid]; @@ -1366,7 +1375,7 @@ struct BtorWorker SigSpec sig = sigmap(cell->getPort(ID::D)); int nid_q = get_sig_nid(sig); int sid = get_bv_sid(GetSize(sig)); - btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell).c_str()); + btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell)); btorf_pop(stringf("next %s", log_id(cell))); } @@ -1425,7 +1434,7 @@ struct BtorWorker } int nid2 = next_nid++; - btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, (mem->cell ? getinfo(mem->cell) : getinfo(mem->mem)).c_str()); + btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, (mem->cell ? getinfo(mem->cell) : getinfo(mem->mem))); btorf_pop(stringf("next %s", log_id(mem->memid))); } @@ -1458,6 +1467,7 @@ struct BtorWorker log_assert(cursor == 0); log_assert(GetSize(todo) == 1); btorf("%d bad %d\n", nid, todo[cursor]); + // What do we do with ywmap_asserts when using single_bad? } } @@ -1484,7 +1494,7 @@ struct BtorWorker std::ofstream f; f.open(info_filename.c_str(), std::ofstream::trunc); if (f.fail()) - log_error("Can't open file `%s' for writing: %s\n", info_filename.c_str(), strerror(errno)); + log_error("Can't open file `%s' for writing: %s\n", info_filename, strerror(errno)); for (auto &it : info_lines) f << it; f.close(); @@ -1494,7 +1504,7 @@ struct BtorWorker { ywmap_json.begin_object(); ywmap_json.entry("version", "Yosys Witness BTOR map"); - ywmap_json.entry("generator", yosys_version_str); + ywmap_json.entry("generator", yosys_maybe_version()); ywmap_json.name("clocks"); ywmap_json.begin_array(); @@ -1523,6 +1533,18 @@ struct BtorWorker emit_ywmap_btor_sig(entry); ywmap_json.end_array(); + ywmap_json.name("asserts"); + ywmap_json.begin_array(); + for (Cell *cell : ywmap_asserts) + ywmap_json.value(witness_path(cell)); + ywmap_json.end_array(); + + ywmap_json.name("assumes"); + ywmap_json.begin_array(); + for (Cell *cell : ywmap_assumes) + ywmap_json.value(witness_path(cell)); + ywmap_json.end_array(); + ywmap_json.end_object(); } } @@ -1608,7 +1630,7 @@ struct BtorBackend : public Backend { log_cmd_error("No top module found.\n"); *f << stringf("; BTOR description generated by %s for module %s.\n", - yosys_version_str, log_id(topmod)); + yosys_maybe_version(), log_id(topmod)); BtorWorker(*f, topmod, verbose, single_bad, cover_mode, print_internal_names, info_filename, ywmap_filename); diff --git a/backends/btor/test_cells.sh b/backends/btor/test_cells.sh index f8bd79782..fc7f5b75c 100755 --- a/backends/btor/test_cells.sh +++ b/backends/btor/test_cells.sh @@ -10,11 +10,11 @@ cd test_cells.tmp for fn in test_*.il; do ../../../yosys -p " - read_ilang $fn + read_rtlil $fn rename gold gate synth - read_ilang $fn + read_rtlil $fn miter -equiv -make_assert -flatten gold gate main hierarchy -top main write_btor ${fn%.il}.btor diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 877a829d3..71913d2db 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -47,7 +47,7 @@ struct Scheduler { struct Vertex { T *data; Vertex *prev, *next; - pool preds, succs; + pool preds, succs; Vertex() : data(NULL), prev(this), next(this) {} Vertex(T *data) : data(data), prev(NULL), next(NULL) {} @@ -200,7 +200,7 @@ bool is_extending_cell(RTLIL::IdString type) bool is_inlinable_cell(RTLIL::IdString type) { return is_unary_cell(type) || is_binary_cell(type) || type.in( - ID($mux), ID($concat), ID($slice), ID($pmux), ID($bmux), ID($demux)); + ID($mux), ID($concat), ID($slice), ID($pmux), ID($bmux), ID($demux), ID($bwmux)); } bool is_ff_cell(RTLIL::IdString type) @@ -300,10 +300,10 @@ struct FlowGraph { }; std::vector nodes; - dict> wire_comb_defs, wire_sync_defs, wire_uses; - dict, hash_ptr_ops> node_comb_defs, node_sync_defs, node_uses; + dict> wire_comb_defs, wire_sync_defs, wire_uses; + dict> node_comb_defs, node_sync_defs, node_uses; dict wire_def_inlinable; - dict> wire_use_inlinable; + dict> wire_use_inlinable; dict bit_has_state; ~FlowGraph() @@ -328,7 +328,7 @@ struct FlowGraph { node_comb_defs[node].insert(chunk.wire); } } - for (auto bit : sig.bits()) + for (auto bit : sig) bit_has_state[bit] |= is_ff; // Only comb defs of an entire wire in the right order can be inlined. if (!is_ff && sig.is_wire()) { @@ -365,7 +365,7 @@ struct FlowGraph { return false; } - bool is_inlinable(const RTLIL::Wire *wire, const pool &nodes) const + bool is_inlinable(const RTLIL::Wire *wire, const pool &nodes) const { // Can the wire be inlined, knowing that the given nodes are reachable? if (nodes.size() != 1) @@ -606,22 +606,30 @@ std::vector split_by(const std::string &str, const std::string &sep return result; } -std::string escape_cxx_string(const std::string &input) +std::string escape_c_string(const std::string &input) { - std::string output = "\""; + std::string output; + output.push_back('"'); for (auto c : input) { if (::isprint(c)) { - if (c == '\\') + if (c == '\\' || c == '"') output.push_back('\\'); output.push_back(c); } else { - char l = c & 0xf, h = (c >> 4) & 0xf; - output.append("\\x"); - output.push_back((h < 10 ? '0' + h : 'a' + h - 10)); - output.push_back((l < 10 ? '0' + l : 'a' + l - 10)); + char l = c & 0x7, m = (c >> 3) & 0x7, h = (c >> 6) & 0x3; + output.append("\\"); + output.push_back('0' + h); + output.push_back('0' + m); + output.push_back('0' + l); } } output.push_back('"'); + return output; +} + +std::string escape_cxx_string(const std::string &input) +{ + std::string output = escape_c_string(input); if (output.find('\0') != std::string::npos) { output.insert(0, "std::string {"); output.append(stringf(", %zu}", input.size())); @@ -629,20 +637,6 @@ std::string escape_cxx_string(const std::string &input) return output; } -std::string basename(const std::string &filepath) -{ -#ifdef _WIN32 - const std::string dir_seps = "\\/"; -#else - const std::string dir_seps = "/"; -#endif - size_t sep_pos = filepath.find_last_of(dir_seps); - if (sep_pos != std::string::npos) - return filepath.substr(sep_pos + 1); - else - return filepath; -} - template std::string get_hdl_name(T *object) { @@ -762,7 +756,7 @@ struct CxxrtlWorker { // 1b. Generated identifiers for internal names (beginning with `$`) start with `i_`. // 2. An underscore is escaped with another underscore, i.e. `__`. // 3. Any other non-alnum character is escaped with underscores around its lowercase hex code, e.g. `@` as `_40_`. - std::string mangle_name(const RTLIL::IdString &name) + std::string mangle_name(RTLIL::IdString name) { std::string mangled; bool first = true; @@ -792,7 +786,7 @@ struct CxxrtlWorker { return mangled; } - std::string mangle_module_name(const RTLIL::IdString &name, bool is_blackbox = false) + std::string mangle_module_name(RTLIL::IdString name, bool is_blackbox = false) { // Class namespace. if (is_blackbox) @@ -800,19 +794,19 @@ struct CxxrtlWorker { return mangle_name(name); } - std::string mangle_memory_name(const RTLIL::IdString &name) + std::string mangle_memory_name(RTLIL::IdString name) { // Class member namespace. return "memory_" + mangle_name(name); } - std::string mangle_cell_name(const RTLIL::IdString &name) + std::string mangle_cell_name(RTLIL::IdString name) { // Class member namespace. return "cell_" + mangle_name(name); } - std::string mangle_wire_name(const RTLIL::IdString &name) + std::string mangle_wire_name(RTLIL::IdString name) { // Class member namespace. return mangle_name(name); @@ -856,7 +850,7 @@ struct CxxrtlWorker { if (!module->has_attribute(ID(cxxrtl_template))) return {}; - if (module->attributes.at(ID(cxxrtl_template)).flags != RTLIL::CONST_FLAG_STRING) + if (!(module->attributes.at(ID(cxxrtl_template)).flags & RTLIL::CONST_FLAG_STRING)) log_cmd_error("Attribute `cxxrtl_template' of module `%s' is not a string.\n", log_id(module)); std::vector param_names = split_by(module->get_string_attribute(ID(cxxrtl_template)), " \t"); @@ -1130,7 +1124,7 @@ struct CxxrtlWorker { f << indent << "// cell " << cell->name.str() << " syncs\n"; for (auto conn : cell->connections()) if (cell->output(conn.first)) - if (is_cxxrtl_sync_port(cell, conn.first)) { + if (is_cxxrtl_sync_port(cell, conn.first) && !conn.second.empty()) { f << indent; dump_sigspec_lhs(conn.second, for_debug); f << " = " << mangle(cell) << access << mangle_wire_name(conn.first) << ".curr;\n"; @@ -1190,6 +1184,14 @@ struct CxxrtlWorker { f << ">("; dump_sigspec_rhs(cell->getPort(ID::S), for_debug); f << ").val()"; + // Bitwise muxes + } else if (cell->type == ID($bwmux)) { + dump_sigspec_rhs(cell->getPort(ID::A), for_debug); + f << ".bwmux("; + dump_sigspec_rhs(cell->getPort(ID::B), for_debug); + f << ","; + dump_sigspec_rhs(cell->getPort(ID::S), for_debug); + f << ").val()"; // Demuxes } else if (cell->type == ID($demux)) { dump_sigspec_rhs(cell->getPort(ID::A), for_debug); @@ -1517,7 +1519,7 @@ struct CxxrtlWorker { } // Internal cells } else if (is_internal_cell(cell->type)) { - log_cmd_error("Unsupported internal cell `%s'.\n", cell->type.c_str()); + log_cmd_error("Unsupported internal cell `%s'.\n", cell->type); // User cells } else if (for_debug) { // Outlines are called on demand when computing the value of a debug item. Nothing to do here. @@ -1652,26 +1654,29 @@ struct CxxrtlWorker { f << signal_temp << " == "; dump_sigspec(compare, /*is_lhs=*/false, for_debug); } else if (compare.is_fully_const()) { - RTLIL::Const compare_mask, compare_value; + RTLIL::Const::Builder compare_mask_builder(compare.size()); + RTLIL::Const::Builder compare_value_builder(compare.size()); for (auto bit : compare.as_const()) { switch (bit) { case RTLIL::S0: case RTLIL::S1: - compare_mask.bits.push_back(RTLIL::S1); - compare_value.bits.push_back(bit); + compare_mask_builder.push_back(RTLIL::S1); + compare_value_builder.push_back(bit); break; case RTLIL::Sx: case RTLIL::Sz: case RTLIL::Sa: - compare_mask.bits.push_back(RTLIL::S0); - compare_value.bits.push_back(RTLIL::S0); + compare_mask_builder.push_back(RTLIL::S0); + compare_value_builder.push_back(RTLIL::S0); break; default: log_assert(false); } } + RTLIL::Const compare_mask = compare_mask_builder.build(); + RTLIL::Const compare_value = compare_value_builder.build(); f << "and_uu<" << compare.size() << ">(" << signal_temp << ", "; dump_const(compare_mask); f << ") == "; @@ -2275,46 +2280,92 @@ struct CxxrtlWorker { dec_indent(); } - void dump_metadata_map(const dict &metadata_map) - { - if (metadata_map.empty()) { - f << "metadata_map()"; - return; - } - f << "metadata_map({\n"; - inc_indent(); - for (auto metadata_item : metadata_map) { - if (!metadata_item.first.isPublic()) - continue; - if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) { - f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n"; - continue; - } - f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; - // In Yosys, a real is a type of string. - if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) { - f << std::showpoint << std::stod(metadata_item.second.decode_string()) << std::noshowpoint; - } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) { - f << escape_cxx_string(metadata_item.second.decode_string()); - } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) { - f << "INT64_C(" << metadata_item.second.as_int(/*is_signed=*/true) << ")"; - } else { - f << "UINT64_C(" << metadata_item.second.as_int(/*is_signed=*/false) << ")"; - } - f << " },\n"; + void dump_serialized_metadata(const dict &metadata_map) { + // Creating thousands metadata_map objects using initializer lists in a single function results in one of: + // 1. Megabytes of stack usage (with __attribute__((optnone))). + // 2. Minutes of compile time (without __attribute__((optnone))). + // So, don't create them. + std::string data; + auto put_u64 = [&](uint64_t value) { + for (size_t count = 0; count < 8; count++) { + data += (char)(value >> 56); + value <<= 8; } - dec_indent(); - f << indent << "})"; + }; + for (auto metadata_item : metadata_map) { + if (!metadata_item.first.isPublic()) + continue; + if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) { + f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n"; + continue; + } + data += metadata_item.first.str().substr(1) + '\0'; + // In Yosys, a real is a type of string. + if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) { + double dvalue = std::stod(metadata_item.second.decode_string()); + uint64_t uvalue; + static_assert(sizeof(dvalue) == sizeof(uvalue), "double must be 64 bits in size"); + memcpy(&uvalue, &dvalue, sizeof(uvalue)); + data += 'd'; + put_u64(uvalue); + } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) { + data += 's'; + data += metadata_item.second.decode_string(); + data += '\0'; + } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) { + data += 'i'; + put_u64((uint64_t)metadata_item.second.as_int(/*is_signed=*/true)); + } else { + data += 'u'; + put_u64(metadata_item.second.as_int(/*is_signed=*/false)); + } + } + f << escape_c_string(data); } - void dump_debug_attrs(const RTLIL::AttrObject *object) + void dump_metadata_map(const dict &metadata_map) { + if (metadata_map.empty()) { + f << "metadata_map()"; + } else { + f << "metadata_map({\n"; + inc_indent(); + for (auto metadata_item : metadata_map) { + if (!metadata_item.first.isPublic()) + continue; + if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) { + f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n"; + continue; + } + f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; + // In Yosys, a real is a type of string. + if (metadata_item.second.flags & RTLIL::CONST_FLAG_REAL) { + f << std::showpoint << std::stod(metadata_item.second.decode_string()) << std::noshowpoint; + } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) { + f << escape_cxx_string(metadata_item.second.decode_string()); + } else if (metadata_item.second.flags & RTLIL::CONST_FLAG_SIGNED) { + f << "INT64_C(" << metadata_item.second.as_int(/*is_signed=*/true) << ")"; + } else { + f << "UINT64_C(" << metadata_item.second.as_int(/*is_signed=*/false) << ")"; + } + f << " },\n"; + } + dec_indent(); + f << indent << "})"; + } + } + + void dump_debug_attrs(const RTLIL::AttrObject *object, bool serialize = true) { dict attributes = object->attributes; // Inherently necessary to get access to the object, so a waste of space to emit. attributes.erase(ID::hdlname); // Internal Yosys attribute that should be removed but isn't. attributes.erase(ID::module_not_derived); - dump_metadata_map(attributes); + if (serialize) { + dump_serialized_metadata(attributes); + } else { + dump_metadata_map(attributes); + } } void dump_debug_info_method(RTLIL::Module *module) @@ -2337,7 +2388,7 @@ struct CxxrtlWorker { // The module is responsible for adding its own scope. f << indent << "scopes->add(path.empty() ? path : path.substr(0, path.size() - 1), "; f << escape_cxx_string(get_hdl_name(module)) << ", "; - dump_debug_attrs(module); + dump_debug_attrs(module, /*serialize=*/false); f << ", std::move(cell_attrs));\n"; count_scopes++; // If there were any submodules that were flattened, the module is also responsible for adding them. @@ -2347,11 +2398,16 @@ struct CxxrtlWorker { auto module_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Module); auto cell_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Cell); cell_attrs.erase(ID::module_not_derived); - f << indent << "scopes->add(path + " << escape_cxx_string(get_hdl_name(cell)) << ", "; - f << escape_cxx_string(cell->get_string_attribute(ID(module))) << ", "; - dump_metadata_map(module_attrs); + f << indent << "scopes->add(path, " << escape_cxx_string(get_hdl_name(cell)) << ", "; + if (module_attrs.count(ID(hdlname))) { + f << escape_cxx_string(module_attrs.at(ID(hdlname)).decode_string()); + } else { + f << escape_cxx_string(cell->get_string_attribute(ID(module))); + } f << ", "; - dump_metadata_map(cell_attrs); + dump_serialized_metadata(module_attrs); + f << ", "; + dump_serialized_metadata(cell_attrs); f << ");\n"; } else log_assert(false && "Unknown $scopeinfo type"); count_scopes++; @@ -2362,8 +2418,6 @@ struct CxxrtlWorker { inc_indent(); for (auto wire : module->wires()) { const auto &debug_wire_type = debug_wire_types[wire]; - if (!wire->name.isPublic()) - continue; count_public_wires++; switch (debug_wire_type.type) { case WireType::BUFFERED: @@ -2371,6 +2425,9 @@ struct CxxrtlWorker { // Member wire std::vector flags; + if (!wire->name.isPublic()) + flags.push_back("GENERATED"); + if (wire->port_input && wire->port_output) flags.push_back("INOUT"); else if (wire->port_output) @@ -2419,20 +2476,22 @@ struct CxxrtlWorker { if (has_driven_sync + has_driven_comb + has_undriven > 1) count_mixed_driver++; - f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset; - bool first = true; - for (auto flag : flags) { - if (first) { - first = false; - f << ", "; - } else { - f << "|"; - } - f << "debug_item::" << flag; - } - f << "), "; + f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", "; dump_debug_attrs(wire); + f << ", " << mangle(wire); + if (wire->start_offset != 0 || !flags.empty()) { + f << ", " << wire->start_offset; + bool first = true; + for (auto flag : flags) { + if (first) { + first = false; + f << ", "; + } else { + f << "|"; + } + f << "debug_item::" << flag; + } + } f << ");\n"; count_member_wires++; break; @@ -2440,16 +2499,18 @@ struct CxxrtlWorker { case WireType::ALIAS: { // Alias of a member wire const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire(); - f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item("; + f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", "; + dump_debug_attrs(wire); + f << ", "; // If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream // tooling has no way to find out about the outline. if (debug_wire_types[aliasee].is_outline()) f << "debug_eval_outline"; else f << "debug_alias()"; - f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), "; - dump_debug_attrs(aliasee); + f << ", " << mangle(aliasee); + if (wire->start_offset != 0) + f << ", " << wire->start_offset; f << ");\n"; count_alias_wires++; break; @@ -2459,18 +2520,22 @@ struct CxxrtlWorker { f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = "; dump_const(debug_wire_type.sig_subst.as_const()); f << ";\n"; - f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "), "; + f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", "; dump_debug_attrs(wire); + f << ", const_" << mangle(wire); + if (wire->start_offset != 0) + f << ", " << wire->start_offset; f << ");\n"; count_const_wires++; break; } case WireType::OUTLINE: { // Localized or inlined, but rematerializable wire - f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "), "; + f << indent << "items->add(path, " << escape_cxx_string(get_hdl_name(wire)) << ", "; dump_debug_attrs(wire); + f << ", debug_eval_outline, " << mangle(wire); + if (wire->start_offset != 0) + f << ", " << wire->start_offset; f << ");\n"; count_inline_wires++; break; @@ -2486,15 +2551,14 @@ struct CxxrtlWorker { for (auto &mem : mod_memories[module]) { if (!mem.memid.isPublic()) continue; - f << indent << "items->add(path + " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem)); - f << ", debug_item(" << mangle(&mem) << ", "; - f << mem.start_offset << "), "; + f << indent << "items->add(path, " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem)) << ", "; if (mem.packed) { dump_debug_attrs(mem.cell); } else { dump_debug_attrs(mem.mem); } - f << ");\n"; + f << ", " << mangle(&mem) << ", "; + f << mem.start_offset << ");\n"; } } dec_indent(); @@ -2506,7 +2570,7 @@ struct CxxrtlWorker { const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; f << indent << mangle(cell) << access; f << "debug_info(items, scopes, path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ", "; - dump_debug_attrs(cell); + dump_debug_attrs(cell, /*serialize=*/false); f << ");\n"; } } @@ -2780,7 +2844,7 @@ struct CxxrtlWorker { } if (split_intf) - f << "#include \"" << basename(intf_filename) << "\"\n"; + f << "#include \"" << name_from_file_path(intf_filename) << "\"\n"; else f << "#include \n"; f << "\n"; @@ -2967,7 +3031,7 @@ struct CxxrtlWorker { if (init == RTLIL::Const()) { init = RTLIL::Const(State::Sx, GetSize(bit.wire)); } - init[bit.offset] = port.init_value[i]; + init.set(bit.offset, port.init_value[i]); } } } @@ -3019,7 +3083,7 @@ struct CxxrtlWorker { // without feedback arcs can generally be evaluated in a single pass, i.e. it always requires only // a single delta cycle. Scheduler scheduler; - dict::Vertex*, hash_ptr_ops> node_vertex_map; + dict::Vertex*> node_vertex_map; for (auto node : flow.nodes) node_vertex_map[node] = scheduler.add(node); for (auto node_comb_def : flow.node_comb_defs) { @@ -3034,7 +3098,7 @@ struct CxxrtlWorker { // Find out whether the order includes any feedback arcs. std::vector node_order; - pool evaluated_nodes; + pool evaluated_nodes; pool feedback_wires; for (auto vertex : scheduler.schedule()) { auto node = vertex->data; @@ -3078,7 +3142,7 @@ struct CxxrtlWorker { } // Discover nodes reachable from primary outputs (i.e. members) and collect reachable wire users. - pool worklist; + pool worklist; for (auto node : flow.nodes) { if (node->type == FlowGraph::Node::Type::CELL_EVAL && !is_internal_cell(node->cell->type)) worklist.insert(node); // node evaluates a submodule @@ -3098,8 +3162,8 @@ struct CxxrtlWorker { worklist.insert(node); // node drives public wires } } - dict> live_wires; - pool live_nodes; + dict> live_wires; + pool live_nodes; while (!worklist.empty()) { auto node = worklist.pop(); live_nodes.insert(node); @@ -3202,6 +3266,7 @@ struct CxxrtlWorker { debug_wire_type = wire_type; // wire is a member if (!debug_alias) continue; + if (wire->port_input || wire->port_output) continue; // preserve input/output metadata in flags const RTLIL::Wire *it = wire; while (flow.is_inlinable(it)) { log_assert(flow.wire_comb_defs[it].size() == 1); @@ -3228,15 +3293,15 @@ struct CxxrtlWorker { // Discover nodes reachable from primary outputs (i.e. outlines) up until primary inputs (i.e. members) // and collect reachable wire users. - pool worklist; + pool worklist; for (auto node : flow.nodes) { if (flow.node_comb_defs.count(node)) for (auto wire : flow.node_comb_defs[node]) if (debug_wire_types[wire].is_outline()) worklist.insert(node); // node drives outline } - dict> debug_live_wires; - pool debug_live_nodes; + dict> debug_live_wires; + pool debug_live_nodes; while (!worklist.empty()) { auto node = worklist.pop(); debug_live_nodes.insert(node); @@ -3402,8 +3467,8 @@ struct CxxrtlWorker { }; struct CxxrtlBackend : public Backend { - static const int DEFAULT_OPT_LEVEL = 6; - static const int DEFAULT_DEBUG_LEVEL = 4; + static constexpr int DEFAULT_OPT_LEVEL = 6; + static constexpr int DEFAULT_DEBUG_LEVEL = 4; CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } void help() override @@ -3724,7 +3789,7 @@ struct CxxrtlBackend : public Backend { if (args[argidx] == "-print-output" && argidx+1 < args.size()) { worker.print_output = args[++argidx]; if (!(worker.print_output == "std::cout" || worker.print_output == "std::cerr")) { - log_cmd_error("Invalid output stream \"%s\".\n", worker.print_output.c_str()); + log_cmd_error("Invalid output stream \"%s\".\n", worker.print_output); worker.print_output = "std::cout"; } continue; diff --git a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc index 3c62401dd..34801c2d1 100644 --- a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc +++ b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc @@ -47,7 +47,7 @@ cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path_) { cxxrtl_handle handle = new _cxxrtl_handle; handle->module = std::move(design->module); - handle->module->debug_info(handle->objects, top_path); + handle->module->debug_info(&handle->objects, nullptr, top_path); delete design; return handle; } diff --git a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h index ae42733ad..62ca38943 100644 --- a/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h +++ b/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h @@ -200,6 +200,10 @@ enum cxxrtl_flag { // node, such as inputs and dangling wires. CXXRTL_UNDRIVEN = 1 << 4, + // Generated correspond to netlist nodes that correspond to state with an internal name, that + // need to be saved, but wouldn't otherwise have a debug item generated. + CXXRTL_GENERATED = 1 << 5, + // More object flags may be added in the future, but the existing ones will never change. }; diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index b834cd120..fbbe2373f 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -498,6 +498,11 @@ struct value : public expr_base> { return result; } + CXXRTL_ALWAYS_INLINE + value bwmux(const value &b, const value &s) const { + return (bit_and(s.bit_not())).bit_or(b.bit_and(s)); + } + template value demux(const value &sel) const { static_assert(Bits << SelBits == ResultBits, "invalid sizes used in demux()"); @@ -625,11 +630,11 @@ struct value : public expr_base> { value remainder; value dividend = sext(); value divisor = other.template sext(); - if (dividend.is_neg()) dividend = dividend.neg(); - if (divisor.is_neg()) divisor = divisor.neg(); + if (is_neg()) dividend = dividend.neg(); + if (other.is_neg()) divisor = divisor.neg(); std::tie(quotient, remainder) = dividend.udivmod(divisor); - if (dividend.is_neg() != divisor.is_neg()) quotient = quotient.neg(); - if (dividend.is_neg()) remainder = remainder.neg(); + if (is_neg() != other.is_neg()) quotient = quotient.neg(); + if (is_neg()) remainder = remainder.neg(); return {quotient.template trunc(), remainder.template trunc()}; } }; @@ -941,6 +946,55 @@ struct metadata { assert(value_type == DOUBLE); return double_value; } + + // Internal CXXRTL use only. + static std::map deserialize(const char *ptr) { + std::map result; + std::string name; + // Grammar: + // string ::= [^\0]+ \0 + // metadata ::= [uid] .{8} | s + // map ::= ( )* \0 + for (;;) { + if (*ptr) { + name += *ptr++; + } else if (!name.empty()) { + ptr++; + auto get_u64 = [&]() { + uint64_t result = 0; + for (size_t count = 0; count < 8; count++) + result = (result << 8) | *ptr++; + return result; + }; + char type = *ptr++; + if (type == 'u') { + uint64_t value = get_u64(); + result.emplace(name, value); + } else if (type == 'i') { + int64_t value = (int64_t)get_u64(); + result.emplace(name, value); + } else if (type == 'd') { + double dvalue; + uint64_t uvalue = get_u64(); + static_assert(sizeof(dvalue) == sizeof(uvalue), "double must be 64 bits in size"); + memcpy(&dvalue, &uvalue, sizeof(dvalue)); + result.emplace(name, dvalue); + } else if (type == 's') { + std::string value; + while (*ptr) + value += *ptr++; + ptr++; + result.emplace(name, value); + } else { + assert(false && "Unknown type specifier"); + return result; + } + name.clear(); + } else { + return result; + } + } + } }; typedef std::map metadata_map; @@ -1010,22 +1064,24 @@ struct observer { // Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. struct fmt_part { enum { - STRING = 0, + LITERAL = 0, INTEGER = 1, - CHARACTER = 2, - VLOG_TIME = 3, + STRING = 2, + UNICHAR = 3, + VLOG_TIME = 4, } type; - // STRING type + // LITERAL type std::string str; - // INTEGER/CHARACTER types + // INTEGER/STRING/UNICHAR types // + value val; - // INTEGER/CHARACTER/VLOG_TIME types + // INTEGER/STRING/VLOG_TIME types enum { RIGHT = 0, LEFT = 1, + NUMERIC = 2, } justify; // = RIGHT; char padding; // = '\0'; size_t width; // = 0; @@ -1033,7 +1089,14 @@ struct fmt_part { // INTEGER type unsigned base; // = 10; bool signed_; // = false; - bool plus; // = false; + enum { + MINUS = 0, + PLUS_MINUS = 1, + SPACE_MINUS = 2, + } sign; // = MINUS; + bool hex_upper; // = false; + bool show_base; // = false; + bool group; // = false; // VLOG_TIME type bool realtime; // = false; @@ -1049,11 +1112,12 @@ struct fmt_part { // We might want to replace some of these bit() calls with direct // chunk access if it turns out to be slow enough to matter. std::string buf; + std::string prefix; switch (type) { - case STRING: + case LITERAL: return str; - case CHARACTER: { + case STRING: { buf.reserve(Bits/8); for (int i = 0; i < Bits; i += 8) { char ch = 0; @@ -1067,35 +1131,76 @@ struct fmt_part { break; } + case UNICHAR: { + uint32_t codepoint = val.template zcast<32>().template get(); + if (codepoint >= 0x10000) + buf += (char)(0xf0 | (codepoint >> 18)); + else if (codepoint >= 0x800) + buf += (char)(0xe0 | (codepoint >> 12)); + else if (codepoint >= 0x80) + buf += (char)(0xc0 | (codepoint >> 6)); + else + buf += (char)codepoint; + if (codepoint >= 0x10000) + buf += (char)(0x80 | ((codepoint >> 12) & 0x3f)); + if (codepoint >= 0x800) + buf += (char)(0x80 | ((codepoint >> 6) & 0x3f)); + if (codepoint >= 0x80) + buf += (char)(0x80 | ((codepoint >> 0) & 0x3f)); + break; + } + case INTEGER: { - size_t width = Bits; + bool negative = signed_ && val.is_neg(); + if (negative) { + prefix = "-"; + val = val.neg(); + } else { + switch (sign) { + case MINUS: break; + case PLUS_MINUS: prefix = "+"; break; + case SPACE_MINUS: prefix = " "; break; + } + } + + size_t val_width = Bits; if (base != 10) { - width = 0; + val_width = 1; for (size_t index = 0; index < Bits; index++) if (val.bit(index)) - width = index + 1; + val_width = index + 1; } if (base == 2) { - for (size_t i = width; i > 0; i--) - buf += (val.bit(i - 1) ? '1' : '0'); + if (show_base) + prefix += "0b"; + for (size_t index = 0; index < val_width; index++) { + if (group && index > 0 && index % 4 == 0) + buf += '_'; + buf += (val.bit(index) ? '1' : '0'); + } } else if (base == 8 || base == 16) { + if (show_base) + prefix += (base == 16) ? (hex_upper ? "0X" : "0x") : "0o"; size_t step = (base == 16) ? 4 : 3; - for (size_t index = 0; index < width; index += step) { + for (size_t index = 0; index < val_width; index += step) { + if (group && index > 0 && index % (4 * step) == 0) + buf += '_'; uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); if (step == 4) value |= val.bit(index + 3) << 3; - buf += "0123456789abcdef"[value]; + buf += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value]; } - std::reverse(buf.begin(), buf.end()); } else if (base == 10) { - bool negative = signed_ && val.is_neg(); - if (negative) - val = val.neg(); + if (show_base) + prefix += "0d"; if (val.is_zero()) buf += '0'; value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>(); + size_t index = 0; while (!xval.is_zero()) { + if (group && index > 0 && index % 3 == 0) + buf += '_'; value<(Bits > 4 ? Bits : 4)> quotient, remainder; if (Bits >= 4) std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u}); @@ -1103,11 +1208,18 @@ struct fmt_part { std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval); buf += '0' + remainder.template trunc<4>().template get(); xval = quotient; + index++; } - if (negative || plus) - buf += negative ? '-' : '+'; - std::reverse(buf.begin(), buf.end()); } else assert(false && "Unsupported base for fmt_part"); + if (justify == NUMERIC && group && padding == '0') { + int group_size = base == 10 ? 3 : 4; + while (prefix.size() + buf.size() < width) { + if (buf.size() % (group_size + 1) == group_size) + buf += '_'; + buf += '0'; + } + } + std::reverse(buf.begin(), buf.end()); break; } @@ -1123,17 +1235,29 @@ struct fmt_part { std::string str; assert(width == 0 || padding != '\0'); - if (justify == RIGHT && buf.size() < width) { - size_t pad_width = width - buf.size(); - if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) { - str += buf.front(); - buf.erase(0, 1); - } - str += std::string(pad_width, padding); + if (prefix.size() + buf.size() < width) { + size_t pad_width = width - prefix.size() - buf.size(); + switch (justify) { + case LEFT: + str += prefix; + str += buf; + str += std::string(pad_width, padding); + break; + case RIGHT: + str += std::string(pad_width, padding); + str += prefix; + str += buf; + break; + case NUMERIC: + str += prefix; + str += std::string(pad_width, padding); + str += buf; + break; + } + } else { + str += prefix; + str += buf; } - str += buf; - if (justify == LEFT && buf.size() < width) - str += std::string(width - buf.size(), padding); return str; } }; @@ -1170,6 +1294,7 @@ struct debug_item : ::cxxrtl_object { DRIVEN_SYNC = CXXRTL_DRIVEN_SYNC, DRIVEN_COMB = CXXRTL_DRIVEN_COMB, UNDRIVEN = CXXRTL_UNDRIVEN, + GENERATED = CXXRTL_GENERATED, }; debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {} @@ -1347,6 +1472,12 @@ struct debug_items { }); } + // This overload exists to reduce excessive stack slot allocation in `CXXRTL_EXTREMELY_COLD void debug_info()`. + template + void add(const std::string &base_path, const char *path, const char *serialized_item_attrs, T&&... args) { + add(base_path + path, debug_item(std::forward(args)...), metadata::deserialize(serialized_item_attrs)); + } + size_t count(const std::string &path) const { if (table.count(path) == 0) return 0; @@ -1393,6 +1524,11 @@ struct debug_scopes { scope.cell_attrs = std::unique_ptr(new debug_attrs { cell_attrs }); } + // This overload exists to reduce excessive stack slot allocation in `CXXRTL_EXTREMELY_COLD void debug_info()`. + void add(const std::string &base_path, const char *path, const char *module_name, const char *serialized_module_attrs, const char *serialized_cell_attrs) { + add(base_path + path, module_name, metadata::deserialize(serialized_module_attrs), metadata::deserialize(serialized_cell_attrs)); + } + size_t contains(const std::string &path) const { return table.count(path); } @@ -1452,7 +1588,7 @@ struct module { // Compatibility method. #if __has_attribute(deprecated) - __attribute__((deprecated("Use `debug_info(path, &items, /*scopes=*/nullptr);` instead. (`path` could be \"top \".)"))) + __attribute__((deprecated("Use `debug_info(&items, /*scopes=*/nullptr, path);` instead."))) #endif void debug_info(debug_items &items, std::string path) { debug_info(&items, /*scopes=*/nullptr, path); @@ -1634,7 +1770,7 @@ value shr_uu(const value &a, const value &b) { template CXXRTL_ALWAYS_INLINE value shr_su(const value &a, const value &b) { - return a.shr(b).template scast(); + return a.template scast().shr(b); } template @@ -1875,7 +2011,7 @@ std::pair, value> divmod_uu(const value &a, const val value quotient; value remainder; value dividend = a.template zext(); - value divisor = b.template zext(); + value divisor = b.template trunc().template zext(); std::tie(quotient, remainder) = dividend.udivmod(divisor); return {quotient.template trunc(), remainder.template trunc()}; } diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h index b8233b007..9aa3e105d 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -512,9 +512,10 @@ public: spool &operator=(const spool &) = delete; ~spool() { - if (int fd = writefd.exchange(-1)) + int fd; + if ((fd = writefd.exchange(-1)) != -1) close(fd); - if (int fd = readfd.exchange(-1)) + if ((fd = readfd.exchange(-1)) != -1) close(fd); } diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h index cb2ccf5fc..e8be70028 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h @@ -50,9 +50,13 @@ class vcd_writer { void emit_scope(const std::vector &scope) { assert(!streaming); - while (current_scope.size() > scope.size() || - (current_scope.size() > 0 && - current_scope[current_scope.size() - 1] != scope[current_scope.size() - 1])) { + size_t same_scope_count = 0; + while ((same_scope_count < current_scope.size()) && + (same_scope_count < scope.size()) && + (current_scope[same_scope_count] == scope[same_scope_count])) { + same_scope_count++; + } + while (current_scope.size() > same_scope_count) { buffer += "$upscope $end\n"; current_scope.pop_back(); } @@ -123,6 +127,8 @@ class vcd_writer { bool bit_curr = var.curr[bit / (8 * sizeof(chunk_t))] & (1 << (bit % (8 * sizeof(chunk_t)))); buffer += (bit_curr ? '1' : '0'); } + if (var.width == 0) + buffer += '0'; buffer += ' '; emit_ident(var.ident); buffer += '\n'; diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc index 553eb23d6..61d6ee254 100644 --- a/backends/edif/edif.cc +++ b/backends/edif/edif.cc @@ -30,9 +30,9 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -#define EDIF_DEF(_id) edif_names(RTLIL::unescape_id(_id), true).c_str() -#define EDIF_DEFR(_id, _ren, _bl, _br) edif_names(RTLIL::unescape_id(_id), true, _ren, _bl, _br).c_str() -#define EDIF_REF(_id) edif_names(RTLIL::unescape_id(_id), false).c_str() +#define EDIF_DEF(_id) edif_names(RTLIL::unescape_id(_id), true) +#define EDIF_DEFR(_id, _ren, _bl, _br) edif_names(RTLIL::unescape_id(_id), true, _ren, _bl, _br) +#define EDIF_REF(_id) edif_names(RTLIL::unescape_id(_id), false) struct EdifNames { @@ -48,8 +48,8 @@ struct EdifNames if (define) { std::string new_id = operator()(id, false); if (port_rename) - return stringf("(rename %s \"%s%c%d:%d%c\")", new_id.c_str(), id.c_str(), delim_left, range_left, range_right, delim_right); - return new_id != id ? stringf("(rename %s \"%s\")", new_id.c_str(), id.c_str()) : id; + return stringf("(rename %s \"%s%c%d:%d%c\")", new_id, id, delim_left, range_left, range_right, delim_right); + return new_id != id ? stringf("(rename %s \"%s\")", new_id, id) : id; } if (name_map.count(id) > 0) @@ -231,7 +231,8 @@ struct EdifBackend : public Backend { *f << stringf(" (edifVersion 2 0 0)\n"); *f << stringf(" (edifLevel 0)\n"); *f << stringf(" (keywordMap (keywordLevel 0))\n"); - *f << stringf(" (comment \"Generated by %s\")\n", yosys_version_str); + + *f << stringf(" (comment \"Generated by %s\")\n", yosys_maybe_version()); *f << stringf(" (external LIB\n"); *f << stringf(" (edifLevel 0)\n"); @@ -333,21 +334,21 @@ struct EdifBackend : public Backend { auto add_prop = [&](IdString name, Const val) { if ((val.flags & RTLIL::CONST_FLAG_STRING) != 0) - *f << stringf("\n (property %s (string \"%s\"))", EDIF_DEF(name), val.decode_string().c_str()); - else if (val.bits.size() <= 32 && RTLIL::SigSpec(val).is_fully_def()) + *f << stringf("\n (property %s (string \"%s\"))", EDIF_DEF(name), val.decode_string()); + else if (val.size() <= 32 && RTLIL::SigSpec(val).is_fully_def()) *f << stringf("\n (property %s (integer %u))", EDIF_DEF(name), val.as_int()); else { std::string hex_string = ""; - for (size_t i = 0; i < val.bits.size(); i += 4) { + for (auto i = 0; i < val.size(); i += 4) { int digit_value = 0; - if (i+0 < val.bits.size() && val.bits.at(i+0) == RTLIL::State::S1) digit_value |= 1; - if (i+1 < val.bits.size() && val.bits.at(i+1) == RTLIL::State::S1) digit_value |= 2; - if (i+2 < val.bits.size() && val.bits.at(i+2) == RTLIL::State::S1) digit_value |= 4; - if (i+3 < val.bits.size() && val.bits.at(i+3) == RTLIL::State::S1) digit_value |= 8; + if (i+0 < val.size() && val.at(i+0) == RTLIL::State::S1) digit_value |= 1; + if (i+1 < val.size() && val.at(i+1) == RTLIL::State::S1) digit_value |= 2; + if (i+2 < val.size() && val.at(i+2) == RTLIL::State::S1) digit_value |= 4; + if (i+3 < val.size() && val.at(i+3) == RTLIL::State::S1) digit_value |= 8; char digit_str[2] = { "0123456789abcdef"[digit_value], 0 }; hex_string = std::string(digit_str) + hex_string; } - *f << stringf("\n (property %s (string \"%d'h%s\"))", EDIF_DEF(name), GetSize(val.bits), hex_string.c_str()); + *f << stringf("\n (property %s (string \"%d'h%s\"))", EDIF_DEF(name), GetSize(val), hex_string); } }; for (auto module : sorted_modules) @@ -512,13 +513,13 @@ struct EdifBackend : public Backend { if (sig.wire == NULL && sig != RTLIL::State::S0 && sig != RTLIL::State::S1) { if (sig == RTLIL::State::Sx) { for (auto &ref : it.second) - log_warning("Exporting x-bit on %s as zero bit.\n", ref.first.c_str()); + log_warning("Exporting x-bit on %s as zero bit.\n", ref.first); sig = RTLIL::State::S0; } else if (sig == RTLIL::State::Sz) { continue; } else { for (auto &ref : it.second) - log_error("Don't know how to handle %s on %s.\n", log_signal(sig), ref.first.c_str()); + log_error("Don't know how to handle %s on %s.\n", log_signal(sig), ref.first); log_abort(); } } @@ -535,7 +536,7 @@ struct EdifBackend : public Backend { } *f << stringf(" (net %s (joined\n", EDIF_DEF(netname)); for (auto &ref : it.second) - *f << stringf(" %s\n", ref.first.c_str()); + *f << stringf(" %s\n", ref.first); if (sig.wire == NULL) { if (nogndvcc) log_error("Design contains constant nodes (map with \"hilomap\" first).\n"); @@ -576,7 +577,7 @@ struct EdifBackend : public Backend { auto &refs = net_join_db.at(mapped_sig); for (auto &ref : refs) if (ref.second) - *f << stringf(" %s\n", ref.first.c_str()); + *f << stringf(" %s\n", ref.first); *f << stringf(" )"); if (attr_properties && raw_sig.wire != NULL) diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc index dc76dbeec..577d95ad7 100644 --- a/backends/firrtl/firrtl.cc +++ b/backends/firrtl/firrtl.cc @@ -149,7 +149,7 @@ std::string dump_const(const RTLIL::Const &data) // Numeric (non-real) parameter. else { - int width = data.bits.size(); + int width = data.size(); // If a standard 32-bit int, then emit standard int value like "56" or // "-56". Firrtl supports negative-valued int literals. @@ -163,7 +163,7 @@ std::string dump_const(const RTLIL::Const &data) for (int i = 0; i < width; i++) { - switch (data.bits[i]) + switch (data[i]) { case State::S0: break; case State::S1: int_val |= (1 << i); break; @@ -205,7 +205,7 @@ std::string dump_const(const RTLIL::Const &data) for (int i = width - 1; i >= 0; i--) { log_assert(i < width); - switch (data.bits[i]) + switch (data[i]) { case State::S0: res_str += "0"; break; case State::S1: res_str += "1"; break; @@ -253,7 +253,7 @@ void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream const std::string extmoduleFileinfo = getFileinfo(cell); // Emit extmodule header. - f << stringf(" extmodule %s: %s\n", exported_name.c_str(), extmoduleFileinfo.c_str()); + f << stringf(" extmodule %s: %s\n", exported_name, extmoduleFileinfo); // Emit extmodule ports. for (auto wire : mod_instance->wires()) @@ -280,7 +280,7 @@ void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream // Emit extmodule "defname" field. This is the name of the verilog blackbox // that is used when verilog is emitted, so we use the name of mod_instance // here. - f << stringf("%sdefname = %s\n", indent.c_str(), blackbox_name.c_str()); + f << stringf("%sdefname = %s\n", indent, blackbox_name); // Emit extmodule generic parameters. for (const auto &p : cell->parameters) @@ -301,7 +301,7 @@ void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream param_name.end() ); - f << stringf("%sparameter %s = %s\n", indent.c_str(), param_name.c_str(), param_value.c_str()); + f << stringf("%sparameter %s = %s\n", indent, param_name, param_value); } f << "\n"; @@ -347,7 +347,7 @@ void emit_elaborated_extmodules(RTLIL::Design *design, std::ostream &f) auto modInstance = design->module(cell->type); // Ensure that we actually have a module instance if (modInstance == nullptr) { - log_error("Unknown cell type %s\n", cell->type.c_str()); + log_error("Unknown cell type %s\n", cell->type); return; } @@ -417,7 +417,7 @@ struct FirrtlWorker else { string wire_id = make_id(chunk.wire->name); - new_expr = stringf("bits(%s, %d, %d)", wire_id.c_str(), chunk.offset + chunk.width - 1, chunk.offset); + new_expr = stringf("bits(%s, %d, %d)", wire_id, chunk.offset + chunk.width - 1, chunk.offset); } if (expr.empty()) @@ -465,7 +465,7 @@ struct FirrtlWorker // If there is no instance for this, just return. if (instModule == NULL) { - log_warning("No instance for %s.%s\n", cell_type.c_str(), cell_name.c_str()); + log_warning("No instance for %s.%s\n", cell_type, cell_name); return; } @@ -477,7 +477,7 @@ struct FirrtlWorker instanceOf; std::string cellFileinfo = getFileinfo(cell); - wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), instanceName.c_str(), cellFileinfo.c_str())); + wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent, cell_name, cell_name_comment, instanceName, cellFileinfo)); for (auto it = cell->connections().begin(); it != cell->connections().end(); ++it) { if (it->second.size() > 0) { @@ -490,7 +490,7 @@ struct FirrtlWorker const SigSpec *sinkSig = nullptr; switch (dir) { case FD_INOUT: - log_warning("Instance port connection %s.%s is INOUT; treating as OUT\n", cell_type.c_str(), log_signal(it->second)); + log_warning("Instance port connection %s.%s is INOUT; treating as OUT\n", cell_type, log_signal(it->second)); YS_FALLTHROUGH case FD_OUT: sourceExpr = firstName; @@ -498,27 +498,27 @@ struct FirrtlWorker sinkSig = &secondSig; break; case FD_NODIRECTION: - log_warning("Instance port connection %s.%s is NODIRECTION; treating as IN\n", cell_type.c_str(), log_signal(it->second)); + log_warning("Instance port connection %s.%s is NODIRECTION; treating as IN\n", cell_type, log_signal(it->second)); YS_FALLTHROUGH case FD_IN: sourceExpr = secondExpr; sinkExpr = firstName; break; default: - log_error("Instance port %s.%s unrecognized connection direction 0x%x !\n", cell_type.c_str(), log_signal(it->second), dir); + log_error("Instance port %s.%s unrecognized connection direction 0x%x !\n", cell_type, log_signal(it->second), dir); break; } // Check for subfield assignment. std::string bitsString = "bits("; if (sinkExpr.compare(0, bitsString.length(), bitsString) == 0) { if (sinkSig == nullptr) - log_error("Unknown subfield %s.%s\n", cell_type.c_str(), sinkExpr.c_str()); + log_error("Unknown subfield %s.%s\n", cell_type, sinkExpr); // Don't generate the assignment here. // Add the source and sink to the "reverse_wire_map" and we'll output the assignment // as part of the coalesced subfield assignments for this wire. register_reverse_wire_map(sourceExpr, *sinkSig); } else { - wire_exprs.push_back(stringf("\n%s%s <= %s %s", indent.c_str(), sinkExpr.c_str(), sourceExpr.c_str(), cellFileinfo.c_str())); + wire_exprs.push_back(stringf("\n%s%s <= %s %s", indent, sinkExpr, sourceExpr, cellFileinfo)); } } } @@ -535,7 +535,7 @@ struct FirrtlWorker int max_shift_width_bits = FIRRTL_MAX_DSH_WIDTH_ERROR - 1; string max_shift_string = stringf("UInt<%d>(%d)", max_shift_width_bits, (1<name), moduleFileinfo.c_str()); + f << stringf(" module %s: %s\n", make_id(module->name), moduleFileinfo); vector port_decls, wire_decls, mem_exprs, cell_exprs, wire_exprs; std::vector memories = Mem::get_all_memories(module); @@ -565,12 +565,12 @@ struct FirrtlWorker { if (wire->port_input && wire->port_output) log_error("Module port %s.%s is inout!\n", log_id(module), log_id(wire)); - port_decls.push_back(stringf("%s%s %s: UInt<%d> %s\n", indent.c_str(), wire->port_input ? "input" : "output", + port_decls.push_back(stringf("%s%s %s: UInt<%d> %s\n", indent, wire->port_input ? "input" : "output", wireName, wire->width, wireFileinfo.c_str())); } else { - wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), wireName, wire->width, wireFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, wireName, wire->width, wireFileinfo)); } } @@ -602,7 +602,7 @@ struct FirrtlWorker if (cell->type.in(ID($not), ID($logic_not), ID($_NOT_), ID($neg), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_bool), ID($reduce_xnor))) { string a_expr = make_expr(cell->getPort(ID::A)); - wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, y_id, y_width, cellFileinfo)); if (a_signed) { a_expr = "asSInt(" + a_expr + ")"; @@ -610,7 +610,7 @@ struct FirrtlWorker // Don't use the results of logical operations (a single bit) to control padding if (!(cell->type.in(ID($eq), ID($eqx), ID($gt), ID($ge), ID($lt), ID($le), ID($ne), ID($nex), ID($reduce_bool), ID($logic_not)) && y_width == 1) ) { - a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width); + a_expr = stringf("pad(%s, %d)", a_expr, y_width); } // Assume the FIRRTL width is a single bit. @@ -622,27 +622,27 @@ struct FirrtlWorker firrtl_width = a_width; } else if (cell->type == ID($logic_not)) { primop = "eq"; - a_expr = stringf("%s, UInt(0)", a_expr.c_str()); + a_expr = stringf("%s, UInt(0)", a_expr); } else if (cell->type == ID($reduce_and)) primop = "andr"; else if (cell->type == ID($reduce_or)) primop = "orr"; else if (cell->type == ID($reduce_xor)) primop = "xorr"; else if (cell->type == ID($reduce_xnor)) { primop = "not"; - a_expr = stringf("xorr(%s)", a_expr.c_str()); + a_expr = stringf("xorr(%s)", a_expr); } else if (cell->type == ID($reduce_bool)) { primop = "neq"; // Use the sign of the a_expr and its width as the type (UInt/SInt) and width of the comparand. - a_expr = stringf("%s, %cInt<%d>(0)", a_expr.c_str(), a_signed ? 'S' : 'U', a_width); + a_expr = stringf("%s, %cInt<%d>(0)", a_expr, a_signed ? 'S' : 'U', a_width); } - string expr = stringf("%s(%s)", primop.c_str(), a_expr.c_str()); + string expr = stringf("%s(%s)", primop, a_expr); if ((firrtl_is_signed && !always_uint)) - expr = stringf("asUInt(%s)", expr.c_str()); + expr = stringf("asUInt(%s)", expr); - cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; @@ -654,13 +654,13 @@ struct FirrtlWorker string a_expr = make_expr(cell->getPort(ID::A)); string b_expr = make_expr(cell->getPort(ID::B)); std::string cellFileinfo = getFileinfo(cell); - wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, y_id, y_width, cellFileinfo)); if (a_signed) { a_expr = "asSInt(" + a_expr + ")"; // Expand the "A" operand to the result width if (a_width < y_width) { - a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width); + a_expr = stringf("pad(%s, %d)", a_expr, y_width); a_width = y_width; } } @@ -670,7 +670,7 @@ struct FirrtlWorker b_expr = "asSInt(" + b_expr + ")"; // Expand the "B" operand to the result width if (b_width < y_width) { - b_expr = stringf("pad(%s, %d)", b_expr.c_str(), y_width); + b_expr = stringf("pad(%s, %d)", b_expr, y_width); b_width = y_width; } } @@ -680,11 +680,11 @@ struct FirrtlWorker if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($xor), ID($_XOR_), ID($xnor), ID($and), ID($_AND_), ID($or), ID($_OR_))) { if (a_width < y_width) { - a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width); + a_expr = stringf("pad(%s, %d)", a_expr, y_width); a_width = y_width; } if (b_width < y_width) { - b_expr = stringf("pad(%s, %d)", b_expr.c_str(), y_width); + b_expr = stringf("pad(%s, %d)", b_expr, y_width); b_width = y_width; } } @@ -856,23 +856,23 @@ struct FirrtlWorker string expr; // Deal with $xnor == ~^ (not xor) if (primop == "xnor") { - expr = stringf("not(xor(%s, %s))", a_expr.c_str(), b_expr.c_str()); + expr = stringf("not(xor(%s, %s))", a_expr, b_expr); } else { - expr = stringf("%s(%s, %s)", primop.c_str(), a_expr.c_str(), b_expr.c_str()); + expr = stringf("%s(%s, %s)", primop, a_expr, b_expr); } // Deal with FIRRTL's "shift widens" semantics, or the need to widen the FIRRTL result. // If the operation is signed, the FIRRTL width will be 1 one bit larger. if (extract_y_bits) { - expr = stringf("bits(%s, %d, 0)", expr.c_str(), y_width - 1); + expr = stringf("bits(%s, %d, 0)", expr, y_width - 1); } else if (firrtl_is_signed && (firrtl_width + 1) < y_width) { - expr = stringf("pad(%s, %d)", expr.c_str(), y_width); + expr = stringf("pad(%s, %d)", expr, y_width); } if ((firrtl_is_signed && !always_uint)) - expr = stringf("asUInt(%s)", expr.c_str()); + expr = stringf("asUInt(%s)", expr); - cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; @@ -885,11 +885,11 @@ struct FirrtlWorker string a_expr = make_expr(cell->getPort(ID::A)); string b_expr = make_expr(cell->getPort(ID::B)); string s_expr = make_expr(cell->getPort(ID::S)); - wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), width, cellFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, y_id, width, cellFileinfo)); - string expr = stringf("mux(%s, %s, %s)", s_expr.c_str(), b_expr.c_str(), a_expr.c_str()); + string expr = stringf("mux(%s, %s, %s)", s_expr, b_expr, a_expr); - cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; @@ -911,9 +911,9 @@ struct FirrtlWorker string expr = make_expr(cell->getPort(ID::D)); string clk_expr = "asClock(" + make_expr(cell->getPort(ID::CLK)) + ")"; - wire_decls.push_back(stringf("%sreg %s: UInt<%d>, %s %s\n", indent.c_str(), y_id.c_str(), width, clk_expr.c_str(), cellFileinfo.c_str())); + wire_decls.push_back(stringf("%sreg %s: UInt<%d>, %s %s\n", indent, y_id, width, clk_expr, cellFileinfo)); - cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo)); register_reverse_wire_map(y_id, cell->getPort(ID::Q)); continue; @@ -926,7 +926,7 @@ struct FirrtlWorker string a_expr = make_expr(cell->getPort(ID::A)); // Get the initial bit selector string b_expr = make_expr(cell->getPort(ID::B)); - wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width)); if (cell->getParam(ID::B_SIGNED).as_bool()) { // Use validif to constrain the selection (test the sign bit) @@ -934,9 +934,9 @@ struct FirrtlWorker int b_sign = cell->parameters.at(ID::B_WIDTH).as_int() - 1; b_expr = stringf("validif(not(bits(%s, %d, %d)), %s)", b_string, b_sign, b_sign, b_string); } - string expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_expr.c_str()); + string expr = stringf("dshr(%s, %s)", a_expr, b_expr); - cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } @@ -948,21 +948,21 @@ struct FirrtlWorker string b_expr = make_expr(cell->getPort(ID::B)); auto b_string = b_expr.c_str(); string expr; - wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width)); if (cell->getParam(ID::B_SIGNED).as_bool()) { // We generate a left or right shift based on the sign of b. - std::string dshl = stringf("bits(dshl(%s, %s), 0, %d)", a_expr.c_str(), gen_dshl(b_expr, b_width).c_str(), y_width); - std::string dshr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string); + std::string dshl = stringf("bits(dshl(%s, %s), 0, %d)", a_expr, gen_dshl(b_expr, b_width), y_width); + std::string dshr = stringf("dshr(%s, %s)", a_expr, b_string); expr = stringf("mux(%s < 0, %s, %s)", b_string, dshl.c_str(), dshr.c_str() ); } else { - expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string); + expr = stringf("dshr(%s, %s)", a_expr, b_string); } - cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str())); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } @@ -973,10 +973,10 @@ struct FirrtlWorker // Verilog appears to treat the result as signed, so if the result is wider than "A", // we need to pad. if (a_width < y_width) { - a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width); + a_expr = stringf("pad(%s, %d)", a_expr, y_width); } - wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); - cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), a_expr.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width)); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, a_expr)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } @@ -999,7 +999,7 @@ struct FirrtlWorker for (int i = 0; i < GetSize(mem.rd_ports); i++) { auto &port = mem.rd_ports[i]; - string port_name(stringf("%s.r%d", mem_id.c_str(), i)); + string port_name(stringf("%s.r%d", mem_id, i)); if (port.clk_enable) log_error("Clocked read port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid)); @@ -1010,17 +1010,17 @@ struct FirrtlWorker string ena_expr = make_expr(State::S1); string clk_expr = make_expr(State::S0); - rpe << stringf("%s%s.addr <= %s\n", indent.c_str(), port_name.c_str(), addr_expr.c_str()); - rpe << stringf("%s%s.en <= %s\n", indent.c_str(), port_name.c_str(), ena_expr.c_str()); - rpe << stringf("%s%s.clk <= asClock(%s)\n", indent.c_str(), port_name.c_str(), clk_expr.c_str()); + rpe << stringf("%s%s.addr <= %s\n", indent, port_name, addr_expr); + rpe << stringf("%s%s.en <= %s\n", indent, port_name, ena_expr); + rpe << stringf("%s%s.clk <= asClock(%s)\n", indent, port_name, clk_expr); cell_exprs.push_back(rpe.str()); - register_reverse_wire_map(stringf("%s.data", port_name.c_str()), port.data); + register_reverse_wire_map(stringf("%s.data", port_name), port.data); } for (int i = 0; i < GetSize(mem.wr_ports); i++) { auto &port = mem.wr_ports[i]; - string port_name(stringf("%s.w%d", mem_id.c_str(), i)); + string port_name(stringf("%s.w%d", mem_id, i)); if (!port.clk_enable) log_error("Unclocked write port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid)); @@ -1037,18 +1037,18 @@ struct FirrtlWorker string ena_expr = make_expr(port.en[0]); string clk_expr = make_expr(port.clk); string mask_expr = make_expr(State::S1); - wpe << stringf("%s%s.data <= %s\n", indent.c_str(), port_name.c_str(), data_expr.c_str()); - wpe << stringf("%s%s.addr <= %s\n", indent.c_str(), port_name.c_str(), addr_expr.c_str()); - wpe << stringf("%s%s.en <= %s\n", indent.c_str(), port_name.c_str(), ena_expr.c_str()); - wpe << stringf("%s%s.clk <= asClock(%s)\n", indent.c_str(), port_name.c_str(), clk_expr.c_str()); - wpe << stringf("%s%s.mask <= %s\n", indent.c_str(), port_name.c_str(), mask_expr.c_str()); + wpe << stringf("%s%s.data <= %s\n", indent, port_name, data_expr); + wpe << stringf("%s%s.addr <= %s\n", indent, port_name, addr_expr); + wpe << stringf("%s%s.en <= %s\n", indent, port_name, ena_expr); + wpe << stringf("%s%s.clk <= asClock(%s)\n", indent, port_name, clk_expr); + wpe << stringf("%s%s.mask <= %s\n", indent, port_name, mask_expr); cell_exprs.push_back(wpe.str()); } std::ostringstream me; - me << stringf(" mem %s:\n", mem_id.c_str()); + me << stringf(" mem %s:\n", mem_id); me << stringf(" data-type => UInt<%d>\n", mem.width); me << stringf(" depth => %d\n", mem.size); for (int i = 0; i < GetSize(mem.rd_ports); i++) @@ -1068,8 +1068,8 @@ struct FirrtlWorker int y_width = GetSize(conn.first); string expr = make_expr(conn.second); - wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width)); - cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width)); + cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr)); register_reverse_wire_map(y_id, conn.first); } @@ -1112,7 +1112,7 @@ struct FirrtlWorker chunk_width++; } - new_expr = stringf("bits(%s, %d, %d)", start_map.first.c_str(), + new_expr = stringf("bits(%s, %d, %d)", start_map.first, start_map.second + chunk_width - 1, start_map.second); is_valid = true; } @@ -1135,13 +1135,13 @@ struct FirrtlWorker if (is_valid) { if (make_unconn_id) { - wire_decls.push_back(stringf("%swire %s: UInt<1> %s\n", indent.c_str(), unconn_id.c_str(), wireFileinfo.c_str())); + wire_decls.push_back(stringf("%swire %s: UInt<1> %s\n", indent, unconn_id, wireFileinfo)); // `invalid` is a firrtl construction for simulation so we will not // tag it with a @[fileinfo] tag as it doesn't directly correspond to // a specific line of verilog code. - wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), unconn_id.c_str())); + wire_decls.push_back(stringf("%s%s is invalid\n", indent, unconn_id)); } - wire_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), make_id(wire->name), expr.c_str(), wireFileinfo.c_str())); + wire_exprs.push_back(stringf("%s%s <= %s %s\n", indent, make_id(wire->name), expr, wireFileinfo)); } else { if (make_unconn_id) { unconn_id.clear(); @@ -1149,7 +1149,7 @@ struct FirrtlWorker // `invalid` is a firrtl construction for simulation so we will not // tag it with a @[fileinfo] tag as it doesn't directly correspond to // a specific line of verilog code. - wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), make_id(wire->name))); + wire_decls.push_back(stringf("%s%s is invalid\n", indent, make_id(wire->name))); } } @@ -1215,9 +1215,6 @@ struct FirrtlBackend : public Backend { } extra_args(f, filename, args, argidx); - if (!design->full_selection()) - log_cmd_error("This command only operates on fully selected designs!\n"); - log_header(design, "Executing FIRRTL backend.\n"); log_push(); @@ -1226,11 +1223,12 @@ struct FirrtlBackend : public Backend { Pass::call(design, "demuxmap"); Pass::call(design, "bwmuxmap"); + used_names.clear(); namecache.clear(); autoid_counter = 0; // Get the top module, or a reasonable facsimile - we need something for the circuit name. - Module *top = design->top_module(); + Module *top = nullptr; Module *last = nullptr; // Generate module and wire names. for (auto module : design->modules()) { @@ -1251,7 +1249,7 @@ struct FirrtlBackend : public Backend { log_cmd_error("There is no top module in this design!\n"); std::string circuitFileinfo = getFileinfo(top); - *f << stringf("circuit %s: %s\n", make_id(top->name), circuitFileinfo.c_str()); + *f << stringf("circuit %s: %s\n", make_id(top->name), circuitFileinfo); emit_elaborated_extmodules(design, *f); @@ -1265,6 +1263,7 @@ struct FirrtlBackend : public Backend { } } + used_names.clear(); namecache.clear(); autoid_counter = 0; } diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc new file mode 100644 index 000000000..16d1c0542 --- /dev/null +++ b/backends/functional/Makefile.inc @@ -0,0 +1,4 @@ +OBJS += backends/functional/cxx.o +OBJS += backends/functional/smtlib.o +OBJS += backends/functional/smtlib_rosette.o +OBJS += backends/functional/test_generic.o diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc new file mode 100644 index 000000000..7f4ad1ea7 --- /dev/null +++ b/backends/functional/cxx.cc @@ -0,0 +1,277 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * 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/yosys.h" +#include "kernel/functional.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +const char *reserved_keywords[] = { + "alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit", + "atomic_noexcept","auto","bitand","bitor","bool","break","case", + "catch","char","char16_t","char32_t","char8_t","class","co_await", + "co_return","co_yield","compl","concept","const","const_cast","consteval", + "constexpr","constinit","continue","decltype","default","delete", + "do","double","dynamic_cast","else","enum","explicit","export", + "extern","false","float","for","friend","goto","if","inline", + "int","long","mutable","namespace","new","noexcept","not","not_eq", + "nullptr","operator","or","or_eq","private","protected","public", + "reflexpr","register","reinterpret_cast","requires","return","short", + "signed","sizeof","static","static_log_assert","static_cast","struct", + "switch","synchronized","template","this","thread_local","throw", + "true","try","typedef","typeid","typename","union","unsigned", + "using","virtual","void","volatile","wchar_t","while","xor","xor_eq", + nullptr +}; + +template struct CxxScope : public Functional::Scope { + CxxScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + this->reserve(*p); + } + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || c == '_' || c == '$'); + } +}; + +struct CxxType { + Functional::Sort sort; + CxxType(Functional::Sort sort) : sort(sort) {} + std::string to_string() const { + if(sort.is_memory()) { + return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width()); + } else if(sort.is_signal()) { + return stringf("Signal<%d>", sort.width()); + } else { + log_error("unknown sort"); + } + } +}; + +using CxxWriter = Functional::Writer; + +struct CxxStruct { + std::string name; + dict types; + CxxScope scope; + CxxStruct(std::string name) : name(name) + { + scope.reserve("fn"); + scope.reserve("visit"); + } + void insert(IdString name, CxxType type) { + scope(name, name); + types.insert({name, type}); + } + void print(CxxWriter &f) { + f.print("\tstruct {} {{\n", name); + for (auto p : types) { + f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first)); + } + f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); + for (auto p : types) { + f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first)); + } + f.print("\t\t}}\n"); + f.print("\t}};\n\n"); + }; + std::string operator[](IdString field) { + return scope(field, field); + } +}; + +std::string cxx_const(RTLIL::Const const &value) { + std::stringstream ss; + ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; + if(value.size() > 32) ss << "{"; + for(int i = 0; i < value.size(); i += 32) { + if(i > 0) ss << ", "; + ss << value.extract(i, 32).as_int(); + } + if(value.size() > 32) ss << "}"; + ss << ")"; + return ss.str(); +} + +template struct CxxPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; + CxxWriter &f; + NodePrinter np; + CxxStruct &input_struct; + CxxStruct &state_struct; + CxxPrintVisitor(CxxWriter &f, NodePrinter np, CxxStruct &input_struct, CxxStruct &state_struct) : f(f), np(np), input_struct(input_struct), state_struct(state_struct) { } + template void print(const char *fmt, Args&&... args) { + f.print_with(np, fmt, std::forward(args)...); + } + void buf(Node, Node n) override { print("{}", n); } + void slice(Node, Node a, int offset, int out_width) override { print("{0}.slice<{2}>({1})", a, offset, out_width); } + void zero_extend(Node, Node a, int out_width) override { print("{}.zero_extend<{}>()", a, out_width); } + void sign_extend(Node, Node a, int out_width) override { print("{}.sign_extend<{}>()", a, out_width); } + void concat(Node, Node a, Node b) override { print("{}.concat({})", a, b); } + void add(Node, Node a, Node b) override { print("{} + {}", a, b); } + void sub(Node, Node a, Node b) override { print("{} - {}", a, b); } + void mul(Node, Node a, Node b) override { print("{} * {}", a, b); } + void unsigned_div(Node, Node a, Node b) override { print("{} / {}", a, b); } + void unsigned_mod(Node, Node a, Node b) override { print("{} % {}", a, b); } + void bitwise_and(Node, Node a, Node b) override { print("{} & {}", a, b); } + void bitwise_or(Node, Node a, Node b) override { print("{} | {}", a, b); } + void bitwise_xor(Node, Node a, Node b) override { print("{} ^ {}", a, b); } + void bitwise_not(Node, Node a) override { print("~{}", a); } + void unary_minus(Node, Node a) override { print("-{}", a); } + void reduce_and(Node, Node a) override { print("{}.all()", a); } + void reduce_or(Node, Node a) override { print("{}.any()", a); } + void reduce_xor(Node, Node a) override { print("{}.parity()", a); } + void equal(Node, Node a, Node b) override { print("{} == {}", a, b); } + void not_equal(Node, Node a, Node b) override { print("{} != {}", a, b); } + void signed_greater_than(Node, Node a, Node b) override { print("{}.signed_greater_than({})", a, b); } + void signed_greater_equal(Node, Node a, Node b) override { print("{}.signed_greater_equal({})", a, b); } + void unsigned_greater_than(Node, Node a, Node b) override { print("{} > {}", a, b); } + void unsigned_greater_equal(Node, Node a, Node b) override { print("{} >= {}", a, b); } + void logical_shift_left(Node, Node a, Node b) override { print("{} << {}", a, b); } + void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); } + void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } + void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } + void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); } + void input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); print("input.{}", input_struct[name]); } + void state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); print("current_state.{}", state_struct[name]); } + void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } + void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); } +}; + +bool equal_def(RTLIL::Const const &a, RTLIL::Const const &b) { + if(a.size() != b.size()) return false; + for(int i = 0; i < a.size(); i++) + if((a[i] == State::S1) != (b[i] == State::S1)) + return false; + return true; +} + +struct CxxModule { + Functional::IR ir; + CxxStruct input_struct, output_struct, state_struct; + std::string module_name; + + explicit CxxModule(Module *module) : + ir(Functional::IR::from_module(module)), + input_struct("Inputs"), + output_struct("Outputs"), + state_struct("State") + { + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); + module_name = CxxScope().unique_name(module->name); + } + void write_header(CxxWriter &f) { + f.print("#include \"sim.h\"\n\n"); + } + void write_struct_def(CxxWriter &f) { + f.print("struct {} {{\n", module_name); + input_struct.print(f); + output_struct.print(f); + state_struct.print(f); + f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); + f.print("\tstatic void initialize(State &);\n"); + f.print("}};\n\n"); + } + void write_initial_def(CxxWriter &f) { + f.print("void {0}::initialize({0}::State &state)\n{{\n", module_name); + for (auto state : ir.states()) { + if (state->sort.is_signal()) + f.print("\tstate.{} = {};\n", state_struct[state->name], cxx_const(state->initial_value_signal())); + else if (state->sort.is_memory()) { + f.print("\t{{\n"); + f.print("\t\tstd::array, {}> mem;\n", state->sort.data_width(), 1<sort.addr_width()); + const auto &contents = state->initial_value_memory(); + f.print("\t\tmem.fill({});\n", cxx_const(contents.default_value())); + for(auto range : contents) + for(auto addr = range.base(); addr < range.limit(); addr++) + if(!equal_def(range[addr], contents.default_value())) + f.print("\t\tmem[{}] = {};\n", addr, cxx_const(range[addr])); + f.print("\t\tstate.{} = mem;\n", state_struct[state->name]); + f.print("\t}}\n"); + } + } + f.print("}}\n\n"); + } + void write_eval_def(CxxWriter &f) { + f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name); + CxxScope locals; + locals.reserve("input"); + locals.reserve("output"); + locals.reserve("current_state"); + locals.reserve("next_state"); + auto node_name = [&](Functional::Node n) { return locals(n.id(), n.name()); }; + CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct); + for (auto node : ir) { + f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node)); + node.visit(printVisitor); + f.print(";\n"); + } + for (auto state : ir.states()) + f.print("\tnext_state.{} = {};\n", state_struct[state->name], node_name(state->next_value())); + for (auto output : ir.outputs()) + f.print("\toutput.{} = {};\n", output_struct[output->name], node_name(output->value())); + f.print("}}\n\n"); + } +}; + +struct FunctionalCxxBackend : public Backend +{ + FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log("TODO: add help message\n"); + log("\n"); + } + + void printCxx(std::ostream &stream, std::string, Module *module) + { + CxxWriter f(stream); + CxxModule mod(module); + mod.write_header(f); + mod.write_struct_def(f); + mod.write_eval_def(f); + mod.write_initial_def(f); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional C++ backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name); + printCxx(*f, filename, module); + } + } +} FunctionalCxxBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h new file mode 100644 index 000000000..941f067b9 --- /dev/null +++ b/backends/functional/cxx_runtime/sim.h @@ -0,0 +1,418 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * 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 SIM_H +#define SIM_H + +#include +#include +#include +#include +#include + +template +class Signal { + template friend class Signal; + std::array _bits; +public: + Signal() { } + Signal(uint32_t val) + { + for(size_t i = 0; i < n; i++) + if(i < 32) + _bits[i] = val & (1< vals) + { + size_t k, i; + + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + _bits[i + k] = val & (1< + static Signal from_array(T vals) + { + size_t k, i; + Signal ret; + + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + ret._bits[i + k] = val & (1< ret; + for(size_t i = 0; i < n; i++) + if(i < 32) + ret._bits[i] = val & (1< ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = b; + return ret; + } + + int size() const { return n; } + bool operator[](int i) const { assert(n >= 0 && i < n); return _bits[i]; } + + template + Signal slice(size_t offset) const + { + Signal ret; + + assert(offset + m <= n); + std::copy(_bits.begin() + offset, _bits.begin() + offset + m, ret._bits.begin()); + return ret; + } + + bool any() const + { + for(int i = 0; i < n; i++) + if(_bits[i]) + return true; + return false; + } + + bool all() const + { + for(int i = 0; i < n; i++) + if(!_bits[i]) + return false; + return true; + } + + bool parity() const + { + bool result = false; + for(int i = 0; i < n; i++) + result ^= _bits[i]; + return result; + } + + bool sign() const { return _bits[n-1]; } + + template + T as_numeric() const + { + T ret = 0; + for(size_t i = 0; i < std::min(sizeof(T) * 8, n); i++) + if(_bits[i]) + ret |= ((T)1)< + T as_numeric_clamped() const + { + for(size_t i = sizeof(T) * 8; i < n; i++) + if(_bits[i]) + return ~((T)0); + return as_numeric(); + } + + uint32_t as_int() const { return as_numeric(); } + +private: + std::string as_string_p2(int b) const { + std::string ret; + for(int i = (n - 1) - (n - 1) % b; i >= 0; i -= b) + ret += "0123456789abcdef"[(*this >> Signal<32>(i)).as_int() & ((1< t = *this; + Signal b = 10; + do{ + ret += (char)('0' + (t % b).as_int()); + t = t / b; + }while(t.any()); + std::reverse(ret.begin(), ret.end()); + return ret; + } +public: + std::string as_string(int base = 16, bool showbase = true) const { + std::string ret; + if(showbase) { + ret += std::to_string(n); + switch(base) { + case 2: ret += "'b"; break; + case 8: ret += "'o"; break; + case 10: ret += "'d"; break; + case 16: ret += "'h"; break; + default: assert(0); + } + } + switch(base) { + case 2: return ret + as_string_p2(1); + case 8: return ret + as_string_p2(3); + case 10: return ret + as_string_b10(); + case 16: return ret + as_string_p2(4); + default: assert(0); + } + } + friend std::ostream &operator << (std::ostream &os, Signal const &s) { return os << s.as_string(); } + + Signal operator ~() const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = !_bits[i]; + return ret; + } + + Signal operator -() const + { + Signal ret; + int x = 1; + for(size_t i = 0; i < n; i++) { + x += (int)!_bits[i]; + ret._bits[i] = (x & 1) != 0; + x >>= 1; + } + return ret; + } + + Signal operator +(Signal const &b) const + { + Signal ret; + int x = 0; + for(size_t i = 0; i < n; i++){ + x += (int)_bits[i] + (int)b._bits[i]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } + + Signal operator -(Signal const &b) const + { + Signal ret; + int x = 1; + for(size_t i = 0; i < n; i++){ + x += (int)_bits[i] + (int)!b._bits[i]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } + + Signal operator *(Signal const &b) const + { + Signal ret; + int x = 0; + for(size_t i = 0; i < n; i++){ + for(size_t j = 0; j <= i; j++) + x += (int)_bits[j] & (int)b._bits[i-j]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } + +private: + Signal divmod(Signal const &b, bool modulo) const + { + if(!b.any()) return 0; + Signal q = 0; + Signal r = 0; + for(size_t i = n; i-- != 0; ){ + r = r << Signal<1>(1); + r._bits[0] = _bits[i]; + if(r >= b){ + r = r - b; + q._bits[i] = true; + } + } + return modulo ? r : q; + } +public: + + Signal operator /(Signal const &b) const { return divmod(b, false); } + Signal operator %(Signal const &b) const { return divmod(b, true); } + + bool operator ==(Signal const &b) const + { + for(size_t i = 0; i < n; i++) + if(_bits[i] != b._bits[i]) + return false; + return true; + } + + bool operator >=(Signal const &b) const + { + for(size_t i = n; i-- != 0; ) + if(_bits[i] != b._bits[i]) + return _bits[i]; + return true; + } + + bool operator >(Signal const &b) const + { + for(size_t i = n; i-- != 0; ) + if(_bits[i] != b._bits[i]) + return _bits[i]; + return false; + } + + bool operator !=(Signal const &b) const { return !(*this == b); } + bool operator <=(Signal const &b) const { return b <= *this; } + bool operator <(Signal const &b) const { return b < *this; } + + bool signed_greater_than(Signal const &b) const + { + if(_bits[n-1] != b._bits[n-1]) + return b._bits[n-1]; + return *this > b; + } + + bool signed_greater_equal(Signal const &b) const + { + if(_bits[n-1] != b._bits[n-1]) + return b._bits[n-1]; + return *this >= b; + } + + Signal operator &(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] && b._bits[i]; + return ret; + } + + Signal operator |(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] || b._bits[i]; + return ret; + } + + Signal operator ^(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] != b._bits[i]; + return ret; + } + + template + Signal operator <<(Signal const &b) const + { + Signal ret = 0; + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin(), _bits.begin() + (n - amount), ret._bits.begin() + amount); + return ret; + } + + template + Signal operator >>(Signal const &b) const + { + Signal ret = 0; + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal arithmetic_shift_right(Signal const &b) const + { + Signal ret = Signal::repeat(sign()); + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal concat(Signal const& b) const + { + Signal ret; + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + std::copy(b._bits.begin(), b._bits.end(), ret._bits.begin() + n); + return ret; + } + + template + Signal zero_extend() const + { + assert(m >= n); + Signal ret = 0; + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal sign_extend() const + { + assert(m >= n); + Signal ret = Signal::repeat(sign()); + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + return ret; + } +}; + +template +class Memory { + std::array, 1< _contents; +public: + Memory() {} + Memory(std::array, 1< const &contents) : _contents(contents) {} + Signal read(Signal addr) const + { + return _contents[addr.template as_numeric()]; + } + Memory write(Signal addr, Signal data) const + { + Memory ret = *this; + ret._contents[addr.template as_numeric()] = data; + return ret; + } +}; + +#endif diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc new file mode 100644 index 000000000..1504c8fba --- /dev/null +++ b/backends/functional/smtlib.cc @@ -0,0 +1,295 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * 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/functional.h" +#include "kernel/yosys.h" +#include "kernel/sexpr.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +using SExprUtil::list; + +const char *reserved_keywords[] = { + // reserved keywords from the smtlib spec + "BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par", + "assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes", + "declare-fun", "declare-sort", "define-fun", "define-fun-rec", "define-funs-rec", "define-sort", + "exit", "get-assertions", "symbol", "sort", "get-assignment", "get-info", "get-model", + "get-option", "get-proof", "get-unsat-assumptions", "get-unsat-core", "get-value", + "pop", "push", "reset", "reset-assertions", "set-info", "set-logic", "set-option", + + // reserved for our own purposes + "pair", "Pair", "first", "second", + "inputs", "state", + nullptr +}; + +struct SmtScope : public Functional::Scope { + SmtScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("~!@$%^&*_-+=<>.?/", c)); + } +}; + +struct SmtSort { + Functional::Sort sort; + SmtSort(Functional::Sort sort) : sort(sort) {} + SExpr to_sexpr() const { + if(sort.is_memory()) { + return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width())); + } else if(sort.is_signal()) { + return list("_", "BitVec", sort.width()); + } else { + log_error("unknown sort"); + } + } +}; + +class SmtStruct { + struct Field { + SmtSort sort; + std::string accessor; + }; + idict field_names; + vector fields; + SmtScope &scope; +public: + std::string name; + SmtStruct(std::string name, SmtScope &scope) : scope(scope), name(name) {} + void insert(IdString field_name, SmtSort sort) { + field_names(field_name); + auto accessor = scope.unique_name("\\" + name + "_" + RTLIL::unescape_id(field_name)); + fields.emplace_back(Field{sort, accessor}); + } + void write_definition(SExprWriter &w) { + w.open(list("declare-datatype", name)); + w.open(list()); + w.open(list(name)); + for(const auto &field : fields) + w << list(field.accessor, field.sort.to_sexpr()); + w.close(3); + } + template void write_value(SExprWriter &w, Fn fn) { + if(field_names.empty()) { + // Zero-argument constructors in SMTLIB must not be called as functions. + w << name; + } else { + w.open(list(name)); + for(auto field_name : field_names) { + w << fn(field_name); + w.comment(RTLIL::unescape_id(field_name), true); + } + w.close(); + } + } + SExpr access(SExpr record, IdString name) { + size_t i = field_names.at(name); + return list(fields[i].accessor, std::move(record)); + } +}; + +std::string smt_const(RTLIL::Const const &c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; +} + +struct SmtPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; + std::function n; + SmtStruct &input_struct; + SmtStruct &state_struct; + + SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} + + SExpr from_bool(SExpr &&arg) { + return list("ite", std::move(arg), "#b1", "#b0"); + } + SExpr to_bool(SExpr &&arg) { + return list("=", std::move(arg), "#b1"); + } + SExpr extract(SExpr &&arg, int offset, int out_width = 1) { + return list(list("_", "extract", offset + out_width - 1, offset), std::move(arg)); + } + + SExpr buf(Node, Node a) override { return n(a); } + SExpr slice(Node, Node a, int offset, int out_width) override { return extract(n(a), offset, out_width); } + SExpr zero_extend(Node, Node a, int out_width) override { return list(list("_", "zero_extend", out_width - a.width()), n(a)); } + SExpr sign_extend(Node, Node a, int out_width) override { return list(list("_", "sign_extend", out_width - a.width()), n(a)); } + SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); } + SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); } + SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); } + SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); } + SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); } + SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); } + SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); } + SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); } + SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } + SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } + SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } + SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), smt_const(RTLIL::Const(State::S1, a.width())))); } + SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), smt_const(RTLIL::Const(State::S0, a.width())))); } + SExpr reduce_xor(Node, Node a) override { + vector s { "bvxor" }; + for(int i = 0; i < a.width(); i++) + s.push_back(extract(n(a), i)); + return s; + } + SExpr equal(Node, Node a, Node b) override { return from_bool(list("=", n(a), n(b))); } + SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("distinct", n(a), n(b))); } + SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); } + SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); } + SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); } + SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); } + + SExpr extend(SExpr &&a, int in_width, int out_width) { + if(in_width < out_width) + return list(list("_", "zero_extend", out_width - in_width), std::move(a)); + else + return std::move(a); + } + SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } + SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); } + SExpr constant(Node, RTLIL::Const const &value) override { return smt_const(value); } + SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } + SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } + + SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); } +}; + +struct SmtModule { + Functional::IR ir; + SmtScope scope; + std::string name; + + SmtStruct input_struct; + SmtStruct output_struct; + SmtStruct state_struct; + + SmtModule(Module *module) + : ir(Functional::IR::from_module(module)) + , scope() + , name(scope.unique_name(module->name)) + , input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope) + , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) + , state_struct(scope.unique_name(module->name.str() + "_State"), scope) + { + scope.reserve(name + "-initial"); + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); + } + + void write_eval(SExprWriter &w) + { + w.push(); + w.open(list("define-fun", name, + list(list("inputs", input_struct.name), + list("state", state_struct.name)), + list("Pair", output_struct.name, state_struct.name))); + auto inlined = [&](Functional::Node n) { + return n.fn() == Functional::Fn::constant; + }; + SmtPrintVisitor visitor(input_struct, state_struct); + auto node_to_sexpr = [&](Functional::Node n) -> SExpr { + if(inlined(n)) + return n.visit(visitor); + else + return scope(n.id(), n.name()); + }; + visitor.n = node_to_sexpr; + for(auto n : ir) + if(!inlined(n)) { + w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false); + w.comment(SmtSort(n.sort()).to_sexpr().to_string(), true); + } + w.open(list("pair")); + output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); }); + state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); }); + w.pop(); + } + + void write_initial(SExprWriter &w) + { + std::string initial = name + "-initial"; + w << list("declare-const", initial, state_struct.name); + for (auto state : ir.states()) { + if(state->sort.is_signal()) + w << list("assert", list("=", state_struct.access(initial, state->name), smt_const(state->initial_value_signal()))); + else if(state->sort.is_memory()) { + const auto &contents = state->initial_value_memory(); + for(int i = 0; i < 1<sort.addr_width(); i++) { + auto addr = smt_const(RTLIL::Const(i, state->sort.addr_width())); + w << list("assert", list("=", list("select", state_struct.access(initial, state->name), addr), smt_const(contents[i]))); + } + } + } + } + + void write(std::ostream &out) + { + SExprWriter w(out); + + input_struct.write_definition(w); + output_struct.write_definition(w); + state_struct.write_definition(w); + + w << list("declare-datatypes", + list(list("Pair", 2)), + list(list("par", list("X", "Y"), list(list("pair", list("first", "X"), list("second", "Y")))))); + + write_eval(w); + write_initial(w); + } +}; + +struct FunctionalSmtBackend : public Backend { + FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} + + void help() override { log("\nFunctional SMT Backend.\n\n"); } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional SMT Backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); + + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name); + SmtModule smt(module); + smt.write(*f); + } + } +} FunctionalSmtBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc new file mode 100644 index 000000000..73e1b48c6 --- /dev/null +++ b/backends/functional/smtlib_rosette.cc @@ -0,0 +1,373 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * 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/functional.h" +#include "kernel/yosys.h" +#include "kernel/sexpr.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +using SExprUtil::list; + +const char *reserved_keywords[] = { + // reserved keywords from the racket spec + "struct", "lambda", "values", "extract", "concat", "bv", "let", "define", "cons", "list", "read", "write", + "stream", "error", "raise", "exit", "for", "begin", "when", "unless", "module", "require", "provide", "apply", + "if", "cond", "even", "odd", "any", "and", "or", "match", "command-line", "ffi-lib", "thread", "kill", "sync", + "future", "touch", "subprocess", "make-custodian", "custodian-shutdown-all", "current-custodian", "make", "tcp", + "connect", "prepare", "malloc", "free", "_fun", "_cprocedure", "build", "path", "file", "peek", "bytes", + "flush", "with", "lexer", "parser", "syntax", "interface", "send", "make-object", "new", "instantiate", + "define-generics", "set", + + // reserved for our own purposes + "inputs", "state", "name", + nullptr +}; + +struct SmtrScope : public Functional::Scope { + SmtrScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("@$%^&_+=.", c)); + } +}; + +struct SmtrSort { + Functional::Sort sort; + SmtrSort(Functional::Sort sort) : sort(sort) {} + SExpr to_sexpr() const { + if(sort.is_memory()) { + return list("list", list("bitvector", sort.addr_width()), list("bitvector", sort.data_width())); + } else if(sort.is_signal()) { + return list("bitvector", sort.width()); + } else { + log_error("unknown sort"); + } + } +}; + +class SmtrStruct { + struct Field { + SmtrSort sort; + std::string accessor; + std::string name; + }; + idict field_names; + vector fields; + SmtrScope &global_scope; + SmtrScope local_scope; +public: + std::string name; + SmtrStruct(std::string name, SmtrScope &scope) : global_scope(scope), local_scope(), name(name) {} + void insert(IdString field_name, SmtrSort sort) { + field_names(field_name); + auto base_name = local_scope.unique_name(field_name); + auto accessor = name + "-" + base_name; + global_scope.reserve(accessor); + fields.emplace_back(Field{sort, accessor, base_name}); + } + void write_definition(SExprWriter &w) { + vector field_list; + for(const auto &field : fields) { + field_list.emplace_back(field.name); + } + w.push(); + w.open(list("struct", name, field_list, "#:transparent")); + if (field_names.size()) { + for (const auto &field : fields) { + auto bv_type = field.sort.to_sexpr(); + w.comment(field.name + " " + bv_type.to_string()); + } + } + w.pop(); + } + template void write_value(SExprWriter &w, Fn fn) { + w.open(list(name)); + for(auto field_name : field_names) { + w << fn(field_name); + w.comment(RTLIL::unescape_id(field_name), true); + } + w.close(); + } + SExpr access(SExpr record, IdString name) { + size_t i = field_names.at(name); + return list(fields[i].accessor, std::move(record)); + } +}; + +std::string smt_const(RTLIL::Const const &c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; +} + +struct SmtrPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; + std::function n; + SmtrStruct &input_struct; + SmtrStruct &state_struct; + + SmtrPrintVisitor(SmtrStruct &input_struct, SmtrStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} + + SExpr from_bool(SExpr &&arg) { + return list("bool->bitvector", std::move(arg)); + } + SExpr to_bool(SExpr &&arg) { + return list("bitvector->bool", std::move(arg)); + } + SExpr to_list(SExpr &&arg) { + return list("bitvector->bits", std::move(arg)); + } + + SExpr buf(Node, Node a) override { return n(a); } + SExpr slice(Node, Node a, int offset, int out_width) override { return list("extract", offset + out_width - 1, offset, n(a)); } + SExpr zero_extend(Node, Node a, int out_width) override { return list("zero-extend", n(a), list("bitvector", out_width)); } + SExpr sign_extend(Node, Node a, int out_width) override { return list("sign-extend", n(a), list("bitvector", out_width)); } + SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); } + SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); } + SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); } + SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); } + SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); } + SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); } + SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); } + SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); } + SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } + SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } + SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } + SExpr reduce_and(Node, Node a) override { return list("apply", "bvand", to_list(n(a))); } + SExpr reduce_or(Node, Node a) override { return list("apply", "bvor", to_list(n(a))); } + SExpr reduce_xor(Node, Node a) override { return list("apply", "bvxor", to_list(n(a))); } + SExpr equal(Node, Node a, Node b) override { return from_bool(list("bveq", n(a), n(b))); } + SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("not", list("bveq", n(a), n(b)))); } + SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); } + SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); } + SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); } + SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); } + + SExpr extend(SExpr &&a, int in_width, int out_width) { + if(in_width < out_width) + return list("zero-extend", std::move(a), list("bitvector", out_width)); + else + return std::move(a); + } + SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } + SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s) override { return list("if", to_bool(n(s)), n(b), n(a)); } + SExpr constant(Node, RTLIL::Const const& value) override { return list("bv", smt_const(value), value.size()); } + SExpr memory_read(Node, Node mem, Node addr) override { return list("list-ref-bv", n(mem), n(addr)); } + SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("list-set-bv", n(mem), n(addr), n(data)); } + + SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); } +}; + +struct SmtrModule { + Functional::IR ir; + SmtrScope scope; + std::string name; + bool use_assoc_list_helpers; + std::optional input_helper_name; + std::optional output_helper_name; + + SmtrStruct input_struct; + SmtrStruct output_struct; + SmtrStruct state_struct; + + SmtrModule(Module *module, bool assoc_list_helpers) + : ir(Functional::IR::from_module(module)), scope(), name(scope.unique_name(module->name)), use_assoc_list_helpers(assoc_list_helpers), + input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope), + output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope), + state_struct(scope.unique_name(module->name.str() + "_State"), scope) + { + scope.reserve(name + "_initial"); + if (assoc_list_helpers) { + input_helper_name = scope.unique_name(module->name.str() + "_inputs_helper"); + scope.reserve(*input_helper_name); + output_helper_name = scope.unique_name(module->name.str() + "_outputs_helper"); + scope.reserve(*output_helper_name); + } + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); + } + + void write_eval(SExprWriter &w) + { + w.push(); + w.open(list("define", list(name, "inputs", "state"))); + auto inlined = [&](Functional::Node n) { + return n.fn() == Functional::Fn::constant; + }; + SmtrPrintVisitor visitor(input_struct, state_struct); + auto node_to_sexpr = [&](Functional::Node n) -> SExpr { + if(inlined(n)) + return n.visit(visitor); + else + return scope(n.id(), n.name()); + }; + visitor.n = node_to_sexpr; + for(auto n : ir) + if(!inlined(n)) { + w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false); + w.comment(SmtrSort(n.sort()).to_sexpr().to_string(), true); + } + w.open(list("cons")); + output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); }); + state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); }); + w.pop(); + } + + void write_initial(SExprWriter &w) + { + w.push(); + auto initial = name + "_initial"; + w.open(list("define", initial)); + w.open(list(state_struct.name)); + for (auto state : ir.states()) { + if (state->sort.is_signal()) + w << list("bv", smt_const(state->initial_value_signal()), state->sort.width()); + else if (state->sort.is_memory()) { + const auto &contents = state->initial_value_memory(); + w.open(list("list")); + for(int i = 0; i < 1<sort.addr_width(); i++) { + w << list("bv", smt_const(contents[i]), state->sort.data_width()); + } + w.close(); + } + } + w.pop(); + } + + void write_assoc_list_helpers(SExprWriter &w) + { + log_assert(output_helper_name && input_helper_name); + + // Input struct keyword-based constructor. + w.push(); + w.open(list("define")); + const auto inputs_name = "inputs"; + w.open(list(*input_helper_name, inputs_name)); + w.close(); + w.open(list(input_struct.name)); + for (auto input : ir.inputs()) { + w.push(); + w.open(list("let")); + w.push(); + w.open(list()); + w.open(list("assoc-result")); + w << list("assoc", "\"" + RTLIL::unescape_id(input->name) + "\"", inputs_name); + w.pop(); + w.open(list("if", "assoc-result")); + w << list("cdr", "assoc-result"); + w.open(list("begin")); + w << list("fprintf", list("current-error-port"), "\"%s not found in inputs\""); + w << "'not-found"; + w.pop(); + } + w.pop(); + // Output struct keyword-based destructuring + w.push(); + w.open(list("define")); + const auto outputs_name = "outputs"; + w << list(*output_helper_name, outputs_name); + w.open(list("list")); + for (auto output : ir.outputs()) { + w << list("cons", "\"" + RTLIL::unescape_id(output->name) + "\"", output_struct.access("outputs", output->name)); + } + w.pop(); + } + + void write(std::ostream &out) + { + SExprWriter w(out); + + input_struct.write_definition(w); + output_struct.write_definition(w); + state_struct.write_definition(w); + + if (use_assoc_list_helpers) { + write_assoc_list_helpers(w); + } + + write_eval(w); + write_initial(w); + } +}; + +struct FunctionalSmtrBackend : public Backend { + FunctionalSmtrBackend() : Backend("functional_rosette", "Generate Rosette compatible Racket from Functional IR") {} + + void help() override { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_functional_rosette [options] [filename]\n"); + log("\n"); + log("Functional Rosette Backend.\n"); + log("\n"); + log(" -provides\n"); + log(" include 'provide' statement(s) for loading output as a module\n"); + log(" -assoc-list-helpers\n"); + log(" provide helper functions which convert inputs/outputs from/to association lists\n"); + log(" \n"); + log("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + auto provides = false; + auto assoc_list_helpers = false; + + log_header(design, "Executing Functional Rosette Backend.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-provides") + provides = true; + else if (args[argidx] == "-assoc-list-helpers") + assoc_list_helpers = true; + else + break; + } + extra_args(f, filename, args, argidx); + + *f << "#lang rosette/safe\n"; + if (provides) { + *f << "(provide (all-defined-out))\n"; + } + + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name.c_str()); + SmtrModule smtr(module, assoc_list_helpers); + smtr.write(*f); + } + } +} FunctionalSmtrBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc new file mode 100644 index 000000000..c01649a0f --- /dev/null +++ b/backends/functional/test_generic.cc @@ -0,0 +1,158 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * 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/yosys.h" +#include "kernel/functional.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct MemContentsTest { + int addr_width, data_width; + MemContents state; + using addr_t = MemContents::addr_t; + std::map reference; + MemContentsTest(int addr_width, int data_width) : addr_width(addr_width), data_width(data_width), state(addr_width, data_width, RTLIL::Const(State::S0, data_width)) {} + void check() { + state.check(); + for(auto addr = 0; addr < (1<second != state[addr]) goto error; + } else { + if(state.count_range(addr, addr + 1) != 0) goto error; + } + } + return; + error: + printf("FAIL\n"); + int digits = (data_width + 3) / 4; + + for(auto addr = 0; addr < (1<second : state.default_value(); + std::string ref_string = stringf("%.*x", digits, ref_value.as_int()); + bool sta_def = state.count_range(addr, addr + 1) == 1; + RTLIL::Const sta_value = state[addr]; + std::string sta_string = stringf("%.*x", digits, sta_value.as_int()); + if(ref_def && sta_def) { + if(ref_value == sta_value) printf("%s%s", ref_string.c_str(), string(digits, ' ').c_str()); + else printf("%s%s", ref_string.c_str(), sta_string.c_str()); + } else if(ref_def) { + printf("%s%s", ref_string.c_str(), string(digits, 'M').c_str()); + } else if(sta_def) { + printf("%s%s", sta_string.c_str(), string(digits, 'X').c_str()); + } else { + printf("%s", string(2*digits, ' ').c_str()); + } + printf(" "); + if(addr % 8 == 7) printf("\n"); + } + printf("\n"); + //log_abort(); + } + void clear_range(addr_t begin_addr, addr_t end_addr) { + for(auto addr = begin_addr; addr != end_addr; addr++) + reference.erase(addr); + state.clear_range(begin_addr, end_addr); + check(); + } + void insert_concatenated(addr_t addr, RTLIL::Const const &values) { + addr_t words = ((addr_t) values.size() + data_width - 1) / data_width; + for(addr_t i = 0; i < words; i++) { + reference.erase(addr + i); + reference.emplace(addr + i, values.extract(i * data_width, data_width)); + } + state.insert_concatenated(addr, values); + check(); + } + template void run(Rnd &rnd, int n) { + std::uniform_int_distribution addr_dist(0, (1< length_dist(10); + std::uniform_int_distribution data_dist(0, ((uint64_t)1< 0) { + addr_t low = addr_dist(rnd); + //addr_t length = std::min((1< high) std::swap(low, high); + if((rnd() & 7) == 0) { + log_debug("clear %.2x to %.2x\n", (int)low, (int)high); + clear_range(low, high + 1); + } else { + log_debug("insert %.2x to %.2x\n", (int)low, (int)high); + RTLIL::Const values; + for(addr_t addr = low; addr <= high; addr++) { + RTLIL::Const word(data_dist(rnd), data_width); + values.append(word); + } + insert_concatenated(low, values); + } + } + } + +}; + +struct FunctionalTestGeneric : public Pass +{ + FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") { + internal(); + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log("TODO: add help message\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Test Generic.\n"); + + size_t argidx = 1; + extra_args(args, argidx, design); +/* + MemContentsTest test(8, 16); + + std::random_device seed_dev; + std::mt19937 rnd(23); //seed_dev()); + test.run(rnd, 1000); +*/ + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name); + auto fir = Functional::IR::from_module(module); + for(auto node : fir) + std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n"; + for(auto output : fir.all_outputs()) + std::cout << RTLIL::unescape_id(output->kind) << " " << RTLIL::unescape_id(output->name) << " = " << RTLIL::unescape_id(output->value().name()) << "\n"; + for(auto state : fir.all_states()) + std::cout << RTLIL::unescape_id(state->kind) << " " << RTLIL::unescape_id(state->name) << " = " << RTLIL::unescape_id(state->next_value().name()) << "\n"; + } + } +} FunctionalCxxBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/intersynth/intersynth.cc b/backends/intersynth/intersynth.cc index 59173c4a2..78eab17da 100644 --- a/backends/intersynth/intersynth.cc +++ b/backends/intersynth/intersynth.cc @@ -100,13 +100,13 @@ struct IntersynthBackend : public Backend { } extra_args(f, filename, args, argidx); - log("Output filename: %s\n", filename.c_str()); + log("Output filename: %s\n", filename); for (auto filename : libfiles) { std::ifstream f; f.open(filename.c_str()); if (f.fail()) - log_error("Can't open lib file `%s'.\n", filename.c_str()); + log_error("Can't open lib file `%s'.\n", filename); RTLIL::Design *lib = new RTLIL::Design; Frontend::frontend_call(lib, &f, filename, (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".il") == 0 ? "rtlil" : "verilog")); libs.push_back(lib); @@ -172,15 +172,15 @@ struct IntersynthBackend : public Backend { if (sig.size() != 0) { conntypes_code.insert(stringf("conntype b%d %d 2 %d\n", sig.size(), sig.size(), sig.size())); celltype_code += stringf(" b%d %s%s", sig.size(), ct.cell_output(cell->type, port.first) ? "*" : "", log_id(port.first)); - node_code += stringf(" %s %s", log_id(port.first), netname(conntypes_code, celltypes_code, constcells_code, sig).c_str()); + node_code += stringf(" %s %s", log_id(port.first), netname(conntypes_code, celltypes_code, constcells_code, sig)); } } for (auto ¶m : cell->parameters) { - celltype_code += stringf(" cfg:%d %s", int(param.second.bits.size()), log_id(param.first)); - if (param.second.bits.size() != 32) { + celltype_code += stringf(" cfg:%d %s", int(param.second.size()), log_id(param.first)); + if (param.second.size() != 32) { node_code += stringf(" %s '", log_id(param.first)); - for (int i = param.second.bits.size()-1; i >= 0; i--) - node_code += param.second.bits[i] == State::S1 ? "1" : "0"; + for (int i = param.second.size()-1; i >= 0; i--) + node_code += param.second[i] == State::S1 ? "1" : "0"; } else node_code += stringf(" %s 0x%x", log_id(param.first), param.second.as_int()); } @@ -199,13 +199,13 @@ struct IntersynthBackend : public Backend { if (!flag_notypes) { *f << stringf("### Connection Types\n"); for (auto code : conntypes_code) - *f << stringf("%s", code.c_str()); + *f << stringf("%s", code); *f << stringf("\n### Cell Types\n"); for (auto code : celltypes_code) - *f << stringf("%s", code.c_str()); + *f << stringf("%s", code); } *f << stringf("\n### Netlists\n"); - *f << stringf("%s", netlists_code.c_str()); + *f << stringf("%s", netlists_code); for (auto lib : libs) delete lib; diff --git a/backends/jny/jny.cc b/backends/jny/jny.cc index 9989feed5..ee0c0d14c 100644 --- a/backends/jny/jny.cc +++ b/backends/jny/jny.cc @@ -124,8 +124,8 @@ struct JnyWriter design->sort(); f << "{\n"; - f << " \"$schema\": \"https://raw.githubusercontent.com/YosysHQ/yosys/master/misc/jny.schema.json\",\n"; - f << stringf(" \"generator\": \"%s\",\n", escape_string(yosys_version_str).c_str()); + f << " \"$schema\": \"https://raw.githubusercontent.com/YosysHQ/yosys/main/misc/jny.schema.json\",\n"; + f << stringf(" \"generator\": \"%s\",\n", escape_string(yosys_maybe_version())); f << " \"version\": \"0.0.1\",\n"; f << " \"invocation\": \"" << escape_string(invk) << "\",\n"; f << " \"features\": ["; @@ -232,7 +232,7 @@ struct JnyWriter const auto _indent = gen_indent(indent_level); f << _indent << "{\n"; - f << stringf(" %s\"name\": \"%s\",\n", _indent.c_str(), escape_string(RTLIL::unescape_id(mod->name)).c_str()); + f << stringf(" %s\"name\": \"%s\",\n", _indent, escape_string(RTLIL::unescape_id(mod->name))); f << _indent << " \"cell_sorts\": [\n"; bool first_sort{true}; @@ -280,7 +280,7 @@ struct JnyWriter f << ",\n"; f << _indent << " {\n"; - f << stringf(" %s\"name\": \"%s\",\n", _indent.c_str(), escape_string(RTLIL::unescape_id(con.first)).c_str()); + f << stringf(" %s\"name\": \"%s\",\n", _indent, escape_string(RTLIL::unescape_id(con.first))); f << _indent << " \"direction\": \""; if (port_cell->input(con.first)) f << "i"; @@ -290,7 +290,7 @@ struct JnyWriter if (con.second.size() == 1) f << _indent << " \"range\": [0, 0]\n"; else - f << stringf(" %s\"range\": [%d, %d]\n", _indent.c_str(), con.second.size(), 0); + f << stringf(" %s\"range\": [%d, %d]\n", _indent, con.second.size(), 0); f << _indent << " }"; first_port = false; @@ -304,7 +304,7 @@ struct JnyWriter const auto _indent = gen_indent(indent_level); f << _indent << "{\n"; - f << stringf(" %s\"type\": \"%s\",\n", _indent.c_str(), sort.first.c_str()); + f << stringf(" %s\"type\": \"%s\",\n", _indent, sort.first); f << _indent << " \"ports\": [\n"; write_cell_ports(port_cell, indent_level + 2); @@ -351,10 +351,10 @@ struct JnyWriter f << stringf(",\n"); const auto param_val = param.second; if (!param_val.empty()) { - f << stringf(" %s\"%s\": ", _indent.c_str(), escape_string(RTLIL::unescape_id(param.first)).c_str()); + f << stringf(" %s\"%s\": ", _indent, escape_string(RTLIL::unescape_id(param.first))); write_param_val(param_val); } else { - f << stringf(" %s\"%s\": true", _indent.c_str(), escape_string(RTLIL::unescape_id(param.first)).c_str()); + f << stringf(" %s\"%s\": true", _indent, escape_string(RTLIL::unescape_id(param.first))); } first_param = false; @@ -366,7 +366,7 @@ struct JnyWriter log_assert(cell != nullptr); f << _indent << " {\n"; - f << stringf(" %s\"name\": \"%s\"", _indent.c_str(), escape_string(RTLIL::unescape_id(cell->name)).c_str()); + f << stringf(" %s\"name\": \"%s\"", _indent, escape_string(RTLIL::unescape_id(cell->name))); if (_include_connections) { f << ",\n" << _indent << " \"connections\": [\n"; @@ -426,7 +426,7 @@ struct JnyBackend : public Backend { log(" Don't include property information in the netlist output.\n"); log("\n"); log("The JSON schema for JNY output files is located in the \"jny.schema.json\" file\n"); - log("which is located at \"https://raw.githubusercontent.com/YosysHQ/yosys/master/misc/jny.schema.json\"\n"); + log("which is located at \"https://raw.githubusercontent.com/YosysHQ/yosys/main/misc/jny.schema.json\"\n"); log("\n"); } @@ -553,7 +553,7 @@ struct JnyPass : public Pass { ff->open(filename.c_str(), std::ofstream::trunc); if (ff->fail()) { delete ff; - log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno)); + log_error("Can't open file `%s' for writing: %s\n", filename, strerror(errno)); } f = ff; invk << filename; @@ -568,7 +568,7 @@ struct JnyPass : public Pass { if (!empty) { delete f; } else { - log("%s", buf.str().c_str()); + log("%s", buf.str()); } } diff --git a/backends/json/json.cc b/backends/json/json.cc index 2f442c494..b04083622 100644 --- a/backends/json/json.cc +++ b/backends/json/json.cc @@ -34,6 +34,7 @@ struct JsonWriter bool use_selection; bool aig_mode; bool compat_int_mode; + bool scopeinfo_mode; Design *design; Module *module; @@ -43,9 +44,9 @@ struct JsonWriter dict sigids; pool aig_models; - JsonWriter(std::ostream &f, bool use_selection, bool aig_mode, bool compat_int_mode) : + JsonWriter(std::ostream &f, bool use_selection, bool aig_mode, bool compat_int_mode, bool scopeinfo_mode) : f(f), use_selection(use_selection), aig_mode(aig_mode), - compat_int_mode(compat_int_mode) { } + compat_int_mode(compat_int_mode), scopeinfo_mode(scopeinfo_mode) { } string get_string(string str) { @@ -134,7 +135,7 @@ struct JsonWriter bool first = true; for (auto ¶m : parameters) { f << stringf("%s\n", first ? "" : ","); - f << stringf(" %s%s: ", for_module ? "" : " ", get_name(param.first).c_str()); + f << stringf(" %s%s: ", for_module ? "" : " ", get_name(param.first)); write_parameter_value(param.second); first = false; } @@ -154,7 +155,7 @@ struct JsonWriter log_error("Module %s contains processes, which are not supported by JSON backend (run `proc` first).\n", log_id(module)); } - f << stringf(" %s: {\n", get_name(module->name).c_str()); + f << stringf(" %s: {\n", get_name(module->name)); f << stringf(" \"attributes\": {"); write_parameters(module->attributes, /*for_module=*/true); @@ -173,7 +174,7 @@ struct JsonWriter if (use_selection && !module->selected(w)) continue; f << stringf("%s\n", first ? "" : ","); - f << stringf(" %s: {\n", get_name(n).c_str()); + f << stringf(" %s: {\n", get_name(n)); f << stringf(" \"direction\": \"%s\",\n", w->port_input ? w->port_output ? "inout" : "input" : "output"); if (w->start_offset) f << stringf(" \"offset\": %d,\n", w->start_offset); @@ -181,7 +182,7 @@ struct JsonWriter f << stringf(" \"upto\": 1,\n"); if (w->is_signed) f << stringf(" \"signed\": %d,\n", w->is_signed); - f << stringf(" \"bits\": %s\n", get_bits(w).c_str()); + f << stringf(" \"bits\": %s\n", get_bits(w)); f << stringf(" }"); first = false; } @@ -192,18 +193,16 @@ struct JsonWriter for (auto c : module->cells()) { if (use_selection && !module->selected(c)) continue; - // Eventually we will want to emit $scopeinfo, but currently this - // will break JSON netlist consumers like nextpnr - if (c->type == ID($scopeinfo)) + if (!scopeinfo_mode && c->type == ID($scopeinfo)) continue; f << stringf("%s\n", first ? "" : ","); - f << stringf(" %s: {\n", get_name(c->name).c_str()); + f << stringf(" %s: {\n", get_name(c->name)); f << stringf(" \"hide_name\": %s,\n", c->name[0] == '$' ? "1" : "0"); - f << stringf(" \"type\": %s,\n", get_name(c->type).c_str()); + f << stringf(" \"type\": %s,\n", get_name(c->type)); if (aig_mode) { Aig aig(c); if (!aig.name.empty()) { - f << stringf(" \"model\": \"%s\",\n", aig.name.c_str()); + f << stringf(" \"model\": \"%s\",\n", aig.name); aig_models.insert(aig); } } @@ -221,7 +220,7 @@ struct JsonWriter if (c->input(conn.first)) direction = c->output(conn.first) ? "inout" : "input"; f << stringf("%s\n", first2 ? "" : ","); - f << stringf(" %s: \"%s\"", get_name(conn.first).c_str(), direction.c_str()); + f << stringf(" %s: \"%s\"", get_name(conn.first), direction); first2 = false; } f << stringf("\n },\n"); @@ -230,7 +229,7 @@ struct JsonWriter bool first2 = true; for (auto &conn : c->connections()) { f << stringf("%s\n", first2 ? "" : ","); - f << stringf(" %s: %s", get_name(conn.first).c_str(), get_bits(conn.second).c_str()); + f << stringf(" %s: %s", get_name(conn.first), get_bits(conn.second)); first2 = false; } f << stringf("\n }\n"); @@ -246,7 +245,7 @@ struct JsonWriter if (use_selection && !module->selected(it.second)) continue; f << stringf("%s\n", first ? "" : ","); - f << stringf(" %s: {\n", get_name(it.second->name).c_str()); + f << stringf(" %s: {\n", get_name(it.second->name)); f << stringf(" \"hide_name\": %s,\n", it.second->name[0] == '$' ? "1" : "0"); f << stringf(" \"attributes\": {"); write_parameters(it.second->attributes); @@ -266,9 +265,9 @@ struct JsonWriter if (use_selection && !module->selected(w)) continue; f << stringf("%s\n", first ? "" : ","); - f << stringf(" %s: {\n", get_name(w->name).c_str()); + f << stringf(" %s: {\n", get_name(w->name)); f << stringf(" \"hide_name\": %s,\n", w->name[0] == '$' ? "1" : "0"); - f << stringf(" \"bits\": %s,\n", get_bits(w).c_str()); + f << stringf(" \"bits\": %s,\n", get_bits(w)); if (w->start_offset) f << stringf(" \"offset\": %d,\n", w->start_offset); if (w->upto) @@ -292,7 +291,7 @@ struct JsonWriter design->sort(); f << stringf("{\n"); - f << stringf(" \"creator\": %s,\n", get_string(yosys_version_str).c_str()); + f << stringf(" \"creator\": %s,\n", get_string(yosys_maybe_version())); f << stringf(" \"modules\": {\n"); vector modules = use_selection ? design->selected_modules() : design->modules(); bool first_module = true; @@ -309,7 +308,7 @@ struct JsonWriter for (auto &aig : aig_models) { if (!first_model) f << stringf(",\n"); - f << stringf(" \"%s\": [\n", aig.name.c_str()); + f << stringf(" \"%s\": [\n", aig.name); int node_idx = 0; for (auto &node : aig.nodes) { if (node_idx != 0) @@ -353,6 +352,12 @@ struct JsonBackend : public Backend { log(" emit 32-bit or smaller fully-defined parameter values directly\n"); log(" as JSON numbers (for compatibility with old parsers)\n"); log("\n"); + log(" -selected\n"); + log(" output only select module\n"); + log("\n"); + log(" -noscopeinfo\n"); + log(" don't include $scopeinfo cells in the output\n"); + log("\n"); log("\n"); log("The general syntax of the JSON output created by this command is as follows:\n"); log("\n"); @@ -403,7 +408,7 @@ struct JsonBackend : public Backend { log("\n"); log("The \"offset\" and \"upto\" fields are skipped if their value would be 0.\n"); log("They don't affect connection semantics, and are only used to preserve original\n"); - log("HDL bit indexing."); + log("HDL bit indexing.\n"); log("And is:\n"); log("\n"); log(" {\n"); @@ -597,6 +602,8 @@ struct JsonBackend : public Backend { { bool aig_mode = false; bool compat_int_mode = false; + bool use_selection = false; + bool scopeinfo_mode = true; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -609,13 +616,21 @@ struct JsonBackend : public Backend { compat_int_mode = true; continue; } + if (args[argidx] == "-selected") { + use_selection = true; + continue; + } + if (args[argidx] == "-noscopeinfo") { + scopeinfo_mode = false; + continue; + } break; } extra_args(f, filename, args, argidx); log_header(design, "Executing JSON backend.\n"); - JsonWriter json_writer(*f, false, aig_mode, compat_int_mode); + JsonWriter json_writer(*f, use_selection, aig_mode, compat_int_mode, scopeinfo_mode); json_writer.write_design(design); } } JsonBackend; @@ -640,6 +655,9 @@ struct JsonPass : public Pass { log(" emit 32-bit or smaller fully-defined parameter values directly\n"); log(" as JSON numbers (for compatibility with old parsers)\n"); log("\n"); + log(" -noscopeinfo\n"); + log(" don't include $scopeinfo cells in the output\n"); + log("\n"); log("See 'help write_json' for a description of the JSON format used.\n"); log("\n"); } @@ -648,6 +666,7 @@ struct JsonPass : public Pass { std::string filename; bool aig_mode = false; bool compat_int_mode = false; + bool scopeinfo_mode = true; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -664,6 +683,10 @@ struct JsonPass : public Pass { compat_int_mode = true; continue; } + if (args[argidx] == "-noscopeinfo") { + scopeinfo_mode = false; + continue; + } break; } extra_args(args, argidx, design); @@ -678,20 +701,20 @@ struct JsonPass : public Pass { ff->open(filename.c_str(), std::ofstream::trunc); if (ff->fail()) { delete ff; - log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno)); + log_error("Can't open file `%s' for writing: %s\n", filename, strerror(errno)); } f = ff; } else { f = &buf; } - JsonWriter json_writer(*f, true, aig_mode, compat_int_mode); + JsonWriter json_writer(*f, true, aig_mode, compat_int_mode, scopeinfo_mode); json_writer.write_design(design); if (!empty) { delete f; } else { - log("%s", buf.str().c_str()); + log("%s", buf.str()); } } } JsonPass; diff --git a/backends/rtlil/rtlil_backend.cc b/backends/rtlil/rtlil_backend.cc index 574eb3aaa..d47bebf82 100644 --- a/backends/rtlil/rtlil_backend.cc +++ b/backends/rtlil/rtlil_backend.cc @@ -24,22 +24,33 @@ #include "rtlil_backend.h" #include "kernel/yosys.h" +#include "kernel/utils.h" #include +#include USING_YOSYS_NAMESPACE using namespace RTLIL_BACKEND; YOSYS_NAMESPACE_BEGIN +void RTLIL_BACKEND::dump_attributes(std::ostream &f, std::string indent, const RTLIL::AttrObject *obj) +{ + for (const auto& [name, value] : reversed(obj->attributes)) { + f << stringf("%s" "attribute %s ", indent, name); + dump_const(f, value); + f << stringf("\n"); + } +} + void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int width, int offset, bool autoint) { if (width < 0) - width = data.bits.size() - offset; - if ((data.flags & RTLIL::CONST_FLAG_STRING) == 0 || width != (int)data.bits.size()) { + width = data.size() - offset; + if ((data.flags & RTLIL::CONST_FLAG_STRING) == 0 || width != (int)data.size()) { if (width == 32 && autoint) { int32_t val = 0; for (int i = 0; i < width; i++) { - log_assert(offset+i < (int)data.bits.size()); - switch (data.bits[offset+i]) { + log_assert(offset+i < (int)data.size()); + switch (data[offset+i]) { case State::S0: break; case State::S1: val |= 1 << i; break; default: val = -1; break; @@ -50,13 +61,18 @@ void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi return; } } - f << stringf("%d'", width); + if ((data.flags & RTLIL::CONST_FLAG_UNSIZED) == 0) { + f << stringf("%d'", width); + } + if (data.flags & RTLIL::CONST_FLAG_SIGNED) { + f << stringf("s"); + } if (data.is_fully_undef_x_only()) { f << "x"; } else { for (int i = offset+width-1; i >= offset; i--) { - log_assert(i < (int)data.bits.size()); - switch (data.bits[i]) { + log_assert(i < (int)data.size()); + switch (data[i]) { case State::S0: f << stringf("0"); break; case State::S1: f << stringf("1"); break; case RTLIL::Sx: f << stringf("x"); break; @@ -93,11 +109,11 @@ void RTLIL_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, dump_const(f, chunk.data, chunk.width, chunk.offset, autoint); } else { if (chunk.width == chunk.wire->width && chunk.offset == 0) - f << stringf("%s", chunk.wire->name.c_str()); + f << stringf("%s", chunk.wire->name); else if (chunk.width == 1) - f << stringf("%s [%d]", chunk.wire->name.c_str(), chunk.offset); + f << stringf("%s [%d]", chunk.wire->name, chunk.offset); else - f << stringf("%s [%d:%d]", chunk.wire->name.c_str(), chunk.offset+chunk.width-1, chunk.offset); + f << stringf("%s [%d:%d]", chunk.wire->name, chunk.offset+chunk.width-1, chunk.offset); } } @@ -107,8 +123,9 @@ void RTLIL_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, boo dump_sigchunk(f, sig.as_chunk(), autoint); } else { f << stringf("{ "); - for (auto it = sig.chunks().rbegin(); it != sig.chunks().rend(); ++it) { - dump_sigchunk(f, *it, false); + auto chunks = sig.chunks(); + for (const auto& chunk : reversed(chunks)) { + dump_sigchunk(f, chunk, false); f << stringf(" "); } f << stringf("}"); @@ -117,12 +134,12 @@ void RTLIL_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, boo void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire) { - for (auto &it : wire->attributes) { - f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str()); - dump_const(f, it.second); - f << stringf("\n"); + dump_attributes(f, indent, wire); + if (wire->driverCell_) { + f << stringf("%s" "# driver %s %s\n", indent, + wire->driverCell()->name, wire->driverPort()); } - f << stringf("%s" "wire ", indent.c_str()); + f << stringf("%s" "wire ", indent); if (wire->width != 1) f << stringf("width %d ", wire->width); if (wire->upto) @@ -137,101 +154,85 @@ void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL:: f << stringf("inout %d ", wire->port_id); if (wire->is_signed) f << stringf("signed "); - f << stringf("%s\n", wire->name.c_str()); + f << stringf("%s\n", wire->name); } void RTLIL_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory) { - for (auto &it : memory->attributes) { - f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str()); - dump_const(f, it.second); - f << stringf("\n"); - } - f << stringf("%s" "memory ", indent.c_str()); + dump_attributes(f, indent, memory); + f << stringf("%s" "memory ", indent); if (memory->width != 1) f << stringf("width %d ", memory->width); if (memory->size != 0) f << stringf("size %d ", memory->size); if (memory->start_offset != 0) f << stringf("offset %d ", memory->start_offset); - f << stringf("%s\n", memory->name.c_str()); + f << stringf("%s\n", memory->name); } void RTLIL_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell) { - for (auto &it : cell->attributes) { - f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str()); - dump_const(f, it.second); + dump_attributes(f, indent, cell); + f << stringf("%s" "cell %s %s\n", indent, cell->type, cell->name); + for (const auto& [name, param] : reversed(cell->parameters)) { + f << stringf("%s parameter%s%s%s %s ", indent, + (param.flags & RTLIL::CONST_FLAG_SIGNED) != 0 ? " signed" : "", + (param.flags & RTLIL::CONST_FLAG_REAL) != 0 ? " real" : "", + (param.flags & RTLIL::CONST_FLAG_UNSIZED) != 0 ? " unsized" : "", + name); + dump_const(f, param); f << stringf("\n"); } - f << stringf("%s" "cell %s %s\n", indent.c_str(), cell->type.c_str(), cell->name.c_str()); - for (auto &it : cell->parameters) { - f << stringf("%s parameter%s%s %s ", indent.c_str(), - (it.second.flags & RTLIL::CONST_FLAG_SIGNED) != 0 ? " signed" : "", - (it.second.flags & RTLIL::CONST_FLAG_REAL) != 0 ? " real" : "", - it.first.c_str()); - dump_const(f, it.second); + for (const auto& [port, sig] : reversed(cell->connections_)) { + f << stringf("%s connect %s ", indent, port); + dump_sigspec(f, sig); f << stringf("\n"); } - for (auto &it : cell->connections()) { - f << stringf("%s connect %s ", indent.c_str(), it.first.c_str()); - dump_sigspec(f, it.second); - f << stringf("\n"); - } - f << stringf("%s" "end\n", indent.c_str()); + f << stringf("%s" "end\n", indent); } void RTLIL_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs) { - for (auto it = cs->actions.begin(); it != cs->actions.end(); ++it) - { - f << stringf("%s" "assign ", indent.c_str()); - dump_sigspec(f, it->first); + for (const auto& [lhs, rhs] : cs->actions) { + f << stringf("%s" "assign ", indent); + dump_sigspec(f, lhs); f << stringf(" "); - dump_sigspec(f, it->second); + dump_sigspec(f, rhs); f << stringf("\n"); } - for (auto it = cs->switches.begin(); it != cs->switches.end(); ++it) - dump_proc_switch(f, indent, *it); + for (const auto& sw : cs->switches) + dump_proc_switch(f, indent, sw); } void RTLIL_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const RTLIL::SwitchRule *sw) { - for (auto it = sw->attributes.begin(); it != sw->attributes.end(); ++it) { - f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str()); - dump_const(f, it->second); - f << stringf("\n"); - } + dump_attributes(f, indent, sw); - f << stringf("%s" "switch ", indent.c_str()); + f << stringf("%s" "switch ", indent); dump_sigspec(f, sw->signal); f << stringf("\n"); - for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) + for (const auto case_ : sw->cases) { - for (auto ait = (*it)->attributes.begin(); ait != (*it)->attributes.end(); ++ait) { - f << stringf("%s attribute %s ", indent.c_str(), ait->first.c_str()); - dump_const(f, ait->second); - f << stringf("\n"); - } - f << stringf("%s case ", indent.c_str()); - for (size_t i = 0; i < (*it)->compare.size(); i++) { + dump_attributes(f, indent, case_); + f << stringf("%s case ", indent); + for (size_t i = 0; i < case_->compare.size(); i++) { if (i > 0) f << stringf(" , "); - dump_sigspec(f, (*it)->compare[i]); + dump_sigspec(f, case_->compare[i]); } f << stringf("\n"); - dump_proc_case_body(f, indent + " ", *it); + dump_proc_case_body(f, indent + " ", case_); } - f << stringf("%s" "end\n", indent.c_str()); + f << stringf("%s" "end\n", indent); } void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RTLIL::SyncRule *sy) { - f << stringf("%s" "sync ", indent.c_str()); + f << stringf("%s" "sync ", indent); switch (sy->type) { case RTLIL::ST0: f << stringf("low "); if (0) case RTLIL::ST1: f << stringf("high "); @@ -246,21 +247,17 @@ void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RT case RTLIL::STi: f << stringf("init\n"); break; } - for (auto &it: sy->actions) { - f << stringf("%s update ", indent.c_str()); - dump_sigspec(f, it.first); + for (const auto& [lhs, rhs] : sy->actions) { + f << stringf("%s update ", indent); + dump_sigspec(f, lhs); f << stringf(" "); - dump_sigspec(f, it.second); + dump_sigspec(f, rhs); f << stringf("\n"); } for (auto &it: sy->mem_write_actions) { - for (auto it2 = it.attributes.begin(); it2 != it.attributes.end(); ++it2) { - f << stringf("%s attribute %s ", indent.c_str(), it2->first.c_str()); - dump_const(f, it2->second); - f << stringf("\n"); - } - f << stringf("%s memwr %s ", indent.c_str(), it.memid.c_str()); + dump_attributes(f, indent, &it); + f << stringf("%s memwr %s ", indent, it.memid); dump_sigspec(f, it.address); f << stringf(" "); dump_sigspec(f, it.data); @@ -274,21 +271,17 @@ void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RT void RTLIL_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc) { - for (auto it = proc->attributes.begin(); it != proc->attributes.end(); ++it) { - f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str()); - dump_const(f, it->second); - f << stringf("\n"); - } - f << stringf("%s" "process %s\n", indent.c_str(), proc->name.c_str()); + dump_attributes(f, indent, proc); + f << stringf("%s" "process %s\n", indent, proc->name); dump_proc_case_body(f, indent + " ", &proc->root_case); - for (auto it = proc->syncs.begin(); it != proc->syncs.end(); ++it) - dump_proc_sync(f, indent + " ", *it); - f << stringf("%s" "end\n", indent.c_str()); + for (auto* sync : proc->syncs) + dump_proc_sync(f, indent + " ", sync); + f << stringf("%s" "end\n", indent); } void RTLIL_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) { - f << stringf("%s" "connect ", indent.c_str()); + f << stringf("%s" "connect ", indent); dump_sigspec(f, left); f << stringf(" "); dump_sigspec(f, right); @@ -297,18 +290,14 @@ void RTLIL_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL:: void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n) { - bool print_header = flag_m || design->selected_whole_module(module->name); - bool print_body = !flag_n || !design->selected_whole_module(module->name); + bool print_header = flag_m || module->is_selected_whole(); + bool print_body = !flag_n || !module->is_selected_whole(); if (print_header) { - for (auto it = module->attributes.begin(); it != module->attributes.end(); ++it) { - f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str()); - dump_const(f, it->second); - f << stringf("\n"); - } + dump_attributes(f, indent, module); - f << stringf("%s" "module %s\n", indent.c_str(), module->name.c_str()); + f << stringf("%s" "module %s\n", indent, module->name); if (!module->avail_parameters.empty()) { if (only_selected) @@ -316,9 +305,9 @@ void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu for (const auto &p : module->avail_parameters) { const auto &it = module->parameter_default_values.find(p); if (it == module->parameter_default_values.end()) { - f << stringf("%s" " parameter %s\n", indent.c_str(), p.c_str()); + f << stringf("%s" " parameter %s\n", indent, p); } else { - f << stringf("%s" " parameter %s ", indent.c_str(), p.c_str()); + f << stringf("%s" " parameter %s ", indent, p); dump_const(f, it->second); f << stringf("\n"); } @@ -328,40 +317,40 @@ void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu if (print_body) { - for (auto it : module->wires()) - if (!only_selected || design->selected(module, it)) { + for (const auto& [_, wire] : reversed(module->wires_)) + if (!only_selected || design->selected(module, wire)) { if (only_selected) f << stringf("\n"); - dump_wire(f, indent + " ", it); + dump_wire(f, indent + " ", wire); } - for (auto it : module->memories) - if (!only_selected || design->selected(module, it.second)) { + for (const auto& [_, mem] : reversed(module->memories)) + if (!only_selected || design->selected(module, mem)) { if (only_selected) f << stringf("\n"); - dump_memory(f, indent + " ", it.second); + dump_memory(f, indent + " ", mem); } - for (auto it : module->cells()) - if (!only_selected || design->selected(module, it)) { + for (const auto& [_, cell] : reversed(module->cells_)) + if (!only_selected || design->selected(module, cell)) { if (only_selected) f << stringf("\n"); - dump_cell(f, indent + " ", it); + dump_cell(f, indent + " ", cell); } - for (auto it : module->processes) - if (!only_selected || design->selected(module, it.second)) { + for (const auto& [_, process] : reversed(module->processes)) + if (!only_selected || design->selected(module, process)) { if (only_selected) f << stringf("\n"); - dump_proc(f, indent + " ", it.second); + dump_proc(f, indent + " ", process); } bool first_conn_line = true; - for (auto it = module->connections().begin(); it != module->connections().end(); ++it) { + for (const auto& [lhs, rhs] : module->connections()) { bool show_conn = !only_selected || design->selected_whole_module(module->name); if (!show_conn) { - RTLIL::SigSpec sigs = it->first; - sigs.append(it->second); + RTLIL::SigSpec sigs = lhs; + sigs.append(rhs); for (auto &c : sigs.chunks()) { if (c.wire == NULL || !design->selected(module, c.wire)) continue; @@ -371,14 +360,14 @@ void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu if (show_conn) { if (only_selected && first_conn_line) f << stringf("\n"); - dump_conn(f, indent + " ", it->first, it->second); + dump_conn(f, indent + " ", lhs, rhs); first_conn_line = false; } } } if (print_header) - f << stringf("%s" "end\n", indent.c_str()); + f << stringf("%s" "end\n", indent); } void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n) @@ -387,7 +376,7 @@ void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool onl if (!flag_m) { int count_selected_mods = 0; - for (auto module : design->modules()) { + for (auto* module : design->modules()) { if (design->selected_whole_module(module->name)) flag_m = true; if (design->selected(module)) @@ -403,7 +392,7 @@ void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool onl f << stringf("autoidx %d\n", autoidx); } - for (auto module : design->modules()) { + for (const auto& [_, module] : reversed(design->modules_)) { if (!only_selected || design->selected(module)) { if (only_selected) f << stringf("\n"); @@ -431,10 +420,14 @@ struct RTLILBackend : public Backend { log(" -selected\n"); log(" only write selected parts of the design.\n"); log("\n"); + log(" -sort\n"); + log(" sort design in-place (used to be default).\n"); + log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { bool selected = false; + bool do_sort = false; log_header(design, "Executing RTLIL backend.\n"); @@ -445,33 +438,24 @@ struct RTLILBackend : public Backend { selected = true; continue; } + if (arg == "-sort") { + do_sort = true; + continue; + } break; } extra_args(f, filename, args, argidx); - design->sort(); + log("Output filename: %s\n", filename); - log("Output filename: %s\n", filename.c_str()); - *f << stringf("# Generated by %s\n", yosys_version_str); + if (do_sort) + design->sort(); + + *f << stringf("# Generated by %s\n", yosys_maybe_version()); RTLIL_BACKEND::dump_design(*f, design, selected, true, false); } } RTLILBackend; -struct IlangBackend : public Backend { - IlangBackend() : Backend("ilang", "(deprecated) alias of write_rtlil") { } - void help() override - { - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| - log("\n"); - log("See `help write_rtlil`.\n"); - log("\n"); - } - void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override - { - RTLILBackend.execute(f, filename, args, design); - } -} IlangBackend; - struct DumpPass : public Pass { DumpPass() : Pass("dump", "print parts of the design in RTLIL format") { } void help() override @@ -535,10 +519,10 @@ struct DumpPass : public Pass { if (!empty) { rewrite_filename(filename); std::ofstream *ff = new std::ofstream; - ff->open(filename.c_str(), append ? std::ofstream::app : std::ofstream::trunc); + ff->open(filename, append ? std::ofstream::app : std::ofstream::trunc); if (ff->fail()) { delete ff; - log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno)); + log_error("Can't open file `%s' for writing: %s\n", filename, strerror(errno)); } f = ff; } else { @@ -550,7 +534,7 @@ struct DumpPass : public Pass { if (!empty) { delete f; } else { - log("%s", buf.str().c_str()); + log("%s", buf.str()); } } } DumpPass; diff --git a/backends/rtlil/rtlil_backend.h b/backends/rtlil/rtlil_backend.h index 35829729c..dd7347def 100644 --- a/backends/rtlil/rtlil_backend.h +++ b/backends/rtlil/rtlil_backend.h @@ -31,6 +31,7 @@ YOSYS_NAMESPACE_BEGIN namespace RTLIL_BACKEND { + void dump_attributes(std::ostream &f, std::string indent, const RTLIL::AttrObject *obj); void dump_const(std::ostream &f, const RTLIL::Const &data, int width = -1, int offset = 0, bool autoint = true); void dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint = true); void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint = true); diff --git a/backends/simplec/simplec.cc b/backends/simplec/simplec.cc index e283dcf7c..8ebc685f0 100644 --- a/backends/simplec/simplec.cc +++ b/backends/simplec/simplec.cc @@ -218,8 +218,8 @@ struct SimplecWorker s[i] -= 'a' - 'A'; util_declarations.push_back(""); - util_declarations.push_back(stringf("#ifndef %s", s.c_str())); - util_declarations.push_back(stringf("#define %s", s.c_str())); + util_declarations.push_back(stringf("#ifndef %s", s)); + util_declarations.push_back(stringf("#define %s", s)); } string util_get_bit(const string &signame, int n, int idx) @@ -232,33 +232,33 @@ struct SimplecWorker if (generated_utils.count(util_name) == 0) { util_ifdef_guard(util_name); - util_declarations.push_back(stringf("static inline bool %s(const %s *sig)", util_name.c_str(), sigtype(n).c_str())); + util_declarations.push_back(stringf("static inline bool %s(const %s *sig)", util_name, sigtype(n))); util_declarations.push_back(stringf("{")); int word_idx = idx / max_uintsize, word_offset = idx % max_uintsize; string value_name = stringf("value_%d_%d", std::min(n-1, (word_idx+1)*max_uintsize-1), word_idx*max_uintsize); - util_declarations.push_back(stringf(" return (sig->%s >> %d) & 1;", value_name.c_str(), word_offset)); + util_declarations.push_back(stringf(" return (sig->%s >> %d) & 1;", value_name, word_offset)); util_declarations.push_back(stringf("}")); util_declarations.push_back(stringf("#endif")); generated_utils.insert(util_name); } - return stringf("%s(&%s)", util_name.c_str(), signame.c_str()); + return stringf("%s(&%s)", util_name, signame); } string util_set_bit(const string &signame, int n, int idx, const string &expr) { if (n == 1 && idx == 0) - return stringf(" %s.value_0_0 = %s;", signame.c_str(), expr.c_str()); + return stringf(" %s.value_0_0 = %s;", signame, expr); string util_name = stringf("yosys_simplec_set_bit_%d_of_%d", idx, n); if (generated_utils.count(util_name) == 0) { util_ifdef_guard(util_name); - util_declarations.push_back(stringf("static inline void %s(%s *sig, bool value)", util_name.c_str(), sigtype(n).c_str())); + util_declarations.push_back(stringf("static inline void %s(%s *sig, bool value)", util_name, sigtype(n))); util_declarations.push_back(stringf("{")); int word_idx = idx / max_uintsize, word_offset = idx % max_uintsize; @@ -266,9 +266,9 @@ struct SimplecWorker #if 0 util_declarations.push_back(stringf(" if (value)")); - util_declarations.push_back(stringf(" sig->%s |= 1UL << %d;", value_name.c_str(), word_offset)); + util_declarations.push_back(stringf(" sig->%s |= 1UL << %d;", value_name, word_offset)); util_declarations.push_back(stringf(" else")); - util_declarations.push_back(stringf(" sig->%s &= ~(1UL << %d);", value_name.c_str(), word_offset)); + util_declarations.push_back(stringf(" sig->%s &= ~(1UL << %d);", value_name, word_offset)); #else util_declarations.push_back(stringf(" sig->%s = (sig->%s & ~((uint%d_t)1 << %d)) | ((uint%d_t)value << %d);", value_name.c_str(), value_name.c_str(), max_uintsize, word_offset, max_uintsize, word_offset)); @@ -279,7 +279,7 @@ struct SimplecWorker generated_utils.insert(util_name); } - return stringf(" %s(&%s, %s);", util_name.c_str(), signame.c_str(), expr.c_str()); + return stringf(" %s(&%s, %s);", util_name, signame, expr); } void create_module_struct(Module *mod) @@ -339,38 +339,38 @@ struct SimplecWorker for (int i = 0; i < GetSize(topo.sorted); i++) topoidx[mod->cell(topo.sorted[i])] = i; - string ifdef_name = stringf("yosys_simplec_%s_state_t", cid(mod->name).c_str()); + string ifdef_name = stringf("yosys_simplec_%s_state_t", cid(mod->name)); for (int i = 0; i < GetSize(ifdef_name); i++) if ('a' <= ifdef_name[i] && ifdef_name[i] <= 'z') ifdef_name[i] -= 'a' - 'A'; struct_declarations.push_back(""); - struct_declarations.push_back(stringf("#ifndef %s", ifdef_name.c_str())); - struct_declarations.push_back(stringf("#define %s", ifdef_name.c_str())); - struct_declarations.push_back(stringf("struct %s_state_t", cid(mod->name).c_str())); + struct_declarations.push_back(stringf("#ifndef %s", ifdef_name)); + struct_declarations.push_back(stringf("#define %s", ifdef_name)); + struct_declarations.push_back(stringf("struct %s_state_t", cid(mod->name))); struct_declarations.push_back("{"); struct_declarations.push_back(" // Input Ports"); for (Wire *w : mod->wires()) if (w->port_input) - struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width).c_str(), cid(w->name).c_str(), log_id(w))); + struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width), cid(w->name), log_id(w))); struct_declarations.push_back(""); struct_declarations.push_back(" // Output Ports"); for (Wire *w : mod->wires()) if (!w->port_input && w->port_output) - struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width).c_str(), cid(w->name).c_str(), log_id(w))); + struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width), cid(w->name), log_id(w))); struct_declarations.push_back(""); struct_declarations.push_back(" // Internal Wires"); for (Wire *w : mod->wires()) if (!w->port_input && !w->port_output) - struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width).c_str(), cid(w->name).c_str(), log_id(w))); + struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width), cid(w->name), log_id(w))); for (Cell *c : mod->cells()) if (design->module(c->type)) - struct_declarations.push_back(stringf(" struct %s_state_t %s; // %s", cid(c->type).c_str(), cid(c->name).c_str(), log_id(c))); + struct_declarations.push_back(stringf(" struct %s_state_t %s; // %s", cid(c->type), cid(c->name), log_id(c))); struct_declarations.push_back(stringf("};")); struct_declarations.push_back("#endif"); @@ -407,14 +407,14 @@ struct SimplecWorker string b_expr = b.wire ? util_get_bit(work->prefix + cid(b.wire->name), b.wire->width, b.offset) : b.data ? "1" : "0"; string expr; - if (cell->type == ID($_AND_)) expr = stringf("%s & %s", a_expr.c_str(), b_expr.c_str()); - if (cell->type == ID($_NAND_)) expr = stringf("!(%s & %s)", a_expr.c_str(), b_expr.c_str()); - if (cell->type == ID($_OR_)) expr = stringf("%s | %s", a_expr.c_str(), b_expr.c_str()); - if (cell->type == ID($_NOR_)) expr = stringf("!(%s | %s)", a_expr.c_str(), b_expr.c_str()); - if (cell->type == ID($_XOR_)) expr = stringf("%s ^ %s", a_expr.c_str(), b_expr.c_str()); - if (cell->type == ID($_XNOR_)) expr = stringf("!(%s ^ %s)", a_expr.c_str(), b_expr.c_str()); - if (cell->type == ID($_ANDNOT_)) expr = stringf("%s & (!%s)", a_expr.c_str(), b_expr.c_str()); - if (cell->type == ID($_ORNOT_)) expr = stringf("%s | (!%s)", a_expr.c_str(), b_expr.c_str()); + if (cell->type == ID($_AND_)) expr = stringf("%s & %s", a_expr, b_expr); + if (cell->type == ID($_NAND_)) expr = stringf("!(%s & %s)", a_expr, b_expr); + if (cell->type == ID($_OR_)) expr = stringf("%s | %s", a_expr, b_expr); + if (cell->type == ID($_NOR_)) expr = stringf("!(%s | %s)", a_expr, b_expr); + if (cell->type == ID($_XOR_)) expr = stringf("%s ^ %s", a_expr, b_expr); + if (cell->type == ID($_XNOR_)) expr = stringf("!(%s ^ %s)", a_expr, b_expr); + if (cell->type == ID($_ANDNOT_)) expr = stringf("%s & (!%s)", a_expr, b_expr); + if (cell->type == ID($_ORNOT_)) expr = stringf("%s | (!%s)", a_expr, b_expr); log_assert(y.wire); funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) + @@ -436,8 +436,8 @@ struct SimplecWorker string c_expr = c.wire ? util_get_bit(work->prefix + cid(c.wire->name), c.wire->width, c.offset) : c.data ? "1" : "0"; string expr; - if (cell->type == ID($_AOI3_)) expr = stringf("!((%s & %s) | %s)", a_expr.c_str(), b_expr.c_str(), c_expr.c_str()); - if (cell->type == ID($_OAI3_)) expr = stringf("!((%s | %s) & %s)", a_expr.c_str(), b_expr.c_str(), c_expr.c_str()); + if (cell->type == ID($_AOI3_)) expr = stringf("!((%s & %s) | %s)", a_expr, b_expr, c_expr); + if (cell->type == ID($_OAI3_)) expr = stringf("!((%s | %s) & %s)", a_expr, b_expr, c_expr); log_assert(y.wire); funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) + @@ -461,8 +461,8 @@ struct SimplecWorker string d_expr = d.wire ? util_get_bit(work->prefix + cid(d.wire->name), d.wire->width, d.offset) : d.data ? "1" : "0"; string expr; - if (cell->type == ID($_AOI4_)) expr = stringf("!((%s & %s) | (%s & %s))", a_expr.c_str(), b_expr.c_str(), c_expr.c_str(), d_expr.c_str()); - if (cell->type == ID($_OAI4_)) expr = stringf("!((%s | %s) & (%s | %s))", a_expr.c_str(), b_expr.c_str(), c_expr.c_str(), d_expr.c_str()); + if (cell->type == ID($_AOI4_)) expr = stringf("!((%s & %s) | (%s & %s))", a_expr, b_expr, c_expr, d_expr); + if (cell->type == ID($_OAI4_)) expr = stringf("!((%s | %s) & (%s | %s))", a_expr, b_expr, c_expr, d_expr); log_assert(y.wire); funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) + @@ -484,9 +484,9 @@ struct SimplecWorker string s_expr = s.wire ? util_get_bit(work->prefix + cid(s.wire->name), s.wire->width, s.offset) : s.data ? "1" : "0"; // casts to bool are a workaround for CBMC bug (https://github.com/diffblue/cbmc/issues/933) - string expr = stringf("%s ? %s(bool)%s : %s(bool)%s", s_expr.c_str(), - cell->type == ID($_NMUX_) ? "!" : "", b_expr.c_str(), - cell->type == ID($_NMUX_) ? "!" : "", a_expr.c_str()); + string expr = stringf("%s ? %s(bool)%s : %s(bool)%s", s_expr, + cell->type == ID($_NMUX_) ? "!" : "", b_expr, + cell->type == ID($_NMUX_) ? "!" : "", a_expr); log_assert(y.wire); funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) + @@ -504,7 +504,7 @@ struct SimplecWorker while (work->dirty) { if (verbose && (!work->dirty_bits.empty() || !work->dirty_cells.empty())) - log(" In %s:\n", work->log_prefix.c_str()); + log(" In %s:\n", work->log_prefix); while (!work->dirty_bits.empty() || !work->dirty_cells.empty()) { @@ -517,8 +517,8 @@ struct SimplecWorker if (chunk.wire == nullptr) continue; if (verbose) - log(" Propagating %s.%s[%d:%d].\n", work->log_prefix.c_str(), log_id(chunk.wire), chunk.offset+chunk.width-1, chunk.offset); - funct_declarations.push_back(stringf(" // Updated signal in %s: %s", work->log_prefix.c_str(), log_signal(chunk))); + log(" Propagating %s.%s[%d:%d].\n", work->log_prefix, log_id(chunk.wire), chunk.offset+chunk.width-1, chunk.offset); + funct_declarations.push_back(stringf(" // Updated signal in %s: %s", work->log_prefix, log_signal(chunk))); } for (SigBit bit : dirtysig) @@ -539,7 +539,7 @@ struct SimplecWorker work->parent->set_dirty(parent_bit); if (verbose) - log(" Propagating %s.%s[%d] -> %s.%s[%d].\n", work->log_prefix.c_str(), log_id(bit.wire), bit.offset, + log(" Propagating %s.%s[%d] -> %s.%s[%d].\n", work->log_prefix, log_id(bit.wire), bit.offset, work->parent->log_prefix.c_str(), log_id(parent_bit.wire), parent_bit.offset); } @@ -556,11 +556,11 @@ struct SimplecWorker child->set_dirty(child_bit); if (verbose) - log(" Propagating %s.%s[%d] -> %s.%s.%s[%d].\n", work->log_prefix.c_str(), log_id(bit.wire), bit.offset, + log(" Propagating %s.%s[%d] -> %s.%s.%s[%d].\n", work->log_prefix, log_id(bit.wire), bit.offset, work->log_prefix.c_str(), log_id(std::get<0>(port)), log_id(child_bit.wire), child_bit.offset); } else { if (verbose) - log(" Marking cell %s.%s (via %s.%s[%d]).\n", work->log_prefix.c_str(), log_id(std::get<0>(port)), + log(" Marking cell %s.%s (via %s.%s[%d]).\n", work->log_prefix, log_id(std::get<0>(port)), work->log_prefix.c_str(), log_id(bit.wire), bit.offset); work->set_dirty(std::get<0>(port)); } @@ -579,7 +579,7 @@ struct SimplecWorker string hiername = work->log_prefix + "." + log_id(cell); if (verbose) - log(" Evaluating %s (%s, best of %d).\n", hiername.c_str(), log_id(cell->type), GetSize(work->dirty_cells)); + log(" Evaluating %s (%s, best of %d).\n", hiername, log_id(cell->type), GetSize(work->dirty_cells)); if (activated_cells.count(hiername)) reactivated_cells.insert(hiername); @@ -630,13 +630,13 @@ struct SimplecWorker void make_func(HierDirtyFlags *work, const string &func_name, const vector &preamble) { - log("Generating function %s():\n", func_name.c_str()); + log("Generating function %s():\n", func_name); activated_cells.clear(); reactivated_cells.clear(); funct_declarations.push_back(""); - funct_declarations.push_back(stringf("static void %s(struct %s_state_t *state)", func_name.c_str(), cid(work->module->name).c_str())); + funct_declarations.push_back(stringf("static void %s(struct %s_state_t *state)", func_name, cid(work->module->name))); funct_declarations.push_back("{"); for (auto &line : preamble) funct_declarations.push_back(line); @@ -657,7 +657,7 @@ struct SimplecWorker { SigSpec sig = sigmaps.at(module)(w); Const val = w->attributes.at(ID::init); - val.bits.resize(GetSize(sig), State::Sx); + val.resize(GetSize(sig), State::Sx); for (int i = 0; i < GetSize(sig); i++) if (val[i] == State::S0 || val[i] == State::S1) { diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index c702d5e7e..d80622029 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -24,6 +24,7 @@ #include "kernel/log.h" #include "kernel/mem.h" #include "libs/json11/json11.hpp" +#include "kernel/utils.h" #include USING_YOSYS_NAMESPACE @@ -81,27 +82,27 @@ struct Smt2Worker if (statebv) { if (width == 0) { - decl_str = stringf("(define-fun |%s| ((state |%s_s|)) Bool (= ((_ extract %d %d) state) #b1))", name.c_str(), get_id(module), statebv_width, statebv_width); + decl_str = stringf("(define-fun |%s| ((state |%s_s|)) Bool (= ((_ extract %d %d) state) #b1))", name, get_id(module), statebv_width, statebv_width); statebv_width += 1; } else { - decl_str = stringf("(define-fun |%s| ((state |%s_s|)) (_ BitVec %d) ((_ extract %d %d) state))", name.c_str(), get_id(module), width, statebv_width+width-1, statebv_width); + decl_str = stringf("(define-fun |%s| ((state |%s_s|)) (_ BitVec %d) ((_ extract %d %d) state))", name, get_id(module), width, statebv_width+width-1, statebv_width); statebv_width += width; } } else if (statedt) { if (width == 0) { - decl_str = stringf(" (|%s| Bool)", name.c_str()); + decl_str = stringf(" (|%s| Bool)", name); } else { - decl_str = stringf(" (|%s| (_ BitVec %d))", name.c_str(), width); + decl_str = stringf(" (|%s| (_ BitVec %d))", name, width); } } else { if (width == 0) { - decl_str = stringf("(declare-fun |%s| (|%s_s|) Bool)", name.c_str(), get_id(module)); + decl_str = stringf("(declare-fun |%s| (|%s_s|) Bool)", name, get_id(module)); } else { - decl_str = stringf("(declare-fun |%s| (|%s_s|) (_ BitVec %d))", name.c_str(), get_id(module), width); + decl_str = stringf("(declare-fun |%s| (|%s_s|) (_ BitVec %d))", name, get_id(module), width); } } @@ -129,7 +130,7 @@ struct Smt2Worker for (auto &mem : memories) { if (is_smtlib2_module) - log_error("Memory %s.%s not allowed in module with smtlib2_module attribute", get_id(module), mem.memid.c_str()); + log_error("Memory %s.%s not allowed in module with smtlib2_module attribute", get_id(module), mem.memid); mem.narrow(); mem_dict[mem.memid] = &mem; @@ -329,13 +330,14 @@ struct Smt2Worker { sigmap.apply(bit); + if (bit_driver.count(bit)) { + export_cell(bit_driver.at(bit)); + sigmap.apply(bit); + } + if (bit.wire == nullptr) return bit == RTLIL::State::S1 ? "true" : "false"; - if (bit_driver.count(bit)) - export_cell(bit_driver.at(bit)); - sigmap.apply(bit); - if (fcache.count(bit) == 0) { if (verbose) log("%*s-> external bool: %s\n", 2+2*GetSize(recursive_cells), "", log_signal(bit)); @@ -381,7 +383,7 @@ struct Smt2Worker } if (fcache.count(sig[i]) && fcache.at(sig[i]).second == -1) { - subexpr.push_back(stringf("(ite %s #b1 #b0)", get_bool(sig[i], state_name).c_str())); + subexpr.push_back(stringf("(ite %s #b1 #b0)", get_bool(sig[i], state_name))); continue; } @@ -493,7 +495,7 @@ struct Smt2Worker } if (width != GetSize(sig_y) && type != 'b') - processed_expr = stringf("((_ extract %d 0) %s)", GetSize(sig_y)-1, processed_expr.c_str()); + processed_expr = stringf("((_ extract %d 0) %s)", GetSize(sig_y)-1, processed_expr); if (verbose) log("%*s-> import cell: %s\n", 2+2*GetSize(recursive_cells), "", log_id(cell)); @@ -615,14 +617,14 @@ struct Smt2Worker string infostr = cell->attributes.count(ID::src) ? cell->attributes.at(ID::src).decode_string().c_str() : get_id(cell); if (cell->attributes.count(ID::reg)) infostr += " " + cell->attributes.at(ID::reg).decode_string(); - decls.push_back(stringf("; yosys-smt2-%s %s#%d %d %s\n", cell->type.c_str() + 1, get_id(module), idcounter, GetSize(cell->getPort(QY)), infostr.c_str())); + decls.push_back(stringf("; yosys-smt2-%s %s#%d %d %s\n", cell->type.c_str() + 1, get_id(module), idcounter, GetSize(cell->getPort(QY)), infostr)); if (cell->getPort(QY).is_wire() && cell->getPort(QY).as_wire()->get_bool_attribute(ID::maximize)){ decls.push_back(stringf("; yosys-smt2-maximize %s#%d\n", get_id(module), idcounter)); - log("Wire %s is maximized\n", cell->getPort(QY).as_wire()->name.str().c_str()); + log("Wire %s is maximized\n", cell->getPort(QY).as_wire()->name.str()); } else if (cell->getPort(QY).is_wire() && cell->getPort(QY).as_wire()->get_bool_attribute(ID::minimize)){ decls.push_back(stringf("; yosys-smt2-minimize %s#%d\n", get_id(module), idcounter)); - log("Wire %s is minimized\n", cell->getPort(QY).as_wire()->name.str().c_str()); + log("Wire %s is minimized\n", cell->getPort(QY).as_wire()->name.str()); } bool init_only = cell->type.in(ID($anyconst), ID($anyinit), ID($allconst)); @@ -720,7 +722,7 @@ struct Smt2Worker 2*GetSize(cell->getPort(ID::A).chunks()) < GetSize(cell->getPort(ID::A))) { bool is_and = cell->type == ID($reduce_and); string bits(GetSize(cell->getPort(ID::A)), is_and ? '1' : '0'); - return export_bvop(cell, stringf("(%s A #b%s)", is_and ? "=" : "distinct", bits.c_str()), 'b'); + return export_bvop(cell, stringf("(%s A #b%s)", is_and ? "=" : "distinct", bits), 'b'); } if (cell->type == ID($reduce_and)) return export_reduce(cell, "(and A)", true); @@ -744,7 +746,7 @@ struct Smt2Worker get_bv(sig_s); for (int i = 0; i < GetSize(sig_s); i++) - processed_expr = stringf("(ite %s %s %s)", get_bool(sig_s[i]).c_str(), + processed_expr = stringf("(ite %s %s %s)", get_bool(sig_s[i]), get_bv(sig_b.extract(i*width, width)).c_str(), processed_expr.c_str()); if (verbose) @@ -1077,24 +1079,24 @@ struct Smt2Worker RTLIL::SigSpec sig = sigmap(wire); Const val = wire->attributes.at(ID::init); - val.bits.resize(GetSize(sig), State::Sx); + val.resize(GetSize(sig), State::Sx); if (bvmode && GetSize(sig) > 1) { Const mask(State::S1, GetSize(sig)); bool use_mask = false; for (int i = 0; i < GetSize(sig); i++) if (val[i] != State::S0 && val[i] != State::S1) { - val[i] = State::S0; - mask[i] = State::S0; + val.set(i, State::S0); + mask.set(i, State::S0); use_mask = true; } if (use_mask) - init_list.push_back(stringf("(= (bvand %s #b%s) #b%s) ; %s", get_bv(sig).c_str(), mask.as_string().c_str(), val.as_string().c_str(), get_id(wire))); + init_list.push_back(stringf("(= (bvand %s #b%s) #b%s) ; %s", get_bv(sig), mask.as_string(), val.as_string(), get_id(wire))); else - init_list.push_back(stringf("(= %s #b%s) ; %s", get_bv(sig).c_str(), val.as_string().c_str(), get_id(wire))); + init_list.push_back(stringf("(= %s #b%s) ; %s", get_bv(sig), val.as_string(), get_id(wire))); } else { for (int i = 0; i < GetSize(sig); i++) if (val[i] == State::S0 || val[i] == State::S1) - init_list.push_back(stringf("(= %s %s) ; %s", get_bool(sig[i]).c_str(), val[i] == State::S1 ? "true" : "false", get_id(wire))); + init_list.push_back(stringf("(= %s %s) ; %s", get_bool(sig[i]), val[i] == State::S1 ? "true" : "false", get_id(wire))); } } @@ -1129,7 +1131,7 @@ struct Smt2Worker } if (private_name && cell->attributes.count(ID::src)) - decls.push_back(stringf("; yosys-smt2-%s %d %s %s\n", cell->type.c_str() + 1, id, get_id(cell), cell->attributes.at(ID::src).decode_string().c_str())); + decls.push_back(stringf("; yosys-smt2-%s %d %s %s\n", cell->type.c_str() + 1, id, get_id(cell), cell->attributes.at(ID::src).decode_string())); else decls.push_back(stringf("; yosys-smt2-%s %d %s\n", cell->type.c_str() + 1, id, get_id(cell))); @@ -1178,11 +1180,11 @@ struct Smt2Worker SigSpec sig = sigmap(conn.second); if (bvmode || GetSize(w) == 1) { - hier.push_back(stringf(" (= %s (|%s_n %s| %s)) ; %s.%s\n", (GetSize(w) > 1 ? get_bv(sig) : get_bool(sig)).c_str(), + hier.push_back(stringf(" (= %s (|%s_n %s| %s)) ; %s.%s\n", (GetSize(w) > 1 ? get_bv(sig) : get_bool(sig)), get_id(cell->type), get_id(w), cell_state.c_str(), get_id(cell->type), get_id(w))); } else { for (int i = 0; i < GetSize(w); i++) - hier.push_back(stringf(" (= %s (|%s_n %s %d| %s)) ; %s.%s[%d]\n", get_bool(sig[i]).c_str(), + hier.push_back(stringf(" (= %s (|%s_n %s %d| %s)) ; %s.%s[%d]\n", get_bool(sig[i]), get_id(cell->type), get_id(w), i, cell_state.c_str(), get_id(cell->type), get_id(w), i)); } } @@ -1202,25 +1204,25 @@ struct Smt2Worker { std::string expr_d = get_bool(cell->getPort(ID::D)); std::string expr_q = get_bool(cell->getPort(ID::Q), "next_state"); - trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell), log_signal(cell->getPort(ID::Q)))); - ex_state_eq.push_back(stringf("(= %s %s)", get_bool(cell->getPort(ID::Q)).c_str(), get_bool(cell->getPort(ID::Q), "other_state").c_str())); + trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d, expr_q, get_id(cell), log_signal(cell->getPort(ID::Q)))); + ex_state_eq.push_back(stringf("(= %s %s)", get_bool(cell->getPort(ID::Q)), get_bool(cell->getPort(ID::Q), "other_state"))); } if (cell->type.in(ID($ff), ID($dff), ID($anyinit))) { std::string expr_d = get_bv(cell->getPort(ID::D)); std::string expr_q = get_bv(cell->getPort(ID::Q), "next_state"); - trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell), log_signal(cell->getPort(ID::Q)))); - ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Q)).c_str(), get_bv(cell->getPort(ID::Q), "other_state").c_str())); + trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d, expr_q, get_id(cell), log_signal(cell->getPort(ID::Q)))); + ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Q)), get_bv(cell->getPort(ID::Q), "other_state"))); } if (cell->type.in(ID($anyconst), ID($allconst))) { std::string expr_d = get_bv(cell->getPort(ID::Y)); std::string expr_q = get_bv(cell->getPort(ID::Y), "next_state"); - trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell), log_signal(cell->getPort(ID::Y)))); + trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d, expr_q, get_id(cell), log_signal(cell->getPort(ID::Y)))); if (cell->type == ID($anyconst)) - ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Y)).c_str(), get_bv(cell->getPort(ID::Y), "other_state").c_str())); + ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Y)), get_bv(cell->getPort(ID::Y), "other_state"))); } } @@ -1339,11 +1341,11 @@ struct Smt2Worker std::string expr_d = stringf("(|%s#%d#%d| state)", get_id(module), arrayid, GetSize(mem->wr_ports)); std::string expr_q = stringf("(|%s#%d#0| next_state)", get_id(module), arrayid); - trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d.c_str(), expr_q.c_str(), get_id(mem->memid))); + trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d, expr_q, get_id(mem->memid))); ex_state_eq.push_back(stringf("(= (|%s#%d#0| state) (|%s#%d#0| other_state))", get_id(module), arrayid, get_id(module), arrayid)); if (has_async_wr) - hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d.c_str(), final_memstate.c_str(), get_id(mem->memid))); + hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d, final_memstate, get_id(mem->memid))); Const init_data = mem->get_init_data(); @@ -1359,10 +1361,10 @@ struct Smt2Worker for (int k = 0; k < GetSize(initword); k++) { if (initword[k] == State::S0 || initword[k] == State::S1) { gen_init_constr = true; - initmask[k] = State::S1; + initmask.set(k, State::S1); } else { - initmask[k] = State::S0; - initword[k] = State::S0; + initmask.set(k, State::S0); + initword.set(k, State::S0); } } @@ -1400,7 +1402,7 @@ struct Smt2Worker expr = "\n " + ex_state_eq.front() + "\n"; } else { for (auto &str : ex_state_eq) - expr += stringf("\n %s", str.c_str()); + expr += stringf("\n %s", str); expr += "\n)"; } } @@ -1413,7 +1415,7 @@ struct Smt2Worker expr = "\n " + ex_input_eq.front() + "\n"; } else { for (auto &str : ex_input_eq) - expr += stringf("\n %s", str.c_str()); + expr += stringf("\n %s", str); expr += "\n)"; } } @@ -1427,7 +1429,7 @@ struct Smt2Worker assert_expr = "\n " + assert_list.front() + "\n"; } else { for (auto &str : assert_list) - assert_expr += stringf("\n %s", str.c_str()); + assert_expr += stringf("\n %s", str); assert_expr += "\n)"; } } @@ -1440,7 +1442,7 @@ struct Smt2Worker assume_expr = "\n " + assume_list.front() + "\n"; } else { for (auto &str : assume_list) - assume_expr += stringf("\n %s", str.c_str()); + assume_expr += stringf("\n %s", str); assume_expr += "\n)"; } } @@ -1453,7 +1455,7 @@ struct Smt2Worker init_expr = "\n " + init_list.front() + "\n"; } else { for (auto &str : init_list) - init_expr += stringf("\n %s", str.c_str()); + init_expr += stringf("\n %s", str); init_expr += "\n)"; } } @@ -1774,7 +1776,7 @@ struct Smt2Backend : public Backend { if (args[argidx] == "-tpl" && argidx+1 < args.size()) { template_f.open(args[++argidx]); if (template_f.fail()) - log_error("Can't open template file `%s'.\n", args[argidx].c_str()); + log_error("Can't open template file `%s'.\n", args[argidx]); continue; } if (args[argidx] == "-bv" || args[argidx] == "-mem") { @@ -1829,7 +1831,7 @@ struct Smt2Backend : public Backend { } } - *f << stringf("; SMT-LIBv2 description generated by %s\n", yosys_version_str); + *f << stringf("; SMT-LIBv2 description generated by %s\n", yosys_maybe_version()); if (!bvmode) *f << stringf("; yosys-smt2-nobv\n"); @@ -1844,7 +1846,7 @@ struct Smt2Backend : public Backend { *f << stringf("; yosys-smt2-stdt\n"); for (auto &it : solver_options) - *f << stringf("; yosys-smt2-solver-option %s %s\n", it.first.c_str(), it.second.c_str()); + *f << stringf("; yosys-smt2-solver-option %s %s\n", it.first, it.second); std::vector sorted_modules; @@ -1911,7 +1913,7 @@ struct Smt2Backend : public Backend { } if (topmod) - *f << stringf("; yosys-smt2-topmod %s\n", topmod_id.c_str()); + *f << stringf("; yosys-smt2-topmod %s\n", topmod_id); *f << stringf("; end of yosys output\n"); diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index cc47bc376..9dfbd2a25 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -57,6 +57,8 @@ keep_going = False check_witness = False detect_loops = False incremental = None +track_assumes = False +minimize_assumes = False so = SmtOpts() @@ -189,6 +191,14 @@ def help(): --incremental run in incremental mode (experimental) + --track-assumes + track individual assumptions and report a subset of used + assumptions that are sufficient for the reported outcome. This + can be used to debug PREUNSAT failures as well as to find a + smaller set of sufficient assumptions. + + --minimize-assumes + when using --track-assumes, solve for a minimal set of sufficient assumptions. """ + so.helpmsg()) def usage(): @@ -200,7 +210,8 @@ try: opts, args = getopt.getopt(sys.argv[1:], so.shortopts + "t:higcm:", so.longopts + ["help", "final-only", "assume-skipped=", "smtc=", "cex=", "aig=", "aig-noheader", "yw=", "btorwit=", "presat", "dump-vcd=", "dump-yw=", "dump-vlogtb=", "vlogtb-top=", "dump-smtc=", "dump-all", "noinfo", "append=", - "smtc-init", "smtc-top=", "noinit", "binary", "keep-going", "check-witness", "detect-loops", "incremental"]) + "smtc-init", "smtc-top=", "noinit", "binary", "keep-going", "check-witness", "detect-loops", "incremental", + "track-assumes", "minimize-assumes"]) except: usage() @@ -289,6 +300,10 @@ for o, a in opts: elif o == "--incremental": from smtbmc_incremental import Incremental incremental = Incremental() + elif o == "--track-assumes": + track_assumes = True + elif o == "--minimize-assumes": + minimize_assumes = True elif so.handle(o, a): pass else: @@ -447,6 +462,9 @@ def get_constr_expr(db, state, final=False, getvalues=False, individual=False): smt = SmtIo(opts=so) +if track_assumes: + smt.smt2_options[':produce-unsat-assumptions'] = 'true' + if noinfo and vcdfile is None and vlogtbfile is None and outconstr is None: smt.produce_models = False @@ -651,18 +669,12 @@ if aimfile is not None: ywfile_hierwitness_cache = None -def ywfile_constraints(inywfile, constr_assumes, map_steps=None, skip_x=False): +def ywfile_hierwitness(): global ywfile_hierwitness_cache - if map_steps is None: - map_steps = {} + if ywfile_hierwitness_cache is None: + ywfile_hierwitness = smt.hierwitness(topmod, allregs=True, blackbox=True) - with open(inywfile, "r") as f: - inyw = ReadWitness(f) - - if ywfile_hierwitness_cache is None: - ywfile_hierwitness_cache = smt.hierwitness(topmod, allregs=True, blackbox=True) - - inits, seqs, clocks, mems = ywfile_hierwitness_cache + inits, seqs, clocks, mems = ywfile_hierwitness smt_wires = defaultdict(list) smt_mems = defaultdict(list) @@ -673,9 +685,158 @@ def ywfile_constraints(inywfile, constr_assumes, map_steps=None, skip_x=False): for mem in mems: smt_mems[mem["path"]].append(mem) - addr_re = re.compile(r'\\\[[0-9]+\]$') - bits_re = re.compile(r'[01?]*$') + ywfile_hierwitness_cache = inits, seqs, clocks, mems, smt_wires, smt_mems + return ywfile_hierwitness_cache + +def_bits_re = re.compile(r'([01]+)') + +def smt_extract_mask(smt_expr, mask): + chunks = [] + def_bits = '' + + mask_index_order = mask[::-1] + + for matched in def_bits_re.finditer(mask_index_order): + chunks.append(matched.span()) + def_bits += matched[0] + + if not chunks: + return + + if len(chunks) == 1: + start, end = chunks[0] + if start == 0 and end == len(mask_index_order): + combined_chunks = smt_expr + else: + combined_chunks = '((_ extract %d %d) %s)' % (end - 1, start, smt_expr) + else: + combined_chunks = '(let ((x %s)) (concat %s))' % (smt_expr, ' '.join( + '((_ extract %d %d) x)' % (end - 1, start) + for start, end in reversed(chunks) + )) + + return combined_chunks, ''.join(mask_index_order[start:end] for start, end in chunks)[::-1] + +def smt_concat(exprs): + if not isinstance(exprs, (tuple, list)): + exprs = tuple(exprs) + if not exprs: + return "" + if len(exprs) == 1: + return exprs[1] + return "(concat %s)" % ' '.join(exprs) + +def ywfile_signal(sig, step, mask=None): + assert sig.width > 0 + + inits, seqs, clocks, mems, smt_wires, smt_mems = ywfile_hierwitness() + sig_end = sig.offset + sig.width + + output = [] + + def ywfile_signal_error(reason, detail=None): + msg = f"Yosys witness signal mismatch for {sig.pretty()}: {reason}" + if detail: + msg += f" ({detail})" + raise ValueError(msg) + + if sig.path in smt_wires: + for wire in smt_wires[sig.path]: + width, offset = wire["width"], wire["offset"] + + smt_bool = smt.net_width(topmod, wire["smtpath"]) == 1 + + offset = max(offset, 0) + + end = width + offset + common_offset = max(sig.offset, offset) + common_end = min(sig_end, end) + if common_end <= common_offset: + continue + + smt_expr = smt.witness_net_expr(topmod, f"s{step}", wire) + + if not smt_bool: + slice_high = common_end - offset - 1 + slice_low = common_offset - offset + smt_expr = "((_ extract %d %d) %s)" % (slice_high, slice_low, smt_expr) + else: + smt_expr = "(ite %s #b1 #b0)" % smt_expr + + output.append(((common_offset - sig.offset), (common_end - sig.offset), smt_expr)) + + if sig.memory_path: + if sig.memory_path in smt_mems: + for mem in smt_mems[sig.memory_path]: + width, size, bv = mem["width"], mem["size"], mem["statebv"] + + if sig.memory_addr is not None and sig.memory_addr >= size: + ywfile_signal_error( + "memory address out of bounds", + f"address={sig.memory_addr} size={size}", + ) + + smt_expr = smt.net_expr(topmod, f"s{step}", mem["smtpath"]) + + if bv: + word_low = sig.memory_addr * width + word_high = word_low + width - 1 + smt_expr = "((_ extract %d %d) %s)" % (word_high, word_low, smt_expr) + else: + addr_width = (size - 1).bit_length() + addr_bits = f"{sig.memory_addr:0{addr_width}b}" + smt_expr = "(select %s #b%s )" % (smt_expr, addr_bits) + + if sig.width < width: + slice_high = sig.offset + sig.width - 1 + smt_expr = "((_ extract %d %d) %s)" % (slice_high, sig.offset, smt_expr) + + output.append((0, sig.width, smt_expr)) + else: + ywfile_signal_error("memory not found in design") + + output.sort() + + output = [chunk for chunk in output if chunk[0] != chunk[1]] + + if not output: + if sig.memory_path: + ywfile_signal_error("memory signal has no matching bits in design") + else: + ywfile_signal_error("signal not found in design") + + pos = 0 + + for start, end, smt_expr in output: + if start != pos: + ywfile_signal_error( + "signal width/offset mismatch", + f"expected coverage at bit {pos}", + ) + pos = end + + if pos != sig.width: + ywfile_signal_error( + "signal width/offset mismatch", + f"covered {pos} of {sig.width} bits", + ) + + if len(output) == 1: + return output[0][-1] + return smt_concat(smt_expr for start, end, smt_expr in reversed(output)) + +def ywfile_constraints(inywfile, constr_assumes, map_steps=None, skip_x=False): + global ywfile_hierwitness_cache + if map_steps is None: + map_steps = {} + + with open(inywfile, "r") as f: + inyw = ReadWitness(f) + + inits, seqs, clocks, mems, smt_wires, smt_mems = ywfile_hierwitness() + + bits_re = re.compile(r'[01?]*$') max_t = -1 for t, step in inyw.steps(): @@ -687,77 +848,17 @@ def ywfile_constraints(inywfile, constr_assumes, map_steps=None, skip_x=False): if not bits_re.match(bits): raise ValueError("unsupported bit value in Yosys witness file") - sig_end = sig.offset + len(bits) - if sig.path in smt_wires: - for wire in smt_wires[sig.path]: - width, offset = wire["width"], wire["offset"] + if bits.count('?') == len(bits): + continue - smt_bool = smt.net_width(topmod, wire["smtpath"]) == 1 + smt_expr = ywfile_signal(sig, map_steps.get(t, t)) - offset = max(offset, 0) + smt_expr, bits = smt_extract_mask(smt_expr, bits) - end = width + offset - common_offset = max(sig.offset, offset) - common_end = min(sig_end, end) - if common_end <= common_offset: - continue + smt_constr = "(= %s #b%s)" % (smt_expr, bits) + constr_assumes[t].append((inywfile, smt_constr)) - smt_expr = smt.witness_net_expr(topmod, f"s{map_steps.get(t, t)}", wire) - - if not smt_bool: - slice_high = common_end - offset - 1 - slice_low = common_offset - offset - smt_expr = "((_ extract %d %d) %s)" % (slice_high, slice_low, smt_expr) - - bit_slice = bits[len(bits) - (common_end - sig.offset):len(bits) - (common_offset - sig.offset)] - - if bit_slice.count("?") == len(bit_slice): - continue - - if smt_bool: - assert width == 1 - smt_constr = "(= %s %s)" % (smt_expr, "true" if bit_slice == "1" else "false") - else: - if "?" in bit_slice: - mask = bit_slice.replace("0", "1").replace("?", "0") - bit_slice = bit_slice.replace("?", "0") - smt_expr = "(bvand %s #b%s)" % (smt_expr, mask) - - smt_constr = "(= %s #b%s)" % (smt_expr, bit_slice) - - constr_assumes[t].append((inywfile, smt_constr)) - - if sig.memory_path: - if sig.memory_path in smt_mems: - for mem in smt_mems[sig.memory_path]: - width, size, bv = mem["width"], mem["size"], mem["statebv"] - - smt_expr = smt.net_expr(topmod, f"s{map_steps.get(t, t)}", mem["smtpath"]) - - if bv: - word_low = sig.memory_addr * width - word_high = word_low + width - 1 - smt_expr = "((_ extract %d %d) %s)" % (word_high, word_low, smt_expr) - else: - addr_width = (size - 1).bit_length() - addr_bits = f"{sig.memory_addr:0{addr_width}b}" - smt_expr = "(select %s #b%s )" % (smt_expr, addr_bits) - - if len(bits) < width: - slice_high = sig.offset + len(bits) - 1 - smt_expr = "((_ extract %d %d) %s)" % (slice_high, sig.offset, smt_expr) - - bit_slice = bits - - if "?" in bit_slice: - mask = bit_slice.replace("0", "1").replace("?", "0") - bit_slice = bit_slice.replace("?", "0") - smt_expr = "(bvand %s #b%s)" % (smt_expr, mask) - - smt_constr = "(= %s #b%s)" % (smt_expr, bit_slice) - constr_assumes[t].append((inywfile, smt_constr)) max_t = t - return max_t if inywfile is not None: @@ -1348,11 +1449,11 @@ def write_yw_trace(steps, index, allregs=False, filename=None): exprs.extend(smt.witness_net_expr(topmod, f"s{k}", sig) for sig in sigs) - all_sigs.append(sigs) + all_sigs.append((step_values, sigs)) bvs = iter(smt.get_list(exprs)) - for sigs in all_sigs: + for (step_values, sigs) in all_sigs: for sig in sigs: value = smt.bv2bin(next(bvs)) step_values[sig["sig"]] = value @@ -1381,6 +1482,10 @@ def write_trace(steps_start, steps_stop, index, allregs=False): if outywfile is not None: write_yw_trace(steps, index, allregs) +def escape_path_segment(segment): + if "." in segment: + return f"\\{segment} " + return segment def print_failed_asserts_worker(mod, state, path, extrainfo, infomap, infokey=()): assert mod in smt.modinfo @@ -1391,7 +1496,8 @@ def print_failed_asserts_worker(mod, state, path, extrainfo, infomap, infokey=() for cellname, celltype in smt.modinfo[mod].cells.items(): cell_infokey = (mod, cellname, infokey) - if print_failed_asserts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), path + "." + cellname, extrainfo, infomap, cell_infokey): + cell_path = path + "." + escape_path_segment(cellname) + if print_failed_asserts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), cell_path, extrainfo, infomap, cell_infokey): found_failed_assert = True for assertfun, assertinfo in smt.modinfo[mod].asserts.items(): @@ -1424,7 +1530,7 @@ def print_anyconsts_worker(mod, state, path): assert mod in smt.modinfo for cellname, celltype in smt.modinfo[mod].cells.items(): - print_anyconsts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), path + "." + cellname) + print_anyconsts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), path + "." + escape_path_segment(cellname)) for fun, info in smt.modinfo[mod].anyconsts.items(): if info[1] is None: @@ -1444,18 +1550,21 @@ def print_anyconsts(state): print_anyconsts_worker(topmod, "s%d" % state, topmod) -def get_cover_list(mod, base): +def get_cover_list(mod, base, path=None): + path = path or mod assert mod in smt.modinfo cover_expr = list() + # A tuple of path and cell name cover_desc = list() for expr, desc in smt.modinfo[mod].covers.items(): cover_expr.append("(ite (|%s| %s) #b1 #b0)" % (expr, base)) - cover_desc.append(desc) + cover_desc.append((path, desc)) for cell, submod in smt.modinfo[mod].cells.items(): - e, d = get_cover_list(submod, "(|%s_h %s| %s)" % (mod, cell, base)) + cell_path = path + "." + escape_path_segment(cell) + e, d = get_cover_list(submod, "(|%s_h %s| %s)" % (mod, cell, base), cell_path) cover_expr += e cover_desc += d @@ -1471,7 +1580,8 @@ def get_assert_map(mod, base, path, key_base=()): assert_map[(expr, key_base)] = ("(|%s| %s)" % (expr, base), path, desc) for cell, submod in smt.modinfo[mod].cells.items(): - assert_map.update(get_assert_map(submod, "(|%s_h %s| %s)" % (mod, cell, base), path + "." + cell, (mod, cell, key_base))) + cell_path = path + "." + escape_path_segment(cell) + assert_map.update(get_assert_map(submod, "(|%s_h %s| %s)" % (mod, cell, base), cell_path, (mod, cell, key_base))) return assert_map @@ -1497,6 +1607,44 @@ def get_active_assert_map(step, active): return assert_map +assume_enables = {} + +def declare_assume_enables(): + def recurse(mod, path, key_base=()): + for expr, desc in smt.modinfo[mod].assumes.items(): + enable = f"|assume_enable {len(assume_enables)}|" + smt.smt2_assumptions[(expr, key_base)] = enable + smt.write(f"(declare-const {enable} Bool)") + assume_enables[(expr, key_base)] = (enable, path, desc) + + for cell, submod in smt.modinfo[mod].cells.items(): + recurse(submod, f"{path}.{cell}", (mod, cell, key_base)) + + recurse(topmod, topmod) + +if track_assumes: + declare_assume_enables() + +def smt_assert_design_assumes(step): + if not track_assumes: + smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + return + + if not assume_enables: + return + + def expr_for_assume(assume_key, base=None): + expr, key_base = assume_key + expr_prefix = f"(|{expr}| " + expr_suffix = ")" + while key_base: + mod, cell, key_base = key_base + expr_prefix += f"(|{mod}_h {cell}| " + expr_suffix += ")" + return f"{expr_prefix} s{step}{expr_suffix}" + + for assume_key, (enable, path, desc) in assume_enables.items(): + smt_assert_consequent(f"(=> {enable} {expr_for_assume(assume_key)})") states = list() asserts_antecedent_cache = [list()] @@ -1651,6 +1799,13 @@ def smt_check_sat(expected=["sat", "unsat"]): smt_forall_assert() return smt.check_sat(expected=expected) +def report_tracked_assumptions(msg): + if track_assumes: + print_msg(msg) + for key in smt.get_unsat_assumptions(minimize=minimize_assumes): + enable, path, descr = assume_enables[key] + print_msg(f" In {path}: {descr}") + if incremental: incremental.mainloop() @@ -1664,7 +1819,7 @@ elif tempind: break smt_state(step) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + smt_assert_design_assumes(step) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step)) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, step)) smt_assert_consequent(get_constr_expr(constr_assumes, step)) @@ -1707,6 +1862,7 @@ elif tempind: else: print_msg("Temporal induction successful.") + report_tracked_assumptions("Used assumptions:") retstatus = "PASSED" break @@ -1732,7 +1888,7 @@ elif covermode: while step < num_steps: smt_state(step) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + smt_assert_design_assumes(step) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step)) smt_assert_consequent(get_constr_expr(constr_assumes, step)) @@ -1747,12 +1903,18 @@ elif covermode: smt_assert_antecedent("(|%s_t| s%d s%d)" % (topmod, step-1, step)) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, step)) + if step < skip_steps: + print_msg("Skipping step %d.." % (step)) + step += 1 + continue + while "1" in cover_mask: print_msg("Checking cover reachability in step %d.." % (step)) smt_push() smt_assert("(distinct (covers_%d s%d) #b%s)" % (coveridx, step, "0" * len(cover_desc))) if smt_check_sat() == "unsat": + report_tracked_assumptions("Used assumptions:") smt_pop() break @@ -1761,13 +1923,14 @@ elif covermode: print_msg("Appending additional step %d." % i) smt_state(i) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, i)) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, i)) + smt_assert_design_assumes(i) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, i)) smt_assert_antecedent("(|%s_t| s%d s%d)" % (topmod, i-1, i)) smt_assert_consequent(get_constr_expr(constr_assumes, i)) print_msg("Re-solving with appended steps..") if smt_check_sat() == "unsat": print("%s Cannot appended steps without violating assumptions!" % smt.timestamp()) + report_tracked_assumptions("Conflicting assumptions:") found_failed_assert = True retstatus = "FAILED" break @@ -1782,7 +1945,9 @@ elif covermode: new_cover_mask.append(cover_mask[i]) continue - print_msg("Reached cover statement at %s in step %d." % (cover_desc[i], step)) + path = cover_desc[i][0] + name = cover_desc[i][1] + print_msg("Reached cover statement in step %d at %s: %s" % (step, path, name)) new_cover_mask.append("0") cover_mask = "".join(new_cover_mask) @@ -1812,7 +1977,7 @@ elif covermode: if "1" in cover_mask: for i in range(len(cover_mask)): if cover_mask[i] == "1": - print_msg("Unreached cover statement at %s." % cover_desc[i]) + print_msg("Unreached cover statement at %s: %s" % (cover_desc[i][0], cover_desc[i][1])) else: # not tempind, covermode active_assert_keys = get_assert_keys() @@ -1823,7 +1988,7 @@ else: # not tempind, covermode retstatus = "PASSED" while step < num_steps: smt_state(step) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + smt_assert_design_assumes(step) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step)) smt_assert_consequent(get_constr_expr(constr_assumes, step)) @@ -1853,7 +2018,7 @@ else: # not tempind, covermode if step+i < num_steps: smt_state(step+i) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, step+i)) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step+i)) + smt_assert_design_assumes(step + i) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step+i)) smt_assert_antecedent("(|%s_t| s%d s%d)" % (topmod, step+i-1, step+i)) smt_assert_consequent(get_constr_expr(constr_assumes, step+i)) @@ -1867,7 +2032,8 @@ else: # not tempind, covermode print_msg("Checking assumptions in steps %d to %d.." % (step, last_check_step)) if smt_check_sat() == "unsat": - print("%s Assumptions are unsatisfiable!" % smt.timestamp()) + print_msg("Assumptions are unsatisfiable!") + report_tracked_assumptions("Conficting assumptions:") retstatus = "PREUNSAT" break @@ -1920,13 +2086,14 @@ else: # not tempind, covermode print_msg("Appending additional step %d." % i) smt_state(i) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, i)) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, i)) + smt_assert_design_assumes(i) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, i)) smt_assert_antecedent("(|%s_t| s%d s%d)" % (topmod, i-1, i)) smt_assert_consequent(get_constr_expr(constr_assumes, i)) print_msg("Re-solving with appended steps..") if smt_check_sat() == "unsat": - print("%s Cannot append steps without violating assumptions!" % smt.timestamp()) + print_msg("Cannot append steps without violating assumptions!") + report_tracked_assumptions("Conflicting assumptions:") retstatus = "FAILED" break print_anyconsts(step) diff --git a/backends/smt2/smtbmc_incremental.py b/backends/smt2/smtbmc_incremental.py index 1a2a45703..0bd280b4a 100644 --- a/backends/smt2/smtbmc_incremental.py +++ b/backends/smt2/smtbmc_incremental.py @@ -1,7 +1,7 @@ from collections import defaultdict import json import typing -from functools import partial +import ywio if typing.TYPE_CHECKING: import smtbmc @@ -15,6 +15,14 @@ class InteractiveError(Exception): pass +def mkkey(data): + if isinstance(data, list): + return tuple(map(mkkey, data)) + elif isinstance(data, dict): + raise InteractiveError(f"JSON objects found in assumption key: {data!r}") + return data + + class Incremental: def __init__(self): self.traceidx = 0 @@ -26,6 +34,7 @@ class Incremental: self._witness_index = None self._yw_constraints = {} + self._define_sorts = {} def setup(self): generic_assert_map = smtbmc.get_assert_map( @@ -73,17 +82,17 @@ class Incremental: if min_len is not None and arg_len < min_len: if min_len == max_len: - raise ( + raise InteractiveError( f"{json.dumps(expr[0])} expression must have " f"{min_len} argument{'s' if min_len != 1 else ''}" ) else: - raise ( + raise InteractiveError( f"{json.dumps(expr[0])} expression must have at least " f"{min_len} argument{'s' if min_len != 1 else ''}" ) if max_len is not None and arg_len > max_len: - raise ( + raise InteractiveError( f"{json.dumps(expr[0])} expression can have at most " f"{min_len} argument{'s' if max_len != 1 else ''}" ) @@ -96,14 +105,31 @@ class Incremental: smt_out.append(f"s{step}") return "module", smtbmc.topmod - def expr_mod_constraint(self, expr, smt_out): - self.expr_arg_len(expr, 1) + def expr_cell(self, expr, smt_out): + self.expr_arg_len(expr, 2) position = len(smt_out) smt_out.append(None) - arg_sort = self.expr(expr[1], smt_out, required_sort=["module", None]) + arg_sort = self.expr(expr[2], smt_out, required_sort=["module", None]) + smt_out.append(")") module = arg_sort[1] + cell = expr[1] + submod = smtbmc.smt.modinfo[module].cells.get(cell) + if submod is None: + raise InteractiveError(f"module {module!r} has no cell {cell!r}") + smt_out[position] = f"(|{module}_h {cell}| " + return ("module", submod) + + def expr_mod_constraint(self, expr, smt_out): suffix = expr[0][3:] - smt_out[position] = f"(|{module}{suffix}| " + self.expr_arg_len(expr, 1, 2 if suffix in ["_a", "_u", "_c"] else 1) + position = len(smt_out) + smt_out.append(None) + arg_sort = self.expr(expr[-1], smt_out, required_sort=["module", None]) + module = arg_sort[1] + if len(expr) == 3: + smt_out[position] = f"(|{module}{suffix} {expr[1]}| " + else: + smt_out[position] = f"(|{module}{suffix}| " smt_out.append(")") return "Bool" @@ -150,11 +176,7 @@ class Incremental: if len(expr) == 1: smt_out.push({"and": "true", "or": "false"}[expr[0]]) elif len(expr) == 2: - arg_sort = self.expr(expr[1], smt_out) - if arg_sort != "Bool": - raise InteractiveError( - f"arguments of {json.dumps(expr[0])} must have sort Bool" - ) + self.expr(expr[1], smt_out, required_sort="Bool") else: sep = f"({expr[0]} " for arg in expr[1:]: @@ -164,7 +186,51 @@ class Incremental: smt_out.append(")") return "Bool" + def expr_bv_binop(self, expr, smt_out): + self.expr_arg_len(expr, 2) + + smt_out.append(f"({expr[0]} ") + arg_sort = self.expr(expr[1], smt_out, required_sort=("BitVec", None)) + smt_out.append(" ") + self.expr(expr[2], smt_out, required_sort=arg_sort) + smt_out.append(")") + return arg_sort + + def expr_extract(self, expr, smt_out): + self.expr_arg_len(expr, 3) + + hi = expr[1] + lo = expr[2] + + smt_out.append(f"((_ extract {hi} {lo}) ") + + arg_sort = self.expr(expr[3], smt_out, required_sort=("BitVec", None)) + smt_out.append(")") + + if not (isinstance(hi, int) and 0 <= hi < arg_sort[1]): + raise InteractiveError( + f"high bit index must be 0 <= index < {arg_sort[1]}, is {hi!r}" + ) + if not (isinstance(lo, int) and 0 <= lo <= hi): + raise InteractiveError( + f"low bit index must be 0 <= index < {hi}, is {lo!r}" + ) + + return "BitVec", hi - lo + 1 + + def expr_bv(self, expr, smt_out): + self.expr_arg_len(expr, 1) + + arg = expr[1] + if not isinstance(arg, str) or arg.count("0") + arg.count("1") != len(arg): + raise InteractiveError("bv argument must contain only 0 or 1 bits") + + smt_out.append("#b" + arg) + + return "BitVec", len(arg) + def expr_yw(self, expr, smt_out): + self.expr_arg_len(expr, 1, 2) if len(expr) == 2: name = None step = expr[1] @@ -194,6 +260,40 @@ class Incremental: return "Bool" + def expr_yw_sig(self, expr, smt_out): + self.expr_arg_len(expr, 3, 4) + + step = expr[1] + path = expr[2] + offset = expr[3] + width = expr[4] if len(expr) == 5 else 1 + + if not isinstance(offset, int) or offset < 0: + raise InteractiveError( + f"offset must be a non-negative integer, got {json.dumps(offset)}" + ) + + if not isinstance(width, int) or width <= 0: + raise InteractiveError( + f"width must be a positive integer, got {json.dumps(width)}" + ) + + if not isinstance(path, list) or not all(isinstance(s, str) for s in path): + raise InteractiveError( + f"path must be a string list, got {json.dumps(path)}" + ) + + if step not in self.state_set: + raise InteractiveError(f"step {step} not declared") + + smt_expr = smtbmc.ywfile_signal( + ywio.WitnessSig(path=path, offset=offset, width=width), step + ) + + smt_out.append(smt_expr) + + return "BitVec", width + def expr_smtlib(self, expr, smt_out): self.expr_arg_len(expr, 2) @@ -206,10 +306,15 @@ class Incremental: f"got {json.dumps(smtlib_expr)}" ) - if not isinstance(sort, str): - raise InteractiveError( - f"raw SMT-LIB sort has to be a string, got {json.dumps(sort)}" - ) + if ( + isinstance(sort, list) + and len(sort) == 2 + and sort[0] == "BitVec" + and (sort[1] is None or isinstance(sort[1], int)) + ): + sort = tuple(sort) + elif not isinstance(sort, str): + raise InteractiveError(f"unsupported raw SMT-LIB sort {json.dumps(sort)}") smt_out.append(smtlib_expr) return sort @@ -223,20 +328,27 @@ class Incremental: subexpr = expr[2] if not isinstance(label, str): - raise InteractiveError(f"expression label has to be a string") + raise InteractiveError("expression label has to be a string") smt_out.append("(! ") - smt_out.appedd(label) - smt_out.append(" ") - sort = self.expr(subexpr, smt_out) - + smt_out.append(" :named ") + smt_out.append(label) smt_out.append(")") return sort + def expr_def(self, expr, smt_out): + self.expr_arg_len(expr, 1) + sort = self._define_sorts.get(expr[1]) + if sort is None: + raise InteractiveError(f"unknown definition {json.dumps(expr)}") + smt_out.append(expr[1]) + return sort + expr_handlers = { "step": expr_step, + "cell": expr_cell, "mod_h": expr_mod_constraint, "mod_is": expr_mod_constraint, "mod_i": expr_mod_constraint, @@ -246,8 +358,15 @@ class Incremental: "not": expr_not, "and": expr_andor, "or": expr_andor, + "bv": expr_bv, + "bvand": expr_bv_binop, + "bvor": expr_bv_binop, + "bvxor": expr_bv_binop, + "extract": expr_extract, + "def": expr_def, "=": expr_eq, "yw": expr_yw, + "yw_sig": expr_yw_sig, "smtlib": expr_smtlib, "!": expr_label, } @@ -281,10 +400,13 @@ class Incremental: raise InteractiveError(f"unknown expression {json.dumps(expr[0])}") def expr_smt(self, expr, required_sort): + return self.expr_smt_and_sort(expr, required_sort)[0] + + def expr_smt_and_sort(self, expr, required_sort=None): smt_out = [] - self.expr(expr, smt_out, required_sort=required_sort) + output_sort = self.expr(expr, smt_out, required_sort=required_sort) out = "".join(smt_out) - return out + return out, output_sort def cmd_new_step(self, cmd): step = self.arg_step(cmd, declare=True) @@ -302,6 +424,29 @@ class Incremental: assert_fn(self.expr_smt(cmd.get("expr"), "Bool")) + def cmd_assert_design_assumes(self, cmd): + step = self.arg_step(cmd) + smtbmc.smt_assert_design_assumes(step) + + def cmd_get_design_assume(self, cmd): + key = mkkey(cmd.get("key")) + return smtbmc.assume_enables.get(key) + + def cmd_update_assumptions(self, cmd): + expr = cmd.get("expr") + key = cmd.get("key") + + key = mkkey(key) + + result = smtbmc.smt.smt2_assumptions.pop(key, None) + if expr is not None: + expr = self.expr_smt(expr, "Bool") + smtbmc.smt.smt2_assumptions[key] = expr + return result + + def cmd_get_unsat_assumptions(self, cmd): + return smtbmc.smt.get_unsat_assumptions(minimize=bool(cmd.get("minimize"))) + def cmd_push(self, cmd): smtbmc.smt_push() @@ -313,11 +458,35 @@ class Incremental: def cmd_smtlib(self, cmd): command = cmd.get("command") + response = cmd.get("response", False) if not isinstance(command, str): raise InteractiveError( f"raw SMT-LIB command must be a string, found {json.dumps(command)}" ) smtbmc.smt.write(command) + if response: + return smtbmc.smt.read() + + def cmd_define(self, cmd): + expr = cmd.get("expr") + if expr is None: + raise InteractiveError("'define' copmmand requires 'expr' parameter") + + expr, sort = self.expr_smt_and_sort(expr) + + if isinstance(sort, tuple) and sort[0] == "module": + raise InteractiveError("'define' does not support module sorts") + + define_name = f"|inc def {len(self._define_sorts)}|" + + self._define_sorts[define_name] = sort + + if isinstance(sort, tuple): + sort = f"(_ {' '.join(map(str, sort))})" + + smtbmc.smt.write(f"(define-const {define_name} {sort} {expr})") + + return {"name": define_name} def cmd_design_hierwitness(self, cmd=None): allregs = (cmd is None) or bool(cmd.get("allreges", False)) @@ -369,6 +538,21 @@ class Incremental: return dict(last_step=last_step) + def cmd_modinfo(self, cmd): + fields = cmd.get("fields", []) + + mod = cmd.get("mod") + if mod is None: + mod = smtbmc.topmod + modinfo = smtbmc.smt.modinfo.get(mod) + if modinfo is None: + return None + + result = dict(name=mod) + for field in fields: + result[field] = getattr(modinfo, field, None) + return result + def cmd_ping(self, cmd): return cmd @@ -377,13 +561,19 @@ class Incremental: "assert": cmd_assert, "assert_antecedent": cmd_assert, "assert_consequent": cmd_assert, + "assert_design_assumes": cmd_assert_design_assumes, + "get_design_assume": cmd_get_design_assume, + "update_assumptions": cmd_update_assumptions, + "get_unsat_assumptions": cmd_get_unsat_assumptions, "push": cmd_push, "pop": cmd_pop, "check": cmd_check, "smtlib": cmd_smtlib, + "define": cmd_define, "design_hierwitness": cmd_design_hierwitness, "write_yw_trace": cmd_write_yw_trace, "read_yw_trace": cmd_read_yw_trace, + "modinfo": cmd_modinfo, "ping": cmd_ping, } diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py index c904aea95..2bc7daddc 100644 --- a/backends/smt2/smtio.py +++ b/backends/smt2/smtio.py @@ -114,6 +114,7 @@ class SmtModInfo: self.clocks = dict() self.cells = dict() self.asserts = dict() + self.assumes = dict() self.covers = dict() self.maximize = set() self.minimize = set() @@ -141,6 +142,7 @@ class SmtIo: self.recheck = False self.smt2cache = [list()] self.smt2_options = dict() + self.smt2_assumptions = dict() self.p = None self.p_index = solvers_index solvers_index += 1 @@ -158,6 +160,7 @@ class SmtIo: self.noincr = opts.noincr self.info_stmts = opts.info_stmts self.nocomments = opts.nocomments + self.smt2_options.update(opts.smt2_options) else: self.solver = "yices" @@ -602,6 +605,12 @@ class SmtIo: else: self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = fields[3] + if fields[1] == "yosys-smt2-assume": + if len(fields) > 4: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = fields[3] + if fields[1] == "yosys-smt2-maximize": self.modinfo[self.curmod].maximize.add(fields[2]) @@ -785,8 +794,13 @@ class SmtIo: return stmt def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]): + if self.smt2_assumptions: + assume_exprs = " ".join(self.smt2_assumptions.values()) + check_stmt = f"(check-sat-assuming ({assume_exprs}))" + else: + check_stmt = "(check-sat)" if self.debug_print: - print("> (check-sat)") + print(f"> {check_stmt}") if self.debug_file and not self.nocomments: print("; running check-sat..", file=self.debug_file) self.debug_file.flush() @@ -800,7 +814,7 @@ class SmtIo: for cache_stmt in cache_ctx: self.p_write(cache_stmt + "\n", False) - self.p_write("(check-sat)\n", True) + self.p_write(f"{check_stmt}\n", True) if self.timeinfo: i = 0 @@ -868,7 +882,7 @@ class SmtIo: if self.debug_file: print("(set-info :status %s)" % result, file=self.debug_file) - print("(check-sat)", file=self.debug_file) + print(check_stmt, file=self.debug_file) self.debug_file.flush() if result not in expected: @@ -945,6 +959,55 @@ class SmtIo: def bv2int(self, v): return int(self.bv2bin(v), 2) + def get_raw_unsat_assumptions(self): + if not self.smt2_assumptions: + return [] + self.write("(get-unsat-assumptions)") + exprs = set(self.unparse(part) for part in self.parse(self.read())) + unsat_assumptions = [] + for key, value in self.smt2_assumptions.items(): + # normalize expression + value = self.unparse(self.parse(value)) + if value in exprs: + exprs.remove(value) + unsat_assumptions.append(key) + return unsat_assumptions + + def get_unsat_assumptions(self, minimize=False): + if not minimize: + return self.get_raw_unsat_assumptions() + orig_assumptions = self.smt2_assumptions + + self.smt2_assumptions = dict(orig_assumptions) + + required_assumptions = {} + + while True: + candidate_assumptions = {} + for key in self.get_raw_unsat_assumptions(): + if key not in required_assumptions: + candidate_assumptions[key] = self.smt2_assumptions[key] + + while candidate_assumptions: + + candidate_key, candidate_assume = candidate_assumptions.popitem() + + self.smt2_assumptions = {} + for key, assume in candidate_assumptions.items(): + self.smt2_assumptions[key] = assume + for key, assume in required_assumptions.items(): + self.smt2_assumptions[key] = assume + result = self.check_sat() + + if result == 'unsat': + candidate_assumptions = None + else: + required_assumptions[candidate_key] = candidate_assume + + if candidate_assumptions is not None: + self.smt2_assumptions = orig_assumptions + return list(required_assumptions) + def get(self, expr): self.write("(get-value (%s))" % (expr)) return self.parse(self.read())[0][1] @@ -1091,7 +1154,7 @@ class SmtIo: class SmtOpts: def __init__(self): self.shortopts = "s:S:v" - self.longopts = ["unroll", "noincr", "noprogress", "timeout=", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments"] + self.longopts = ["unroll", "noincr", "noprogress", "timeout=", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments", "smt2-option="] self.solver = "yices" self.solver_opts = list() self.debug_print = False @@ -1104,6 +1167,7 @@ class SmtOpts: self.logic = None self.info_stmts = list() self.nocomments = False + self.smt2_options = {} def handle(self, o, a): if o == "-s": @@ -1130,6 +1194,13 @@ class SmtOpts: self.info_stmts.append(a) elif o == "--nocomments": self.nocomments = True + elif o == "--smt2-option": + args = a.split('=', 1) + if len(args) != 2: + print("--smt2-option expects an