pax_global_header00006660000000000000000000000064152005723230014511gustar00rootroot0000000000000052 comment=b85cad634782fafac275e5f540c056bfacb2b5d2 yosys-0.65/000077500000000000000000000000001520057232300126315ustar00rootroot00000000000000yosys-0.65/.clang-format000066400000000000000000000004251520057232300152050ustar00rootroot00000000000000# Default Linux style BasedOnStyle: LLVM IndentWidth: 8 UseTab: Always BreakBeforeBraces: Linux AllowShortIfStatementsOnASingleLine: false IndentCaseLabels: false # From guidelines/CodingStyle TabWidth: 8 ContinuationIndentWidth: 2 ColumnLimit: 150 # BreakBeforeBraces: Linux yosys-0.65/.dockerignore000066400000000000000000000002071520057232300153040ustar00rootroot00000000000000.editorconfig .gitignore .gitmodules .github .git Dockerfile README.md manual guidelines CodeOfConduct .travis .travis.yml yosys-0.65/.editorconfig000066400000000000000000000004361520057232300153110ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = tab trim_trailing_whitespace = true insert_final_newline = true [abc/**] indent_style = space indent_size = 2 trim_trailing_whitespace = false [*.rst] indent_style = space indent_size = 3 [*.yml] indent_style = space indent_size = 2 yosys-0.65/.gitattributes000066400000000000000000000000671520057232300155270ustar00rootroot00000000000000*.v linguist-language=Verilog /.gitcommit export-subst yosys-0.65/.gitcommit000066400000000000000000000000511520057232300146220ustar00rootroot00000000000000b85cad634782fafac275e5f540c056bfacb2b5d2 yosys-0.65/.github/000077500000000000000000000000001520057232300141715ustar00rootroot00000000000000yosys-0.65/.github/ISSUE_TEMPLATE/000077500000000000000000000000001520057232300163545ustar00rootroot00000000000000yosys-0.65/.github/ISSUE_TEMPLATE/bug_report.yml000066400000000000000000000044611520057232300212540ustar00rootroot00000000000000name: Bug Report description: Report an issue or regression with Yosys labels: ["pending-verification"] body: - type: markdown attributes: value: > Learn more in our [Reporting bugs](https://yosyshq.readthedocs.io/projects/yosys/en/latest/yosys_internals/extending_yosys/contributing.html#reporting-bugs) docs section. We fix well-reported bugs the fastest. If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/). 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 or work done for Yosys. - type: input id: yosys_version attributes: label: Version description: "The version of yosys this bug was encountered on." placeholder: "The output of `yosys --version`" validations: required: true - type: dropdown id: os attributes: label: On which OS did this happen? options: - Linux - macOS - Windows - BSD - WebAssembly multiple: true validations: required: true - type: markdown attributes: value: > When providing steps to reproduce the issue, please ensure that the issue is reproducible in the current git main of Yosys. Also ensure to provide all necessary source files needed. Please see [https://stackoverflow.com/help/mcve](https://stackoverflow.com/help/mcve) for information on how to create a Minimal, Complete, and Verifiable Example (MCVE). - type: textarea id: reproduction_steps attributes: label: Reproduction Steps description: "Please provide clear and concise steps to reproduce the issue." validations: required: true - type: textarea id: expected_behavior attributes: label: Expected Behavior description: "Please describe the behavior you would have expected from the tool." validations: required: true - type: textarea id: actual_behavior attributes: label: Actual Behavior description: "Please describe how the behavior you see differs from the expected behavior." validations: required: true yosys-0.65/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000003571520057232300203510ustar00rootroot00000000000000contact_links: - 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" yosys-0.65/.github/ISSUE_TEMPLATE/docs_report.yml000066400000000000000000000032501520057232300214220ustar00rootroot00000000000000name: 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 yosys-0.65/.github/ISSUE_TEMPLATE/feature_request.yml000066400000000000000000000013211520057232300222770ustar00rootroot00000000000000name: Feature Request description: "Submit a feature request for Yosys" labels: ["feature-request"] 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 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. - type: textarea id: feature_description attributes: label: Feature Description description: "A clear and detailed description of the feature." validations: required: true yosys-0.65/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000006611520057232300177750ustar00rootroot00000000000000_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._yosys-0.65/.github/actions/000077500000000000000000000000001520057232300156315ustar00rootroot00000000000000yosys-0.65/.github/actions/setup-build-env/000077500000000000000000000000001520057232300206545ustar00rootroot00000000000000yosys-0.65/.github/actions/setup-build-env/action.yml000066400000000000000000000047101520057232300226560ustar00rootroot00000000000000name: 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 libgtest-dev libgmock-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 - 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 }} yosys-0.65/.github/actions/setup-iverilog/000077500000000000000000000000001520057232300206075ustar00rootroot00000000000000yosys-0.65/.github/actions/setup-iverilog/action.yml000066400000000000000000000036571520057232300226220ustar00rootroot00000000000000name: 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 }} yosys-0.65/.github/workflows/000077500000000000000000000000001520057232300162265ustar00rootroot00000000000000yosys-0.65/.github/workflows/codeql.yml000066400000000000000000000013251520057232300202210ustar00rootroot00000000000000name: "CodeQL" on: workflow_dispatch: schedule: - cron: '0 3 * * *' jobs: analyze: name: Analyze runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 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@v4 with: languages: cpp queries: security-extended,security-and-quality - name: Build run: make yosys -j6 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 yosys-0.65/.github/workflows/extra-builds.yml000066400000000000000000000116231520057232300213570ustar00rootroot00000000000000name: Test extra build flows on: pull_request: merge_group: #push: # branches: [ main ] workflow_dispatch: jobs: pre_job: runs-on: ubuntu-latest outputs: should_skip: ${{ steps.set_output.outputs.should_skip }} steps: - id: skip_check if: ${{ github.event_name != 'merge_group' }} uses: fkirc/skip-duplicate-actions@v5 with: # don't run on documentation changes paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]' # cancel previous builds if a new commit is pushed # but never cancel main cancel_others: ${{ github.ref != 'refs/heads/main' }} - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then echo "should_skip=false" >> $GITHUB_OUTPUT else echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT fi vs-prep: name: Prepare Visual Studio build runs-on: ubuntu-latest needs: [pre_job] if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true' steps: - uses: actions/checkout@v5 with: submodules: true persist-credentials: false - run: sudo apt-get install libfl-dev - name: Build run: make vcxsrc YOSYS_COMPILER="Visual Studio" VCX_DIR_NAME=yosys-win32-vcxsrc-latest - uses: actions/upload-artifact@v7 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: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true' steps: - uses: actions/download-artifact@v8 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: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: submodules: true persist-credentials: false - name: Build run: | WASI_SDK=wasi-sdk-33.0-x86_64-linux WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz if ! [ -d ${WASI_SDK} ]; then curl -L ${WASI_SDK_URL} | tar xzf -; fi FLEX_VER=2.6.4 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 - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then echo "should_skip=false" >> $GITHUB_OUTPUT else echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT fi prepare-docs: # docs builds are needed for anything on main, any tagged versions, and any tag # or branch starting with docs-preview needs: check_docs_rebuild if: ${{ needs.check_docs_rebuild.outputs.should_skip != 'true' && github.repository_owner == 'YosysHQ' }} runs-on: [self-hosted, linux, x64, fast] steps: - name: Checkout Yosys uses: actions/checkout@v4 with: persist-credentials: false submodules: true - name: Runtime environment run: | echo "procs=$(nproc)" >> $GITHUB_ENV - name: 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' && github.repository == 'YosysHQ/yosys' }} uses: dfm/rtds-action@v1.1.0 with: webhook_url: ${{ secrets.RTDS_WEBHOOK_URL }} webhook_token: ${{ secrets.RTDS_WEBHOOK_TOKEN }} commit_ref: ${{ github.ref }} yosys-0.65/.github/workflows/source-vendor.yml000066400000000000000000000021011520057232300215360ustar00rootroot00000000000000name: Create source archive with vendored dependencies on: pull_request: merge_group: push: branches: [ main ] workflow_dispatch: jobs: vendor-sources: runs-on: ubuntu-latest steps: - name: Checkout repository with submodules uses: actions/checkout@v5 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@v7 with: name: vendored-sources path: yosys-src-vendored.tar.gz retention-days: 1 yosys-0.65/.github/workflows/test-build.yml000066400000000000000000000174571520057232300210430ustar00rootroot00000000000000name: Build and run tests on: pull_request: merge_group: #push: # branches: [ main ] workflow_dispatch: jobs: pre_job: runs-on: ubuntu-latest outputs: should_skip: ${{ steps.set_output.outputs.should_skip }} steps: - id: skip_check if: ${{ github.event_name != 'merge_group' }} uses: fkirc/skip-duplicate-actions@v5 with: # don't run on documentation changes paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]' # cancel previous builds if a new commit is pushed # but never cancel main cancel_others: ${{ github.ref != 'refs/heads/main' }} - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then echo "should_skip=false" >> $GITHUB_OUTPUT else echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT fi pre_docs_job: runs-on: ubuntu-latest outputs: should_skip: ${{ steps.set_output.outputs.should_skip }} steps: - id: skip_check if: ${{ github.event_name != 'merge_group' }} uses: fkirc/skip-duplicate-actions@v5 with: # don't run on readme changes paths_ignore: '["**/README.md"]' # cancel previous builds if a new commit is pushed # but never cancel main cancel_others: ${{ github.ref != 'refs/heads/main' }} - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then echo "should_skip=false" >> $GITHUB_OUTPUT else echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT fi 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@v5 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 make -f ../Makefile unit-test -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-* libyosys.so - name: Store build artifact uses: actions/upload-artifact@v7 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@v5 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@v8 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 vanilla-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@v5 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@v8 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@v5 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@v8 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_owner == 'YosysHQ' }} 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 test-build-result: runs-on: ubuntu-latest needs: - test-yosys - test-cells - test-docs - test-docs-build if: always() steps: - name: Check results run: | echo "Needs results: ${{ join(needs.*.result, ',') }}" if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \ [[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then echo "Some jobs failed or were cancelled" exit 1 fi - run: echo "All good" yosys-0.65/.github/workflows/test-compile.yml000066400000000000000000000063101520057232300213560ustar00rootroot00000000000000name: Compiler testing on: pull_request: merge_group: #push: # branches: [ main ] workflow_dispatch: jobs: pre_job: runs-on: ubuntu-latest outputs: should_skip: ${{ steps.set_output.outputs.should_skip }} steps: - id: skip_check if: ${{ github.event_name != 'merge_group' }} uses: fkirc/skip-duplicate-actions@v5 with: # don't run on documentation changes paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]' # cancel previous builds if a new commit is pushed # but never cancel main cancel_others: ${{ github.ref != 'refs/heads/main' }} - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then echo "should_skip=false" >> $GITHUB_OUTPUT else echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT fi test-compile: runs-on: ${{ matrix.os }} needs: pre_job if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true' env: CXXFLAGS: ${{ startsWith(matrix.compiler, 'gcc') && '-Wp,-D_GLIBCXX_ASSERTIONS' || ''}} CC_SHORT: ${{ startsWith(matrix.compiler, 'gcc') && 'gcc' || 'clang' }} 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@v5 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 test-compile-result: runs-on: ubuntu-latest needs: - test-compile if: always() steps: - name: Check results run: | echo "Needs results: ${{ join(needs.*.result, ',') }}" if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \ [[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then echo "Some jobs failed or were cancelled" exit 1 fi - run: echo "All good" yosys-0.65/.github/workflows/test-sanitizers.yml000066400000000000000000000053411520057232300221240ustar00rootroot00000000000000name: Check clang sanitizers on: pull_request: merge_group: #push: # branches: [ main ] workflow_dispatch: jobs: pre_job: runs-on: ubuntu-latest outputs: should_skip: ${{ steps.set_output.outputs.should_skip }} steps: - id: skip_check if: ${{ github.event_name != 'merge_group' }} uses: fkirc/skip-duplicate-actions@v5 with: # don't run on documentation changes paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]' # cancel previous builds if a new commit is pushed # but never cancel main cancel_others: ${{ github.ref != 'refs/heads/main' }} - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then echo "should_skip=false" >> $GITHUB_OUTPUT else echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT fi run_san: name: Build and run tests runs-on: ${{ matrix.os }} needs: pre_job if: (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch') && needs.pre_job.outputs.should_skip != 'true' env: CC: clang ASAN_OPTIONS: halt_on_error=1 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@v5 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 vanilla-test TARGETS= EXTRA_TARGETS= - name: Report errors if: ${{ failure() }} shell: bash run: | find tests/**/*.err -print -exec cat {} \; test-sanitizers-result: runs-on: ubuntu-latest needs: - run_san if: always() steps: - name: Check results run: | echo "Needs results: ${{ join(needs.*.result, ',') }}" if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \ [[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then echo "Some jobs failed or were cancelled" exit 1 fi - run: echo "All good"yosys-0.65/.github/workflows/test-verific-cfg.yml000066400000000000000000000110051520057232300221070ustar00rootroot00000000000000name: Build various Verific configurations on: workflow_dispatch: jobs: test-verific-cfg: if: github.repository_owner == 'YosysHQ' runs-on: [self-hosted, linux, x64, fast] steps: - name: Checkout Yosys uses: actions/checkout@v4 with: persist-credentials: false submodules: true - name: Runtime environment run: | echo "procs=$(nproc)" >> $GITHUB_ENV - name: verific [SV] run: | make config-clang echo "ENABLE_VERIFIC := 1" >> Makefile.conf echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf echo "ENABLE_VERIFIC_VHDL := 0" >> Makefile.conf echo "ENABLE_VERIFIC_HIER_TREE := 0" >> Makefile.conf echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf make -j$procs - name: verific [VHDL] run: | make config-clang echo "ENABLE_VERIFIC := 1" >> Makefile.conf echo "ENABLE_VERIFIC_SYSTEMVERILOG := 0" >> Makefile.conf echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf echo "ENABLE_VERIFIC_HIER_TREE := 0" >> Makefile.conf echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf make -j$procs - name: verific [SV + VHDL] run: | make config-clang echo "ENABLE_VERIFIC := 1" >> Makefile.conf echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf echo "ENABLE_VERIFIC_HIER_TREE := 0" >> Makefile.conf echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf make -j$procs - name: verific [SV + HIER] run: | make config-clang echo "ENABLE_VERIFIC := 1" >> Makefile.conf echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf echo "ENABLE_VERIFIC_VHDL := 0" >> Makefile.conf echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf make -j$procs - name: verific [VHDL + HIER] run: | make config-clang echo "ENABLE_VERIFIC := 1" >> Makefile.conf echo "ENABLE_VERIFIC_SYSTEMVERILOG := 0" >> Makefile.conf echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf make -j$procs - name: verific [SV + VHDL + HIER] run: | make config-clang echo "ENABLE_VERIFIC := 1" >> Makefile.conf echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf echo "ENABLE_VERIFIC_EDIF := 0" >> Makefile.conf echo "ENABLE_VERIFIC_LIBERTY := 0" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf make -j$procs - name: verific [SV + VHDL + HIER + EDIF + LIBERTY] run: | make config-clang echo "ENABLE_VERIFIC := 1" >> Makefile.conf echo "ENABLE_VERIFIC_SYSTEMVERILOG := 1" >> Makefile.conf echo "ENABLE_VERIFIC_VHDL := 1" >> Makefile.conf echo "ENABLE_VERIFIC_HIER_TREE := 1" >> Makefile.conf echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 0" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf make -j$procs yosys-0.65/.github/workflows/test-verific.yml000066400000000000000000000107161520057232300213620ustar00rootroot00000000000000name: Build and run tests with Verific (Linux) on: pull_request: merge_group: #push: # branches: [ main ] workflow_dispatch: jobs: pre_job: runs-on: ubuntu-latest outputs: should_skip: ${{ steps.set_output.outputs.should_skip }} steps: - id: skip_check if: ${{ github.event_name != 'merge_group' }} uses: fkirc/skip-duplicate-actions@v5 with: # don't run on documentation changes paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]' # cancel previous builds if a new commit is pushed # but never cancel main cancel_others: ${{ github.ref != 'refs/heads/main' }} - id: set_output run: | if [ "${{ github.event_name }}" = "merge_group" ]; then echo "should_skip=false" >> $GITHUB_OUTPUT else echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT fi test-verific: needs: pre_job if: ${{ needs.pre_job.outputs.should_skip != 'true' && github.repository_owner == 'YosysHQ' }} runs-on: [self-hosted, linux, x64, fast] steps: - name: Checkout Yosys 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 vanilla-test - name: Run Verific specific Yosys tests run: | make -C tests/sva make -C tests/svtypes - name: Run SBY tests if: ${{ github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' }} run: | make -C sby run_ci test-pyosys: needs: pre_job if: ${{ needs.pre_job.outputs.should_skip != 'true' && github.repository_owner == 'YosysHQ' }} 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 test-verific-result: runs-on: ubuntu-latest needs: - test-verific - test-pyosys if: always() steps: - name: Check results run: | echo "Needs results: ${{ join(needs.*.result, ',') }}" if [[ "${{ join(needs.*.result, ',') }}" == *failure* ]] || \ [[ "${{ join(needs.*.result, ',') }}" == *cancelled* ]]; then echo "Some jobs failed or were cancelled" exit 1 fi - run: echo "All good" yosys-0.65/.github/workflows/wheels.yml000066400000000000000000000113001520057232300202330ustar00rootroot00000000000000name: Build Wheels for PyPI # run every Sunday at 10 AM on: workflow_dispatch: schedule: - cron: "0 10 * * 0" jobs: build_wheels: strategy: fail-fast: false matrix: os: [ { name: "Ubuntu 22.04", family: "linux", runner: "ubuntu-22.04", archs: "x86_64", }, { name: "Ubuntu 22.04", family: "linux", runner: "ubuntu-22.04-arm", archs: "aarch64", }, { name: "macOS 15 x64", family: "macos", runner: "macos-15-intel", archs: "x86_64", }, { name: "macOS 15 arm64", family: "macos", runner: "macos-15", archs: "arm64", }, ## Windows is disabled because of an issue with compiling FFI as ## under MinGW in the GitHub Actions environment (SHELL variable has ## whitespace.) # { # name: "Windows Server 2019", # family: "windows", # runner: "windows-2019", # archs: "AMD64", # }, ] name: Build Wheels | ${{ matrix.os.name }} | ${{ matrix.os.archs }} runs-on: ${{ matrix.os.runner }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 submodules: true persist-credentials: false - uses: actions/setup-python@v5 - name: Get FFI shell: bash run: | mkdir -p ffi curl -L https://github.com/libffi/libffi/releases/download/v3.4.8/libffi-3.4.8.tar.gz | tar --strip-components=1 -xzC ffi - if: ${{ matrix.os.family == 'linux' }} name: "[Linux] Bison 3.8.2" shell: bash run: | mkdir -p bison curl -L https://ftpmirror.gnu.org/gnu/bison/bison-3.8.2.tar.gz | tar --strip-components=1 -xzC bison ## Software installed by default in GitHub Action Runner VMs: ## https://github.com/actions/runner-images - if: ${{ matrix.os.family == 'macos' }} name: "[macOS] Flex/Bison" run: | brew install flex bison echo "PATH=$(brew --prefix flex)/bin:$(brew --prefix bison)/bin:$PATH" >> $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@v7 with: name: python-wheels-${{ matrix.os.runner }} path: ./wheelhouse/*.whl upload_wheels: name: Upload Wheels if: (github.repository == 'YosysHQ/yosys') && (github.event_name == 'workflow_dispatch') 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@v8 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 yosys-0.65/.github/workflows/wheels/000077500000000000000000000000001520057232300175155ustar00rootroot00000000000000yosys-0.65/.github/workflows/wheels/_run_cibw_linux.py000066400000000000000000000033631520057232300232620ustar00rootroot00000000000000#!/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) yosys-0.65/.github/workflows/wheels/cibw_before_all.sh000066400000000000000000000020571520057232300231530ustar00rootroot00000000000000#!/usr/bin/env 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 ) yosys-0.65/.github/workflows/wheels/cibw_before_build.sh000066400000000000000000000005711520057232300235010ustar00rootroot00000000000000set -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 yosys-0.65/.gitignore000066400000000000000000000025061520057232300146240ustar00rootroot00000000000000## user config /Makefile.conf ## homebrew /Brewfile.lock.json ## build artifacts /.git-abc-submodule-hash # compiler intermediate files *.o *.d *.dwo *.gch *.gcda *.gcno *.so.dSYM/ ## test artifacts **/run-test.mk *.err *.log *.tmp # compiler output files /kernel/version_*.cc /share /yosys /yosys.exe /yosys.js /yosys.wasm /yosys-abc /yosys-abc.exe /yosys-config /yosys-smtbmc /yosys-smtbmc.exe /yosys-smtbmc-script.py /yosys-witness /yosys-witness.exe /yosys-witness-script.py /yosys-filterlib /yosys-filterlib.exe /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 yosys-0.65/.gitmodules000066400000000000000000000003241520057232300150050ustar00rootroot00000000000000[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 yosys-0.65/.mailmap000066400000000000000000000006431520057232300142550ustar00rootroot00000000000000Wanda Phinode Wanda Phinode Wanda Phinode Claire Xenia Wolf Claire Xenia Wolf Claire Xenia Wolf Claire Xenia Wolf yosys-0.65/.readthedocs.yaml000066400000000000000000000005101520057232300160540ustar00rootroot00000000000000# .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 yosys-0.65/Brewfile000066400000000000000000000002721520057232300143140ustar00rootroot00000000000000brew "bison" brew "flex" brew "gawk" brew "libffi" brew "git" brew "graphviz" brew "pkg-config" brew "python3" brew "uv" brew "xdot" brew "bash" brew "llvm" brew "lld" brew "googletest" yosys-0.65/CHANGELOG000066400000000000000000002070261520057232300140520ustar00rootroot00000000000000 List of major changes and improvements between releases ======================================================= Yosys 0.64 .. Yosys 0.65 -------------------------- * New commands and options - Added "arith_tree" pass to convert add/sub/macc chains to carry-save adder trees. - Removed "-force" option from "share" pass. * Various - read_verilog: support positional assignment patterns for unpacked arrays. Yosys 0.63 .. Yosys 0.64 -------------------------- * New commands and options - Added "synth_analogdevices" pass to support synthesis for Analog Devices FPGAs. * Various - Removed rarely-used options from ABC/ABC9. - Removed "-S" option from "abc" pass. - Removed "-fast" option from "abc9" and "abc9_exe". - Calls to "abc -g AND -fast" to map logic to AND-Inverter Graph form should be replaced with "aigmap". - The above change was made to SBY, so we recommend updating it. - Added hardware latch support for Gowin FPGAs. Yosys 0.62 .. Yosys 0.63 -------------------------- * Various - Added DSP inference for Gowin GW1N and GW2A. - Added support for subtract in preadder for Xilinx arch. - Added infrastructure to run a sat solver as a command. * New commands and options - Added "-ignore-unknown-cells" option to "equiv_induct" and "equiv_simple" pass. - Added "-force-params" option to "memory_libmap" pass. - Added "-select-solver" option to "sat" pass. - Added "-default_params" option to "write_verilog" pass. - Added "-nodsp" option to "synth_gowin" pass. Yosys 0.61 .. Yosys 0.62 -------------------------- * Various - verific: Added "-sv2017" flag option to support System Verilog 2017. - verific: Added VHDL related flags to "-f" and "-F" and support reading VHDL file from file lists. - Updated cell libs with proper module declaration where non standard (...) style was used. * New commands and options - Added "-word" option to "lut2mux" pass to enable emitting word level cells. - Added experimental "opt_balance_tree" pass to convert cascaded cells into tree of cells to improve timing. - Added "-gatesi" option to "write_blif" pass to init gates under gates_mode in BLIF format. - Added "-on" and "-off" options to "debug" pass for persistent debug logging. - Added "linux_perf" pass to control performance recording. 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 - Added option "-tech" to "opt_lut" pass. - Added option "-nokeep_prints" to "hierarchy" pass. - Added option "-nolower" to "async2sync" and "clk2fflogic" pass. - Added option "-lower" to "chformal" pass. * Various - Added $check cell to represent assertions with messages. - Allow capturing $print cell output in CXXRTL. - Added API to overwrite existing pass from plugin. - Follow the XDG Base Directory Specification for storing history files. - Without a known top module, derive all deferred modules (hierarchy pass). - Detect and error out on combinational loops in write_aiger. * Verific support - Added option "-no-split-complex-ports" to "verific -import". Yosys 0.36 .. Yosys 0.37 -------------------------- * New commands and options - Added option "-nodisplay" to read_verilog. * SystemVerilog - Correct hierarchical path names for structs and unions. * Various - Print hierarchy for failed assertions in "sim" pass. - Add "--present-only" option to "yosys-witness" to omit unused signals. - Implement a generic record/replay interface for CXXRTL. - Improved readability of emitted code with "write_verilog". Yosys 0.35 .. Yosys 0.36 -------------------------- * New commands and options - Added option "--" to pass arguments down to tcl when using -c option. - Added ability on MacOS and Windows to pass options after arguments on cli. - Added option "-cmp2softlogic" to synth_lattice. - Added option "-lowpower" to "booth" pass. * QuickLogic support - Added "K6N10f" support. - Added "-nodsp", "-nocarry", "-nobram" and "-bramtypes" options to "synth_quicklogic" pass. - Added "ql_bram_merge" pass to merge 18K BRAM cells into TDP36K. - Added "ql_bram_types" pass to change TDP36K depending on configuration. - Added "ql_dsp_io_regs" pass to update QL_DSP2 depending on configuration. - Added "ql_dsp_macc" pass to infer multiplier-accumulator DSP cells. - Added "ql_dsp_simd" pass to merge DSP pairs to operate in SIMD mode. * ECP5,iCE40 and Gowin support - Enabled abc9 by default, added "-noabc9" option to disable. * MachXO3 support - Quality of results improvements. - Enabled "booth" pass by default for it in "synth_lattice". * Various - Improved "peepopt" by adding shiftadd pattern support. - Added "--incremental" mode to smtbmc. Yosys 0.34 .. Yosys 0.35 -------------------------- * Various - Improvements on "peepopt" shiftmul matcher. - Improvements on "ram_style" attributes handling. * Verific support - Improved static elaboration for VHDL and mixed HDL designs. - Expose "hdlname" attribute with original module name. - Expose "architecture" attribute with VHDL architecture name. Yosys 0.33 .. Yosys 0.34 -------------------------- * New commands and options - Added option "-assert" to "sim" pass. - Added option "-noinitstate" to "sim" pass. - Added option "-dont_use" to "abc" pass. - Added "dft_tag" pass to create tagging logic for data flow tracking. - Added "future" pass to resolve future sampled value functions. - Added "booth" pass to map $mul cells to Booth multipliers. - Added option "-booth" to "synth" pass. * SystemVerilog - Added support for assignments within expressions, e.g., `x[y++] = z;` or `x = (y *= 2) - 1;`. * Verific support - "src" attribute contain full location info. - module parameters are kept after import. - accurate access order semantics in memory inference. - better "bind" support for mixed language projects. * Various - "show" command displays dot instead of box for wire aliases. Yosys 0.32 .. Yosys 0.33 -------------------------- * Various - Added "$print" cell, produced by "$display" and "$write" Verilog tasks. - Added "$print" cell handling in CXXRTL. * Lattice FPGA support - Added generic "synth_lattice" pass (for now MachXO2/XO3/XO3D) - Removed "synth_machxo2" pass - Pass "ecp5_gsr" renamed to "lattice_gsr" - "synth_machxo2" equivalent is "synth_lattice -family xo2" Yosys 0.31 .. Yosys 0.32 -------------------------- * Verific support - Added sub option "-lib" to reading commands for VHDL and SystemVerilog, that will later import all units/modules from marked files as blackboxes. * Various - Added support for $lt, $le, $gt, $ge to the code generating AIGs. Yosys 0.30 .. Yosys 0.31 -------------------------- * New commands and options - Added option "-lsbidx" to "write_edif" pass. * Various - Added support for $divfloor operator to cxxrtl backend. - dfflegalize: allow setting mince and minsrst args from scratchpad. Yosys 0.29 .. Yosys 0.30 -------------------------- * New commands and options - Added "recover_names" pass to recover names post-mapping. * Gowin support - Added remaining primitives blackboxes. * Various - "show -colorattr" will now color the cells, wires, and connection arrows. - "show -viewer none" will not execute viewer. Yosys 0.28 .. Yosys 0.29 -------------------------- * New commands and options - Added "synthprop" pass for synthesizable properties. * Verific support - Handle conditions on clocked concurrent assertions in unclocked procedural contexts. * Verilog - Fix const eval of unbased unsized constants. - Handling of attributes for struct / union variables. Yosys 0.27 .. Yosys 0.28 -------------------------- * Verilog - Out of bounds checking for struct/union members. * Verific support - Fix enum_values support and signed attribute values. * ECP5 support - Added "synth_ecp5 -iopad" * MachXO2 support - Added "synth_machxo2 -ccu2" Yosys 0.26 .. Yosys 0.27 -------------------------- * New commands and options - Added option "-make_assert" to "equiv_make" pass. - Added option "-coverenable" to "chformal" pass. * Verilog - Resolve package types in interfaces. - Handle range offsets in packed arrays within packed structs. - Support for data and array queries on struct/union item expressions. * GateMate support - Enable register initialization. Yosys 0.25 .. Yosys 0.26 -------------------------- * New commands and options - Added "bwmuxmap" pass to replace $bwmux cells with equivalent logic. - Added "xprop" experimental pass for formal x propagation. - Added "splitcells" pass to split up multi-bit cells. - Added "viz" pass to visualize data flow graph. - Added option "-make_cover" to "miter" pass. - Added option "-noparallelcase" to "write_verilog" pass. - Added option "-chain" to "insbuf" pass. - Added options "-hierarchy" and "-assume" to "formalff" pass. - Added options "-append" and "-summary" to "sim" pass. - Added option "-ywmap" to "write_btor" pass. - Added option "-ignore-self-reset" to "fsm_detect" pass. * Verilog - Support for struct members of union type. - Support for struct member package types. * Various - Added Yosys witness (.yw) cosimulation. - GCC 4.8 is deprecated, compiler with full C++11 support is required. Yosys 0.24 .. Yosys 0.25 -------------------------- * Verific support - Respect "noblackbox" attribute for modules. * Various - Documentation is hosted at https://yosyshq.readthedocs.io/projects/yosys/en/latest/ Yosys 0.23 .. Yosys 0.24 -------------------------- * New commands and options - Added option "-set-def-formal" to "sat" pass. - Added option "-s" to "tee" command. * Verilog - Support for module-scoped identifiers referring to tasks and functions. - Support for arrays with swapped ranges within structs. * Verific support - Support for importing verilog configurations per name. - "verific -set-XXXXX" commands are now able to set severity to all messages of certain type (errors, warnings, infos and comments) * Various - TCL shell support (use "yosys -C") - Added FABulous eFPGA frontend Yosys 0.22 .. Yosys 0.23 -------------------------- * New commands and options - Added option "-cross" to "miter" pass. - Added option "-nocheck" to "equiv_opt" pass. * Formal Verification - yosys-smtbmc: Added "--detect-loops" option for checking if states are unique in temporal induction counter examples. * Verific support - Added support for reading Liberty files using Verific library. (Optinally enabled with ENABLE_VERIFIC_LIBERTY) - Added option "-cells" to "verific -import" enabling import of all cells from verific design. * Various - MinGW build (Windows) plugin support. - Added YOSYS_ABORT_ON_LOG_ERROR environment variable for debugging. Setting it to 1 causes abort() to be called when Yosys terminates with an error message. Yosys 0.21 .. Yosys 0.22 -------------------------- * Verific support - Added support for here-document for "verific" command (for reading source files). - Added support for reading EDIF files using Verific library. (Optinally enabled with ENABLE_VERIFIC_EDIF) * Various - Added tech specific utilization to "stat" json. Yosys 0.20 .. Yosys 0.21 -------------------------- * New commands and options - Added "formalff" pass - transforms FFs for formal verification - Added option "-formal" to "memory_map" pass - Added option "-witness" to "rename" - give public names to all signals present in yosys witness traces - Added option "-hdlname" to "sim" pass - preserves hiearachy when writing simulation output for a flattened design - Addded option "-scramble-name" to "rename" pass * Formal Verification - Added $anyinit cell to directly represent FFs with an unconstrained initialization value. These can be generated by the new formalff pass. - New JSON based yosys witness format for formal verification traces. - yosys-smtbmc: Reading and writing of yosys witness traces. - write_smt2: Emit inline metadata to support yosys witness trace. - yosys-witness is a new tool to inspect and convert yosys witness traces. - write_aiger: Option to write a map file for yosys witness trace conversion. - yosys-witness: Conversion from and to AIGER witness traces. * Verific support - Filename re-writing support for "verific" pass. * Various - ABC performance improvements - Filename re-writing added for "show -lib". * SmartFusion2 support - Added $alu support - Added SYSRESET and XTLOSC cells - Compatible now with LiberoSoc flow Yosys 0.19 .. Yosys 0.20 -------------------------- * New commands and options - Added option "-wb" to "read_liberty" pass * Various - Added support for $modfloor operator to cxxrtl backend - Support build on OpenBSD - Fixed smt2 backend use of $shift/$shiftx with negative shift amounts, which affects bit/part-select assignments with a dynamic index. Shift operators were not affected. * Verific support - Proper import of port ranges into Yosys, may result in reversed bit-order of top-level ports for some synthesis flows. Yosys 0.18 .. Yosys 0.19 -------------------------- * New commands and options - Added option "-rom-only" to "memory_libmap" pass - Added option "-smtcheck" to "hierarchy" pass - Added option "-keepdc" to "memory_libmap" pass - Added option "-suffix" to "rename" pass - Added "gatemate_foldinv" pass * Formal Verification - Added support for $pos cell in btor backend - Added the "smtlib2_module" and "smtlib2_comb_expr" attributes * GateMate support - Added LUT tree mapping * Verific support - Added option "-pp" to "verific -import" Yosys 0.17 .. Yosys 0.18 -------------------------- * Various - Migrated most flows to use memory_libmap based memory inference * New commands and options - Added "memory_libmap" pass - Added "memory_bmux2rom" pass - converts muxes to ROMs - Added "memory_dff -no-rw-check" - Added "opt_ffinv" pass - push inverters through FFs - Added "proc_rom" pass - convert switches to ROMs - Added "proc -norom" option - will omit the proc_rom pass - Added option "-no-rw-check" to synth passes - Added "synth_ice40 -spram" option for automatic inference of SB_SPRAM256KA - Added options "-nobram" and "-nolutram" to synth_machxo2 pass * Formal Verification - Fixed the signedness of $past's return value to be the same as the argument's instead of always unsigned. * Verilog - Fixed an issue where simplifying case statements by removing unreachable cases could result in the wrong signedness being used for comparison with the remaining cases - Fixed size and signedness computation for expressions containing array querying functions - Fixed size and signedness computation of functions used in ternary expressions or case item expressions * Verific support - Proper file location for readmem commands - Added "-vlog-libext" option to specify search extension for libraries Yosys 0.16 .. Yosys 0.17 -------------------------- * New commands and options - Added "write_jny" ( JSON netlist metadata format ) - Added "tribuf -formal" * SystemVerilog - Fixed automatic `nosync` inference for local variables in `always_comb` procedures not applying to nested blocks and blocks in functions Yosys 0.15 .. Yosys 0.16 -------------------------- * Various - Added BTOR2 witness file co-simulation. - Simulation calls external vcd2fst for VCD conversion. - Added fst2tb pass - generates testbench for the circuit using the given top-level module and simulus signal from FST file. - yosys-smtbmc: Option to keep going after failed assertions in BMC mode * Verific support - Import modules in alphabetic (reproducable) order. Yosys 0.14 .. Yosys 0.15 -------------------------- * Various - clk2fflogic: nice names for autogenerated signals - simulation include support for all flip-flop types. - Added AIGER witness file co-simulation. * Verilog - Fixed evaluation of constant functions with variables or arguments with reversed dimensions - Fixed elaboration of dynamic range assignments where the vector is reversed or is not zero-indexed - Added frontend support for time scale delay values (e.g., `#1ns`) * SystemVerilog - Added support for accessing whole sub-structures in expressions * New commands and options - Added glift command, used to create gate-level information flow tracking (GLIFT) models by the "constructive mapping" approach * Verific support - Ability to override default parser mode for verific -f command. Yosys 0.13 .. Yosys 0.14 -------------------------- * Various - Added $bmux and $demux cells and related optimization patterns. * New commands and options - Added "bmuxmap" and "dmuxmap" passes - Added "-fst" option to "sim" pass for writing FST files - Added "-r", "-scope", "-start", "-stop", "-at", "-sim", "-sim-gate", "-sim-gold" options to "sim" pass for co-simulation * Anlogic support - Added support for BRAMs Yosys 0.12 .. Yosys 0.13 -------------------------- * Various - Use "read" command to parse HDL files from Yosys command-line - Added "yosys -r " command line option - write_verilog: dump zero width sigspecs correctly * SystemVerilog - Fixed regression preventing the use array querying functions in case expressions and case item expressions - Fixed static size casts inadvertently limiting the result width of binary operations - Fixed static size casts ignoring expression signedness - Fixed static size casts not extending unbased unsized literals - Added automatic `nosync` inference for local variables in `always_comb` procedures which are always assigned before they are used to avoid errant latch inference * New commands and options - Added "clean_zerowidth" pass * Verific support - Add YOSYS to the implicitly defined verilog macros in verific Yosys 0.11 .. Yosys 0.12 -------------------------- * Various - Added iopadmap native support for negative-polarity output enable - ABC update * SystemVerilog - Support parameters using struct as a wiretype * New commands and options - Added "-genlib" option to "abc" pass - Added "sta" very crude static timing analysis pass * Verific support - Fixed memory block size in import * New back-ends - Added support for GateMate FPGA from Cologne Chip AG * Intel ALM support - Added preliminary Arria V support Yosys 0.10 .. Yosys 0.11 -------------------------- * Various - Added $aldff and $aldffe (flip-flops with async load) cells * SystemVerilog - Fixed an issue which prevented writing directly to a memory word via a connection to an output port - Fixed an issue which prevented unbased unsized literals (e.g., `'1`) from filling the width of a cell input - Fixed an issue where connecting a slice covering the entirety of a signed signal to a cell input would cause a failed assertion * Verific support - Importer support for {PRIM,WIDE_OPER}_DFF - Importer support for PRIM_BUFIF1 - Option to use Verific without VHDL support - Importer support for {PRIM,WIDE_OPER}_DLATCH{,RS} - Added -cfg option for getting/setting Verific runtime flags Yosys 0.9 .. Yosys 0.10 -------------------------- * Various - Added automatic gzip decompression for frontends - Added $_NMUX_ cell type - Added automatic gzip compression (based on filename extension) for backends - Improve attribute and parameter encoding in JSON to avoid ambiguities between bit vectors and strings containing [01xz]* - Improvements in pmgen: subpattern and recursive matches - Support explicit FIRRTL properties - Improvements in pmgen: slices, choices, define, generate - Added "_TECHMAP_WIREINIT_*_" parameter and "_TECHMAP_REMOVEINIT_*_" wire for "techmap" pass - Added +/mul2dsp.v for decomposing wide multipliers to custom-sized ones - Added new frontend: rpc - Added --version and -version as aliases for -V - Improve yosys-smtbmc "solver not found" handling - Improved support of $readmem[hb] Memory Content File inclusion - Added CXXRTL backend - Use YosysHQ/abc instead of upstream berkeley-abc/abc - Added WASI platform support. - Added extmodule support to firrtl backend - Added $divfloor and $modfloor cells - Added $adffe, $dffsre, $sdff, $sdffe, $sdffce, $adlatch cells - Added "_TECHMAP_CELLNAME_" parameter for "techmap" pass - Added firrtl backend support for generic parameters in blackbox components - Added $meminit_v2 cells (with support for write mask) - Added $mem_v2, $memrd_v2, $memwr_v2, with the following features: - write priority masks, per write/write port pair - transparency and undefined collision behavior masks, per read/write port pair - read port reset and initialization - wide ports (accessing a naturally aligned power-of-two number of memory cells) * New commands and options - Added "write_xaiger" backend - Added "read_xaiger" - Added "abc9" pass for timing-aware techmapping (experimental, FPGA only) - Added "synth -abc9" (experimental) - Added "script -scriptwire" - Added "clkbufmap" pass - Added "extractinv" pass and "invertible_pin" attribute - Added "proc_clean -quiet" - Added "proc_prune" pass - Added "stat -tech cmos" - Added "opt_share" pass, run as part of "opt -full" - Added "-match-init" option to "dff2dffs" pass - Added "equiv_opt -multiclock" - Added "techmap_autopurge" support to techmap - Added "add -mod " - Added "paramap" pass - Added "portlist" command - Added "check -mapped" - Added "check -allow-tbuf" - Added "autoname" pass - Added "write_verilog -extmem" - Added "opt_mem" pass - Added "scratchpad" pass - Added "fminit" pass - Added "opt_lut_ins" pass - Added "logger" pass - Added "show -nobg" - Added "exec" command - Added "design -delete" - Added "design -push-copy" - Added "qbfsat" command - Added "select -unset" - Added "dfflegalize" pass - Removed "opt_expr -clkinv" option, made it the default - Added "proc -nomux - Merged "dffsr2dff", "opt_rmdff", "dff2dffe", "dff2dffs", "peepopt.dffmux" passes into a new "opt_dff" pass * SystemVerilog - Added checking of always block types (always_comb, always_latch and always_ff) - Added support for wildcard port connections (.*) - Added support for enum typedefs - Added support for structs and packed unions. - Allow constant function calls in for loops and generate if and case - Added support for static cast - Added support for logic typed parameters - Fixed generate scoping issues - Added support for real-valued parameters - Allow localparams in constant functions - Module name scope support - Support recursive functions using ternary expressions - Extended support for integer types - Support for parameters without default values - Allow globals in one file to depend on globals in another - Added support for: *=, /=, %=, <<=, >>=, <<<=, >>>= - Added support for parsing the 'bind' construct - support declaration in procedural for initialization - support declaration in generate for initialization - Support wand and wor of data types * Verific support - Added "verific -L" - Add Verific SVA support for "always" properties - Add Verific support for SVA nexttime properties - Improve handling of verific primitives in "verific -import -V" mode - Import attributes for wires - Support VHDL enums - Added support for command files * New back-ends - Added initial EFINIX support - Added Intel ALM: alternative synthesis for Intel FPGAs - Added initial Nexus support - Added initial MachXO2 support - Added initial QuickLogic PolarPro 3 support * ECP5 support - Renamed labels/options in synth_ecp5 (e.g. dram -> map_lutram; -nodram -> -nolutram) - Added "synth_ecp5 -abc9" (experimental) - Added "synth_ecp5 -nowidelut" - "synth_ecp5" to now infer DSP blocks (-nodsp to disable, experimental) * iCE40 support - Added "synth_ice40 -abc9" (experimental) - Added "synth_ice40 -device" - Renamed labels/options in synth_ice40 (e.g. dram -> map_lutram; -nodram -> -nolutram) - Added "ice40_wrapcarry" to encapsulate SB_LUT+SB_CARRY pairs for techmapping - Removed "ice40_unlut" - Added "ice40_dsp" for Lattice iCE40 DSP packing - "synth_ice40 -dsp" to infer DSP blocks * Xilinx support - Added "synth_xilinx -abc9" (experimental) - Added "synth_xilinx -nocarry" - Added "synth_xilinx -nowidelut" - "synth_xilinx" to now infer wide multiplexers (-widemux to enable) - Renamed labels/options in synth_xilinx (e.g. dram -> map_lutram; -nodram -> -nolutram) - Added "synth_xilinx -family xc6s" for Spartan 6 support (experimental) - Added "synth_xilinx -ise" (experimental) - Added "synth_xilinx -iopad" - "synth_xilinx" now automatically inserts clock buffers (add -noclkbuf to disable) - Added "xilinx_srl" for Xilinx shift register extraction - Removed "shregmap -tech xilinx" (superseded by "xilinx_srl") - Added "xilinx_dsp" for Xilinx DSP packing - "synth_xilinx" to now infer DSP blocks (-nodsp to disable) - Added latch support to synth_xilinx - Added support for flip-flops with synchronous reset to synth_xilinx - Added support for flip-flops with reset and enable to synth_xilinx - Added "xilinx_dffopt" pass - Added "synth_xilinx -dff" * Intel support - Renamed labels in synth_intel (e.g. bram -> map_bram) - synth_intel: cyclone10 -> cyclone10lp, a10gx -> arria10gx - Added "intel_alm -abc9" (experimental) * CoolRunner2 support - Separate and improve buffer cell insertion pass - Use extract_counter to optimize counters Yosys 0.8 .. Yosys 0.9 ---------------------- * Various - Many bugfixes and small improvements - Added support for SystemVerilog interfaces and modports - Added "write_edif -attrprop" - Added "opt_lut" pass - Added "gate2lut.v" techmap rule - Added "rename -src" - Added "equiv_opt" pass - Added "flowmap" LUT mapping pass - Added "rename -wire" to rename cells based on the wires they drive - Added "bugpoint" for creating minimised testcases - Added "write_edif -gndvccy" - "write_verilog" to escape Verilog keywords - Fixed sign handling of real constants - "write_verilog" to write initial statement for initial flop state - Added pmgen pattern matcher generator - Fixed opt_rmdff handling of $_DFFSR_???_ and $_DLATCHSR_???_ - Added "setundef -params" to replace undefined cell parameters - Renamed "yosys -D" to "yosys -U", added "yosys -D" to set Verilog defines - Fixed handling of defparam when default_nettype is none - Fixed "wreduce" flipflop handling - Fixed FIRRTL to Verilog process instance subfield assignment - Added "write_verilog -siminit" - Several fixes and improvements for mem2reg memories - Fixed handling of task output ports in clocked always blocks - Improved handling of and-with-1 and or-with-0 in "opt_expr" - Added "read_aiger" frontend - Added "mutate" pass - Added "hdlname" attribute - Added "rename -output" - Added "read_ilang -lib" - Improved "proc" full_case detection and handling - Added "whitebox" and "lib_whitebox" attributes - Added "read_verilog -nowb", "flatten -wb" and "wbflip" - Added Python bindings and support for Python plug-ins - Added "pmux2shiftx" - Added log_debug framework for reduced default verbosity - Improved "opt_expr" and "opt_clean" handling of (partially) undriven and/or unused wires - Added "peepopt" peephole optimisation pass using pmgen - Added approximate support for SystemVerilog "var" keyword - Added parsing of "specify" blocks into $specrule and $specify[23] - Added support for attributes on parameters and localparams - Added support for parsing attributes on port connections - Added "wreduce -keepdc" - Added support for optimising $dffe and $_DFFE_* cells in "opt_rmdff" - Added Verilog wand/wor wire type support - Added support for elaboration system tasks - Added "muxcover -mux{4,8,16}=" - Added "muxcover -dmux=" - Added "muxcover -nopartial" - Added "muxpack" pass - Added "pmux2shiftx -norange" - Added support for "~" in filename parsing - Added "read_verilog -pwires" feature to turn parameters into wires - Fixed sign extension of unsized constants with 'bx and 'bz MSB - Fixed genvar to be a signed type - Added support for attributes on case rules - Added "upto" and "offset" to JSON frontend and backend - Several liberty file parser improvements - Fixed handling of more complex BRAM patterns - Add "write_aiger -I -O -B" * Formal Verification - Added $changed support to read_verilog - Added "read_verilog -noassert -noassume -assert-assumes" - Added btor ops for $mul, $div, $mod and $concat - Added yosys-smtbmc support for btor witnesses - Added "supercover" pass - Fixed $global_clock handling vs autowire - Added $dffsr support to "async2sync" - Added "fmcombine" pass - Added memory init support in "write_btor" - Added "cutpoint" pass - Changed "ne" to "neq" in btor2 output - Added support for SVA "final" keyword - Added "fmcombine -initeq -anyeq" - Added timescale and generated-by header to yosys-smtbmc vcd output - Improved BTOR2 handling of undriven wires * Verific support - Enabled Verific flags vhdl_support_variable_slice and veri_elaborate_top_level_modules_having_interface_ports - Improved support for asymmetric memories - Added "verific -chparam" - Fixed "verific -extnets" for more complex situations - Added "read -verific" and "read -noverific" - Added "hierarchy -chparam" * New back-ends - Added initial Anlogic support - Added initial SmartFusion2 and IGLOO2 support * ECP5 support - Added "synth_ecp5 -nowidelut" - Added BRAM inference support to "synth_ecp5" - Added support for transforming Diamond IO and flipflop primitives * iCE40 support - Added "ice40_unlut" pass - Added "synth_ice40 -relut" - Added "synth_ice40 -noabc" - Added "synth_ice40 -dffe_min_ce_use" - Added DSP inference support using pmgen - Added support for initialising BRAM primitives from a file - Added iCE40 Ultra RGB LED driver cells * Xilinx support - Use "write_edif -pvector bra" for Xilinx EDIF files - Fixes for VPR place and route support with "synth_xilinx" - Added more cell simulation models - Added "synth_xilinx -family" - Added "stat -tech xilinx" to estimate logic cell usage - Added "synth_xilinx -nocarry" - Added "synth_xilinx -nowidelut" - "synth_xilinx" to now infer hard shift registers (-nosrl to disable) - Added support for mapping RAM32X1D Yosys 0.7 .. Yosys 0.8 ---------------------- * Various - Many bugfixes and small improvements - Strip debug symbols from installed binary - Replace -ignore_redef with -[no]overwrite in front-ends - Added write_verilog hex dump support, add -nohex option - Added "write_verilog -decimal" - Added "scc -set_attr" - Added "verilog_defines" command - Remember defines from one read_verilog to next - Added support for hierarchical defparam - Added FIRRTL back-end - Improved ABC default scripts - Added "design -reset-vlog" - Added "yosys -W regex", "yosys -w regex", and "yosys -e regex" - Added Verilog $rtoi and $itor support - Added "check -initdrv" - Added "read_blif -wideports" - Added support for SystemVerilog "++" and "--" operators - Added support for SystemVerilog unique, unique0, and priority case - Added "write_edif" options for edif "flavors" - Added support for resetall compiler directive - Added simple C beck-end (bitwise combinatorical only atm) - Added $_ANDNOT_ and $_ORNOT_ cell types - Added cell library aliases to "abc -g" - Added "setundef -anyseq" - Added "chtype" command - Added "design -import" - Added "write_table" command - Added "read_json" command - Added "sim" command - Added "extract_fa" and "extract_reduce" commands - Added "extract_counter" command - Added "opt_demorgan" command - Added support for $size and $bits SystemVerilog functions - Added "blackbox" command - Added "ltp" command - Added support for editline as replacement for readline - Added warnings for driver-driver conflicts between FFs (and other cells) and constants - Added "yosys -E" for creating Makefile dependencies files - Added "synth -noshare" - Added "memory_nordff" - Added "setundef -undef -expose -anyconst" - Added "expose -input" - Added specify/specparam parser support (simply ignore them) - Added "write_blif -inames -iattr" - Added "hierarchy -simcheck" - Added an option to statically link abc into yosys - Added protobuf back-end - Added BLIF parsing support for .conn and .cname - Added read_verilog error checking for reg/wire/logic misuse - Added "make coverage" and ENABLE_GCOV build option * Changes in Yosys APIs - Added ConstEval defaultval feature - Added {get,set}_src_attribute() methods on RTLIL::AttrObject - Added SigSpec::is_fully_ones() and Const::is_fully_ones() - Added log_file_warning() and log_file_error() functions * Formal Verification - Added "write_aiger" - Added "yosys-smtbmc --aig" - Added "always " to .smtc format - Added $cover cell type and support for cover properties - Added $fair/$live cell type and support for liveness properties - Added smtbmc support for memory vcd dumping - Added "chformal" command - Added "write_smt2 -stbv" and "write_smt2 -stdt" - Fix equiv_simple, old behavior now available with "equiv_simple -short" - Change to Yices2 as default SMT solver (it is GPL now) - Added "yosys-smtbmc --presat" (now default in SymbiYosys) - Added "yosys-smtbmc --smtc-init --smtc-top --noinit" - Added a brand new "write_btor" command for BTOR2 - Added clk2fflogic memory support and other improvements - Added "async memory write" support to write_smt2 - Simulate clock toggling in yosys-smtbmc VCD output - Added $allseq/$allconst cells for EA-solving - Make -nordff the default in "prep" - Added (* gclk *) attribute - Added "async2sync" pass for single-clock designs with async resets * Verific support - Many improvements in Verific front-end - Added proper handling of concurent SVA properties - Map "const" and "rand const" to $anyseq/$anyconst - Added "verific -import -flatten" and "verific -import -extnets" - Added "verific -vlog-incdir -vlog-define -vlog-libdir" - Remove PSL support (because PSL has been removed in upstream Verific) - Improve integration with "hierarchy" command design elaboration - Added YOSYS_NOVERIFIC for running non-verific test cases with verific bin - Added simpilied "read" command that automatically uses verific if available - Added "verific -set- .." - Added "verific -work " * New back-ends - Added initial Coolrunner-II support - Added initial eASIC support - Added initial ECP5 support * GreenPAK Support - Added support for GP_DLATCH, GP_SPI, GP_DCMx, GP_COUNTx, etc. * iCE40 Support - Add "synth_ice40 -vpr" - Add "synth_ice40 -nodffe" - Add "synth_ice40 -json" - Add Support for UltraPlus cells * MAX10 and Cyclone IV Support - Added initial version of metacommand "synth_intel". - Improved write_verilog command to produce VQM netlist for Quartus Prime. - Added support for MAX10 FPGA family synthesis. - Added support for Cyclone IV family synthesis. - Added example of implementation for DE2i-150 board. - Added example of implementation for MAX10 development kit. - Added LFSR example from Asic World. - Added "dffinit -highlow" for mapping to Intel primitives Yosys 0.6 .. Yosys 0.7 ---------------------- * Various - Added "yosys -D" feature - Added support for installed plugins in $(DATDIR)/plugins/ - Renamed opt_const to opt_expr - Renamed opt_share to opt_merge - Added "prep -flatten" and "synth -flatten" - Added "prep -auto-top" and "synth -auto-top" - Using "mfs" and "lutpack" in ABC lut mapping - Support for abstract modules in chparam - Cleanup abstract modules at end of "hierarchy -top" - Added tristate buffer support to iopadmap - Added opt_expr support for div/mod by power-of-two - Added "select -assert-min -assert-max " - Added "attrmvcp" pass - Added "attrmap" command - Added "tee +INT -INT" - Added "zinit" pass - Added "setparam -type" - Added "shregmap" pass - Added "setundef -init" - Added "nlutmap -assert" - Added $sop cell type and "abc -sop -I -P " - Added "dc2" to default ABC scripts - Added "deminout" - Added "insbuf" command - Added "prep -nomem" - Added "opt_rmdff -keepdc" - Added "prep -nokeepdc" - Added initial version of "synth_gowin" - Added "fsm_expand -full" - Added support for fsm_encoding="user" - Many improvements in GreenPAK4 support - Added black box modules for all Xilinx 7-series lib cells - Added synth_ice40 support for latches via logic loops - Fixed ice40_opt lut unmapping, added "ice40_opt -unlut" * Build System - Added ABCEXTERNAL and ABCURL make variables - Added BINDIR, LIBDIR, and DATDIR make variables - Added PKG_CONFIG make variable - Added SEED make variable (for "make test") - Added YOSYS_VER_STR make variable - Updated min GCC requirement to GCC 4.8 - Updated required Bison version to Bison 3.x * Internal APIs - Added ast.h to exported headers - Added ScriptPass helper class for script-like passes - Added CellEdgesDatabase API * Front-ends and Back-ends - Added filename glob support to all front-ends - Added avail (black-box) module params to ilang format - Added $display %m support - Added support for $stop Verilog system task - Added support for SystemVerilog packages - Fixed procedural assignments to non-unique lvalues, e.g. {y,y} = {a,b} - Added support for "active high" and "active low" latches in read_blif and write_blif - Use init value "2" for all uninitialized FFs in BLIF back-end - Added "read_blif -sop" - Added "write_blif -noalias" - Added various write_blif options for VTR support - write_json: also write module attributes. - Added "write_verilog -nodec -nostr -defparam" - Added "read_verilog -norestrict -assume-asserts" - Added support for bus interfaces to "read_liberty -lib" - Added liberty parser support for types within cell decls - Added "write_verilog -renameprefix -v" - Added "write_edif -nogndvcc" * Formal Verification - Support for hierarchical designs in smt2 back-end - Yosys-smtbmc: Support for hierarchical VCD dumping - Added $initstate cell type and vlog function - Added $anyconst and $anyseq cell types and vlog functions - Added printing of code loc of failed asserts to yosys-smtbmc - Added memory_memx pass, "memory -memx", and "prep -memx" - Added "proc_mux -ifx" - Added "yosys-smtbmc -g" - Deprecated "write_smt2 -regs" (by default on now) - Made "write_smt2 -bv -mem" default, added "write_smt2 -nobv -nomem" - Added support for memories to smtio.py - Added "yosys-smtbmc --dump-vlogtb" - Added "yosys-smtbmc --smtc --dump-smtc" - Added "yosys-smtbmc --dump-all" - Added assertpmux command - Added "yosys-smtbmc --unroll" - Added $past, $stable, $rose, $fell SVA functions - Added "yosys-smtbmc --noinfo and --dummy" - Added "yosys-smtbmc --noincr" - Added "yosys-smtbmc --cex " - Added $ff and $_FF_ cell types - Added $global_clock verilog syntax support for creating $ff cells - Added clk2fflogic Yosys 0.5 .. Yosys 0.6 ---------------------- * Various - Added Contributor Covenant Code of Conduct - Various improvements in dict<> and pool<> - Added hashlib::mfp and refactored SigMap - Improved support for reals as module parameters - Various improvements in SMT2 back-end - Added "keep_hierarchy" attribute - Verilog front-end: define `BLACKBOX in -lib mode - Added API for converting internal cells to AIGs - Added ENABLE_LIBYOSYS Makefile option - Removed "techmap -share_map" (use "-map +/filename" instead) - Switched all Python scripts to Python 3 - Added support for $display()/$write() and $finish() to Verilog front-end - Added "yosys-smtbmc" formal verification flow - Added options for clang sanitizers to Makefile * New commands and options - Added "scc -expect -nofeedback" - Added "proc_dlatch" - Added "check" - Added "select %xe %cie %coe %M %C %R" - Added "sat -dump_json" (WaveJSON format) - Added "sat -tempinduct-baseonly -tempinduct-inductonly" - Added "sat -stepsize" and "sat -tempinduct-step" - Added "sat -show-regs -show-public -show-all" - Added "write_json" (Native Yosys JSON format) - Added "write_blif -attr" - Added "dffinit" - Added "chparam" - Added "muxcover" - Added "pmuxtree" - Added memory_bram "make_outreg" feature - Added "splice -wires" - Added "dff2dffe -direct-match" - Added simplemap $lut support - Added "read_blif" - Added "opt_share -share_all" - Added "aigmap" - Added "write_smt2 -mem -regs -wires" - Added "memory -nordff" - Added "write_smv" - Added "synth -nordff -noalumacc" - Added "rename -top new_name" - Added "opt_const -clkinv" - Added "synth -nofsm" - Added "miter -assert" - Added "read_verilog -noautowire" - Added "read_verilog -nodpi" - Added "tribuf" - Added "lut2mux" - Added "nlutmap" - Added "qwp" - Added "test_cell -noeval" - Added "edgetypes" - Added "equiv_struct" - Added "equiv_purge" - Added "equiv_mark" - Added "equiv_add -try -cell" - Added "singleton" - Added "abc -g -luts" - Added "torder" - Added "write_blif -cname" - Added "submod -copy" - Added "dffsr2dff" - Added "stat -liberty" * Synthesis metacommands - Various improvements in synth_xilinx - Added synth_ice40 and synth_greenpak4 - Added "prep" metacommand for "synthesis lite" * Cell library changes - Added cell types to "help" system - Added $meminit cell type - Added $assume cell type - Added $_MUX4_, $_MUX8_, and $_MUX16_ cells - Added $tribuf and $_TBUF_ cell types - Added read-enable to memory model * YosysJS - Various improvements in emscripten build - Added alternative webworker-based JS API - Added a few example applications Yosys 0.4 .. Yosys 0.5 ---------------------- * API changes - Added log_warning() - Added eval_select_args() and eval_select_op() - Added cell->known(), cell->input(portname), cell->output(portname) - Skip blackbox modules in design->selected_modules() - Replaced std::map<> and std::set<> with dict<> and pool<> - New SigSpec::extend() is what used to be SigSpec::extend_u0() - Added YS_OVERRIDE, YS_FINAL, YS_ATTRIBUTE, YS_NORETURN * Cell library changes - Added flip-flops with enable ($dffe etc.) - Added $equiv cells for equivalence checking framework * Various - Updated ABC to hg rev 61ad5f908c03 - Added clock domain partitioning to ABC pass - Improved plugin building (see "yosys-config --build") - Added ENABLE_NDEBUG Makefile flag for high-performance builds - Added "yosys -d", "yosys -L" and other driver improvements - Added support for multi-bit (array) cell ports to "write_edif" - Now printing most output to stdout, not stderr - Added "onehot" attribute (set by "fsm_map") - Various performance improvements - Vastly improved Xilinx flow - Added "make unsintall" * Equivalence checking - Added equivalence checking commands: equiv_make equiv_simple equiv_status equiv_induct equiv_miter equiv_add equiv_remove * Block RAM support: - Added "memory_bram" command - Added BRAM support to Xilinx flow * Other New Commands and Options - Added "dff2dffe" - Added "fsm -encfile" - Added "dfflibmap -prepare" - Added "write_blid -unbuf -undef -blackbox" - Added "write_smt2" for writing SMT-LIBv2 files - Added "test_cell -w -muxdiv" - Added "select -read" Yosys 0.3.0 .. Yosys 0.4 ------------------------ * Platform Support - Added support for mxe-based cross-builds for win32 - Added sourcecode-export as VisualStudio project - Added experimental EMCC (JavaScript) support * Verilog Frontend - Added -sv option for SystemVerilog (and automatic *.sv file support) - Added support for real-valued constants and constant expressions - Added support for non-standard "via_celltype" attribute on task/func - Added support for non-standard "module mod_name(...);" syntax - Added support for non-standard """ macro bodies - Added support for array with more than one dimension - Added support for $readmemh and $readmemb - Added support for DPI functions * Changes in internal cell library - Added $shift and $shiftx cell types - Added $alu, $lcu, $fa and $macc cell types - Removed $bu0 and $safe_pmux cell types - $mem/$memwr WR_EN input is now a per-data-bit enable signal - Added $_NAND_ $_NOR_ $_XNOR_ $_AOI3_ $_OAI3_ $_AOI4_ $_OAI4_ - Renamed ports of $lut cells (from I->O to A->Y) - Renamed $_INV_ to $_NOT_ * Changes for simple synthesis flows - There is now a "synth" command with a recommended default script - Many improvements in synthesis of arithmetic functions to gates - Multipliers and adders with many operands are using carry-save adder trees - Remaining adders are now implemented using Brent-Kung carry look-ahead adders - Various new high-level optimizations on RTL netlist - Various improvements in FSM optimization - Updated ABC to hg 5b5af75f1dda (from 2014-11-07) * Changes in internal APIs and RTLIL - Added log_id() and log_cell() helper functions - Added function-like cell creation helpers - Added GetSize() function (like .size() but with int) - Major refactoring of RTLIL::Module and related classes - Major refactoring of RTLIL::SigSpec and related classes - Now RTLIL::IdString is essentially an int - Added macros for code coverage counters - Added some Makefile magic for pretty make logs - Added "kernel/yosys.h" with all the core definitions - Changed a lot of code from FILE* to c++ streams - Added RTLIL::Monitor API and "trace" command - Added "Yosys" C++ namespace * Changes relevant to SAT solving - Added ezSAT::keep_cnf() and ezSAT::non_incremental() - Added native ezSAT support for vector shift ops - Updated MiniSAT to git 37dc6c67e2 (from 2013-09-25) * New commands (or large improvements to commands) - Added "synth" command with default script - Added "share" (finally some real resource sharing) - Added "memory_share" (reduce number of ports on memories) - Added "wreduce" and "alumacc" commands - Added "opt -keepdc -fine -full -fast" - Added some "test_*" commands * Various other changes - Added %D and %c select operators - Added support for labels in yosys scripts - Added support for here-documents in yosys scripts - Support "+/" prefix for files from proc_share_dir - Added "autoidx" statement to ilang language - Switched from "yosys-svgviewer" to "xdot" - Renamed "stdcells.v" to "techmap.v" - Various bug fixes and small improvements - Improved welcome and bye messages Yosys 0.2.0 .. Yosys 0.3.0 -------------------------- * Driver program and overall behavior: - Added "design -push" and "design -pop" - Added "tee" command for redirecting log output * Changes in the internal cell library: - Added $dlatchsr and $_DLATCHSR_???_ cell types * Improvements in Verilog frontend: - Improved support for const functions (case, always, repeat) - The generate..endgenerate keywords are now optional - Added support for arrays of module instances - Added support for "`default_nettype" directive - Added support for "`line" directive * Other front- and back-ends: - Various changes to "write_blif" options - Various improvements in EDIF backend - Added "vhdl2verilog" pseudo-front-end - Added "verific" pseudo-front-end * Improvements in technology mapping: - Added support for recursive techmap - Added CONSTMSK and CONSTVAL features to techmap - Added _TECHMAP_CONNMAP_*_ feature to techmap - Added _TECHMAP_REPLACE_ feature to techmap - Added "connwrappers" command for wrap-extract-unwrap method - Added "extract -map %" feature - Added "extract -ignore_param ..." and "extract -ignore_parameters" - Added "techmap -max_iter" option * Improvements to "eval" and "sat" framework: - Now include a copy of Minisat (with build fixes applied) - Switched to Minisat::SimpSolver as SAT back-end - Added "sat -dump_vcd" feature - Added "sat -dump_cnf" feature - Added "sat -initsteps " feature - Added "freduce -stop " feature - Added "freduce -dump " feature * Integration with ABC: - Updated ABC rev to 7600ffb9340c * Improvements in the internal APIs: - Added RTLIL::Module::add... helper methods - Various build fixes for OSX (Darwin) and OpenBSD Yosys 0.1.0 .. Yosys 0.2.0 -------------------------- * Changes to the driver program: - Added "yosys -h" and "yosys -H" - Added support for backslash line continuation in scripts - Added support for #-comments in same line as command - Added "echo" and "log" commands * Improvements in Verilog frontend: - Added support for local registers in named blocks - Added support for "case" in "generate" blocks - Added support for $clog2 system function - Added support for basic SystemVerilog assert statements - Added preprocessor support for macro arguments - Added preprocessor support for `elsif statement - Added "verilog_defaults" command - Added read_verilog -icells option - Added support for constant sizes from parameters - Added "read_verilog -setattr" - Added support for function returning 'integer' - Added limited support for function calls in parameter values - Added "read_verilog -defer" to suppress evaluation of modules with default parameters * Other front- and back-ends: - Added BTOR backend - Added Liberty frontend * Improvements in technology mapping: - The "dfflibmap" command now strongly prefers solutions with no inverters in clock paths - The "dfflibmap" command now prefers cells with smaller area - Added support for multiple -map options to techmap - Added "dfflibmap" support for //-comments in liberty files - Added "memory_unpack" command to revert "memory_collect" - Added standard techmap rule "techmap -share_map pmux2mux.v" - Added "iopadmap -bits" - Added "setundef" command - Added "hilomap" command * Changes in the internal cell library: - Major rewrite of simlib.v for better compatibility with other tools - Added PRIORITY parameter to $memwr cells - Added TRANSPARENT parameter to $memrd cells - Added RD_TRANSPARENT parameter to $mem cells - Added $bu0 cell (always 0-extend, even undef MSB) - Added $assert cell type - Added $slice and $concat cell types * Integration with ABC: - Updated ABC to hg rev 2058c8ccea68 - Tighter integration of ABC build with Yosys build. The make targets 'make abc' and 'make install-abc' are now obsolete. - Added support for passing FFs from one clock domain through ABC - Now always use BLIF as exchange format with ABC - Added support for "abc -script +" - Improved standard ABC recipe - Added support for "keep" attribute to abc command - Added "abc -dff / -clk / -keepff" options * Improvements to "eval" and "sat" framework: - Added support for "0" and "~0" in right-hand side -set expressions - Added "eval -set-undef" and "eval -table" - Added "sat -set-init" and "sat -set-init-*" for sequential problems - Added undef support to SAT solver, incl. various new "sat" options - Added correct support for === and !== for "eval" and "sat" - Added "sat -tempinduct" (default -seq is now non-induction sequential) - Added "sat -prove-asserts" - Complete rewrite of the 'freduce' command - Added "miter" command - Added "sat -show-inputs" and "sat -show-outputs" - Added "sat -ignore_unknown_cells" (now produce an error by default) - Added "sat -falsify" - Now "sat -verify" and "sat -falsify" can also be used without "-prove" - Added "expose" command - Added support for @ to sat and eval signal expressions * Changes in the 'make test' framework and auxiliary test tools: - Added autotest.sh -p and -f options - Replaced autotest.sh ISIM support with XSIM support - Added test cases for SAT framework * Added "abbreviated IDs": - Now $$foo can be abbreviated as $foo. - Usually this last part is a unique id (from RTLIL::autoidx) - This abbreviated IDs are now also used in "show" output * Other changes to selection framework: - Now */ is optional in */: expressions - Added "select -assert-none" and "select -assert-any" - Added support for matching modules by attribute (A:) - Added "select -none" - Added support for r: pattern for matching cell parameters - Added support for !=, <, <=, >=, > for attribute and parameter matching - Added support for %s for selecting sub-modules - Added support for %m for expanding selections to whole modules - Added support for i:*, o:* and x:* pattern for selecting module ports - Added support for s: pattern for matching wire width - Added support for %a operation to select wire aliases * Various other changes to commands and options: - The "ls" command now supports wildcards - Added "show -pause" and "show -format dot" - Added "show -color" support for cells - Added "show -label" and "show -notitle" - Added "dump -m" and "dump -n" - Added "history" command - Added "rename -hide" - Added "connect" command - Added "splitnets -driver" - Added "opt_const -mux_undef" - Added "opt_const -mux_bool" - Added "opt_const -undriven" - Added "opt -mux_undef -mux_bool -undriven -purge" - Added "hierarchy -libdir" - Added "hierarchy -purge_lib" (by default now do not remove lib cells) - Added "delete" command - Added "dump -append" - Added "setattr" and "setparam" commands - Added "design -stash/-copy-from/-copy-to" - Added "copy" command - Added "splice" command yosys-0.65/CODEOWNERS000066400000000000000000000034721520057232300142320ustar00rootroot00000000000000## CODE NOTIFICATIONS # Register yourself here to be notified about modifications # for any files you have an interest in/know your way around. # Each line is a file pattern followed by one or more users. # Both github usernames and email addresses are supported. # Order is important; the last matching pattern takes the most # precedence. Previous matches will not be applied. # PATH (can use glob) USERNAME(S) CODEOWNERS @nakengelhardt passes/cmds/scratchpad.cc @nakengelhardt frontends/rpc/ @whitequark backends/cxxrtl/ @whitequark passes/cmds/bugpoint.cc @whitequark 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 # requests automatically, but we add information for other # contributors here too, so we know who to ask to take a look. # These still override previous lines, so be careful not to # accidentally disable any of the above rules. frontends/verilog/ @widlarizer frontends/ast/ @widlarizer techlibs/intel_alm/ @Ravenslofty techlibs/gowin/ @pepijndevos techlibs/gatemate/ @pu-cc # pyosys pyosys/* @donn setup.py @donn backends/firrtl @ucbjrl @azidar passes/sat/qbfsat.cc @boqwxp passes/sat/qbfsat.h @boqwxp passes/cmds/exec.cc @boqwxp passes/cmds/glift.cc @boqwxp passes/cmds/printattrs.cc @boqwxp yosys-0.65/CODE_OF_CONDUCT.md000066400000000000000000000121531520057232300154320ustar00rootroot00000000000000# 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. yosys-0.65/CONTRIBUTING.md000066400000000000000000000012311520057232300150570ustar00rootroot00000000000000# Contributing to Yosys Thanks for considering helping out. If this is your first time contributing to an open source project, please take a look at the following guide about the basics: https://opensource.guide/how-to-contribute/#orienting-yourself-to-a-new-project. Check out our [Contributing guidelines](https://yosys.readthedocs.io/en/latest/yosys_internals/extending_yosys/contributing.html) to learn the best ways to + get help + report bugs + contribute code + review code If you're reading this file offline and don't have internet access, you can [read the contributing.rst file locally](docs/source/yosys_internals/extending_yosys/contributing.rst). yosys-0.65/COPYING000066400000000000000000000014111520057232300136610ustar00rootroot00000000000000ISC License 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 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-0.65/Makefile000066400000000000000000001202651520057232300142770ustar00rootroot00000000000000 CONFIG := none # CONFIG := clang # CONFIG := gcc # CONFIG := wasi # CONFIG := msys2-32 # CONFIG := msys2-64 # features (the more the better) ENABLE_TCL := 1 ENABLE_ABC := 1 ENABLE_GLOB := 1 ENABLE_PLUGINS := 1 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 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_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 = # SANITIZER = address # SANITIZER = memory # 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) PREFIX ?= /usr/local INSTALL_SUDO := ifneq ($(filter MINGW%,$(OS)),) OS := MINGW endif ifneq ($(wildcard Makefile.conf),) include Makefile.conf endif ifeq ($(ENABLE_PYOSYS),1) ENABLE_LIBYOSYS := 1 endif BINDIR := $(PREFIX)/bin LIBDIR := $(PREFIX)/lib/$(PROGRAM_PREFIX)yosys DATDIR := $(PREFIX)/share/$(PROGRAM_PREFIX)yosys EXE = OBJS = GENFILES = EXTRA_OBJS = EXTRA_TARGETS = TARGETS = $(PROGRAM_PREFIX)yosys$(EXE) $(PROGRAM_PREFIX)yosys-config PRETTY = 1 SMALL = 0 all: top-all YOSYS_SRC := $(dir $(firstword $(MAKEFILE_LIST))) VPATH := $(YOSYS_SRC) # 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 STRIP ?= strip 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}]) 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) # macports search paths else ifneq ($(shell :; command -v port),) PORT_PREFIX := $(patsubst %/bin/port,%,$(shell :; command -v port)) CXXFLAGS += -I$(PORT_PREFIX)/include LINKFLAGS += -L$(PORT_PREFIX)/lib PKG_CONFIG_PATH := $(PORT_PREFIX)/lib/pkgconfig:$(PKG_CONFIG_PATH) export PATH := $(PORT_PREFIX)/bin:$(PATH) endif else LINKFLAGS += -rdynamic ifneq ($(OS), OpenBSD) LIBS += -lrt endif endif ifeq ($(OS), Haiku) # Allow usage of non-posix vasprintf, mkstemps functions CXXFLAGS += -D_DEFAULT_SOURCE endif YOSYS_VER := 0.65 ifneq (, $(shell command -v git 2>/dev/null)) ifneq (, $(shell git rev-parse --git-dir 2>/dev/null)) GIT_COMMIT_COUNT := $(or $(shell git rev-list --count v$(YOSYS_VER)..HEAD 2>/dev/null),0) ifneq ($(GIT_COMMIT_COUNT),0) YOSYS_VER := $(YOSYS_VER)+$(GIT_COMMIT_COUNT) endif else # YOSYS_VER := $(YOSYS_VER)+post endif endif YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | 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) 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 ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) # set ABCEXTERNAL = to use an external ABC instance # Note: The in-tree ABC (yosys-abc) will not be installed when ABCEXTERNAL is set. ABCEXTERNAL ?= define newline endef ifneq ($(wildcard Makefile.conf),) # don't echo Makefile.conf contents when invoked to print source versions ifeq ($(findstring echo-,$(MAKECMDGOALS)),) $(info $(subst $$--$$,$(newline),$(shell sed 's,^,[Makefile.conf] ,; s,$$,$$--$$,;' < Makefile.conf | tr -d '\n' | sed 's,\$$--\$$$$,,'))) endif include Makefile.conf endif 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.) 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]);") # Reload Makefile.conf to override python specific variables if defined ifneq ($(wildcard Makefile.conf),) include Makefile.conf endif endif ABC_ARCHFLAGS = "" ifeq ($(OS), OpenBSD) ABC_ARCHFLAGS += "-DABC_NO_RLIMIT" endif # This gets overridden later. LTOFLAGS := $(GCC_LTO) ifeq ($(CONFIG),clang) CXX = clang++ 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 memory,$(SANITIZER)),) CXXFLAGS += -fPIE -fsanitize-memory-track-origins LINKFLAGS += -fPIE -fsanitize-memory-track-origins endif ifneq ($(findstring cfi,$(SANITIZER)),) CXXFLAGS += -flto LINKFLAGS += -flto LTOFLAGS = endif endif else ifeq ($(CONFIG),gcc) CXX = g++ 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) $(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),wasi) ifeq ($(WASI_SDK),) CXX = clang++ AR = llvm-ar RANLIB = llvm-ranlib WASIFLAGS := -target wasm32-wasip1 $(WASIFLAGS) else CXX = $(WASI_SDK)/bin/clang++ AR = $(WASI_SDK)/bin/ar RANLIB = $(WASI_SDK)/bin/ranlib endif CXXFLAGS := $(WASIFLAGS) -std=$(CXXSTD) $(OPT_LEVEL) -D_WASI_EMULATED_PROCESS_CLOCKS -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false $(filter-out -fPIC,$(CXXFLAGS)) LINKFLAGS := $(WASIFLAGS) -Wl,-z,stack-size=1048576 $(filter-out -rdynamic,$(LINKFLAGS)) -fwasm-exceptions -lunwind LIBS := -lwasi-emulated-process-clocks $(filter-out -lrt,$(LIBS)) ABCMKARGS += AR="$(AR)" RANLIB="$(RANLIB)" ABCMKARGS += ARCHFLAGS="$(WASIFLAGS) -D_WASI_EMULATED_PROCESS_CLOCKS -DABC_USE_STDINT_H -DABC_NO_DYNAMIC_LINKING -DABC_NO_RLIMIT" ABCMKARGS += OPTFLAGS="-Os" LTOFLAGS = EXE = .wasm DISABLE_SPAWN := 1 ifeq ($(ENABLE_ABC),1) LINK_ABC := 1 ENABLE_THREADS := 0 DISABLE_ABC_THREADS := 1 endif else ifeq ($(CONFIG),msys2-32) CXX = i686-w64-mingw32-g++ 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 -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) $(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 -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 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) # python-config --ldflags includes -l and -L, but LINKFLAGS is only -L UV_ENV := ifeq ($(PYOSYS_USE_UV),1) UV_ENV := uv run --no-project --with 'pybind11>3,<4' --with 'cxxheaderparser' endif LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags)) 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 OBJS += $(PY_WRAPPER_FILE).o 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) CXXFLAGS += -DYOSYS_ENABLE_READLINE ifeq ($(OS), $(filter $(OS),FreeBSD OpenBSD NetBSD)) CXXFLAGS += -I/usr/local/include endif LIBS += -lreadline ifeq ($(LINK_CURSES),1) LIBS += -lcurses ABCMKARGS += "ABC_READLINE_LIBRARIES=-lcurses -lreadline" endif ifeq ($(LINK_TERMCAP),1) LIBS += -ltermcap ABCMKARGS += "ABC_READLINE_LIBRARIES=-lreadline -ltermcap" endif else ifeq ($(ENABLE_EDITLINE),1) CXXFLAGS += -DYOSYS_ENABLE_EDITLINE 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 ifeq ($(ENABLE_PLUGINS),1) CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --cflags libffi) -DYOSYS_ENABLE_PLUGINS ifeq ($(OS), MINGW) CXXFLAGS += -Ilibs/dlfcn-win32 endif LIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) --silence-errors --libs libffi || echo -lffi) ifneq ($(OS), $(filter $(OS),FreeBSD OpenBSD NetBSD MINGW)) LIBS += -ldl endif endif ifeq ($(ENABLE_GLOB),1) CXXFLAGS += -DYOSYS_ENABLE_GLOB endif ifeq ($(ENABLE_ZLIB),1) CXXFLAGS += -DYOSYS_ENABLE_ZLIB LIBS += -lz endif ifeq ($(ENABLE_TCL),1) TCL_VERSION ?= tcl$(shell bash -c "tclsh <(echo 'puts [info tclversion]')") ifeq ($(OS), $(filter $(OS),FreeBSD OpenBSD NetBSD)) # BSDs usually use tcl8.6, but the lib is named "libtcl86" TCL_INCLUDE ?= /usr/local/include/$(TCL_VERSION) TCL_LIBS ?= -l$(subst .,,$(TCL_VERSION)) else TCL_INCLUDE ?= /usr/include/$(TCL_VERSION) TCL_LIBS ?= -l$(TCL_VERSION) endif 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 ifeq ($(ENABLE_GCOV),1) CXXFLAGS += --coverage LINKFLAGS += --coverage endif ifeq ($(ENABLE_GPROF),1) CXXFLAGS += -pg LINKFLAGS += -pg endif ifeq ($(ENABLE_DEBUG),1) CXXFLAGS := -Og -DDEBUG $(filter-out $(OPT_LEVEL),$(CXXFLAGS)) STRIP := endif ifeq ($(ENABLE_THREADS),1) CXXFLAGS += -DYOSYS_ENABLE_THREADS LIBS += -lpthread endif ifeq ($(ENABLE_ABC),1) CXXFLAGS += -DYOSYS_ENABLE_ABC ifeq ($(LINK_ABC),1) CXXFLAGS += -DYOSYS_LINK_ABC ifeq ($(DISABLE_ABC_THREADS),0) LIBS += -lpthread endif else ifeq ($(ABCEXTERNAL),) 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 ifeq ($(ENABLE_GHDL),1) GHDL_PREFIX ?= $(PREFIX) GHDL_INCLUDE_DIR ?= $(GHDL_PREFIX)/include GHDL_LIB_DIR ?= $(GHDL_PREFIX)/lib CXXFLAGS += -I$(GHDL_INCLUDE_DIR) -DYOSYS_ENABLE_GHDL LIBS += $(GHDL_LIB_DIR)/libghdl.a $(file <$(GHDL_LIB_DIR)/libghdl.link) endif LIBS_VERIFIC = ifeq ($(ENABLE_VERIFIC),1) VERIFIC_DIR ?= /usr/local/src/verific_lib 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 ifneq ($(wildcard $(VERIFIC_DIR)/vhdl),) VERIFIC_COMPONENTS += vhdl endif endif ifeq ($(ENABLE_VERIFIC_EDIF),1) VERIFIC_COMPONENTS += edif CXXFLAGS += -DVERIFIC_EDIF_SUPPORT endif ifeq ($(ENABLE_VERIFIC_LIBERTY),1) VERIFIC_COMPONENTS += synlib CXXFLAGS += -DVERIFIC_LIBERTY_SUPPORT endif 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) LIBS_VERIFIC += $(foreach comp,$(patsubst %,$(VERIFIC_DIR)/%/*-mac.a,$(VERIFIC_COMPONENTS)),-Wl,-force_load $(comp)) -lz else LIBS_VERIFIC += -Wl,--whole-archive $(patsubst %,$(VERIFIC_DIR)/%/*-linux.a,$(VERIFIC_COMPONENTS)) -Wl,--no-whole-archive -lz endif endif ifeq ($(ENABLE_CCACHE),1) CXX := ccache $(CXX) else ifeq ($(ENABLE_SCCACHE),1) CXX := sccache $(CXX) endif endif define add_share_file EXTRA_TARGETS += $(subst //,/,$(1)/$(notdir $(2))) $(subst //,/,$(1)/$(notdir $(2))): $(2) $$(P) mkdir -p $(1) $$(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) $$(P) mkdir -p $(1) $$(Q) cp $(2) $(subst //,/,$(1)/$(notdir $(2))) endef define add_include_file $(eval $(call add_share_file,$(dir share/include/$(1)),$(1))) endef define add_extra_objs EXTRA_OBJS += $(1) .SECONDARY: $(1) endef ifeq ($(PRETTY), 1) P_STATUS = 0 P_OFFSET = 0 P_UPDATE = $(eval P_STATUS=$(shell echo $(OBJS) $(PROGRAM_PREFIX)yosys$(EXE) | $(AWK) 'BEGIN { RS = " "; I = $(P_STATUS)+0; } $$1 == "$@" && NR > I { I = NR; } END { print I; }')) P_SHOW = [$(shell $(AWK) "BEGIN { N=$(words $(OBJS) $(PROGRAM_PREFIX)yosys$(EXE)); printf \"%3d\", $(P_OFFSET)+90*$(P_STATUS)/N; exit; }")%] P = @echo "$(if $(findstring $@,$(TARGETS) $(EXTRA_TARGETS)),$(eval P_OFFSET = 10))$(call P_UPDATE)$(call P_SHOW) Building $@"; Q = @ S = -s else P_SHOW = -> P = Q = 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/newcelltypes.h)) $(eval $(call add_include_file,kernel/consteval.h)) $(eval $(call add_include_file,kernel/constids.inc)) $(eval $(call add_include_file,kernel/cost.h)) $(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)) $(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)) $(eval $(call add_include_file,kernel/modtools.h)) $(eval $(call add_include_file,kernel/mem.h)) $(eval $(call add_include_file,kernel/qcsat.h)) $(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)) $(eval $(call add_include_file,libs/ezsat/ezcmdline.h)) ifeq ($(ENABLE_ZLIB),1) $(eval $(call add_include_file,libs/fst/fstapi.h)) 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,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 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 ifeq ($(ENABLE_PLUGINS),1) ifeq ($(OS), MINGW) OBJS += libs/dlfcn-win32/dlfcn.o endif endif kernel/log.o: CXXFLAGS += -DYOSYS_SRC='"$(YOSYS_SRC)"' kernel/yosys.o: CXXFLAGS += -DYOSYS_DATDIR='"$(DATDIR)"' -DYOSYS_PROGRAM_PREFIX='"$(PROGRAM_PREFIX)"' ifeq ($(ENABLE_ABC),1) ifneq ($(ABCEXTERNAL),) kernel/yosys.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"' endif endif OBJS += libs/bigint/BigIntegerAlgorithms.o libs/bigint/BigInteger.o libs/bigint/BigIntegerUtils.o OBJS += libs/bigint/BigUnsigned.o libs/bigint/BigUnsignedInABase.o OBJS += libs/sha1/sha1.o OBJS += libs/json11/json11.o OBJS += libs/ezsat/ezsat.o OBJS += libs/ezsat/ezminisat.o OBJS += libs/ezsat/ezcmdline.o OBJS += libs/minisat/Options.o OBJS += libs/minisat/SimpSolver.o OBJS += libs/minisat/Solver.o OBJS += libs/minisat/System.o ifeq ($(ENABLE_ZLIB),1) OBJS += libs/fst/fstapi.o 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 include $(YOSYS_SRC)/frontends/*/Makefile.inc include $(YOSYS_SRC)/passes/*/Makefile.inc include $(YOSYS_SRC)/backends/*/Makefile.inc include $(YOSYS_SRC)/techlibs/*/Makefile.inc else include $(YOSYS_SRC)/frontends/verilog/Makefile.inc ifeq ($(ENABLE_VERIFIC),1) include $(YOSYS_SRC)/frontends/verific/Makefile.inc endif include $(YOSYS_SRC)/frontends/rtlil/Makefile.inc include $(YOSYS_SRC)/frontends/ast/Makefile.inc include $(YOSYS_SRC)/frontends/blif/Makefile.inc OBJS += passes/hierarchy/hierarchy.o OBJS += passes/cmds/select.o OBJS += passes/cmds/show.o OBJS += passes/cmds/stat.o OBJS += passes/cmds/design.o OBJS += passes/cmds/plugin.o include $(YOSYS_SRC)/passes/proc/Makefile.inc include $(YOSYS_SRC)/passes/opt/Makefile.inc include $(YOSYS_SRC)/passes/techmap/Makefile.inc include $(YOSYS_SRC)/backends/verilog/Makefile.inc include $(YOSYS_SRC)/backends/rtlil/Makefile.inc include $(YOSYS_SRC)/techlibs/common/Makefile.inc endif ifeq ($(LINK_ABC),1) OBJS += $(PROGRAM_PREFIX)yosys-libabc.a endif # prevent the CXXFLAGS set by this Makefile from reaching abc/Makefile, # especially the -MD flag which will break the build when CXX is clang unexport CXXFLAGS top-all: $(TARGETS) $(EXTRA_TARGETS) @echo "" @echo " Build successful." @echo "" .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) $(EXE_LIBS) $(LIBS) $(LIBS_VERIFIC) libyosys.so: $(filter-out kernel/driver.o,$(OBJS)) ifeq ($(OS), Darwin) $(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,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) $< %.pyh: %.h $(Q) mkdir -p $(dir $@) $(P) cat $< | grep -E -v "#[ ]*(include|error)" | $(CXX) $(CXXFLAGS) -x c++ -o $@ -E -P - ifeq ($(ENABLE_PYOSYS),1) $(PY_WRAPPER_FILE).cc: $(PY_GEN_SCRIPT) pyosys/wrappers_tpl.cc $(PY_WRAP_INCLUDES) pyosys/hashlib.h $(Q) mkdir -p $(dir $@) $(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_REPO := ifneq (, $(shell command -v git 2>/dev/null)) ifneq (, $(shell git rev-parse --git-dir 2>/dev/null)) GIT_REMOTE := $(strip $(shell git config --get remote.origin.url 2>/dev/null | $(AWK) '{print tolower($$0)}')) ifneq ($(strip $(GIT_REMOTE)),) YOSYS_REPO := $(strip $(shell echo $(GIT_REMOTE) | $(AWK) -F '[:/]' '{gsub(/\.git$$/, "", $$NF); printf "%s/%s", $$(NF-1), $$NF}')) endif ifeq ($(strip $(YOSYS_REPO)),yosyshq/yosys) YOSYS_REPO := endif GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) ifeq ($(filter main HEAD release/v%,$(GIT_BRANCH)),) YOSYS_REPO := $(YOSYS_REPO) at $(GIT_BRANCH) endif YOSYS_REPO := $(strip $(YOSYS_REPO)) endif endif YOSYS_GIT_STR := $(GIT_REV)$(GIT_DIRTY) YOSYS_COMPILER := $(notdir $(CXX)) $(shell $(CXX) --version | tr ' ()' '\n' | grep '^[0-9]' | head -n1) $(filter -f% -m% -O% -DNDEBUG,$(CXXFLAGS)) YOSYS_VER_STR := Yosys $(YOSYS_VER) (git sha1 $(YOSYS_GIT_STR), $(YOSYS_COMPILER)) ifneq ($(strip $(YOSYS_REPO)),) YOSYS_VER_STR := $(YOSYS_VER_STR) [$(YOSYS_REPO)] endif 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)\"; 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))) LIBS_NOVERIFIC = $(foreach v,$(LIBS),$(if $(findstring $(VERIFIC_DIR),$(v)),,$(v))) else CXXFLAGS_NOVERIFIC = $(CXXFLAGS) LIBS_NOVERIFIC = $(LIBS) endif $(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 .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) $(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) $(PROGRAM_PREFIX)yosys-abc$(EXE): abc/abc$(EXE) $(P) cp $< $(PROGRAM_PREFIX)yosys-abc$(EXE) $(PROGRAM_PREFIX)yosys-libabc.a: abc/libabc.a $(P) cp $< $(PROGRAM_PREFIX)yosys-libabc.a ifneq ($(SEED),) SEEDOPT="-S $(SEED)" else SEEDOPT="" endif ifneq ($(ABCEXTERNAL),) ABCOPT="-A $(ABCEXTERNAL)" else ABCOPT="" endif test: vanilla-test unit-test .PHONY: vanilla-test vanilla-test: $(TARGETS) $(EXTRA_TARGETS) @$(MAKE) -C tests vanilla-test \ $(if $(ENABLE_VERIFIC),ENABLE_VERIFIC=$(ENABLE_VERIFIC)) \ $(if $(YOSYS_NOVERIFIC),YOSYS_NOVERIFIC=$(YOSYS_NOVERIFIC)) \ SEEDOPT=$(SEEDOPT) ABCOPT=$(ABCOPT) VALGRIND ?= valgrind --error-exitcode=1 --leak-check=full --show-reachable=yes --errors-for-leak-kinds=all vgtest: $(TARGETS) $(EXTRA_TARGETS) $(VALGRIND) ./yosys -p 'setattr -mod -unset top; synth' $$( ls tests/simple/*.v | grep -v repwhile.v ) @echo "" @echo " Passed \"make vgtest\"." @echo "" vloghtb: $(TARGETS) $(EXTRA_TARGETS) +cd tests/vloghtb && bash run-test.sh @echo "" @echo " Passed \"make vloghtb\"." @echo "" ystests: $(TARGETS) $(EXTRA_TARGETS) rm -rf tests/ystests git clone https://github.com/YosysHQ/yosys-tests.git tests/ystests +$(MAKE) PATH="$$PWD:$$PATH" -C tests/ystests @echo "" @echo " Finished \"make ystests\"." @echo "" # Unit test unit-test: libyosys.so @$(MAKE) -f $(UNITESTPATH)/Makefile CXX="$(CXX)" CC="$(CC)" CPPFLAGS="$(CPPFLAGS)" \ CXXFLAGS="$(CXXFLAGS)" LINKFLAGS="$(LINKFLAGS)" LIBS="$(LIBS)" ROOTPATH="$(CURDIR)" clean-unit-test: @$(MAKE) -f $(UNITESTPATH)/Makefile 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 libyosys.a,$(TARGETS)) $(DESTDIR)$(BINDIR) ifneq ($(filter $(PROGRAM_PREFIX)yosys$(EXE),$(TARGETS)),) if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys$(EXE); fi endif ifneq ($(filter $(PROGRAM_PREFIX)yosys-abc$(EXE),$(TARGETS)),) if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-abc$(EXE); fi endif ifneq ($(filter $(PROGRAM_PREFIX)yosys-filterlib$(EXE),$(TARGETS)),) if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys-filterlib$(EXE); fi endif $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(DATDIR) $(INSTALL_SUDO) cp -r share/. $(DESTDIR)$(DATDIR)/. ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(LIBDIR) $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(LIBDIR)/ 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 -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) ifeq ($(OS), MINGW) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(LIBDIR) $(INSTALL_SUDO) cp libyosys_exe.a $(DESTDIR)$(LIBDIR)/ endif endif uninstall: $(INSTALL_SUDO) rm -vf $(addprefix $(DESTDIR)$(BINDIR)/,$(notdir $(TARGETS))) $(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 $(INSTALL_SUDO) rmdir $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys endif endif docs/source/generated/cmds.json: docs/source/generated $(TARGETS) $(EXTRA_TARGETS) $(Q) ./$(PROGRAM_PREFIX)yosys -p 'help -dump-cmds-json $@' docs/source/generated/cells.json: docs/source/generated $(TARGETS) $(EXTRA_TARGETS) $(Q) ./$(PROGRAM_PREFIX)yosys -p 'help -dump-cells-json $@' 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/prep $(Q) $(MAKE) -C docs $(DOC_TARGET) clean: clean-py clean-unit-test rm -rf share 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 vloghtb/Makefile vloghtb/refdat vloghtb/rtl vloghtb/scripts vloghtb/spec vloghtb/check_yosys vloghtb/vloghammer_tb.tar.bz2 vloghtb/temp vloghtb/log_test_* -$(MAKE) -C $(YOSYS_SRC)/tests clean -$(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 $(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 coverage: ./$(PROGRAM_PREFIX)yosys -qp 'help; help -all' rm -rf coverage.info coverage_html 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.creator 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++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 config-clean: clean rm -f Makefile.conf config-clang: clean echo 'CONFIG := clang' > Makefile.conf config-gcc: clean echo 'CONFIG := gcc' > Makefile.conf config-gcc-static: clean echo 'CONFIG := gcc-static' > Makefile.conf echo 'ENABLE_PLUGINS := 0' >> Makefile.conf echo 'ENABLE_READLINE := 0' >> Makefile.conf echo 'ENABLE_TCL := 0' >> Makefile.conf config-wasi: clean echo 'CONFIG := wasi' > 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-msys2-32: clean echo 'CONFIG := msys2-32' > Makefile.conf echo "PREFIX := $(MINGW_PREFIX)" >> Makefile.conf config-msys2-64: clean echo 'CONFIG := msys2-64' > Makefile.conf echo "PREFIX := $(MINGW_PREFIX)" >> Makefile.conf config-gcov: clean echo 'CONFIG := gcc' > Makefile.conf echo 'ENABLE_GCOV := 1' >> Makefile.conf echo 'ENABLE_DEBUG := 1' >> Makefile.conf config-gprof: clean echo 'CONFIG := gcc' > Makefile.conf echo 'ENABLE_GPROF := 1' >> Makefile.conf config-sudo: echo "INSTALL_SUDO := sudo" >> Makefile.conf echo-yosys-ver: @echo "$(YOSYS_VER)" echo-git-rev: @echo "$(GIT_REV)" echo-cxx: @echo "$(CXX)" -include libs/*/*.d -include frontends/*/*.d -include passes/*/*.d -include backends/*/*.d -include kernel/*.d -include techlibs/*/*.d 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 yosys-0.65/README.md000066400000000000000000000224371520057232300141200ustar00rootroot00000000000000yosys – Yosys Open SYnthesis Suite =================================== This is a framework for RTL synthesis tools. It currently has extensive Verilog-2005 support and provides a basic set of synthesis algorithms for various application domains. Yosys can be adapted to perform any synthesis job by combining the existing passes (algorithms) using synthesis scripts and adding additional passes as needed by extending the yosys C++ code base. 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 ============================ More information and documentation can be found on the Yosys web site: - https://yosyshq.net/yosys/ If you have any Yosys-related questions, please post them on the Discourse group: - https://yosyshq.discourse.group 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, 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 ============ Yosys is part of the [Tabby CAD Suite](https://www.yosyshq.com/tabby-cad-datasheet) and the [OSS CAD Suite](https://github.com/YosysHQ/oss-cad-suite-build)! The easiest way to use yosys is to install the binary software suite, which contains all required dependencies and related tools. * [Contact YosysHQ](https://www.yosyshq.com/contact) for a [Tabby CAD Suite](https://www.yosyshq.com/tabby-cad-datasheet) Evaluation License and download link * OR go to https://github.com/YosysHQ/oss-cad-suite-build/releases to download the free OSS CAD Suite * Follow the [Install Instructions on GitHub](https://github.com/YosysHQ/oss-cad-suite-build#installation) Make sure to get a Tabby CAD Suite Evaluation License if you need features such as industry-grade SystemVerilog and VHDL parsers! For more information about the difference between Tabby CAD Suite and the OSS CAD Suite, please visit https://www.yosyshq.com/tabby-cad-datasheet Many Linux distributions also provide Yosys binaries, some more up to date than others. Check with your package manager! Building from Source ==================== 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 22.04 LTS the following commands will install all prerequisites for building yosys: $ 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 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 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 To build Yosys simply type 'make' in this directory. $ make $ sudo make install Tests are located in the tests subdirectory and can be executed using the test target. Note that you need gawk, a recent version of iverilog, and gtest. Execute tests via: $ make test To use a separate (out-of-tree) build directory, provide a path to the Makefile. $ mkdir build; cd build $ make -f ../Makefile Out-of-tree builds require a clean source tree. Getting Started =============== Yosys can be used with the interactive command shell, with synthesis scripts or with command line arguments. Let's perform a simple synthesis job using the interactive command shell: $ ./yosys yosys> the command ``help`` can be used to print a list of all available commands and ``help `` to print details on the specified command: yosys> help help reading and elaborating the design using the Verilog frontend: yosys> read -sv tests/simple/fiedler-cooley.v yosys> hierarchy -top up3down5 writing the design to the console in the RTLIL format used by Yosys internally: yosys> write_rtlil convert processes (``always`` blocks) to netlist elements and perform some simple optimizations: yosys> proc; opt display design netlist using ``xdot``: yosys> show the same thing using ``gv`` as postscript viewer: yosys> show -format ps -viewer gv translating netlist to gate logic and perform some simple optimizations: yosys> techmap; opt write design netlist to a new Verilog file: yosys> write_verilog synth.v or using a simple synthesis script: $ cat synth.ys read -sv tests/simple/fiedler-cooley.v hierarchy -top up3down5 proc; opt; techmap; opt write_verilog synth.v $ ./yosys synth.ys If ABC is enabled in the Yosys build configuration and a cell library is given in the liberty file ``mycells.lib``, the following synthesis script will synthesize for the given cell library: # read design read -sv tests/simple/fiedler-cooley.v hierarchy -top up3down5 # the high-level stuff proc; fsm; opt; memory; opt # mapping to internal cell library techmap; opt # mapping flip-flops to mycells.lib dfflibmap -liberty mycells.lib # mapping logic to mycells.lib abc -liberty mycells.lib # cleanup clean If you do not have a liberty file but want to test this synthesis script, you can use the file ``examples/cmos/cmos_cells.lib`` from the yosys sources as simple example. Liberty file downloads for and information about free and open ASIC standard cell libraries can be found here: - http://www.vlsitechnology.org/html/libraries.html - http://www.vlsitechnology.org/synopsys/vsclib013.lib The command ``synth`` provides a good default synthesis script (see ``help synth``): read -sv tests/simple/fiedler-cooley.v synth -top up3down5 # mapping to target cells dfflibmap -liberty mycells.lib abc -liberty mycells.lib clean The command ``prep`` provides a good default word-level synthesis script, as used in SMT-based formal verification. 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 ========================== 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. If you're offline, you can read the sources, replacing `.../en/latest` with `docs/source`. In addition to those packages listed above for building Yosys from source, the following are used for building the website: $ 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. 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`: $ pip install -U sphinx -r docs/source/requirements.txt 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 `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. yosys-0.65/abc/000077500000000000000000000000001520057232300133565ustar00rootroot00000000000000yosys-0.65/backends/000077500000000000000000000000001520057232300144035ustar00rootroot00000000000000yosys-0.65/backends/aiger/000077500000000000000000000000001520057232300154725ustar00rootroot00000000000000yosys-0.65/backends/aiger/Makefile.inc000066400000000000000000000001011520057232300176720ustar00rootroot00000000000000 OBJS += backends/aiger/aiger.o OBJS += backends/aiger/xaiger.o yosys-0.65/backends/aiger/aiger.cc000066400000000000000000000705621520057232300171020ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/json.h" #include "kernel/yw.h" #include "libs/json11/json11.hpp" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN void aiger_encode(std::ostream &f, int x) { log_assert(x >= 0); while (x & ~0x7f) { f.put((x & 0x7f) | 0x80); x = x >> 7; } f.put(x); } struct AigerWriter { Module *module; bool zinit_mode; SigMap sigmap; dict init_map; pool input_bits, output_bits; dict not_map, ff_map, alias_map; dict> and_map; vector> asserts, assumes; vector> liveness, fairness; pool initstate_bits; vector> aig_gates; vector aig_latchin, aig_latchinit, aig_outputs; vector bit2aig_stack; size_t next_loop_check = 1024; int aig_m = 0, aig_i = 0, aig_l = 0, aig_o = 0, aig_a = 0; int aig_b = 0, aig_c = 0, aig_j = 0, aig_f = 0; dict aig_map; dict ordered_outputs; dict ordered_latches; dict init_inputs; int initstate_ff = 0; dict ywmap_clocks; vector ywmap_asserts; vector ywmap_assumes; int mkgate(int a0, int a1) { aig_m++, aig_a++; aig_gates.push_back(a0 > a1 ? make_pair(a0, a1) : make_pair(a1, a0)); return 2*aig_m; } int bit2aig(SigBit bit) { auto it = aig_map.find(bit); if (it != aig_map.end()) { log_assert(it->second >= 0); return it->second; } if (bit2aig_stack.size() == next_loop_check) { for (size_t i = 0; i < next_loop_check; ++i) { SigBit report_bit = bit2aig_stack[i]; if (report_bit != bit) continue; for (size_t 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 combinational 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 int a = -1; if (not_map.count(bit)) { a = bit2aig(not_map.at(bit)) ^ 1; } else if (and_map.count(bit)) { auto args = and_map.at(bit); int a0 = bit2aig(args.first); int a1 = bit2aig(args.second); a = mkgate(a0, a1); } else if (alias_map.count(bit)) { a = bit2aig(alias_map.at(bit)); } else if (initstate_bits.count(bit)) { a = initstate_ff; } bit2aig_stack.pop_back(); if (bit == State::Sx || bit == State::Sz) log_error("Design contains 'x' or 'z' bits. Use 'setundef' to replace those constants.\n"); log_assert(a >= 0); aig_map[bit] = a; return a; } 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; // promote public wires for (auto wire : module->wires()) if (wire->name.isPublic()) sigmap.add(wire); // promote output wires for (auto wire : module->wires()) if (wire->port_output) sigmap.add(wire); // promote input wires for (auto wire : module->wires()) 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)) { SigSpec initsig = sigmap(wire); Const initval = wire->attributes.at(ID::init); for (int i = 0; i < GetSize(wire) && i < GetSize(initval); i++) if (initval[i] == State::S0 || initval[i] == State::S1) init_map[initsig[i]] = initval[i] == State::S1; } for (int i = 0; i < GetSize(wire); i++) { SigBit wirebit(wire, i); SigBit bit = sigmap(wirebit); if (bit.wire == nullptr) continue; if (wire->port_input || wire->port_output) continue; undriven_bits.insert(bit); unused_bits.insert(bit); } if (wire->width == 1) { auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk); if (gclk_attr != wire->attributes.end()) { SigBit bit = sigmap(wire); if (gclk_attr->second == State::S1) ywmap_clocks[bit] |= 1; else if (gclk_attr->second == State::S0) ywmap_clocks[bit] |= 2; } } } for (auto cell : module->cells()) { if (cell->type == ID($_NOT_)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit Y = sigmap(cell->getPort(ID::Y).as_bit()); unused_bits.erase(A); undriven_bits.erase(Y); not_map[Y] = A; continue; } if (cell->type.in(ID($_FF_), ID($_DFF_N_), ID($_DFF_P_))) { SigBit D = sigmap(cell->getPort(ID::D).as_bit()); SigBit Q = sigmap(cell->getPort(ID::Q).as_bit()); unused_bits.erase(D); undriven_bits.erase(Q); ff_map[Q] = D; if (cell->type != ID($_FF_)) { auto sig_clk = sigmap(cell->getPort(ID::C).as_bit()); ywmap_clocks[sig_clk] |= cell->type == ID($_DFF_N_) ? 2 : 1; } continue; } if (cell->type == ID($anyinit)) { auto sig_d = sigmap(cell->getPort(ID::D)); auto sig_q = sigmap(cell->getPort(ID::Q)); for (int i = 0; i < sig_d.size(); i++) { undriven_bits.erase(sig_q[i]); ff_map[sig_q[i]] = sig_d[i]; } continue; } if (cell->type == ID($_AND_)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit B = sigmap(cell->getPort(ID::B).as_bit()); SigBit Y = sigmap(cell->getPort(ID::Y).as_bit()); unused_bits.erase(A); unused_bits.erase(B); undriven_bits.erase(Y); and_map[Y] = make_pair(A, B); continue; } if (cell->type == ID($initstate)) { SigBit Y = sigmap(cell->getPort(ID::Y).as_bit()); undriven_bits.erase(Y); initstate_bits.insert(Y); continue; } if (cell->type == ID($assert)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit EN = sigmap(cell->getPort(ID::EN).as_bit()); unused_bits.erase(A); unused_bits.erase(EN); asserts.push_back(make_pair(A, EN)); ywmap_asserts.push_back(cell); continue; } if (cell->type == ID($assume)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit EN = sigmap(cell->getPort(ID::EN).as_bit()); unused_bits.erase(A); unused_bits.erase(EN); assumes.push_back(make_pair(A, EN)); ywmap_assumes.push_back(cell); continue; } if (cell->type == ID($live)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit EN = sigmap(cell->getPort(ID::EN).as_bit()); unused_bits.erase(A); unused_bits.erase(EN); liveness.push_back(make_pair(A, EN)); continue; } if (cell->type == ID($fair)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit EN = sigmap(cell->getPort(ID::EN).as_bit()); unused_bits.erase(A); unused_bits.erase(EN); fairness.push_back(make_pair(A, EN)); continue; } if (cell->type == ID($anyconst)) { for (auto bit : sigmap(cell->getPort(ID::Y))) { undriven_bits.erase(bit); ff_map[bit] = bit; } continue; } if (cell->type == ID($anyseq)) { for (auto bit : sigmap(cell->getPort(ID::Y))) { undriven_bits.erase(bit); input_bits.insert(bit); } continue; } if (cell->type == ID($scopeinfo)) continue; log_error("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell)); } for (auto bit : unused_bits) undriven_bits.erase(bit); if (!undriven_bits.empty()) { undriven_bits.sort(); for (auto bit : undriven_bits) { log_warning("Treating undriven bit %s.%s like $anyseq.\n", log_id(module), log_signal(bit)); input_bits.insert(bit); } log_warning("Treating a total of %d undriven bits in %s like $anyseq.\n", GetSize(undriven_bits), log_id(module)); } init_map.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(); aig_map[State::S0] = 0; aig_map[State::S1] = 1; for (auto bit : input_bits) { aig_m++, aig_i++; aig_map[bit] = 2*aig_m; } if (imode && input_bits.empty()) { aig_m++, aig_i++; } if (zinit_mode) { for (auto it : ff_map) { if (init_map.count(it.first)) continue; aig_m++, aig_i++; init_inputs[it.first] = 2*aig_m; } } int fair_live_inputs_cnt = GetSize(liveness); int fair_live_inputs_m = aig_m; aig_m += fair_live_inputs_cnt; aig_i += fair_live_inputs_cnt; for (auto it : ff_map) { aig_m++, aig_l++; aig_map[it.first] = 2*aig_m; ordered_latches[it.first] = aig_l-1; if (init_map.count(it.first) == 0) aig_latchinit.push_back(2); else aig_latchinit.push_back(init_map.at(it.first) ? 1 : 0); } if (!initstate_bits.empty() || !init_inputs.empty()) { aig_m++, aig_l++; initstate_ff = 2*aig_m+1; aig_latchinit.push_back(0); } int fair_live_latches_cnt = GetSize(fairness) + 2*GetSize(liveness); int fair_live_latches_m = aig_m; int fair_live_latches_l = aig_l; aig_m += fair_live_latches_cnt; aig_l += fair_live_latches_cnt; for (int i = 0; i < fair_live_latches_cnt; i++) aig_latchinit.push_back(0); if (zinit_mode) { for (auto it : ff_map) { int l = ordered_latches[it.first]; if (aig_latchinit.at(l) == 1) aig_map[it.first] ^= 1; if (aig_latchinit.at(l) == 2) { int gated_ffout = mkgate(aig_map[it.first], initstate_ff^1); int gated_initin = mkgate(init_inputs[it.first], initstate_ff); aig_map[it.first] = mkgate(gated_ffout^1, gated_initin^1)^1; } } } for (auto it : ff_map) { int a = bit2aig(it.second); int l = ordered_latches[it.first]; if (zinit_mode && aig_latchinit.at(l) == 1) aig_latchin.push_back(a ^ 1); else aig_latchin.push_back(a); } if (lmode && aig_l == 0) { aig_m++, aig_l++; aig_latchinit.push_back(0); aig_latchin.push_back(0); } if (!initstate_bits.empty() || !init_inputs.empty()) aig_latchin.push_back(1); for (auto bit : output_bits) { aig_o++; ordered_outputs[bit] = aig_o-1; aig_outputs.push_back(bit2aig(bit)); } if (omode && output_bits.empty()) { aig_o++; aig_outputs.push_back(0); } for (auto it : asserts) { aig_b++; int bit_a = bit2aig(it.first); int bit_en = bit2aig(it.second); aig_outputs.push_back(mkgate(bit_a^1, bit_en)); } if (bmode && asserts.empty()) { aig_b++; aig_outputs.push_back(0); } for (auto it : assumes) { aig_c++; int bit_a = bit2aig(it.first); int bit_en = bit2aig(it.second); aig_outputs.push_back(mkgate(bit_a^1, bit_en)^1); } for (auto it : liveness) { int input_m = ++fair_live_inputs_m; int latch_m1 = ++fair_live_latches_m; int latch_m2 = ++fair_live_latches_m; log_assert(GetSize(aig_latchin) == fair_live_latches_l); fair_live_latches_l += 2; int bit_a = bit2aig(it.first); int bit_en = bit2aig(it.second); int bit_s = 2*input_m; int bit_q1 = 2*latch_m1; int bit_q2 = 2*latch_m2; int bit_d1 = mkgate(mkgate(bit_s, bit_en)^1, bit_q1^1)^1; int bit_d2 = mkgate(mkgate(bit_d1, bit_a)^1, bit_q2^1)^1; aig_j++; aig_latchin.push_back(bit_d1); aig_latchin.push_back(bit_d2); aig_outputs.push_back(mkgate(bit_q1, bit_q2^1)); } for (auto it : fairness) { int latch_m = ++fair_live_latches_m; log_assert(GetSize(aig_latchin) == fair_live_latches_l); fair_live_latches_l += 1; int bit_a = bit2aig(it.first); int bit_en = bit2aig(it.second); int bit_q = 2*latch_m; aig_f++; aig_latchin.push_back(mkgate(mkgate(bit_q^1, bit_en^1)^1, bit_a^1)); aig_outputs.push_back(bit_q^1); } } void write_aiger(std::ostream &f, bool ascii_mode, bool miter_mode, bool symbols_mode) { int aig_obc = aig_o + aig_b + aig_c; int aig_obcj = aig_obc + aig_j; int aig_obcjf = aig_obcj + aig_f; log_assert(aig_m == aig_i + aig_l + aig_a); log_assert(aig_l == GetSize(aig_latchin)); log_assert(aig_l == GetSize(aig_latchinit)); log_assert(aig_obcjf == GetSize(aig_outputs)); if (miter_mode) { if (aig_b || aig_c || aig_j || aig_f) log_error("Running AIGER back-end in -miter mode, but design contains $assert, $assume, $live and/or $fair cells!\n"); f << stringf("%s %d %d %d 0 %d %d\n", ascii_mode ? "aag" : "aig", aig_m, aig_i, aig_l, aig_a, aig_o); } else { f << stringf("%s %d %d %d %d %d", ascii_mode ? "aag" : "aig", aig_m, aig_i, aig_l, aig_o, aig_a); if (aig_b || aig_c || aig_j || aig_f) f << stringf(" %d %d %d %d", aig_b, aig_c, aig_j, aig_f); f << stringf("\n"); } if (ascii_mode) { for (int i = 0; i < aig_i; i++) f << stringf("%d\n", 2*i+2); for (int i = 0; i < aig_l; i++) { if (zinit_mode || aig_latchinit.at(i) == 0) f << stringf("%d %d\n", 2*(aig_i+i)+2, aig_latchin.at(i)); else if (aig_latchinit.at(i) == 1) f << stringf("%d %d 1\n", 2*(aig_i+i)+2, aig_latchin.at(i)); else if (aig_latchinit.at(i) == 2) f << stringf("%d %d %d\n", 2*(aig_i+i)+2, aig_latchin.at(i), 2*(aig_i+i)+2); } for (int i = 0; i < aig_obc; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("1\n"); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obcj; i < aig_obcjf; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = 0; i < aig_a; i++) f << stringf("%d %d %d\n", 2*(aig_i+aig_l+i)+2, aig_gates.at(i).first, aig_gates.at(i).second); } else { for (int i = 0; i < aig_l; i++) { if (zinit_mode || aig_latchinit.at(i) == 0) f << stringf("%d\n", aig_latchin.at(i)); else if (aig_latchinit.at(i) == 1) f << stringf("%d 1\n", aig_latchin.at(i)); else if (aig_latchinit.at(i) == 2) f << stringf("%d %d\n", aig_latchin.at(i), 2*(aig_i+i)+2); } for (int i = 0; i < aig_obc; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("1\n"); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obcj; i < aig_obcjf; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = 0; i < aig_a; i++) { int lhs = 2*(aig_i+aig_l+i)+2; int rhs0 = aig_gates.at(i).first; int rhs1 = aig_gates.at(i).second; int delta0 = lhs - rhs0; int delta1 = rhs0 - rhs1; aiger_encode(f, delta0); aiger_encode(f, delta1); } } if (symbols_mode) { dict> symbols; for (auto wire : module->wires()) { if (wire->name[0] == '$') continue; SigSpec sig = sigmap(wire); for (int i = 0; i < GetSize(wire); i++) { if (sig[i].wire == nullptr) { if (wire->port_output) sig[i] = SigBit(wire, i); else continue; } if (wire->port_input) { int a = aig_map.at(sig[i]); log_assert((a & 1) == 0); if (GetSize(wire) != 1) symbols[stringf("i%d", (a >> 1)-1)].push_back(stringf("%s[%d]", log_id(wire), i)); else symbols[stringf("i%d", (a >> 1)-1)].push_back(stringf("%s", log_id(wire))); } if (wire->port_output) { int o = ordered_outputs.at(SigSpec(wire, i)); if (GetSize(wire) != 1) symbols[stringf("%c%d", miter_mode ? 'b' : 'o', o)].push_back(stringf("%s[%d]", log_id(wire), i)); else symbols[stringf("%c%d", miter_mode ? 'b' : 'o', o)].push_back(stringf("%s", log_id(wire))); } if (init_inputs.count(sig[i])) { int a = init_inputs.at(sig[i]); log_assert((a & 1) == 0); if (GetSize(wire) != 1) symbols[stringf("i%d", (a >> 1)-1)].push_back(stringf("init:%s[%d]", log_id(wire), i)); else symbols[stringf("i%d", (a >> 1)-1)].push_back(stringf("init:%s", log_id(wire))); } if (ordered_latches.count(sig[i])) { int l = ordered_latches.at(sig[i]); const char *p = (zinit_mode && (aig_latchinit.at(l) == 1)) ? "!" : ""; if (GetSize(wire) != 1) symbols[stringf("l%d", l)].push_back(stringf("%s%s[%d]", p, log_id(wire), i)); else symbols[stringf("l%d", l)].push_back(stringf("%s%s", p, log_id(wire))); } } } symbols.sort(); for (auto &sym : symbols) { f << sym.first; std::sort(sym.second.begin(), sym.second.end()); for (auto &s : sym.second) f << " " << s; f << std::endl; } } f << stringf("c\nGenerated by %s\n", yosys_maybe_version()); } void write_map(std::ostream &f, bool verbose_map, bool no_startoffset) { dict input_lines; dict init_lines; dict output_lines; dict latch_lines; dict wire_lines; for (auto wire : module->wires()) { if (!verbose_map && wire->name[0] == '$') continue; SigSpec sig = sigmap(wire); for (int i = 0; i < GetSize(wire); i++) { if (aig_map.count(sig[i]) == 0 || sig[i].wire == nullptr) continue; int a = aig_map.at(sig[i]); int index = no_startoffset ? i : (wire->start_offset+i); if (verbose_map) wire_lines[a] += stringf("wire %d %d %s\n", a, index, log_id(wire)); if (wire->port_input) { log_assert((a & 1) == 0); input_lines[a] += stringf("input %d %d %s\n", (a >> 1)-1, index, log_id(wire)); } if (wire->port_output) { int o = ordered_outputs.at(SigSpec(wire, i)); output_lines[o] += stringf("output %d %d %s\n", o, index, log_id(wire)); } if (init_inputs.count(sig[i])) { int a = init_inputs.at(sig[i]); log_assert((a & 1) == 0); init_lines[a] += stringf("init %d %d %s\n", (a >> 1)-1, index, log_id(wire)); } if (ordered_latches.count(sig[i])) { int l = ordered_latches.at(sig[i]); if (zinit_mode && (aig_latchinit.at(l) == 1)) latch_lines[l] += stringf("invlatch %d %d %s\n", l, index, log_id(wire)); else latch_lines[l] += stringf("latch %d %d %s\n", l, index, log_id(wire)); } } } input_lines.sort(); for (auto &it : input_lines) f << it.second; init_lines.sort(); for (auto &it : init_lines) f << it.second; output_lines.sort(); for (auto &it : output_lines) f << it.second; latch_lines.sort(); for (auto &it : latch_lines) f << it.second; if (initstate_ff) f << stringf("ninitff %d\n", ((initstate_ff >> 1)-1-aig_i)); wire_lines.sort(); for (auto &it : wire_lines) f << it.second; } void write_ywmap(PrettyJson &json) { json.begin_object(); json.entry("version", "Yosys Witness Aiger map"); json.entry("gennerator", yosys_maybe_version()); json.entry("latch_count", aig_l); json.entry("input_count", aig_i); dict clock_lines; dict input_lines; dict init_lines; dict seq_lines; for (auto cell : module->cells()) { if (cell->type.in(ID($_FF_), ID($_DFF_N_), ID($_DFF_P_), ID($anyinit), ID($anyconst), ID($anyseq))) { // Use sig_q to get the FF output name, but sig to lookup aiger bits auto sig_qy = cell->getPort(cell->type.in(ID($anyconst), ID($anyseq)) ? ID::Y : ID::Q); SigSpec sig = sigmap(sig_qy); if (cell->get_bool_attribute(ID(clk2fflogic))) sig_qy = cell->getPort(ID::D); // For a clk2fflogic $_FF_ the named signal is the D input not the Q output for (int i = 0; i < GetSize(sig_qy); i++) { if (sig_qy[i].wire == nullptr || sig[i].wire == nullptr) continue; auto wire = sig_qy[i].wire; if (init_inputs.count(sig[i])) { int a = init_inputs.at(sig[i]); log_assert((a & 1) == 0); init_lines[a] = json11::Json(json11::Json::object { { "path", witness_path(wire) }, { "input", (a >> 1) - 1 }, { "offset", sig_qy[i].offset }, }); } if (input_bits.count(sig[i])) { int a = aig_map.at(sig[i]); log_assert((a & 1) == 0); seq_lines[a] = json11::Json(json11::Json::object { { "path", witness_path(wire) }, { "input", (a >> 1) - 1 }, { "offset", sig_qy[i].offset }, }); } } } } for (auto wire : module->wires()) { SigSpec sig = sigmap(wire); if (wire->port_input) { auto path = witness_path(wire); for (int i = 0; i < GetSize(wire); i++) { if (aig_map.count(sig[i]) == 0 || sig[i].wire == nullptr) continue; int a = aig_map.at(sig[i]); log_assert((a & 1) == 0); input_lines[a] = json11::Json(json11::Json::object { { "path", path }, { "input", (a >> 1) - 1 }, { "offset", i }, }); if (ywmap_clocks.count(sig[i])) { int clock_mode = ywmap_clocks[sig[i]]; if (clock_mode != 3) { clock_lines[a] = json11::Json(json11::Json::object { { "path", path }, { "input", (a >> 1) - 1 }, { "offset", i }, { "edge", clock_mode == 1 ? "posedge" : "negedge" }, }); } } } } } json.name("clocks"); json.begin_array(); clock_lines.sort(); for (auto &it : clock_lines) json.value(it.second); json.end_array(); json.name("inputs"); json.begin_array(); input_lines.sort(); for (auto &it : input_lines) json.value(it.second); json.end_array(); json.name("seqs"); json.begin_array(); input_lines.sort(); for (auto &it : seq_lines) json.value(it.second); json.end_array(); json.name("inits"); json.begin_array(); input_lines.sort(); for (auto &it : init_lines) json.value(it.second); json.end_array(); json.name("asserts"); json.begin_array(); for (Cell *cell : ywmap_asserts) json.value(witness_path(cell)); json.end_array(); json.name("assumes"); json.begin_array(); for (Cell *cell : ywmap_assumes) json.value(witness_path(cell)); json.end_array(); json.end_object(); } }; struct AigerBackend : public Backend { AigerBackend() : Backend("aiger", "write design to AIGER file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_aiger [options] [filename]\n"); log("\n"); log("Write the current design to an AIGER file. The design must be flattened and\n"); log("must not contain any cell types except $_AND_, $_NOT_, simple FF types,\n"); log("$assert and $assume cells, and $initstate cells.\n"); log("\n"); log("$assert and $assume cells are converted to AIGER bad state properties and\n"); log("invariant constraints.\n"); log("\n"); log(" -ascii\n"); log(" write ASCII version of AIGER format\n"); log("\n"); log(" -zinit\n"); log(" convert FFs to zero-initialized FFs, adding additional inputs for\n"); log(" uninitialized FFs.\n"); log("\n"); log(" -miter\n"); log(" design outputs are AIGER bad state properties\n"); log("\n"); 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"); log(" -vmap \n"); log(" like -map, but more verbose\n"); log("\n"); log(" -no-startoffset\n"); log(" make indexes zero based, enable using map files with smt solvers.\n"); log("\n"); log(" -ywmap \n"); log(" write a map file for conversion to and from yosys witness traces,\n"); log(" also allows for mapping AIGER bad-state properties and invariant\n"); log(" constraints back to individual formal properties by name.\n"); log("\n"); log(" -I, -O, -B, -L\n"); log(" If the design contains no input/output/assert/flip-flop then create one\n"); log(" dummy input/output/bad_state-pin or latch to make the tools reading the\n"); log(" AIGER file happy.\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { bool ascii_mode = false; 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; bool bmode = false; bool lmode = false; bool no_startoffset = false; std::string map_filename; std::string yw_map_filename; log_header(design, "Executing AIGER backend.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-ascii") { ascii_mode = true; continue; } if (args[argidx] == "-zinit") { zinit_mode = true; continue; } if (args[argidx] == "-miter") { miter_mode = true; continue; } if (args[argidx] == "-symbols") { 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; } if (map_filename.empty() && args[argidx] == "-vmap" && argidx+1 < args.size()) { map_filename = args[++argidx]; verbose_map = true; continue; } if (yw_map_filename.empty() && args[argidx] == "-ywmap" && argidx+1 < args.size()) { yw_map_filename = args[++argidx]; continue; } if (args[argidx] == "-no-startoffset") { no_startoffset = true; continue; } if (args[argidx] == "-I") { imode = true; continue; } if (args[argidx] == "-O") { omode = true; continue; } if (args[argidx] == "-B") { bmode = true; continue; } if (args[argidx] == "-L") { lmode = true; continue; } break; } extra_args(f, filename, args, argidx, !ascii_mode); if (!yw_map_filename.empty() && !zinit_mode) log_error("Currently -ywmap requires -zinit.\n"); Module *top_module = design->top_module(); if (top_module == nullptr) log_error("Can't find top module in current design!\n"); if (!design->selected_whole_module(top_module)) log_cmd_error("Can't handle partially selected module %s!\n", log_id(top_module)); if (!top_module->processes.empty()) log_error("Found unmapped processes in module %s: unmapped processes are not supported in AIGER backend!\n", log_id(top_module)); 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, no_sort, zinit_mode, imode, omode, bmode, lmode); writer.write_aiger(*f, ascii_mode, miter_mode, symbols_mode); if (!map_filename.empty()) { rewrite_filename(filename); 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, strerror(errno)); writer.write_map(mapf, verbose_map, no_startoffset); } if (!yw_map_filename.empty()) { std::ofstream mapf; mapf.open(yw_map_filename.c_str(), std::ofstream::trunc); PrettyJson json; if (!json.write_to_file(yw_map_filename)) log_error("Can't open file `%s' for writing: %s\n", yw_map_filename, strerror(errno)); writer.write_ywmap(json); } } } AigerBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/aiger/xaiger.cc000066400000000000000000000571331520057232300172710ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * 2019 Eddie Hung * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/utils.h" #include "kernel/timinginfo.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN void aiger_encode(std::ostream &f, int x) { log_assert(x >= 0); while (x & ~0x7f) { f.put((x & 0x7f) | 0x80); x = x >> 7; } f.put(x); } struct XAigerWriter { Design *design; Module *module; SigMap sigmap; dict init_map; pool input_bits, output_bits; dict not_map, alias_map; dict> and_map; vector ci_bits, co_bits; vector ff_list; 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; dict aig_map; dict ordered_outputs; vector box_list; int mkgate(int a0, int a1) { aig_m++, aig_a++; aig_gates.push_back(a0 > a1 ? make_pair(a0, a1) : make_pair(a1, a0)); return 2*aig_m; } int bit2aig(SigBit bit) { auto it = aig_map.find(bit); if (it != aig_map.end()) { log_assert(it->second >= 0); 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 int a = -1; if (not_map.count(bit)) { a = bit2aig(not_map.at(bit)) ^ 1; } else if (and_map.count(bit)) { auto args = and_map.at(bit); int a0 = bit2aig(args.first); int a1 = bit2aig(args.second); a = mkgate(a0, a1); } else if (alias_map.count(bit)) { 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); } log_assert(a >= 0); aig_map[bit] = a; return a; } XAigerWriter(Module *module, bool dff_mode) : design(module->design), module(module), sigmap(module) { pool undriven_bits; pool unused_bits; // promote public wires for (auto wire : module->wires()) if (wire->name.isPublic()) sigmap.add(wire); // promote input wires for (auto wire : module->wires()) if (wire->port_input) sigmap.add(wire); // promote keep wires for (auto wire : module->wires()) if (wire->get_bool_attribute(ID::keep)) sigmap.add(wire); for (auto wire : module->wires()) { auto it = wire->attributes.find(ID::init); 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; } undriven_bits.insert(bit); unused_bits.insert(bit); if (wire->port_input) input_bits.insert(bit); bool keep = wire->get_bool_attribute(ID::keep); if (wire->port_output || keep) { if (bit != wirebit) alias_map[wirebit] = bit; output_bits.insert(wirebit); } if (it != wire->attributes.end()) { auto s = it->second[i]; if (s != State::Sx) { auto r = init_map.insert(std::make_pair(bit, it->second[i])); if (!r.second && r.first->second != it->second[i]) log_error("Bit '%s' has a conflicting (* init *) value.\n", log_signal(bit)); } } } } TimingInfo timing; for (auto cell : module->cells()) { if (!cell->has_keep_attr()) { if (cell->type == ID($_NOT_)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit Y = sigmap(cell->getPort(ID::Y).as_bit()); unused_bits.erase(A); undriven_bits.erase(Y); not_map[Y] = A; continue; } if (cell->type == ID($_AND_)) { SigBit A = sigmap(cell->getPort(ID::A).as_bit()); SigBit B = sigmap(cell->getPort(ID::B).as_bit()); SigBit Y = sigmap(cell->getPort(ID::Y).as_bit()); unused_bits.erase(A); unused_bits.erase(B); undriven_bits.erase(Y); and_map[Y] = make_pair(A, B); continue; } if (dff_mode && cell->type.in(ID($_DFF_N_), ID($_DFF_P_)) && !cell->get_bool_attribute(ID::abc9_keep)) { SigBit D = sigmap(cell->getPort(ID::D).as_bit()); SigBit Q = sigmap(cell->getPort(ID::Q).as_bit()); unused_bits.erase(D); undriven_bits.erase(Q); alias_map[Q] = D; ff_list.emplace_back(cell); continue; } if (cell->type.in(ID($specify2), ID($specify3), ID($specrule))) continue; } RTLIL::Module* inst_module = design->module(cell->type); if (inst_module && inst_module->get_blackbox_attribute()) { bool abc9_flop = false; auto it = cell->attributes.find(ID::abc9_box_seq); if (it != cell->attributes.end()) { log_assert(!cell->has_keep_attr()); log_assert(cell->parameters.empty()); int abc9_box_seq = it->second.as_int(); if (GetSize(box_list) <= abc9_box_seq) box_list.resize(abc9_box_seq+1); box_list[abc9_box_seq] = cell; // Only flop boxes may have arrival times // (all others are combinatorial) log_assert(cell->parameters.empty()); abc9_flop = inst_module->get_bool_attribute(ID::abc9_flop); if (!abc9_flop) continue; } if (!timing.count(inst_module->name)) timing.setup_module(inst_module); for (auto &i : timing.at(inst_module->name).arrival) { if (!cell->hasPort(i.first.name)) continue; auto port_wire = inst_module->wire(i.first.name); log_assert(port_wire->port_output); auto d = i.second.first; if (d == 0) continue; auto offset = i.first.offset; auto rhs = cell->getPort(i.first.name); if (offset >= rhs.size()) continue; #ifndef NDEBUG if (ys_debug(1)) { static pool> seen; if (seen.emplace(inst_module->name, i.first).second) log("%s.%s[%d] abc9_arrival = %d\n", log_id(cell->type), log_id(i.first.name), offset, d); } #endif arrival_times[rhs[offset]] = d; } if (abc9_flop) continue; } bool cell_known = inst_module || cell->known(); for (const auto &c : cell->connections()) { if (c.second.is_fully_const()) continue; auto port_wire = inst_module ? inst_module->wire(c.first) : nullptr; auto is_input = (port_wire && port_wire->port_input) || !cell_known || cell->input(c.first); auto is_output = (port_wire && port_wire->port_output) || !cell_known || cell->output(c.first); if (!is_input && !is_output) log_error("Connection '%s' on cell '%s' (type '%s') not recognised!\n", log_id(c.first), log_id(cell), log_id(cell->type)); if (is_input) for (auto b : c.second) { Wire *w = b.wire; if (!w) continue; // Do not add as PO if bit is already a PI if (input_bits.count(b)) continue; if (!w->port_output || !cell_known) { SigBit I = sigmap(b); if (I != b) alias_map[b] = I; output_bits.insert(b); } } } //log_warning("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell)); } dict> box_ports; for (auto cell : box_list) { log_assert(cell); RTLIL::Module* box_module = design->module(cell->type); log_assert(box_module); log_assert(box_module->has_attribute(ID::abc9_box_id)); auto r = box_ports.insert(cell->type); if (r.second) { // Make carry in the last PI, and carry out the last PO // since ABC requires it this way IdString carry_in, carry_out; for (const auto &port_name : box_module->ports) { auto w = box_module->wire(port_name); log_assert(w); if (w->get_bool_attribute(ID::abc9_carry)) { if (w->port_input) { if (carry_in != IdString()) log_error("Module '%s' contains more than one 'abc9_carry' input port.\n", log_id(box_module)); carry_in = port_name; } if (w->port_output) { if (carry_out != IdString()) log_error("Module '%s' contains more than one 'abc9_carry' output port.\n", log_id(box_module)); carry_out = port_name; } } else r.first->second.push_back(port_name); } if (carry_in != IdString() && carry_out == IdString()) log_error("Module '%s' contains an 'abc9_carry' input port but no output port.\n", log_id(box_module)); if (carry_in == IdString() && carry_out != IdString()) log_error("Module '%s' contains an 'abc9_carry' output port but no input port.\n", log_id(box_module)); if (carry_in != IdString()) { r.first->second.push_back(carry_in); r.first->second.push_back(carry_out); } } for (auto port_name : r.first->second) { auto w = box_module->wire(port_name); log_assert(w); auto rhs = cell->connections_.at(port_name, SigSpec()); rhs.append(Const(State::Sx, GetSize(w)-GetSize(rhs))); if (w->port_input) for (auto b : rhs) { SigBit I = sigmap(b); if (b == RTLIL::Sx) b = State::S0; else if (I != b) { if (I == RTLIL::Sx) alias_map[b] = State::S0; else alias_map[b] = I; } co_bits.emplace_back(b); unused_bits.erase(I); } if (w->port_output) for (const auto &b : rhs) { SigBit O = sigmap(b); if (O != b) alias_map[O] = b; ci_bits.emplace_back(b); undriven_bits.erase(O); } } } for (auto bit : input_bits) undriven_bits.erase(bit); for (auto bit : output_bits) unused_bits.erase(sigmap(bit)); for (auto bit : unused_bits) undriven_bits.erase(bit); // Make all undriven bits a primary input for (auto bit : undriven_bits) { input_bits.insert(bit); undriven_bits.erase(bit); } struct sort_by_port_id { bool operator()(const RTLIL::SigBit& a, const RTLIL::SigBit& b) const { return a.wire->port_id < b.wire->port_id || (a.wire->port_id == b.wire->port_id && a.offset < b.offset); } }; input_bits.sort(sort_by_port_id()); output_bits.sort(sort_by_port_id()); aig_map[State::S0] = 0; aig_map[State::S1] = 1; for (const auto &bit : input_bits) { aig_m++, aig_i++; log_assert(!aig_map.count(bit)); aig_map[bit] = 2*aig_m; } for (auto cell : ff_list) { const SigBit &q = sigmap(cell->getPort(ID::Q)); aig_m++, aig_i++; log_assert(!aig_map.count(q)); aig_map[q] = 2*aig_m; } for (auto &bit : ci_bits) { aig_m++, aig_i++; // 1'bx may exist here due to a box output // that has been padded to its full width if (bit == State::Sx) continue; if (aig_map.count(bit)) log_error("Visited AIG node more than once; this could be a combinatorial loop that has not been broken\n"); aig_map[bit] = 2*aig_m; } for (auto bit : co_bits) { ordered_outputs[bit] = aig_o++; aig_outputs.push_back(bit2aig(bit)); } for (const auto &bit : output_bits) { ordered_outputs[bit] = aig_o++; int aig; // Unlike bit2aig() which checks aig_map first for // inout/scc bits, since aig_map will point to // the PI, first attempt to find the NOT/AND driver // before resorting to an aig_map lookup (which // could be another PO) if (input_bits.count(bit)) { if (not_map.count(bit)) { aig = bit2aig(not_map.at(bit)) ^ 1; } else if (and_map.count(bit)) { auto args = and_map.at(bit); int a0 = bit2aig(args.first); int a1 = bit2aig(args.second); aig = mkgate(a0, a1); } else aig = aig_map.at(bit); } else aig = bit2aig(bit); aig_outputs.push_back(aig); } for (auto cell : ff_list) { const SigBit &d = sigmap(cell->getPort(ID::D)); aig_o++; aig_outputs.push_back(aig_map.at(d)); } } void write_aiger(std::ostream &f, bool ascii_mode) { int aig_obc = aig_o; int aig_obcj = aig_obc; int aig_obcjf = aig_obcj; log_assert(aig_m == aig_i + aig_l + aig_a); log_assert(aig_obcjf == GetSize(aig_outputs)); f << stringf("%s %d %d %d %d %d", ascii_mode ? "aag" : "aig", aig_m, aig_i, aig_l, aig_o, aig_a); f << stringf("\n"); if (ascii_mode) { for (int i = 0; i < aig_i; i++) f << stringf("%d\n", 2*i+2); for (int i = 0; i < aig_obc; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("1\n"); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obcj; i < aig_obcjf; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = 0; i < aig_a; i++) f << stringf("%d %d %d\n", 2*(aig_i+aig_l+i)+2, aig_gates.at(i).first, aig_gates.at(i).second); } else { for (int i = 0; i < aig_obc; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("1\n"); for (int i = aig_obc; i < aig_obcj; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = aig_obcj; i < aig_obcjf; i++) f << stringf("%d\n", aig_outputs.at(i)); for (int i = 0; i < aig_a; i++) { int lhs = 2*(aig_i+aig_l+i)+2; int rhs0 = aig_gates.at(i).first; int rhs1 = aig_gates.at(i).second; int delta0 = lhs - rhs0; int delta1 = rhs0 - rhs1; aiger_encode(f, delta0); aiger_encode(f, delta1); } } f << "c"; 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); write_h_buffer(1); log_debug("ciNum = %d\n", GetSize(input_bits) + GetSize(ff_list) + GetSize(ci_bits)); write_h_buffer(GetSize(input_bits) + GetSize(ff_list) + GetSize(ci_bits)); log_debug("coNum = %d\n", GetSize(output_bits) + GetSize(ff_list) + GetSize(co_bits)); write_h_buffer(GetSize(output_bits) + GetSize(ff_list) + GetSize(co_bits)); log_debug("piNum = %d\n", GetSize(input_bits) + GetSize(ff_list)); write_h_buffer(GetSize(input_bits) + GetSize(ff_list)); log_debug("poNum = %d\n", GetSize(output_bits) + GetSize(ff_list)); write_h_buffer(GetSize(output_bits) + GetSize(ff_list)); log_debug("boxNum = %d\n", GetSize(box_list)); write_h_buffer(GetSize(box_list)); auto write_buffer_float = [](std::stringstream &buffer, float f32) { buffer.write(reinterpret_cast(&f32), sizeof(f32)); }; std::stringstream i_buffer; auto write_i_buffer = std::bind(write_buffer_float, std::ref(i_buffer), std::placeholders::_1); for (auto bit : input_bits) write_i_buffer(arrival_times.at(bit, 0)); //std::stringstream o_buffer; //auto write_o_buffer = std::bind(write_buffer_float, std::ref(o_buffer), std::placeholders::_1); //for (auto bit : output_bits) // write_o_buffer(0); if (!box_list.empty() || !ff_list.empty()) { dict> cell_cache; int box_count = 0; for (auto cell : box_list) { log_assert(cell); log_assert(cell->parameters.empty()); auto r = cell_cache.insert(cell->type); auto &v = r.first->second; if (r.second) { RTLIL::Module* box_module = design->module(cell->type); log_assert(box_module); int box_inputs = 0, box_outputs = 0; for (auto port_name : box_module->ports) { RTLIL::Wire *w = box_module->wire(port_name); log_assert(w); if (w->port_input) box_inputs += GetSize(w); if (w->port_output) box_outputs += GetSize(w); } std::get<0>(v) = box_inputs; std::get<1>(v) = box_outputs; std::get<2>(v) = box_module->attributes.at(ID::abc9_box_id).as_int(); } write_h_buffer(std::get<0>(v)); write_h_buffer(std::get<1>(v)); write_h_buffer(std::get<2>(v)); write_h_buffer(box_count++); } std::stringstream r_buffer; auto write_r_buffer = std::bind(write_buffer, std::ref(r_buffer), std::placeholders::_1); log_debug("flopNum = %d\n", GetSize(ff_list)); write_r_buffer(ff_list.size()); std::stringstream s_buffer; auto write_s_buffer = std::bind(write_buffer, std::ref(s_buffer), std::placeholders::_1); write_s_buffer(ff_list.size()); dict clk_to_mergeability; for (const auto cell : ff_list) { const SigBit &d = sigmap(cell->getPort(ID::D)); const SigBit &q = sigmap(cell->getPort(ID::Q)); SigSpec clk_and_pol{sigmap(cell->getPort(ID::C)), cell->type[6] == 'P' ? State::S1 : State::S0}; auto r = clk_to_mergeability.insert(std::make_pair(clk_and_pol, clk_to_mergeability.size()+1)); int mergeability = r.first->second; log_assert(mergeability > 0); write_r_buffer(mergeability); State init = init_map.at(q, State::Sx); log_debug("Cell '%s' (type %s) has (* init *) value '%s'.\n", log_id(cell), log_id(cell->type), log_signal(init)); if (init == State::S1) write_s_buffer(1); else if (init == State::S0) write_s_buffer(0); else { log_assert(init == State::Sx); write_s_buffer(2); } // Use arrival time from output of flop box write_i_buffer(arrival_times.at(d, 0)); //write_o_buffer(0); } f << "r"; std::string buffer_str = r_buffer.str(); write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); f << "s"; buffer_str = s_buffer.str(); write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); RTLIL::Design *holes_design; auto it = saved_designs.find("$abc9_holes"); if (it != saved_designs.end()) holes_design = it->second; else holes_design = nullptr; RTLIL::Module *holes_module = holes_design ? holes_design->module(module->name) : nullptr; if (holes_module) { std::stringstream a_buffer; XAigerWriter writer(holes_module, false /* dff_mode */); writer.write_aiger(a_buffer, false /*ascii_mode*/); f << "a"; std::string buffer_str = a_buffer.str(); write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); } } f << "h"; std::string buffer_str = h_buffer.str(); write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); f << "i"; buffer_str = i_buffer.str(); write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); //f << "o"; //buffer_str = o_buffer.str(); //buffer_size_be = to_big_endian(buffer_str.size()); //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_maybe_version()); design->scratchpad_set_int("write_xaiger.num_ands", and_map.size()); design->scratchpad_set_int("write_xaiger.num_wires", aig_map.size()); design->scratchpad_set_int("write_xaiger.num_inputs", input_bits.size()); design->scratchpad_set_int("write_xaiger.num_outputs", output_bits.size()); } void write_map(std::ostream &f) { dict input_lines; dict output_lines; for (auto wire : module->wires()) { for (int i = 0; i < GetSize(wire); i++) { RTLIL::SigBit b(wire, i); if (input_bits.count(b)) { int a = aig_map.at(b); log_assert((a & 1) == 0); input_lines[a] += stringf("input %d %d %s\n", (a >> 1)-1, wire->start_offset+i, log_id(wire)); } if (output_bits.count(b)) { int o = ordered_outputs.at(b); output_lines[o] += stringf("output %d %d %s\n", o - GetSize(co_bits), wire->start_offset+i, log_id(wire)); } } } input_lines.sort(); for (auto &it : input_lines) f << it.second; log_assert(input_lines.size() == input_bits.size()); int box_count = 0; for (auto cell : box_list) f << stringf("box %d %d %s\n", box_count++, 0, log_id(cell->name)); output_lines.sort(); for (auto &it : output_lines) f << it.second; log_assert(output_lines.size() == output_bits.size()); } }; struct XAigerBackend : public Backend { XAigerBackend() : Backend("xaiger", "write design to XAIGER file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_xaiger [options] [filename]\n"); log("\n"); log("Write the top module (according to the (* top *) attribute or if only one module\n"); log("is currently selected) to an XAIGER file. Any non $_NOT_, $_AND_, (optionally\n"); log("$_DFF_N_, $_DFF_P_), or non (* abc9_box *) cells will be converted into psuedo-\n"); log("inputs and pseudo-outputs. Whitebox contents will be taken from the equivalent\n"); log("module in the '$abc9_holes' design, if it exists.\n"); log("\n"); log(" -ascii\n"); log(" write ASCII version of AIGER format\n"); log("\n"); log(" -map \n"); log(" write an extra file with port and box symbols\n"); log("\n"); log(" -dff\n"); log(" write $_DFF_[NP]_ cells\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { bool ascii_mode = false, dff_mode = false; std::string map_filename; log_header(design, "Executing XAIGER backend.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-ascii") { ascii_mode = true; continue; } if (map_filename.empty() && args[argidx] == "-map" && argidx+1 < args.size()) { map_filename = args[++argidx]; continue; } if (args[argidx] == "-dff") { dff_mode = true; continue; } break; } extra_args(f, filename, args, argidx, !ascii_mode); Module *top_module = design->top_module(); if (top_module == nullptr) log_error("Can't find top module in current design!\n"); if (!design->selected_whole_module(top_module)) log_cmd_error("Can't handle partially selected module %s!\n", log_id(top_module)); if (!top_module->processes.empty()) log_error("Found unmapped processes in module %s: unmapped processes are not supported in XAIGER backend!\n", log_id(top_module)); if (!top_module->memories.empty()) log_error("Found unmapped memories in module %s: unmapped memories are not supported in XAIGER backend!\n", log_id(top_module)); XAigerWriter writer(top_module, dff_mode); writer.write_aiger(*f, ascii_mode); if (!map_filename.empty()) { 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, strerror(errno)); writer.write_map(mapf); } } } XAigerBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/aiger2/000077500000000000000000000000001520057232300155545ustar00rootroot00000000000000yosys-0.65/backends/aiger2/Makefile.inc000066400000000000000000000000401520057232300177560ustar00rootroot00000000000000OBJS += backends/aiger2/aiger.o yosys-0.65/backends/aiger2/aiger.cc000066400000000000000000001232631520057232300171610ustar00rootroot00000000000000/* * 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 // - decide how to unify this with cellaigs // - break up Index into something smaller // - (C++20) remove snprintf-into-std::ostream weirdness #include "kernel/register.h" #include "kernel/newcelltypes.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) static constexpr auto known_ops = []() constexpr { StaticCellTypes::Categories::Category c{}; for (auto id : {BITWISE_OPS}) c.set_id(id); for (auto id : {REDUCE_OPS}) c.set_id(id); for (auto id : {LOGIC_OPS}) c.set_id(id); for (auto id : {GATE_OPS}) c.set_id(id); for (auto id : {CMP_OPS}) c.set_id(id); for (auto id : {ID($pos), ID($pmux), ID($bmux)}) c.set_id(id); return c; }(); template struct Index { 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 (known_ops(cell->type) || 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 { // AigMaker::node2index // In XAIGER, the ordering of inputs is used to distinguish between AND // and XOR gates. AND gates have their first input literal be larger // than their second, and vice-versa for XORs. if (a < b) std::swap(a, b); auto pair = std::make_pair(a, b); 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) { Lit not_a = NOT(a); Lit not_b = NOT(b); return NOT(AND(not_a, not_b)); } Lit MUX(Lit a, Lit b, Lit s) { if (const_folding) { if (a == b) return a; if (s == CFALSE) return a; if (s == CTRUE) return b; } Lit not_s = NOT(s); Lit a_active = AND(a, not_s); Lit b_active = AND(b, s); return OR(a_active, b_active); } Lit XOR(Lit a, Lit b) { Lit not_a = NOT(a); Lit not_b = NOT(b); Lit a_and_not_b = AND(a, not_b); Lit not_a_and_b = AND(not_a, b); return OR(a_and_not_b, not_a_and_b); } Lit XNOR(Lit a, Lit b) { return NOT(XOR(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); } } Lit a_or_b = OR(a, b); Lit a_or_b_and_c = AND(c, a_or_b); Lit a_and_b = AND(a, b); return OR(a_and_b, a_or_b_and_c); } Lit REDUCE(std::vector 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 XNOR(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 */ { Lit a_and_b = AND(a, b); Lit c_and_ab = AND(c, ab); return OR(a_and_b, c_and_ab); } } else if (cell->type.in(ID($_AOI3_), ID($_OAI3_), ID($_AOI4_), ID($_OAI4_))) { Lit c, d; 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_))) { Lit a_and_b = AND(a, b); Lit c_and_d = AND(c, d); return NOT(OR(a_and_b, c_and_d)); } else { Lit a_or_b = OR(a, b); Lit c_or_d = OR(c, d); return NOT(AND(a_or_b, c_or_d)); } } else { log_abort(); } } } 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)); } Lit reduce_sels = REDUCE(sels); Lit reduce_sels_and_a = AND(reduce_sels, a); Lit reduce_bar = NOT(REDUCE(bar)); return OR(reduce_sels_and_a, reduce_bar); } else if (cell->type == ID($bmux)) { SigSpec aport = cell->getPort(ID::A); SigSpec sport = cell->getPort(ID::S); 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 unsupported cell %s (%s in %s)\n", log_id(cell->type), log_id(cell), log_id(cell->module)); Module *def = design->module(cell->type); log_assert(def); levels.push_back(Level(index.modules.at(def), cell)); 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 [minfo, cell] : levels) { if (!first) ret += "."; if (!cell) ret += RTLIL::unescape_id(minfo.module->name); else ret += RTLIL::unescape_id(cell->name); first = false; } return ret; } int hash() const { int hash = 0; for (auto [_, cell] : levels) hash += (uintptr_t) cell; return hash; } bool operator==(const HierCursor &other) const { if (levels.size() != other.levels.size()) return false; for (int i = 0; i < levels.size(); i++) { auto* cell = levels[i].second; auto* other_cell = other.levels[i].second; if (cell != other_cell) return false; } return true; } }; 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 (known_ops(driver->type)) { 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; // In XAIGER, the ordering of inputs is used to distinguish between AND // and XOR gates. AND gates have their first input literal be larger // than their second, and vice-versa for XORs. if (a < b) std::swap(a, b); encode(out - a); encode(a - b); 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), "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++) { auto bit = SigBit(w, i); pi_literal(bit) = lit_counter; inputs.push_back(bit); 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), "%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)) // Each call to eval_po eventually reaches emit_gate and // encode which writes to f. outputs.push_back({bit, eval_po(bit)}); } auto data_end = f->tellp(); // revisit header and the list of outputs f->seekp(file_start); write_header(); for (auto [_, po] : outputs) { char buf[16]; snprintf(buf, sizeof(buf), "%08d\n", po); 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 [bit, _] : outputs) { if (SigSpec(bit).is_wire()) { // primary output symbol char buf[32]; snprintf(buf, sizeof(buf), "o%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++; } i = 0; for (auto bit : inputs) { if (SigSpec(bit).is_wire()) { // primary input symbol char buf[32]; snprintf(buf, sizeof(buf), "i%d ", i); f->write(buf, strlen(buf)); std::string name = RTLIL::unescape_id(bit.wire->name); f->write(name.data(), name.size()); 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), "%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), "%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()); // XAIGER extensions f->seekp(0, std::ios::end); f->put('c'); // 'c': comment (marks beginning of extensions) // insert empty 'r' and 's' sections (abc crashes if we provide 'a' without those) f->put('r'); // 'r': register classes write_be32(*f, 4); // length in bytes write_be32(*f, 0); // no register classes f->put('s'); // 's': register initial values write_be32(*f, 4); // length in bytes write_be32(*f, 0); // no register initial values f->put('h'); // 'h': hierarchy information // TODO: get rid of std::string copy std::string h_buffer_str = h_buffer.str(); write_be32(*f, h_buffer_str.size()); // length in bytes f->write(h_buffer_str.data(), h_buffer_str.size()); // data #if 1 f->put('a'); // 'a': additional AIG (used for holes) write_be32(*f, 0); // length in bytes (to be filled later) auto holes_aiger_start = f->tellp(); { AigerWriter holes_writer; 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); // length in bytes #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"); log(" "); int col = 0; for (size_t i = 0; i < StaticCellTypes::builder.count; i++) { auto &cell = StaticCellTypes::builder.cells[i]; if (!cell.features.is_evaluable) continue; if (cell.features.is_stdcell) continue; if (known_ops(cell.type)) continue; std::string name = log_id(cell.type); if (col + name.size() + 2 > 72) { log("\n "); col = 0; } col += name.size() + 2; log("%s, ", name.c_str()); } log("\n"); log("\n"); log("And all combinational gates except for:\n"); log("\n"); log(" "); col = 0; for (size_t i = 0; i < StaticCellTypes::builder.count; i++) { auto &cell = StaticCellTypes::builder.cells[i]; if (!cell.features.is_evaluable) continue; if (!cell.features.is_stdcell) continue; if (known_ops(cell.type)) continue; std::string name = log_id(cell.type); if (col + name.size() + 2 > 72) { log("\n "); col = 0; } col += name.size() + 2; log("%s, ", name.c_str()); } 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 yosys-0.65/backends/blif/000077500000000000000000000000001520057232300153175ustar00rootroot00000000000000yosys-0.65/backends/blif/Makefile.inc000066400000000000000000000000371520057232300175270ustar00rootroot00000000000000 OBJS += backends/blif/blif.o yosys-0.65/backends/blif/blif.cc000066400000000000000000000572501520057232300165530ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ // [[CITE]] Berkeley Logic Interchange Format (BLIF) // University of California. Berkeley. July 28, 1992 // http://www.ece.cmu.edu/~ee760/760docs/blif.pdf #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/newcelltypes.h" #include "kernel/log.h" #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct BlifDumperConfig { bool icells_mode; bool conn_mode; bool impltf_mode; bool gates_mode; bool cname_mode; bool iname_mode; bool param_mode; bool attr_mode; bool iattr_mode; bool blackbox_mode; bool noalias_mode; bool gatesi_mode; std::string buf_type, buf_in, buf_out; std::map> unbuf_types; std::string true_type, true_out, false_type, false_out, undef_type, undef_out; BlifDumperConfig() : icells_mode(false), conn_mode(false), impltf_mode(false), gates_mode(false), cname_mode(false), iname_mode(false), param_mode(false), attr_mode(false), iattr_mode(false), blackbox_mode(false), noalias_mode(false), gatesi_mode(false) { } }; struct BlifDumper { std::ostream &f; RTLIL::Module *module; RTLIL::Design *design; BlifDumperConfig *config; NewCellTypes ct; SigMap sigmap; dict init_bits; BlifDumper(std::ostream &f, RTLIL::Module *module, RTLIL::Design *design, BlifDumperConfig *config) : f(f), module(module), design(design), config(config), ct(design), sigmap(module) { for (Wire *wire : module->wires()) if (wire->attributes.count(ID::init)) { SigSpec initsig = sigmap(wire); Const initval = wire->attributes.at(ID::init); for (int i = 0; i < GetSize(initsig) && i < GetSize(initval); i++) switch (initval[i]) { case State::S0: init_bits[initsig[i]] = 0; break; case State::S1: init_bits[initsig[i]] = 1; break; default: break; } } } pool cstr_bits_seen; const std::string str(RTLIL::IdString id) { std::string str = RTLIL::unescape_id(id); for (size_t i = 0; i < str.size(); i++) if (str[i] == '#' || str[i] == '=' || str[i] == '<' || str[i] == '>') str[i] = '?'; return str; } const std::string str(RTLIL::SigBit sig) { cstr_bits_seen.insert(sig); if (sig.wire == NULL) { if (sig == RTLIL::State::S0) return config->false_type == "-" || config->false_type == "+" ? config->false_out.c_str() : "$false"; if (sig == RTLIL::State::S1) return config->true_type == "-" || config->true_type == "+" ? config->true_out.c_str() : "$true"; return config->undef_type == "-" || config->undef_type == "+" ? config->undef_out.c_str() : "$undef"; } std::string str = RTLIL::unescape_id(sig.wire->name); for (size_t i = 0; i < str.size(); i++) if (str[i] == '#' || str[i] == '=' || str[i] == '<' || str[i] == '>') str[i] = '?'; if (sig.wire->width != 1) str += stringf("[%d]", sig.wire->upto ? sig.wire->start_offset+sig.wire->width-sig.offset-1 : sig.wire->start_offset+sig.offset); return str; } template const std::string str_init(RTLIL::SigBit sig) { sigmap.apply(sig); if (init_bits.count(sig) == 0) { if constexpr (Space) return " 2"; else return "2"; } if constexpr (Space) return stringf(" %d", init_bits.at(sig)); else return stringf("%d", init_bits.at(sig)); } const char *subckt_or_gate(std::string cell_type) { if (!config->gates_mode) return "subckt"; if (design->module(RTLIL::escape_id(cell_type)) == nullptr) return "gate"; if (design->module(RTLIL::escape_id(cell_type))->get_blackbox_attribute()) return "gate"; return "subckt"; } void dump_params(const char *command, dict ¶ms) { for (auto ¶m : params) { f << stringf("%s %s ", command, log_id(param.first)); if (param.second.flags & RTLIL::CONST_FLAG_STRING) { std::string str = param.second.decode_string(); f << stringf("\""); for (char ch : str) if (ch == '"' || ch == '\\') f << stringf("\\%c", ch); else if (ch < 32 || ch >= 127) f << stringf("\\%03o", ch); else f << stringf("%c", ch); f << stringf("\"\n"); } else f << stringf("%s\n", param.second.as_string()); } } void dump() { f << stringf("\n"); f << stringf(".model %s\n", str(module->name)); std::map inputs, outputs; for (auto wire : module->wires()) { if (wire->port_input) inputs[wire->port_id] = wire; if (wire->port_output) outputs[wire->port_id] = wire; } f << stringf(".inputs"); 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))); } f << stringf("\n"); f << stringf(".outputs"); 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))); } f << stringf("\n"); if (module->get_blackbox_attribute()) { f << stringf(".blackbox\n"); f << stringf(".end\n"); return; } if (!config->impltf_mode) { if (!config->false_type.empty()) { if (config->false_type == "+") 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()); } else f << stringf(".names $false\n"); if (!config->true_type.empty()) { if (config->true_type == "+") 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()); } else f << stringf(".names $true\n1\n"); if (!config->undef_type.empty()) { if (config->undef_type == "+") 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()); } else f << stringf(".names $undef\n"); } for (auto cell : module->cells()) { if (cell->type == ID($scopeinfo)) continue; if (config->unbuf_types.count(cell->type)) { auto portnames = config->unbuf_types.at(cell->type); f << stringf(".names %s %s\n1 1\n", str(cell->getPort(portnames.first)).c_str(), str(cell->getPort(portnames.second)).c_str()); continue; } if (!config->icells_mode && cell->type == ID($_NOT_)) { f << stringf(".names %s %s\n0 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_AND_)) { f << stringf(".names %s %s %s\n11 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_OR_)) { f << stringf(".names %s %s %s\n1- 1\n-1 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_XOR_)) { f << stringf(".names %s %s %s\n10 1\n01 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_NAND_)) { f << stringf(".names %s %s %s\n0- 1\n-0 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_NOR_)) { f << stringf(".names %s %s %s\n00 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_XNOR_)) { f << stringf(".names %s %s %s\n11 1\n00 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_ANDNOT_)) { f << stringf(".names %s %s %s\n10 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_ORNOT_)) { f << stringf(".names %s %s %s\n1- 1\n-0 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_AOI3_)) { f << stringf(".names %s %s %s %s\n-00 1\n0-0 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_OAI3_)) { f << stringf(".names %s %s %s %s\n00- 1\n--0 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_AOI4_)) { f << stringf(".names %s %s %s %s %s\n-0-0 1\n-00- 1\n0--0 1\n0-0- 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_OAI4_)) { f << stringf(".names %s %s %s %s %s\n00-- 1\n--00 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_MUX_)) { f << stringf(".names %s %s %s %s\n1-0 1\n-11 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::S)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_NMUX_)) { f << stringf(".names %s %s %s %s\n0-0 1\n-01 1\n", str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::S)).c_str(), str(cell->getPort(ID::Y)).c_str()); goto internal_cell; } if (!config->icells_mode && cell->type == ID($_FF_)) { 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)), 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)), 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)), 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)), 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($lut)) { f << stringf(".names"); auto &inputs = cell->getPort(ID::A); 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))); auto &output = cell->getPort(ID::Y); log_assert(output.size() == 1); f << stringf(" %s", str(output)); f << stringf("\n"); RTLIL::SigSpec mask = cell->parameters.at(ID::LUT); for (int i = 0; i < (1 << width); i++) if (mask[i] == State::S1) { for (int j = width-1; j >= 0; j--) { f << ((i>>j)&1 ? '1' : '0'); } f << " 1\n"; } goto internal_cell; } if (!config->icells_mode && cell->type == ID($sop)) { f << stringf(".names"); 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).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))); auto &output = cell->getPort(ID::Y); log_assert(output.size() == 1); f << stringf(" %s", str(output)); f << stringf("\n"); for (int i = 0; i < depth; i++) { for (int j = 0; j < width; j++) { bool pat0 = table.at(2*width*i + 2*j + 0) == State::S1; bool pat1 = table.at(2*width*i + 2*j + 1) == State::S1; if (pat0 && !pat1) f << "0"; else if (!pat0 && pat1) f << "1"; else f << "-"; } f << " 1\n"; } goto internal_cell; } 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), str(conn.second[0])); continue; } Module *m = design->module(cell->type); Wire *w = m ? m->wire(conn.first) : nullptr; if (w == nullptr) { for (int i = 0; i < GetSize(conn.second); i++) 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), 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()); } } } f << stringf("\n"); if (config->cname_mode) f << stringf(".cname %s\n", str(cell->name)); if (config->attr_mode) dump_params(".attr", cell->attributes); if (config->param_mode) dump_params(".param", cell->parameters); if (0) { internal_cell: if (config->iname_mode) f << stringf(".cname %s\n", str(cell->name)); if (config->iattr_mode) dump_params(".attr", cell->attributes); } } for (auto &conn : module->connections()) for (int i = 0; i < conn.first.size(); i++) { SigBit lhs_bit = conn.first[i]; SigBit rhs_bit = conn.second[i]; if (config->noalias_mode && cstr_bits_seen.count(lhs_bit) == 0) continue; if (config->conn_mode) 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, 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), str(lhs_bit)); } if (config->gatesi_mode) { for (auto &&init_bit : init_bits) f << stringf(".gateinit %s=%s\n", str(init_bit.first), str_init(init_bit.first)); } f << stringf(".end\n"); } static void dump(std::ostream &f, RTLIL::Module *module, RTLIL::Design *design, BlifDumperConfig &config) { BlifDumper dumper(f, module, design, &config); dumper.dump(); } }; struct BlifBackend : public Backend { BlifBackend() : Backend("blif", "write design to BLIF file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_blif [options] [filename]\n"); log("\n"); log("Write the current design to an BLIF file.\n"); log("\n"); log(" -top top_module\n"); log(" set the specified module as design top module\n"); log("\n"); log(" -buf \n"); log(" use cells of type with the specified port names for buffers\n"); log("\n"); log(" -unbuf \n"); log(" replace buffer cells with the specified name and port names with\n"); log(" a .names statement that models a buffer\n"); log("\n"); log(" -true \n"); log(" -false \n"); log(" -undef \n"); log(" use the specified cell types to drive nets that are constant 1, 0, or\n"); log(" undefined. when '-' is used as , then specifies\n"); log(" the wire name to be used for the constant signal and no cell driving\n"); log(" that wire is generated. when '+' is used as , then \n"); log(" specifies the wire name to be used for the constant signal and a .names\n"); log(" statement is generated to drive the wire.\n"); log("\n"); log(" -noalias\n"); log(" if a net name is aliasing another net name, then by default a net\n"); log(" without fanout is created that is driven by the other net. This option\n"); log(" suppresses the generation of this nets without fanout.\n"); log("\n"); log("The following options can be useful when the generated file is not going to be\n"); log("read by a BLIF parser but a custom tool. It is recommended not to name the\n"); log("output file *.blif when any of these options are used.\n"); log("\n"); log(" -icells\n"); log(" do not translate Yosys's internal gates to generic BLIF logic\n"); log(" functions. Instead create .subckt or .gate lines for all cells.\n"); log("\n"); log(" -gates\n"); log(" print .gate instead of .subckt lines for all cells that are not\n"); log(" instantiations of other modules from this design.\n"); log("\n"); log(" -conn\n"); log(" do not generate buffers for connected wires. instead use the\n"); log(" non-standard .conn statement.\n"); log("\n"); log(" -attr\n"); log(" use the non-standard .attr statement to write cell attributes\n"); log("\n"); log(" -param\n"); log(" use the non-standard .param statement to write cell parameters\n"); log("\n"); log(" -cname\n"); log(" use the non-standard .cname statement to write cell names\n"); log("\n"); log(" -iname, -iattr\n"); log(" enable -cname and -attr functionality for .names statements\n"); log(" (the .cname and .attr statements will be included in the BLIF\n"); log(" output after the truth table for the .names statement)\n"); log("\n"); log(" -blackbox\n"); log(" write blackbox cells with .blackbox statement.\n"); log("\n"); log(" -impltf\n"); log(" do not write definitions for the $true, $false and $undef wires.\n"); log("\n"); log(" -gatesi\n"); log(" write initial bit(s) with .gateinit for gates that needs to be initialized.\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { std::string top_module_name; std::string buf_type, buf_in, buf_out; std::string true_type, true_out; std::string false_type, false_out; BlifDumperConfig config; log_header(design, "Executing BLIF backend.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-top" && argidx+1 < args.size()) { top_module_name = args[++argidx]; continue; } if (args[argidx] == "-buf" && argidx+3 < args.size()) { config.buf_type = args[++argidx]; config.buf_in = args[++argidx]; config.buf_out = args[++argidx]; continue; } if (args[argidx] == "-unbuf" && argidx+3 < args.size()) { RTLIL::IdString unbuf_type = RTLIL::escape_id(args[++argidx]); RTLIL::IdString unbuf_in = RTLIL::escape_id(args[++argidx]); RTLIL::IdString unbuf_out = RTLIL::escape_id(args[++argidx]); config.unbuf_types[unbuf_type] = std::pair(unbuf_in, unbuf_out); continue; } if (args[argidx] == "-true" && argidx+2 < args.size()) { config.true_type = args[++argidx]; config.true_out = args[++argidx]; continue; } if (args[argidx] == "-false" && argidx+2 < args.size()) { config.false_type = args[++argidx]; config.false_out = args[++argidx]; continue; } if (args[argidx] == "-undef" && argidx+2 < args.size()) { config.undef_type = args[++argidx]; config.undef_out = args[++argidx]; continue; } if (args[argidx] == "-icells") { config.icells_mode = true; continue; } if (args[argidx] == "-gates") { config.gates_mode = true; continue; } if (args[argidx] == "-conn") { config.conn_mode = true; continue; } if (args[argidx] == "-cname") { config.cname_mode = true; continue; } if (args[argidx] == "-param") { config.param_mode = true; continue; } if (args[argidx] == "-attr") { config.attr_mode = true; continue; } if (args[argidx] == "-iname") { config.iname_mode = true; continue; } if (args[argidx] == "-iattr") { config.iattr_mode = true; continue; } if (args[argidx] == "-blackbox") { config.blackbox_mode = true; continue; } if (args[argidx] == "-impltf") { config.impltf_mode = true; continue; } if (args[argidx] == "-noalias") { config.noalias_mode = true; continue; } if (args[argidx] == "-gatesi") { config.gatesi_mode = true; continue; } break; } extra_args(f, filename, args, argidx); if (top_module_name.empty()) for (auto module : design->modules()) if (module->get_bool_attribute(ID::top)) top_module_name = module->name.str(); *f << stringf("# Generated by %s\n", yosys_maybe_version()); std::vector mod_list; design->sort(); for (auto module : design->modules()) { if (module->get_blackbox_attribute() && !config.blackbox_mode) continue; if (module->processes.size() != 0) log_error("Found unmapped processes in module %s: unmapped processes are not supported in BLIF backend!\n", log_id(module->name)); if (module->memories.size() != 0) log_error("Found unmapped memories in module %s: unmapped memories are not supported in BLIF backend!\n", log_id(module->name)); if (module->name == RTLIL::escape_id(top_module_name)) { BlifDumper::dump(*f, module, design, config); top_module_name.clear(); continue; } mod_list.push_back(module); } if (!top_module_name.empty()) log_error("Can't find top module `%s'!\n", top_module_name); for (auto module : mod_list) BlifDumper::dump(*f, module, design, config); } } BlifBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/btor/000077500000000000000000000000001520057232300153515ustar00rootroot00000000000000yosys-0.65/backends/btor/.gitignore000066400000000000000000000000211520057232300173320ustar00rootroot00000000000000/test_cells.tmp/ yosys-0.65/backends/btor/Makefile.inc000066400000000000000000000000371520057232300175610ustar00rootroot00000000000000 OBJS += backends/btor/btor.o yosys-0.65/backends/btor/btor.cc000066400000000000000000001315771520057232300166440ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ // [[CITE]] Btor2 , BtorMC and Boolector 3.0 // Aina Niemetz, Mathias Preiner, C. Wolf, Armin Biere // Computer Aided Verification - 30th International Conference, CAV 2018 // https://cs.stanford.edu/people/niemetz/publication/2018/niemetzpreinerwolfbiere-cav18/ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/log.h" #include "kernel/mem.h" #include "kernel/json.h" #include "kernel/yw.h" #include "kernel/utils.h" #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct BtorWorker { std::ostream &f; SigMap sigmap; RTLIL::Module *module; bool verbose; bool single_bad; bool cover_mode; bool print_internal_names; int next_nid = 1; int initstate_nid = -1; // => dict sorts_bv; // (, ) => dict, int> sorts_mem; // SigBit => (, ) dict> bit_nid; // => dict nid_width; // SigSpec => dict sig_nid; // bit to driving cell dict bit_cell; // nids for constants dict consts; // ff inputs that need to be evaluated (, ) vector> ff_todo; vector> mem_todo; pool cell_recursion_guard; vector bad_properties; dict initbits; pool statewires; pool srcsymbols; vector memories; dict mem_cells; string indent, info_filename; vector info_lines; dict info_clocks; struct ywmap_btor_sig { SigSpec sig; Cell *cell = nullptr; ywmap_btor_sig(const SigSpec &sig) : sig(sig) {} ywmap_btor_sig(Cell *cell) : cell(cell) {} }; vector ywmap_inputs; vector ywmap_states; dict ywmap_clock_bits; dict ywmap_clock_inputs; vector ywmap_asserts; vector ywmap_assumes; PrettyJson ywmap_json; template void btorf(FmtString...> fmt, const Args &... args) { f << indent << fmt.format(args...); } template void infof(FmtString...> fmt, const Args &... args) { info_lines.push_back(fmt.format(args...)); } template string getinfo(T *obj, bool srcsym = false) { string infostr = log_id(obj); if (!srcsym && !print_internal_names && infostr[0] == '$') return ""; if (obj->attributes.count(ID::src)) { string src = obj->attributes.at(ID::src).decode_string().c_str(); if (srcsym && infostr[0] == '$') { 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, i); if (!srcsymbols.count(s) && !module->count_id("\\" + s)) { src = s; break; } } } srcsymbols.insert(src); infostr = src; } else { infostr += " ; " + src; } } return " " + infostr; } void ywmap_state(const SigSpec &sig) { if (ywmap_json.active()) ywmap_states.emplace_back(sig); } void ywmap_state(Cell *cell) { if (ywmap_json.active()) ywmap_states.emplace_back(cell); } void ywmap_input(const SigSpec &sig) { if (ywmap_json.active()) ywmap_inputs.emplace_back(sig); } void emit_ywmap_btor_sig(const ywmap_btor_sig &btor_sig) { if (btor_sig.cell == nullptr) { if (btor_sig.sig.empty()) { ywmap_json.value(nullptr); return; } ywmap_json.begin_array(); ywmap_json.compact(); for (auto &chunk : btor_sig.sig.chunks()) { log_assert(chunk.is_wire()); ywmap_json.begin_object(); ywmap_json.entry("path", witness_path(chunk.wire)); ywmap_json.entry("width", chunk.width); ywmap_json.entry("offset", chunk.offset); ywmap_json.end_object(); } ywmap_json.end_array(); } else { ywmap_json.begin_object(); ywmap_json.compact(); ywmap_json.entry("path", witness_path(btor_sig.cell)); Mem *mem = mem_cells[btor_sig.cell]; ywmap_json.entry("width", mem->width); ywmap_json.entry("size", mem->size); ywmap_json.end_object(); } } void btorf_push(const string &id) { if (verbose) { f << indent << stringf(" ; begin %s\n", id); indent += " "; } } void btorf_pop(const string &id) { if (verbose) { indent = indent.substr(4); f << indent << stringf(" ; end %s\n", id); } } int get_bv_sid(int width) { if (sorts_bv.count(width) == 0) { int nid = next_nid++; btorf("%d sort bitvec %d\n", nid, width); sorts_bv[width] = nid; } return sorts_bv.at(width); } int get_mem_sid(int abits, int dbits) { pair key(abits, dbits); if (sorts_mem.count(key) == 0) { int addr_sid = get_bv_sid(abits); int data_sid = get_bv_sid(dbits); int nid = next_nid++; btorf("%d sort array %d %d\n", nid, addr_sid, data_sid); sorts_mem[key] = nid; } return sorts_mem.at(key); } void add_nid_sig(int nid, const SigSpec &sig) { if (verbose) f << indent << stringf("; %d %s\n", nid, log_signal(sig)); for (int i = 0; i < GetSize(sig); i++) bit_nid[sig[i]] = make_pair(nid, i); sig_nid[sig] = nid; nid_width[nid] = GetSize(sig); } void export_cell(Cell *cell) { if (cell_recursion_guard.count(cell)) { 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); } cell_recursion_guard.insert(cell); btorf_push(log_id(cell)); if (cell->type.in(ID($add), ID($sub), ID($mul), ID($and), ID($or), ID($xor), ID($xnor), ID($shl), ID($sshl), ID($shr), ID($sshr), ID($shift), ID($shiftx), ID($concat), ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_))) { string btor_op; if (cell->type == ID($add)) btor_op = "add"; if (cell->type == ID($sub)) btor_op = "sub"; if (cell->type == ID($mul)) btor_op = "mul"; if (cell->type.in(ID($shl), ID($sshl))) btor_op = "sll"; if (cell->type == ID($shr)) btor_op = "srl"; if (cell->type == ID($sshr)) btor_op = "sra"; if (cell->type.in(ID($shift), ID($shiftx))) btor_op = "shift"; if (cell->type.in(ID($and), ID($_AND_))) btor_op = "and"; if (cell->type.in(ID($or), ID($_OR_))) btor_op = "or"; if (cell->type.in(ID($xor), ID($_XOR_))) btor_op = "xor"; if (cell->type == ID($concat)) btor_op = "concat"; if (cell->type == ID($_NAND_)) btor_op = "nand"; if (cell->type == ID($_NOR_)) btor_op = "nor"; if (cell->type.in(ID($xnor), ID($_XNOR_))) btor_op = "xnor"; log_assert(!btor_op.empty()); int width_ay = std::max(GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::Y))); int width = std::max(width_ay, GetSize(cell->getPort(ID::B))); bool a_signed = cell->hasParam(ID::A_SIGNED) ? cell->getParam(ID::A_SIGNED).as_bool() : false; bool b_signed = cell->hasParam(ID::B_SIGNED) ? cell->getParam(ID::B_SIGNED).as_bool() : false; if (btor_op == "shift" && !b_signed) btor_op = "srl"; if (cell->type.in(ID($shl), ID($sshl), ID($shr), ID($sshr))) b_signed = false; if (cell->type == ID($sshr) && !a_signed) btor_op = "srl"; int sid = get_bv_sid(width); int nid; int nid_a; if (cell->type.in(ID($shl), ID($shr), ID($shift), ID($shiftx)) && a_signed && width_ay < width) { // sign-extend A up to the width of Y int nid_a_padded = get_sig_nid(cell->getPort(ID::A), width_ay, a_signed); // zero-extend the rest int zeroes = get_sig_nid(Const(0, width-width_ay)); nid_a = next_nid++; btorf("%d concat %d %d %d\n", nid_a, sid, zeroes, nid_a_padded); } else { nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); } int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed); if (btor_op == "shift") { int nid_r = next_nid++; btorf("%d srl %d %d %d\n", nid_r, sid, nid_a, nid_b); int nid_b_neg = next_nid++; btorf("%d neg %d %d\n", nid_b_neg, sid, nid_b); int nid_l = next_nid++; btorf("%d sll %d %d %d\n", nid_l, sid, nid_a, nid_b_neg); int sid_bit = get_bv_sid(1); int nid_zero = get_sig_nid(Const(0, width)); int nid_b_ltz = next_nid++; 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)); } else { nid = next_nid++; 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)); if (GetSize(sig) < width) { int sid = get_bv_sid(GetSize(sig)); int nid2 = next_nid++; btorf("%d slice %d %d %d 0\n", nid2, sid, nid, GetSize(sig)-1); nid = nid2; } add_nid_sig(nid, sig); goto okay; } if (cell->type.in(ID($div), ID($mod), ID($modfloor))) { bool a_signed = cell->hasParam(ID::A_SIGNED) ? cell->getParam(ID::A_SIGNED).as_bool() : false; bool b_signed = cell->hasParam(ID::B_SIGNED) ? cell->getParam(ID::B_SIGNED).as_bool() : false; string btor_op; if (cell->type == ID($div)) btor_op = "div"; // "rem" = truncating modulo if (cell->type == ID($mod)) btor_op = "rem"; // "mod" = flooring modulo if (cell->type == ID($modfloor)) { // "umod" doesn't exist because it's the same as "urem" btor_op = a_signed || b_signed ? "mod" : "rem"; } log_assert(!btor_op.empty()); int width = GetSize(cell->getPort(ID::Y)); width = std::max(width, GetSize(cell->getPort(ID::A))); width = std::max(width, GetSize(cell->getPort(ID::B))); int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed); 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, sid, nid_a, nid_b, getinfo(cell)); SigSpec sig = sigmap(cell->getPort(ID::Y)); if (GetSize(sig) < width) { int sid = get_bv_sid(GetSize(sig)); int nid2 = next_nid++; btorf("%d slice %d %d %d 0\n", nid2, sid, nid, GetSize(sig)-1); nid = nid2; } add_nid_sig(nid, sig); goto okay; } if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_))) { int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid_b = get_sig_nid(cell->getPort(ID::B)); int nid1 = next_nid++; int nid2 = next_nid++; 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)); } 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)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); add_nid_sig(nid2, sig); goto okay; } if (cell->type.in(ID($_OAI3_), ID($_AOI3_))) { int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid_b = get_sig_nid(cell->getPort(ID::B)); int nid_c = get_sig_nid(cell->getPort(ID::C)); int nid1 = next_nid++; int nid2 = next_nid++; int nid3 = next_nid++; 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)); } 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)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); add_nid_sig(nid3, sig); goto okay; } if (cell->type.in(ID($_OAI4_), ID($_AOI4_))) { int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid_b = get_sig_nid(cell->getPort(ID::B)); int nid_c = get_sig_nid(cell->getPort(ID::C)); int nid_d = get_sig_nid(cell->getPort(ID::D)); int nid1 = next_nid++; int nid2 = next_nid++; int nid3 = next_nid++; int nid4 = next_nid++; if (cell->type == ID($_OAI4_)) { 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)); } 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)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); add_nid_sig(nid4, sig); goto okay; } if (cell->type.in(ID($lt), ID($le), ID($eq), ID($eqx), ID($ne), ID($nex), ID($ge), ID($gt))) { string btor_op; if (cell->type == ID($lt)) btor_op = "lt"; if (cell->type == ID($le)) btor_op = "lte"; if (cell->type.in(ID($eq), ID($eqx))) btor_op = "eq"; if (cell->type.in(ID($ne), ID($nex))) btor_op = "neq"; if (cell->type == ID($ge)) btor_op = "gte"; if (cell->type == ID($gt)) btor_op = "gt"; log_assert(!btor_op.empty()); int width = 1; width = std::max(width, GetSize(cell->getPort(ID::A))); width = std::max(width, GetSize(cell->getPort(ID::B))); bool a_signed = cell->hasParam(ID::A_SIGNED) ? cell->getParam(ID::A_SIGNED).as_bool() : false; bool b_signed = cell->hasParam(ID::B_SIGNED) ? cell->getParam(ID::B_SIGNED).as_bool() : false; int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed); 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, sid, nid_a, nid_b, getinfo(cell)); } else { 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)); if (GetSize(sig) > 1) { int sid = get_bv_sid(GetSize(sig)); int nid2 = next_nid++; btorf("%d uext %d %d %d\n", nid2, sid, nid, GetSize(sig) - 1); nid = nid2; } add_nid_sig(nid, sig); goto okay; } 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"; if (cell->type == ID($neg)) btor_op = "neg"; int width = std::max(GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::Y))); bool a_signed = cell->hasParam(ID::A_SIGNED) ? cell->getParam(ID::A_SIGNED).as_bool() : false; int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed); SigSpec sig = sigmap(cell->getPort(ID::Y)); // the $pos/$buf cells just pass through, all other cells need an actual operation applied int nid = nid_a; 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, sid, nid_a, getinfo(cell)); } if (GetSize(sig) < width) { int sid = get_bv_sid(GetSize(sig)); int nid2 = next_nid++; btorf("%d slice %d %d %d 0\n", nid2, sid, nid, GetSize(sig)-1); nid = nid2; } add_nid_sig(nid, sig); goto okay; } if (cell->type.in(ID($logic_and), ID($logic_or), ID($logic_not))) { string btor_op; if (cell->type == ID($logic_and)) btor_op = "and"; if (cell->type == ID($logic_or)) btor_op = "or"; if (cell->type == ID($logic_not)) btor_op = "not"; log_assert(!btor_op.empty()); int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid_b = btor_op != "not" ? get_sig_nid(cell->getPort(ID::B)) : 0; if (GetSize(cell->getPort(ID::A)) > 1) { int nid_red_a = next_nid++; btorf("%d redor %d %d\n", nid_red_a, sid, nid_a); nid_a = nid_red_a; } if (btor_op != "not" && GetSize(cell->getPort(ID::B)) > 1) { int nid_red_b = next_nid++; btorf("%d redor %d %d\n", nid_red_b, sid, nid_b); nid_b = nid_red_b; } int nid = next_nid++; if (btor_op != "not") 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, sid, nid_a, getinfo(cell)); SigSpec sig = sigmap(cell->getPort(ID::Y)); if (GetSize(sig) > 1) { int sid = get_bv_sid(GetSize(sig)); int zeros_nid = get_sig_nid(Const(0, GetSize(sig)-1)); int nid2 = next_nid++; btorf("%d concat %d %d %d\n", nid2, sid, zeros_nid, nid); nid = nid2; } add_nid_sig(nid, sig); goto okay; } if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool), ID($reduce_xor), ID($reduce_xnor))) { string btor_op; if (cell->type == ID($reduce_and)) btor_op = "redand"; if (cell->type.in(ID($reduce_or), ID($reduce_bool))) btor_op = "redor"; if (cell->type.in(ID($reduce_xor), ID($reduce_xnor))) btor_op = "redxor"; log_assert(!btor_op.empty()); int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid = next_nid++; if (cell->type == ID($reduce_xnor)) { int nid2 = next_nid++; 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, sid, nid_a, getinfo(cell)); } SigSpec sig = sigmap(cell->getPort(ID::Y)); if (GetSize(sig) > 1) { int sid = get_bv_sid(GetSize(sig)); int zeros_nid = get_sig_nid(Const(0, GetSize(sig)-1)); int nid2 = next_nid++; btorf("%d concat %d %d %d\n", nid2, sid, zeros_nid, nid); nid = nid2; } add_nid_sig(nid, sig); goto okay; } if (cell->type.in(ID($mux), ID($_MUX_), ID($_NMUX_))) { SigSpec sig_a = sigmap(cell->getPort(ID::A)); SigSpec sig_b = sigmap(cell->getPort(ID::B)); SigSpec sig_s = sigmap(cell->getPort(ID::S)); SigSpec sig_y = sigmap(cell->getPort(ID::Y)); int nid_a = get_sig_nid(sig_a); int nid_b = get_sig_nid(sig_b); int nid_s = get_sig_nid(sig_s); int sid = get_bv_sid(GetSize(sig_y)); int nid = next_nid++; if (cell->type == ID($_NMUX_)) { 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)); } else { 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); goto okay; } if (cell->type == ID($pmux)) { SigSpec sig_a = sigmap(cell->getPort(ID::A)); SigSpec sig_b = sigmap(cell->getPort(ID::B)); SigSpec sig_s = sigmap(cell->getPort(ID::S)); SigSpec sig_y = sigmap(cell->getPort(ID::Y)); int width = GetSize(sig_a); int sid = get_bv_sid(width); int nid = get_sig_nid(sig_a); for (int i = 0; i < GetSize(sig_s); i++) { int nid_b = get_sig_nid(sig_b.extract(i*width, width)); 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)); else btorf("%d ite %d %d %d %d\n", nid2, sid, nid_s, nid_b, nid); nid = nid2; } add_nid_sig(nid, sig_y); goto okay; } if (cell->type.in(ID($dff), ID($ff), ID($anyinit), ID($_DFF_P_), ID($_DFF_N), ID($_FF_))) { SigSpec sig_d = sigmap(cell->getPort(ID::D)); SigSpec sig_q = sigmap(cell->getPort(ID::Q)); if ((!info_filename.empty() || ywmap_json.active()) && cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_))) { SigSpec sig_c = sigmap(cell->getPort(cell->type == ID($dff) ? ID::CLK : ID::C)); int nid = get_sig_nid(sig_c); bool negedge = false; if (cell->type == ID($_DFF_N_)) negedge = true; if (cell->type == ID($dff) && !cell->getParam(ID::CLK_POLARITY).as_bool()) negedge = true; if (!info_filename.empty()) info_clocks[nid] |= negedge ? 2 : 1; if (ywmap_json.active()) ywmap_clock_bits[sig_c] |= negedge ? 2 : 1; } IdString symbol; if (sig_q.is_wire()) { Wire *w = sig_q.as_wire(); if (w->port_id == 0) { statewires.insert(w); symbol = w->name; } } 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); else initval_bits.push_back(State::Sx); Const initval = initval_bits.build(); int nid_init_val = -1; if (!initval.is_fully_undef()) nid_init_val = get_sig_nid(initval, -1, false, true); int sid = get_bv_sid(GetSize(sig_q)); int nid = next_nid++; if (symbol.empty() || (!print_internal_names && symbol[0] == '$')) btorf("%d state %d\n", nid, sid); else btorf("%d state %d %s\n", nid, sid, log_id(symbol)); if (cell->get_bool_attribute(ID(clk2fflogic))) ywmap_state(cell->getPort(ID::D)); // For a clk2fflogic FF the named signal is the D input not the Q output else ywmap_state(sig_q); if (nid_init_val >= 0) { int nid_init = next_nid++; if (verbose) btorf("; initval = %s\n", log_signal(initval)); btorf("%d init %d %d %d\n", nid_init, sid, nid, nid_init_val); } ff_todo.push_back(make_pair(nid, cell)); add_nid_sig(nid, sig_q); goto okay; } if (cell->type.in(ID($anyconst), ID($anyseq))) { SigSpec sig_y = sigmap(cell->getPort(ID::Y)); int sid = get_bv_sid(GetSize(sig_y)); int nid = next_nid++; btorf("%d state %d%s\n", nid, sid, getinfo(cell)); ywmap_state(sig_y); if (cell->type == ID($anyconst)) { int nid2 = next_nid++; btorf("%d next %d %d %d\n", nid2, sid, nid, nid); } add_nid_sig(nid, sig_y); goto okay; } if (cell->type == ID($initstate)) { SigSpec sig_y = sigmap(cell->getPort(ID::Y)); if (initstate_nid < 0) { int sid = get_bv_sid(1); 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)); 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); ywmap_state(sig_y); } add_nid_sig(initstate_nid, sig_y); goto okay; } if (cell->is_mem_cell()) { Mem *mem = mem_cells[cell]; int abits = ceil_log2(mem->size); bool asyncwr = false; bool syncwr = false; for (auto &port : mem->wr_ports) { if (port.clk_enable) syncwr = true; else asyncwr = true; } if (asyncwr && syncwr) log_error("Memory %s.%s has mixed async/sync write ports.\n", log_id(module), log_id(mem->memid)); for (auto &port : mem->rd_ports) { if (port.clk_enable) log_error("Memory %s.%s has sync read ports. Please use memory_nordff to convert them first.\n", log_id(module), log_id(mem->memid)); } int data_sid = get_bv_sid(mem->width); int bool_sid = get_bv_sid(1); int sid = get_mem_sid(abits, mem->width); int nid_init_val = -1; if (!mem->inits.empty()) { Const initdata = mem->get_init_data(); bool constword = true; Const firstword = initdata.extract(0, mem->width); for (int i = 1; i < mem->size; i++) { Const thisword = initdata.extract(i*mem->width, mem->width); if (thisword != firstword) { constword = false; break; } } // 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)); nid_init_val = get_sig_nid(firstword, -1, false, true); } else { nid_init_val = next_nid++; btorf("%d state %d\n", nid_init_val, sid); ywmap_state(nullptr); for (int i = 0; i < mem->size; i++) { Const thisword = initdata.extract(i*mem->width, mem->width); if (thisword.is_fully_undef()) continue; Const thisaddr(i, abits); int nid_thisword = get_sig_nid(thisword, -1, false, true); int nid_thisaddr = get_sig_nid(thisaddr, -1, false, true); int last_nid_init_val = nid_init_val; nid_init_val = next_nid++; if (verbose) btorf("; initval[%d] = %s\n", i, log_signal(thisword)); btorf("%d write %d %d %d %d\n", nid_init_val, sid, last_nid_init_val, nid_thisaddr, nid_thisword); } } } int nid = next_nid++; int nid_head = nid; if (mem->memid[0] == '$') btorf("%d state %d\n", nid, sid); else btorf("%d state %d %s\n", nid, sid, log_id(mem->memid)); ywmap_state(cell); if (nid_init_val >= 0) { int nid_init = next_nid++; btorf("%d init %d %d %d\n", nid_init, sid, nid, nid_init_val); } if (asyncwr) { for (auto &port : mem->wr_ports) { SigSpec wa = port.addr; wa.extend_u0(abits); int wa_nid = get_sig_nid(wa); int wd_nid = get_sig_nid(port.data); int we_nid = get_sig_nid(port.en); int nid2 = next_nid++; btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid); int nid3 = next_nid++; btorf("%d not %d %d\n", nid3, data_sid, we_nid); int nid4 = next_nid++; btorf("%d and %d %d %d\n", nid4, data_sid, nid2, nid3); int nid5 = next_nid++; btorf("%d and %d %d %d\n", nid5, data_sid, wd_nid, we_nid); int nid6 = next_nid++; btorf("%d or %d %d %d\n", nid6, data_sid, nid5, nid4); int nid7 = next_nid++; btorf("%d write %d %d %d %d\n", nid7, sid, nid_head, wa_nid, nid6); int nid8 = next_nid++; btorf("%d redor %d %d\n", nid8, bool_sid, we_nid); int nid9 = next_nid++; btorf("%d ite %d %d %d %d\n", nid9, sid, nid8, nid7, nid_head); nid_head = nid9; } } for (auto &port : mem->rd_ports) { SigSpec ra = port.addr; ra.extend_u0(abits); int ra_nid = get_sig_nid(ra); int rd_nid = next_nid++; btorf("%d read %d %d %d\n", rd_nid, data_sid, nid_head, ra_nid); add_nid_sig(rd_nid, port.data); } if (!asyncwr) { mem_todo.push_back(make_pair(nid, mem)); } else { int nid2 = next_nid++; btorf("%d next %d %d %d\n", nid2, sid, nid, nid_head); } goto okay; } if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) { log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_btor`.\n", log_id(cell->type), log_id(module), log_id(cell)); } if (cell->type.in(ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($dffsr), ID($dffsre)) || cell->type.str().substr(0, 5) == "$_DFF" || cell->type.str().substr(0, 7) == "$_ALDFF") { log_error("Unsupported cell type %s for cell %s.%s -- please run `async2sync; dffunmap` or `clk2fflogic` before `write_btor`.\n", log_id(cell->type), log_id(module), log_id(cell)); } if (cell->type.in(ID($sr), ID($dlatch), ID($adlatch), ID($dlatchsr)) || cell->type.str().substr(0, 8) == "$_DLATCH" || cell->type.str().substr(0, 5) == "$_SR_") { log_error("Unsupported cell type %s for cell %s.%s -- please run `clk2fflogic` before `write_btor`.\n", log_id(cell->type), log_id(module), log_id(cell)); } log_error("Unsupported cell type %s for cell %s.%s.\n", log_id(cell->type), log_id(module), log_id(cell)); okay: btorf_pop(log_id(cell)); cell_recursion_guard.erase(cell); } int get_sig_nid(SigSpec sig, int to_width = -1, bool is_signed = false, bool is_init = false) { int nid = -1; sigmap.apply(sig); for (auto bit : sig) if (bit == State::Sx) goto has_undef_bits; if (0) { has_undef_bits: SigSpec sig_mask_undef, sig_noundef; int first_undef = -1; for (int i = 0; i < GetSize(sig); i++) if (sig[i] == State::Sx) { if (first_undef < 0) first_undef = i; sig_mask_undef.append(State::S1); sig_noundef.append(State::S0); } else { sig_mask_undef.append(State::S0); sig_noundef.append(sig[i]); } if (to_width < 0 || first_undef < to_width) { int sid = get_bv_sid(GetSize(sig)); int nid_input = next_nid++; if (is_init) { btorf("%d state %d\n", nid_input, sid); ywmap_state(sig); } else { btorf("%d input %d\n", nid_input, sid); ywmap_input(sig); } int nid_masked_input; if (sig_mask_undef.is_fully_ones()) { nid_masked_input = nid_input; } else { int nid_mask_undef = get_sig_nid(sig_mask_undef); nid_masked_input = next_nid++; btorf("%d and %d %d %d\n", nid_masked_input, sid, nid_input, nid_mask_undef); } if (sig_noundef.is_fully_zero()) { nid = nid_masked_input; } else { int nid_noundef = get_sig_nid(sig_noundef); nid = next_nid++; btorf("%d or %d %d %d\n", nid, sid, nid_masked_input, nid_noundef); } goto extend_or_trim; } sig = sig_noundef; } if (sig_nid.count(sig) == 0) { // , vector> nidbits; // collect all bits for (int i = 0; i < GetSize(sig); i++) { SigBit bit = sig[i]; if (bit_nid.count(bit) == 0) { if (bit.wire == nullptr) { 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()); consts[c] = nid; nid_width[nid] = GetSize(c); } int nid = consts.at(c); for (int j = 0; j < GetSize(c); j++) nidbits.push_back(make_pair(nid, j)); i += GetSize(c)-1; continue; } else { if (bit_cell.count(bit) == 0) { SigSpec s = bit; while (i+GetSize(s) < GetSize(sig) && sig[i+GetSize(s)].wire != nullptr && bit_cell.count(sig[i+GetSize(s)]) == 0) s.append(sig[i+GetSize(s)]); log_warning("No driver for signal %s.\n", log_signal(s)); int sid = get_bv_sid(GetSize(s)); int nid = next_nid++; 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)); i += GetSize(s)-1; continue; } else { export_cell(bit_cell.at(bit)); log_assert(bit_nid.count(bit)); } } } nidbits.push_back(bit_nid.at(bit)); } int width = 0; int nid = -1; // group bits and emit slice-concat chain for (int i = 0; i < GetSize(nidbits); i++) { int nid2 = nidbits[i].first; int lower = nidbits[i].second; int upper = lower; while (i+1 < GetSize(nidbits) && nidbits[i+1].first == nidbits[i].first && nidbits[i+1].second == nidbits[i].second+1) upper++, i++; int nid3 = nid2; if (lower != 0 || upper+1 != nid_width.at(nid2)) { int sid = get_bv_sid(upper-lower+1); nid3 = next_nid++; btorf("%d slice %d %d %d %d\n", nid3, sid, nid2, upper, lower); } int nid4 = nid3; if (nid >= 0) { int sid = get_bv_sid(width+upper-lower+1); nid4 = next_nid++; btorf("%d concat %d %d %d\n", nid4, sid, nid3, nid); } width += upper-lower+1; nid = nid4; } sig_nid[sig] = nid; nid_width[nid] = width; } nid = sig_nid.at(sig); extend_or_trim: if (to_width >= 0 && to_width != GetSize(sig)) { if (to_width < GetSize(sig)) { int sid = get_bv_sid(to_width); int nid2 = next_nid++; btorf("%d slice %d %d %d 0\n", nid2, sid, nid, to_width-1); nid = nid2; } else { int sid = get_bv_sid(to_width); int nid2 = next_nid++; btorf("%d %s %d %d %d\n", nid2, is_signed ? "sext" : "uext", sid, nid, to_width - GetSize(sig)); nid = nid2; } } return nid; } BtorWorker(std::ostream &f, RTLIL::Module *module, bool verbose, bool single_bad, bool cover_mode, bool print_internal_names, string info_filename, string ywmap_filename) : f(f), sigmap(module), module(module), verbose(verbose), single_bad(single_bad), cover_mode(cover_mode), print_internal_names(print_internal_names), info_filename(info_filename) { if (!info_filename.empty()) infof("name %s\n", log_id(module)); if (!ywmap_filename.empty()) ywmap_json.write_to_file(ywmap_filename); memories = Mem::get_all_memories(module); dict mem_dict; for (auto &mem : memories) { mem.narrow(); mem_dict[mem.memid] = &mem; } for (auto cell : module->cells()) if (cell->is_mem_cell()) mem_cells[cell] = mem_dict[cell->parameters.at(ID::MEMID).decode_string()]; btorf_push("inputs"); if (ywmap_json.active()) { for (auto wire : module->wires()) { auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk); if (gclk_attr == wire->attributes.end()) continue; SigSpec sig = sigmap(wire); if (gclk_attr->second == State::S1) ywmap_clock_bits[sig] |= 1; else if (gclk_attr->second == State::S0) ywmap_clock_bits[sig] |= 2; } } for (auto wire : module->wires()) { if (wire->attributes.count(ID::init)) { Const attrval = wire->attributes.at(ID::init); for (int i = 0; i < GetSize(wire) && i < GetSize(attrval); i++) if (attrval[i] == State::S0 || attrval[i] == State::S1) initbits[sigmap(SigBit(wire, i))] = (attrval[i] == State::S1); } if (!wire->port_id || !wire->port_input) continue; SigSpec sig = sigmap(wire); int sid = get_bv_sid(GetSize(sig)); int nid = next_nid++; btorf("%d input %d%s\n", nid, sid, getinfo(wire)); ywmap_input(wire); add_nid_sig(nid, sig); if (!info_filename.empty()) { auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk); if (gclk_attr != wire->attributes.end()) { if (gclk_attr->second == State::S1) info_clocks[nid] |= 1; else if (gclk_attr->second == State::S0) info_clocks[nid] |= 2; } } if (ywmap_json.active()) { for (int i = 0; i < GetSize(sig); i++) { auto input_bit = SigBit(wire, i); auto bit = sigmap(input_bit); if (!ywmap_clock_bits.count(bit)) continue; ywmap_clock_inputs[input_bit] = ywmap_clock_bits[bit]; } } } btorf_pop("inputs"); for (auto cell : module->cells()) for (auto &conn : cell->connections()) { if (!cell->output(conn.first)) continue; for (auto bit : sigmap(conn.second)) bit_cell[bit] = cell; } for (auto wire : module->wires()) { if (!wire->port_id || !wire->port_output) continue; 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)); btorf_pop(stringf("output %s", log_id(wire))); } for (auto cell : module->cells()) { if (cell->type == ID($assume)) { btorf_push(log_id(cell)); int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid_en = get_sig_nid(cell->getPort(ID::EN)); int nid_not_en = next_nid++; int nid_a_or_not_en = next_nid++; int nid = next_nid++; btorf("%d not %d %d\n", nid_not_en, sid, nid_en); 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)); } if (cell->type == ID($assert)) { btorf_push(log_id(cell)); int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid_en = get_sig_nid(cell->getPort(ID::EN)); int nid_not_a = next_nid++; int nid_en_and_not_a = next_nid++; btorf("%d not %d %d\n", nid_not_a, sid, nid_a); btorf("%d and %d %d %d\n", nid_en_and_not_a, sid, nid_en, nid_not_a); if (single_bad && !cover_mode) { 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)); } else { int nid = next_nid++; btorf("%d bad %d%s\n", nid, nid_en_and_not_a, getinfo(cell, true)); if (ywmap_json.active()) ywmap_asserts.emplace_back(cell); } } btorf_pop(log_id(cell)); } if (cell->type == ID($cover) && cover_mode) { btorf_push(log_id(cell)); int sid = get_bv_sid(1); int nid_a = get_sig_nid(cell->getPort(ID::A)); int nid_en = get_sig_nid(cell->getPort(ID::EN)); int nid_en_and_a = next_nid++; btorf("%d and %d %d %d\n", nid_en_and_a, sid, nid_en, nid_a); if (single_bad) { 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)); } btorf_pop(log_id(cell)); } } for (auto wire : module->wires()) { if (wire->port_id || wire->name[0] == '$') continue; btorf_push(stringf("wire %s", log_id(wire))); int sid = get_bv_sid(GetSize(wire)); int nid = get_sig_nid(sigmap(wire)); if (statewires.count(wire)) continue; int this_nid = next_nid++; 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]; btorf_pop(stringf("wire %s", log_id(wire))); continue; } while (!ff_todo.empty() || !mem_todo.empty()) { vector> todo; todo.swap(ff_todo); for (auto &it : todo) { int nid = it.first; Cell *cell = it.second; btorf_push(stringf("next %s", log_id(cell))); 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)); btorf_pop(stringf("next %s", log_id(cell))); } vector> mtodo; mtodo.swap(mem_todo); for (auto &it : mtodo) { int nid = it.first; Mem *mem = it.second; btorf_push(stringf("next %s", log_id(mem->memid))); int abits = ceil_log2(mem->size); int data_sid = get_bv_sid(mem->width); int bool_sid = get_bv_sid(1); int sid = get_mem_sid(abits, mem->width); int nid_head = nid; for (auto &port : mem->wr_ports) { SigSpec wa = port.addr; wa.extend_u0(abits); int wa_nid = get_sig_nid(wa); int wd_nid = get_sig_nid(port.data); int we_nid = get_sig_nid(port.en); int nid2 = next_nid++; btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid); int nid3 = next_nid++; btorf("%d not %d %d\n", nid3, data_sid, we_nid); int nid4 = next_nid++; btorf("%d and %d %d %d\n", nid4, data_sid, nid2, nid3); int nid5 = next_nid++; btorf("%d and %d %d %d\n", nid5, data_sid, wd_nid, we_nid); int nid6 = next_nid++; btorf("%d or %d %d %d\n", nid6, data_sid, nid5, nid4); int nid7 = next_nid++; btorf("%d write %d %d %d %d\n", nid7, sid, nid_head, wa_nid, nid6); int nid8 = next_nid++; btorf("%d redor %d %d\n", nid8, bool_sid, we_nid); int nid9 = next_nid++; btorf("%d ite %d %d %d %d\n", nid9, sid, nid8, nid7, nid_head); nid_head = nid9; } 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))); btorf_pop(stringf("next %s", log_id(mem->memid))); } } while (!bad_properties.empty()) { vector todo; bad_properties.swap(todo); int sid = get_bv_sid(1); int cursor = 0; while (cursor+1 < GetSize(todo)) { int nid_a = todo[cursor++]; int nid_b = todo[cursor++]; int nid = next_nid++; bad_properties.push_back(nid); btorf("%d or %d %d %d\n", nid, sid, nid_a, nid_b); } if (!bad_properties.empty()) { if (cursor < GetSize(todo)) bad_properties.push_back(todo[cursor++]); log_assert(cursor == GetSize(todo)); } else { int nid = next_nid++; 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? } } if (!info_filename.empty()) { for (auto &it : info_clocks) { switch (it.second) { case 1: infof("posedge %d\n", it.first); break; case 2: infof("negedge %d\n", it.first); break; case 3: infof("event %d\n", it.first); break; default: log_abort(); } } 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, strerror(errno)); for (auto &it : info_lines) f << it; f.close(); } if (ywmap_json.active()) { ywmap_json.begin_object(); ywmap_json.entry("version", "Yosys Witness BTOR map"); ywmap_json.entry("generator", yosys_maybe_version()); ywmap_json.name("clocks"); ywmap_json.begin_array(); for (auto &entry : ywmap_clock_inputs) { if (entry.second != 1 && entry.second != 2) continue; log_assert(entry.first.is_wire()); ywmap_json.begin_object(); ywmap_json.compact(); ywmap_json.entry("path", witness_path(entry.first.wire)); ywmap_json.entry("offset", entry.first.offset); ywmap_json.entry("edge", entry.second == 1 ? "posedge" : "negedge"); ywmap_json.end_object(); } ywmap_json.end_array(); ywmap_json.name("inputs"); ywmap_json.begin_array(); for (auto &entry : ywmap_inputs) emit_ywmap_btor_sig(entry); ywmap_json.end_array(); ywmap_json.name("states"); ywmap_json.begin_array(); for (auto &entry : ywmap_states) 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(); } } }; struct BtorBackend : public Backend { BtorBackend() : Backend("btor", "write design to BTOR file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_btor [options] [filename]\n"); log("\n"); log("Write a BTOR description of the current design.\n"); log("\n"); log(" -v\n"); log(" Add comments and indentation to BTOR output file\n"); log("\n"); log(" -s\n"); log(" Output only a single bad property for all asserts\n"); log("\n"); log(" -c\n"); log(" Output cover properties using 'bad' statements instead of asserts\n"); log("\n"); log(" -i \n"); log(" Create additional info file with auxiliary information\n"); log("\n"); log(" -x\n"); log(" Output symbols for internal netnames (starting with '$')\n"); log("\n"); log(" -ywmap \n"); log(" Create a map file for conversion to and from Yosys witness traces\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { bool verbose = false, single_bad = false, cover_mode = false, print_internal_names = false; string info_filename; string ywmap_filename; log_header(design, "Executing BTOR backend.\n"); log_push(); Pass::call(design, "bmuxmap"); Pass::call(design, "demuxmap"); Pass::call(design, "bwmuxmap"); log_pop(); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-v") { verbose = true; continue; } if (args[argidx] == "-s") { single_bad = true; continue; } if (args[argidx] == "-c") { cover_mode = true; continue; } if (args[argidx] == "-i" && argidx+1 < args.size()) { info_filename = args[++argidx]; continue; } if (args[argidx] == "-x") { print_internal_names = true; continue; } if (args[argidx] == "-ywmap" && argidx+1 < args.size()) { ywmap_filename = args[++argidx]; continue; } break; } extra_args(f, filename, args, argidx); RTLIL::Module *topmod = design->top_module(); if (topmod == nullptr) log_cmd_error("No top module found.\n"); *f << stringf("; BTOR description generated by %s for module %s.\n", yosys_maybe_version(), log_id(topmod)); BtorWorker(*f, topmod, verbose, single_bad, cover_mode, print_internal_names, info_filename, ywmap_filename); *f << stringf("; end of yosys output\n"); } } BtorBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/btor/test_cells.sh000077500000000000000000000011631520057232300200520ustar00rootroot00000000000000#!/usr/bin/env bash set -ex rm -rf test_cells.tmp mkdir -p test_cells.tmp cd test_cells.tmp ../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$sop /$macc /$mul /$div /$mod /$divfloor /$modfloor /$shiftx' for fn in test_*.il; do ../../../yosys -p " read_rtlil $fn rename gold gate synth read_rtlil $fn miter -equiv -make_assert -flatten gold gate main hierarchy -top main write_btor ${fn%.il}.btor " btormc -kmax 1 --trace-gen --stop-first -v ${fn%.il}.btor > ${fn%.il}.out if grep " SATISFIABLE" ${fn%.il}.out; then echo "Check failed for ${fn%.il}." exit 1 fi done echo "OK." yosys-0.65/backends/cxxrtl/000077500000000000000000000000001520057232300157275ustar00rootroot00000000000000yosys-0.65/backends/cxxrtl/Makefile.inc000066400000000000000000000012651520057232300201430ustar00rootroot00000000000000 OBJS += backends/cxxrtl/cxxrtl_backend.o $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl.h)) $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h)) $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h)) $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h)) $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc)) $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h)) $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.cc)) $(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.h)) yosys-0.65/backends/cxxrtl/cxxrtl_backend.cc000066400000000000000000004144611520057232300212430ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2019-2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/utils.h" #include "kernel/celltypes.h" #include "kernel/mem.h" #include "kernel/log.h" #include "kernel/fmt.h" #include "kernel/scopeinfo.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN // [[CITE]] // Peter Eades; Xuemin Lin; W. F. Smyth, "A Fast Effective Heuristic For The Feedback Arc Set Problem" // Information Processing Letters, Vol. 47, pp 319-323, 1993 // https://pdfs.semanticscholar.org/c7ed/d9acce96ca357876540e19664eb9d976637f.pdf // A topological sort (on a cell/wire graph) is always possible in a fully flattened RTLIL design without // processes or logic loops where every wire has a single driver. Logic loops are illegal in RTLIL and wires // with multiple drivers can be split by the `splitnets` pass; however, interdependencies between processes // or module instances can create strongly connected components without introducing evaluation nondeterminism. // We wish to support designs with such benign SCCs (as well as designs with multiple drivers per wire), so // we sort the graph in a way that minimizes feedback arcs. If there are no feedback arcs in the sorted graph, // then a more efficient evaluation method is possible, since eval() will always immediately converge. template struct Scheduler { struct Vertex { T *data; Vertex *prev, *next; pool preds, succs; Vertex() : data(NULL), prev(this), next(this) {} Vertex(T *data) : data(data), prev(NULL), next(NULL) {} bool empty() const { log_assert(data == NULL); if (next == this) { log_assert(prev == next); return true; } return false; } void link(Vertex *list) { log_assert(prev == NULL && next == NULL); next = list; prev = list->prev; list->prev->next = this; list->prev = this; } void unlink() { log_assert(prev->next == this && next->prev == this); prev->next = next; next->prev = prev; next = prev = NULL; } int delta() const { return succs.size() - preds.size(); } }; std::vector vertices; Vertex *sources = new Vertex; Vertex *sinks = new Vertex; dict bins; ~Scheduler() { delete sources; delete sinks; for (auto bin : bins) delete bin.second; for (auto vertex : vertices) delete vertex; } Vertex *add(T *data) { Vertex *vertex = new Vertex(data); vertices.push_back(vertex); return vertex; } void relink(Vertex *vertex) { if (vertex->succs.empty()) vertex->link(sinks); else if (vertex->preds.empty()) vertex->link(sources); else { int delta = vertex->delta(); if (!bins.count(delta)) bins[delta] = new Vertex; vertex->link(bins[delta]); } } Vertex *remove(Vertex *vertex) { vertex->unlink(); for (auto pred : vertex->preds) { if (pred == vertex) continue; log_assert(pred->succs[vertex]); pred->unlink(); pred->succs.erase(vertex); relink(pred); } for (auto succ : vertex->succs) { if (succ == vertex) continue; log_assert(succ->preds[vertex]); succ->unlink(); succ->preds.erase(vertex); relink(succ); } vertex->preds.clear(); vertex->succs.clear(); return vertex; } std::vector schedule() { std::vector s1, s2r; for (auto vertex : vertices) relink(vertex); bool bins_empty = false; while (!(sinks->empty() && sources->empty() && bins_empty)) { while (!sinks->empty()) s2r.push_back(remove(sinks->next)); while (!sources->empty()) s1.push_back(remove(sources->next)); // Choosing u in this implementation isn't O(1), but the paper handwaves which data structure they suggest // using to get O(1) relinking *and* find-max-key ("it is clear"... no it isn't), so this code uses a very // naive implementation of find-max-key. bins_empty = true; bins.template sort>(); for (auto bin : bins) { if (!bin.second->empty()) { bins_empty = false; s1.push_back(remove(bin.second->next)); break; } } } s1.insert(s1.end(), s2r.rbegin(), s2r.rend()); return s1; } }; bool is_unary_cell(RTLIL::IdString type) { return type.in( ID($not), ID($logic_not), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool), ID($pos), ID($neg)); } bool is_binary_cell(RTLIL::IdString type) { return type.in( ID($and), ID($or), ID($xor), ID($xnor), ID($logic_and), ID($logic_or), ID($shl), ID($sshl), ID($shr), ID($sshr), ID($shift), ID($shiftx), ID($eq), ID($ne), ID($eqx), ID($nex), ID($gt), ID($ge), ID($lt), ID($le), ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($modfloor), ID($divfloor)); } bool is_extending_cell(RTLIL::IdString type) { return !type.in( ID($logic_not), ID($logic_and), ID($logic_or), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool)); } 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($bwmux)); } bool is_ff_cell(RTLIL::IdString type) { return type.in( ID($dff), ID($dffe), ID($sdff), ID($sdffe), ID($sdffce), ID($adff), ID($adffe), ID($dffsr), ID($dffsre), ID($aldff), ID($aldffe), ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr)); } bool is_internal_cell(RTLIL::IdString type) { return !type.isPublic() && !type.begins_with("$paramod"); } bool is_effectful_cell(RTLIL::IdString type) { return type.in(ID($print), ID($check)); } bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell) { RTLIL::Module *cell_module = cell->module->design->module(cell->type); log_assert(cell_module != nullptr); return cell_module->get_bool_attribute(ID(cxxrtl_blackbox)); } bool is_memwr_process(const RTLIL::Process *process) { for (auto sync : process->syncs) if (!sync->mem_write_actions.empty()) return true; return false; } enum class CxxrtlPortType { UNKNOWN = 0, // or mixed comb/sync COMB = 1, SYNC = 2, }; CxxrtlPortType cxxrtl_port_type(RTLIL::Module *module, RTLIL::IdString port) { RTLIL::Wire *output_wire = module->wire(port); log_assert(output_wire != nullptr); bool is_comb = output_wire->get_bool_attribute(ID(cxxrtl_comb)); bool is_sync = output_wire->get_bool_attribute(ID(cxxrtl_sync)); if (is_comb && is_sync) log_cmd_error("Port `%s.%s' is marked as both `cxxrtl_comb` and `cxxrtl_sync`.\n", log_id(module), log_signal(output_wire)); else if (is_comb) return CxxrtlPortType::COMB; else if (is_sync) return CxxrtlPortType::SYNC; return CxxrtlPortType::UNKNOWN; } CxxrtlPortType cxxrtl_port_type(const RTLIL::Cell *cell, RTLIL::IdString port) { RTLIL::Module *cell_module = cell->module->design->module(cell->type); if (cell_module == nullptr || !cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) return CxxrtlPortType::UNKNOWN; return cxxrtl_port_type(cell_module, port); } bool is_cxxrtl_comb_port(const RTLIL::Cell *cell, RTLIL::IdString port) { return cxxrtl_port_type(cell, port) == CxxrtlPortType::COMB; } bool is_cxxrtl_sync_port(const RTLIL::Cell *cell, RTLIL::IdString port) { return cxxrtl_port_type(cell, port) == CxxrtlPortType::SYNC; } struct FlowGraph { struct Node { enum class Type { CONNECT, CELL_SYNC, CELL_EVAL, EFFECT_SYNC, PROCESS_SYNC, PROCESS_CASE, MEM_RDPORT, MEM_WRPORTS, }; Type type; RTLIL::SigSig connect = {}; const RTLIL::Cell *cell = nullptr; std::vector cells; const RTLIL::Process *process = nullptr; const Mem *mem = nullptr; int portidx; }; std::vector nodes; 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 bit_has_state; ~FlowGraph() { for (auto node : nodes) delete node; } void add_defs(Node *node, const RTLIL::SigSpec &sig, bool is_ff, bool inlinable) { for (auto chunk : sig.chunks()) if (chunk.wire) { if (is_ff) { // A sync def means that a wire holds design state because it is driven directly by // a flip-flop output. Such a wire can never be unbuffered. wire_sync_defs[chunk.wire].insert(node); node_sync_defs[node].insert(chunk.wire); } else { // A comb def means that a wire doesn't hold design state. It might still be connected, // indirectly, to a flip-flop output. wire_comb_defs[chunk.wire].insert(node); node_comb_defs[node].insert(chunk.wire); } } 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()) { // Only a single def of a wire can be inlined. (Multiple defs of a wire are unsound, but we // handle them anyway to avoid assertion failures later.) if (!wire_def_inlinable.count(sig.as_wire())) wire_def_inlinable[sig.as_wire()] = inlinable; else wire_def_inlinable[sig.as_wire()] = false; } } void add_uses(Node *node, const RTLIL::SigSpec &sig) { for (auto chunk : sig.chunks()) if (chunk.wire) { wire_uses[chunk.wire].insert(node); node_uses[node].insert(chunk.wire); // Only a single use of an entire wire in the right order can be inlined. (But the use can include // other chunks.) This is tracked per-node because a wire used by multiple nodes can still be inlined // if all but one of those nodes is dead. if (!wire_use_inlinable[chunk.wire].count(node)) wire_use_inlinable[chunk.wire][node] = true; else wire_use_inlinable[chunk.wire][node] = false; } } bool is_inlinable(const RTLIL::Wire *wire) const { // Can the wire be inlined at all? if (wire_def_inlinable.count(wire)) return wire_def_inlinable.at(wire); return false; } 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) return false; Node *node = *nodes.begin(); log_assert(node_uses.at(node).count(wire)); if (is_inlinable(wire) && wire_use_inlinable.count(wire) && wire_use_inlinable.at(wire).count(node)) return wire_use_inlinable.at(wire).at(node); return false; } // Connections void add_connect_defs_uses(Node *node, const RTLIL::SigSig &conn) { add_defs(node, conn.first, /*is_ff=*/false, /*inlinable=*/true); add_uses(node, conn.second); } Node *add_node(const RTLIL::SigSig &conn) { Node *node = new Node; node->type = Node::Type::CONNECT; node->connect = conn; nodes.push_back(node); add_connect_defs_uses(node, conn); return node; } // Cells void add_cell_sync_defs(Node *node, const RTLIL::Cell *cell) { // To understand why this node type is necessary and why it produces comb defs, consider a cell // with input \i and sync output \o, used in a design such that \i is connected to \o. This does // not result in a feedback arc because the output is synchronous. However, a naive implementation // of code generation for cells that assigns to inputs, evaluates cells, assigns from outputs // would not be able to immediately converge... // // wire<1> i_tmp; // cell->p_i = i_tmp.curr; // cell->eval(); // i_tmp.next = cell->p_o.curr; // // ... since the wire connecting the input and output ports would not be localizable. To solve // this, the cell is split into two scheduling nodes; one exclusively for sync outputs, and // another for inputs and all non-sync outputs. This way the generated code can be rearranged... // // value<1> i_tmp; // i_tmp = cell->p_o.curr; // cell->p_i = i_tmp; // cell->eval(); // // eliminating the unnecessary delta cycle. Conceptually, the CELL_SYNC node type is a series of // connections of the form `connect \lhs \cell.\sync_output`; the right-hand side of these is not // expressible as a wire in RTLIL. If it was expressible, then `\cell.\sync_output` would have // a sync def, and this node would be an ordinary CONNECT node, with `\lhs` having a comb def. // Because it isn't, a special node type is used, the right-hand side does not appear anywhere, // and the left-hand side has a comb def. for (auto conn : cell->connections()) if (cell->output(conn.first)) if (is_cxxrtl_sync_port(cell, conn.first)) { // See note regarding inlinability below. add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false); } } void add_cell_eval_defs_uses(Node *node, const RTLIL::Cell *cell) { for (auto conn : cell->connections()) { if (cell->output(conn.first)) { if (is_inlinable_cell(cell->type)) add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/true); else if (is_ff_cell(cell->type)) add_defs(node, conn.second, /*is_ff=*/true, /*inlinable=*/false); else if (is_internal_cell(cell->type)) add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false); else if (!is_cxxrtl_sync_port(cell, conn.first)) { // Although at first it looks like outputs of user-defined cells may always be inlined, the reality is // more complex. Fully sync outputs produce no defs and so don't participate in inlining. Fully comb // outputs are assigned in a different way depending on whether the cell's eval() immediately converged. // Unknown/mixed outputs could be inlined, but should be rare in practical designs and don't justify // the infrastructure required to inline outputs of cells with many of them. add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false); } } if (cell->input(conn.first)) add_uses(node, conn.second); } } Node *add_node(const RTLIL::Cell *cell) { log_assert(cell->known()); bool has_fully_sync_outputs = false; for (auto conn : cell->connections()) if (cell->output(conn.first) && is_cxxrtl_sync_port(cell, conn.first)) { has_fully_sync_outputs = true; break; } if (has_fully_sync_outputs) { Node *node = new Node; node->type = Node::Type::CELL_SYNC; node->cell = cell; nodes.push_back(node); add_cell_sync_defs(node, cell); } Node *node = new Node; node->type = Node::Type::CELL_EVAL; node->cell = cell; nodes.push_back(node); add_cell_eval_defs_uses(node, cell); return node; } Node *add_effect_sync_node(std::vector cells) { Node *node = new Node; node->type = Node::Type::EFFECT_SYNC; node->cells = cells; nodes.push_back(node); return node; } // Processes void add_case_rule_defs_uses(Node *node, const RTLIL::CaseRule *case_) { for (auto &action : case_->actions) { add_defs(node, action.first, /*is_ff=*/false, /*inlinable=*/false); add_uses(node, action.second); } for (auto sub_switch : case_->switches) { add_uses(node, sub_switch->signal); for (auto sub_case : sub_switch->cases) { for (auto &compare : sub_case->compare) add_uses(node, compare); add_case_rule_defs_uses(node, sub_case); } } } void add_sync_rules_defs_uses(Node *node, const RTLIL::Process *process) { for (auto sync : process->syncs) { for (auto &action : sync->actions) { if (sync->type == RTLIL::STp || sync->type == RTLIL::STn || sync->type == RTLIL::STe) add_defs(node, action.first, /*is_ff=*/true, /*inlinable=*/false); else add_defs(node, action.first, /*is_ff=*/false, /*inlinable=*/false); add_uses(node, action.second); } for (auto &memwr : sync->mem_write_actions) { add_uses(node, memwr.address); add_uses(node, memwr.data); add_uses(node, memwr.enable); } } } Node *add_node(const RTLIL::Process *process) { Node *node = new Node; node->type = Node::Type::PROCESS_SYNC; node->process = process; nodes.push_back(node); add_sync_rules_defs_uses(node, process); node = new Node; node->type = Node::Type::PROCESS_CASE; node->process = process; nodes.push_back(node); add_case_rule_defs_uses(node, &process->root_case); return node; } // Memories void add_node(const Mem *mem) { for (int i = 0; i < GetSize(mem->rd_ports); i++) { auto &port = mem->rd_ports[i]; Node *node = new Node; node->type = Node::Type::MEM_RDPORT; node->mem = mem; node->portidx = i; nodes.push_back(node); add_defs(node, port.data, /*is_ff=*/port.clk_enable, /*inlinable=*/false); add_uses(node, port.clk); add_uses(node, port.en); add_uses(node, port.arst); add_uses(node, port.srst); add_uses(node, port.addr); bool transparent = false; for (int j = 0; j < GetSize(mem->wr_ports); j++) { auto &wrport = mem->wr_ports[j]; if (port.transparency_mask[j]) { // Our implementation of transparent read ports reads en, addr and data from every write port // the read port is transparent with. add_uses(node, wrport.en); add_uses(node, wrport.addr); add_uses(node, wrport.data); transparent = true; } } // Also we read the read address twice in this case (prevent inlining). if (transparent) add_uses(node, port.addr); } if (!mem->wr_ports.empty()) { Node *node = new Node; node->type = Node::Type::MEM_WRPORTS; node->mem = mem; nodes.push_back(node); for (auto &port : mem->wr_ports) { add_uses(node, port.clk); add_uses(node, port.en); add_uses(node, port.addr); add_uses(node, port.data); } } } }; std::vector split_by(const std::string &str, const std::string &sep) { std::vector result; size_t prev = 0; while (true) { size_t curr = str.find_first_of(sep, prev); if (curr == std::string::npos) { std::string part = str.substr(prev); if (!part.empty()) result.push_back(part); break; } else { std::string part = str.substr(prev, curr - prev); if (!part.empty()) result.push_back(part); prev = curr + 1; } } return result; } std::string escape_c_string(const std::string &input) { std::string output; output.push_back('"'); for (auto c : input) { if (::isprint(c)) { if (c == '\\' || c == '"') output.push_back('\\'); output.push_back(c); } else { 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())); } return output; } template std::string get_hdl_name(T *object) { if (object->has_attribute(ID::hdlname)) return object->get_string_attribute(ID::hdlname); else return object->name.str().substr(1); } struct WireType { enum Type { // Non-referenced wire; is not a part of the design. UNUSED, // Double-buffered wire; is a class member, and holds design state. BUFFERED, // Single-buffered wire; is a class member, but holds no state. MEMBER, // Single-buffered wire; is a class member, and is computed on demand. OUTLINE, // Local wire; is a local variable in eval method. LOCAL, // Inline wire; is an unnamed temporary in eval method. INLINE, // Alias wire; is replaced with aliasee, except in debug info. ALIAS, // Const wire; is replaced with constant, except in debug info. CONST, }; Type type = UNUSED; const RTLIL::Cell *cell_subst = nullptr; // for INLINE RTLIL::SigSpec sig_subst = {}; // for INLINE, ALIAS, and CONST WireType() = default; WireType(Type type) : type(type) { log_assert(type == UNUSED || type == BUFFERED || type == MEMBER || type == OUTLINE || type == LOCAL); } WireType(Type type, const RTLIL::Cell *cell) : type(type), cell_subst(cell) { log_assert(type == INLINE && is_inlinable_cell(cell->type)); } WireType(Type type, RTLIL::SigSpec sig) : type(type), sig_subst(sig) { log_assert(type == INLINE || (type == ALIAS && sig.is_wire()) || (type == CONST && sig.is_fully_const())); } bool is_buffered() const { return type == BUFFERED; } bool is_member() const { return type == BUFFERED || type == MEMBER || type == OUTLINE; } bool is_outline() const { return type == OUTLINE; } bool is_named() const { return is_member() || type == LOCAL; } bool is_local() const { return type == LOCAL || type == INLINE; } bool is_exact() const { return type == ALIAS || type == CONST; } }; // Tests for a SigSpec that is a valid clock input, clocks have to have a backing wire and be a single bit // using this instead of sig.is_wire() solves issues when the clock is a slice instead of a full wire bool is_valid_clock(const RTLIL::SigSpec& sig) { return sig.is_chunk() && sig.is_bit() && sig[0].wire; } struct CxxrtlWorker { bool split_intf = false; std::string intf_filename; std::string design_ns = "cxxrtl_design"; std::string print_output = "std::cout"; std::ostream *impl_f = nullptr; std::ostream *intf_f = nullptr; bool print_wire_types = false; bool print_debug_wire_types = false; bool run_hierarchy = false; bool run_flatten = false; bool run_proc = false; bool unbuffer_internal = false; bool unbuffer_public = false; bool localize_internal = false; bool localize_public = false; bool inline_internal = false; bool inline_public = false; bool debug_info = false; bool debug_member = false; bool debug_alias = false; bool debug_eval = false; std::ostringstream f; std::string indent; int temporary = 0; dict sigmaps; dict> mod_memories; pool> writable_memories; pool edge_wires; dict wire_init; dict edge_types; dict> schedule, debug_schedule; dict wire_types, debug_wire_types; dict bit_has_state; dict> blackbox_specializations; dict eval_converges; void inc_indent() { indent += "\t"; } void dec_indent() { indent.resize(indent.size() - 1); } // RTLIL allows any characters in names other than whitespace. This presents an issue for generating C++ code // because C++ identifiers may be only alphanumeric, cannot clash with C++ keywords, and cannot clash with cxxrtl // identifiers. This issue can be solved with a name mangling scheme. We choose a name mangling scheme that results // in readable identifiers, does not depend on an up-to-date list of C++ keywords, and is easy to apply. Its rules: // 1. All generated identifiers start with `_`. // 1a. Generated identifiers for public names (beginning with `\`) start with `p_`. // 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(RTLIL::IdString name) { std::string mangled; bool first = true; for (char c : name.str()) { if (first) { first = false; if (c == '\\') mangled += "p_"; else if (c == '$') mangled += "i_"; else log_assert(false); } else { if (isalnum(c)) { mangled += c; } else if (c == '_') { mangled += "__"; } else { char l = c & 0xf, h = (c >> 4) & 0xf; mangled += '_'; mangled += (h < 10 ? '0' + h : 'a' + h - 10); mangled += (l < 10 ? '0' + l : 'a' + l - 10); mangled += '_'; } } } return mangled; } std::string mangle_module_name(RTLIL::IdString name, bool is_blackbox = false) { // Class namespace. if (is_blackbox) return "bb_" + mangle_name(name); return mangle_name(name); } std::string mangle_memory_name(RTLIL::IdString name) { // Class member namespace. return "memory_" + mangle_name(name); } std::string mangle_cell_name(RTLIL::IdString name) { // Class member namespace. return "cell_" + mangle_name(name); } std::string mangle_wire_name(RTLIL::IdString name) { // Class member namespace. return mangle_name(name); } std::string mangle(const RTLIL::Module *module) { return mangle_module_name(module->name, /*is_blackbox=*/module->get_bool_attribute(ID(cxxrtl_blackbox))); } std::string mangle(const Mem *mem) { return mangle_memory_name(mem->memid); } std::string mangle(const RTLIL::Memory *memory) { return mangle_memory_name(memory->name); } std::string mangle(const RTLIL::Cell *cell) { return mangle_cell_name(cell->name); } std::string mangle(const RTLIL::Wire *wire) { return mangle_wire_name(wire->name); } std::string mangle(RTLIL::SigBit sigbit) { log_assert(sigbit.wire != NULL); if (sigbit.wire->width == 1) return mangle(sigbit.wire); return mangle(sigbit.wire) + "_" + std::to_string(sigbit.offset); } std::vector template_param_names(const RTLIL::Module *module) { if (!module->has_attribute(ID(cxxrtl_template))) return {}; 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"); for (const auto ¶m_name : param_names) { // Various lowercase prefixes (p_, i_, cell_, ...) are used for member variables, so require // parameters to start with an uppercase letter to avoid name conflicts. (This is the convention // in both Verilog and C++, anyway.) if (!isupper(param_name[0])) log_cmd_error("Attribute `cxxrtl_template' of module `%s' includes a parameter `%s', " "which does not start with an uppercase letter.\n", log_id(module), param_name.c_str()); } return param_names; } std::string template_params(const RTLIL::Module *module, bool is_decl) { std::vector param_names = template_param_names(module); if (param_names.empty()) return ""; std::string params = "<"; bool first = true; for (const auto ¶m_name : param_names) { if (!first) params += ", "; first = false; if (is_decl) params += "size_t "; params += param_name; } params += ">"; return params; } std::string template_args(const RTLIL::Cell *cell) { RTLIL::Module *cell_module = cell->module->design->module(cell->type); log_assert(cell_module != nullptr); if (!cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) return ""; std::vector param_names = template_param_names(cell_module); if (param_names.empty()) return ""; std::string params = "<"; bool first = true; for (const auto ¶m_name : param_names) { if (!first) params += ", "; first = false; params += "/*" + param_name + "=*/"; RTLIL::IdString id_param_name = '\\' + param_name; if (!cell->hasParam(id_param_name)) log_cmd_error("Cell `%s.%s' does not have a parameter `%s', which is required by the templated module `%s'.\n", log_id(cell->module), log_id(cell), param_name.c_str(), log_id(cell_module)); RTLIL::Const param_value = cell->getParam(id_param_name); if (((param_value.flags & ~RTLIL::CONST_FLAG_SIGNED) != 0) || param_value.as_int() < 0) log_cmd_error("Parameter `%s' of cell `%s.%s', which is required by the templated module `%s', " "is not a positive integer.\n", param_name.c_str(), log_id(cell->module), log_id(cell), log_id(cell_module)); params += std::to_string(cell->getParam(id_param_name).as_int()); } params += ">"; return params; } std::string fresh_temporary() { return stringf("tmp_%d", temporary++); } void dump_attrs(const RTLIL::AttrObject *object) { for (auto attr : object->attributes) { f << indent << "// " << attr.first.str() << ": "; if (attr.second.flags & RTLIL::CONST_FLAG_STRING) { f << attr.second.decode_string(); } else { f << attr.second.as_int(/*is_signed=*/attr.second.flags & RTLIL::CONST_FLAG_SIGNED); } f << "\n"; } } void dump_const_init(const RTLIL::Const &data, int width, int offset = 0, bool fixed_width = false) { const int CHUNK_SIZE = 32; f << "{"; while (width > 0) { int chunk_width = min(width, CHUNK_SIZE); uint32_t chunk = data.extract(offset, chunk_width).as_int(); if (fixed_width) f << stringf("0x%.*xu", (3 + chunk_width) / 4, chunk); else f << stringf("%#xu", chunk); if (width > CHUNK_SIZE) f << ','; offset += CHUNK_SIZE; width -= CHUNK_SIZE; } f << "}"; } void dump_const(const RTLIL::Const &data, int width, int offset = 0, bool fixed_width = false) { f << "value<" << width << ">"; dump_const_init(data, width, offset, fixed_width); } void dump_const(const RTLIL::Const &data) { dump_const(data, data.size()); } bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs, bool for_debug = false) { if (chunk.wire == NULL) { dump_const(chunk.data, chunk.width, chunk.offset); return false; } else { const auto &wire_type = (for_debug ? debug_wire_types : wire_types)[chunk.wire]; switch (wire_type.type) { case WireType::BUFFERED: f << mangle(chunk.wire) << (is_lhs ? ".next" : ".curr"); break; case WireType::MEMBER: case WireType::LOCAL: case WireType::OUTLINE: f << mangle(chunk.wire); break; case WireType::INLINE: log_assert(!is_lhs); if (wire_type.cell_subst != nullptr) { dump_cell_expr(wire_type.cell_subst, for_debug); break; } YS_FALLTHROUGH case WireType::ALIAS: case WireType::CONST: log_assert(!is_lhs); return dump_sigspec(wire_type.sig_subst.extract(chunk.offset, chunk.width), is_lhs, for_debug); case WireType::UNUSED: log_assert(is_lhs); f << "value<" << chunk.width << ">()"; return false; } if (chunk.width == chunk.wire->width && chunk.offset == 0) return false; else if (chunk.width == 1) f << ".slice<" << chunk.offset << ">()"; else f << ".slice<" << chunk.offset+chunk.width-1 << "," << chunk.offset << ">()"; return true; } } bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs, bool for_debug = false) { if (sig.empty()) { f << "value<0>()"; return false; } else if (sig.is_chunk()) { return dump_sigchunk(sig.as_chunk(), is_lhs, for_debug); } else { bool first = true; auto chunks = sig.chunks(); for (auto it = chunks.rbegin(); it != chunks.rend(); it++) { if (!first) f << ".concat("; bool is_complex = dump_sigchunk(*it, is_lhs, for_debug); if (!is_lhs && it->width == 1) { size_t repeat = 1; while ((it + repeat) != chunks.rend() && *(it + repeat) == *it) repeat++; if (repeat > 1) { if (is_complex) f << ".val()"; f << ".repeat<" << repeat << ">()"; } it += repeat - 1; } if (!first) f << ")"; first = false; } return true; } } void dump_sigspec_lhs(const RTLIL::SigSpec &sig, bool for_debug = false) { dump_sigspec(sig, /*is_lhs=*/true, for_debug); } void dump_sigspec_rhs(const RTLIL::SigSpec &sig, bool for_debug = false) { // In the contexts where we want template argument deduction to occur for `template ... value`, // it is necessary to have the argument to already be a `value`, since template argument deduction and implicit // type conversion are mutually exclusive. In these contexts, we use dump_sigspec_rhs() to emit an explicit // type conversion, but only if the expression needs it. bool is_complex = dump_sigspec(sig, /*is_lhs=*/false, for_debug); if (is_complex) f << ".val()"; } void dump_inlined_cells(const std::vector &cells) { if (cells.empty()) { f << indent << "// connection\n"; } else if (cells.size() == 1) { dump_attrs(cells.front()); f << indent << "// cell " << cells.front()->name.str() << "\n"; } else { f << indent << "// cells"; for (auto cell : cells) f << " " << cell->name.str(); f << "\n"; } } void collect_sigspec_rhs(const RTLIL::SigSpec &sig, bool for_debug, std::vector &cells) { for (auto chunk : sig.chunks()) { if (!chunk.wire) continue; const auto &wire_type = wire_types[chunk.wire]; switch (wire_type.type) { case WireType::INLINE: if (wire_type.cell_subst != nullptr) { collect_cell_eval(wire_type.cell_subst, for_debug, cells); break; } YS_FALLTHROUGH case WireType::ALIAS: collect_sigspec_rhs(wire_type.sig_subst, for_debug, cells); break; default: break; } } } void dump_connect_expr(const RTLIL::SigSig &conn, bool for_debug = false) { dump_sigspec_rhs(conn.second, for_debug); } void dump_connect(const RTLIL::SigSig &conn, bool for_debug = false) { std::vector inlined_cells; collect_sigspec_rhs(conn.second, for_debug, inlined_cells); dump_inlined_cells(inlined_cells); f << indent; dump_sigspec_lhs(conn.first, for_debug); f << " = "; dump_connect_expr(conn, for_debug); f << ";\n"; } void collect_connect(const RTLIL::SigSig &conn, bool for_debug, std::vector &cells) { collect_sigspec_rhs(conn.second, for_debug, cells); } void dump_cell_sync(const RTLIL::Cell *cell, bool for_debug = false) { const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; 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) && !conn.second.empty()) { f << indent; dump_sigspec_lhs(conn.second, for_debug); f << " = " << mangle(cell) << access << mangle_wire_name(conn.first) << ".curr;\n"; } } void dump_cell_expr(const RTLIL::Cell *cell, bool for_debug = false) { // Unary cells if (is_unary_cell(cell->type)) { f << cell->type.substr(1); if (is_extending_cell(cell->type)) f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u'); f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">("; dump_sigspec_rhs(cell->getPort(ID::A), for_debug); f << ")"; // Binary cells } else if (is_binary_cell(cell->type)) { f << cell->type.substr(1); if (is_extending_cell(cell->type)) f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') << (cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u'); f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">("; dump_sigspec_rhs(cell->getPort(ID::A), for_debug); f << ", "; dump_sigspec_rhs(cell->getPort(ID::B), for_debug); f << ")"; // Muxes } else if (cell->type == ID($mux)) { f << "("; dump_sigspec_rhs(cell->getPort(ID::S), for_debug); f << " ? "; dump_sigspec_rhs(cell->getPort(ID::B), for_debug); f << " : "; dump_sigspec_rhs(cell->getPort(ID::A), for_debug); f << ")"; // Parallel (one-hot) muxes } else if (cell->type == ID($pmux)) { int width = cell->getParam(ID::WIDTH).as_int(); int s_width = cell->getParam(ID::S_WIDTH).as_int(); for (int part = 0; part < s_width; part++) { f << "("; dump_sigspec_rhs(cell->getPort(ID::S).extract(part), for_debug); f << " ? "; dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width), for_debug); f << " : "; } dump_sigspec_rhs(cell->getPort(ID::A), for_debug); for (int part = 0; part < s_width; part++) { f << ")"; } // Big muxes } else if (cell->type == ID($bmux)) { dump_sigspec_rhs(cell->getPort(ID::A), for_debug); f << ".bmux<"; f << cell->getParam(ID::WIDTH).as_int(); 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); f << ".demux<"; f << GetSize(cell->getPort(ID::Y)); f << ">("; dump_sigspec_rhs(cell->getPort(ID::S), for_debug); f << ").val()"; // Concats } else if (cell->type == ID($concat)) { dump_sigspec_rhs(cell->getPort(ID::B), for_debug); f << ".concat("; dump_sigspec_rhs(cell->getPort(ID::A), for_debug); f << ").val()"; // Slices } else if (cell->type == ID($slice)) { dump_sigspec_rhs(cell->getPort(ID::A), for_debug); f << ".slice<"; f << cell->getParam(ID::OFFSET).as_int() + cell->getParam(ID::Y_WIDTH).as_int() - 1; f << ","; f << cell->getParam(ID::OFFSET).as_int(); f << ">().val()"; } else { log_assert(false); } } void dump_print(const RTLIL::Cell *cell) { Fmt fmt; fmt.parse_rtlil(cell); f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1>{1u}) {\n"; inc_indent(); dict fmt_args; f << indent << "struct : public lazy_fmt {\n"; inc_indent(); f << indent << "std::string operator() () const override {\n"; inc_indent(); fmt.emit_cxxrtl(f, indent, [&](const RTLIL::SigSpec &sig) { if (sig.size() == 0) f << "value<0>()"; else { std::string arg_name = "arg" + std::to_string(fmt_args.size()); fmt_args[arg_name] = sig; f << arg_name; } }, "performer"); dec_indent(); f << indent << "}\n"; f << indent << "struct performer *performer;\n"; for (auto arg : fmt_args) f << indent << "value<" << arg.second.size() << "> " << arg.first << ";\n"; dec_indent(); f << indent << "} formatter;\n"; f << indent << "formatter.performer = performer;\n"; for (auto arg : fmt_args) { f << indent << "formatter." << arg.first << " = "; dump_sigspec_rhs(arg.second); f << ";\n"; } f << indent << "if (performer) {\n"; inc_indent(); f << indent << "static const metadata_map attributes = "; dump_metadata_map(cell->attributes); f << ";\n"; f << indent << "performer->on_print(formatter, attributes);\n"; dec_indent(); f << indent << "} else {\n"; inc_indent(); f << indent << print_output << " << formatter();\n"; dec_indent(); f << indent << "}\n"; dec_indent(); f << indent << "}\n"; } void dump_effect(const RTLIL::Cell *cell) { Fmt fmt; fmt.parse_rtlil(cell); f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::EN)); f << ") {\n"; inc_indent(); dict fmt_args; f << indent << "struct : public lazy_fmt {\n"; inc_indent(); f << indent << "std::string operator() () const override {\n"; inc_indent(); fmt.emit_cxxrtl(f, indent, [&](const RTLIL::SigSpec &sig) { if (sig.size() == 0) f << "value<0>()"; else { std::string arg_name = "arg" + std::to_string(fmt_args.size()); fmt_args[arg_name] = sig; f << arg_name; } }, "performer"); dec_indent(); f << indent << "}\n"; f << indent << "struct performer *performer;\n"; for (auto arg : fmt_args) f << indent << "value<" << arg.second.size() << "> " << arg.first << ";\n"; dec_indent(); f << indent << "} formatter;\n"; f << indent << "formatter.performer = performer;\n"; for (auto arg : fmt_args) { f << indent << "formatter." << arg.first << " = "; dump_sigspec_rhs(arg.second); f << ";\n"; } if (cell->hasPort(ID::A)) { f << indent << "bool condition = (bool)"; dump_sigspec_rhs(cell->getPort(ID::A)); f << ";\n"; } f << indent << "if (performer) {\n"; inc_indent(); f << indent << "static const metadata_map attributes = "; dump_metadata_map(cell->attributes); f << ";\n"; if (cell->type == ID($print)) { f << indent << "performer->on_print(formatter, attributes);\n"; } else if (cell->type == ID($check)) { std::string flavor = cell->getParam(ID::FLAVOR).decode_string(); f << indent << "performer->on_check("; if (flavor == "assert") f << "flavor::ASSERT"; else if (flavor == "assume") f << "flavor::ASSUME"; else if (flavor == "live") f << "flavor::ASSERT_EVENTUALLY"; else if (flavor == "fair") f << "flavor::ASSUME_EVENTUALLY"; else if (flavor == "cover") f << "flavor::COVER"; else log_assert(false); f << ", condition, formatter, attributes);\n"; } else log_assert(false); dec_indent(); f << indent << "} else {\n"; inc_indent(); if (cell->type == ID($print)) { f << indent << print_output << " << formatter();\n"; } else if (cell->type == ID($check)) { std::string flavor = cell->getParam(ID::FLAVOR).decode_string(); if (flavor == "assert" || flavor == "assume") { f << indent << "if (!condition) {\n"; inc_indent(); f << indent << "std::cerr << formatter();\n"; dec_indent(); f << indent << "}\n"; f << indent << "CXXRTL_ASSERT(condition && \"Check failed\");\n"; } } else log_assert(false); dec_indent(); f << indent << "}\n"; dec_indent(); f << indent << "}\n"; } void dump_cell_eval(const RTLIL::Cell *cell, bool for_debug = false) { std::vector inlined_cells; collect_cell_eval(cell, for_debug, inlined_cells); dump_inlined_cells(inlined_cells); // Elidable cells if (is_inlinable_cell(cell->type)) { f << indent; dump_sigspec_lhs(cell->getPort(ID::Y), for_debug); f << " = "; dump_cell_expr(cell, for_debug); f << ";\n"; // Effectful cells } else if (is_effectful_cell(cell->type)) { log_assert(!for_debug); // Sync effectful cells are grouped into EFFECT_SYNC nodes in the FlowGraph. log_assert(!cell->getParam(ID::TRG_ENABLE).as_bool() || (cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0)); if (!cell->getParam(ID::TRG_ENABLE).as_bool()) { // async effectful cell f << indent << "auto " << mangle(cell) << "_next = "; dump_sigspec_rhs(cell->getPort(ID::EN)); f << ".concat("; if (cell->type == ID($print)) dump_sigspec_rhs(cell->getPort(ID::ARGS)); else if (cell->type == ID($check)) dump_sigspec_rhs(cell->getPort(ID::A)); else log_assert(false); f << ").val();\n"; f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_next) {\n"; inc_indent(); dump_effect(cell); f << indent << mangle(cell) << " = " << mangle(cell) << "_next;\n"; dec_indent(); f << indent << "}\n"; } else { // initial effectful cell f << indent << "if (!" << mangle(cell) << ") {\n"; inc_indent(); dump_effect(cell); f << indent << mangle(cell) << " = value<1>{1u};\n"; dec_indent(); f << indent << "}\n"; } // Flip-flops } else if (is_ff_cell(cell->type)) { log_assert(!for_debug); // Clocks might be slices of larger signals but should only ever be single bit if (cell->hasPort(ID::CLK) && is_valid_clock(cell->getPort(ID::CLK))) { // Edge-sensitive logic RTLIL::SigBit clk_bit = cell->getPort(ID::CLK)[0]; clk_bit = sigmaps[clk_bit.wire->module](clk_bit); if (clk_bit.wire) { f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_") << mangle(clk_bit) << ") {\n"; } else { f << indent << "if (false) {\n"; } inc_indent(); if (cell->hasPort(ID::EN)) { f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1> {" << cell->getParam(ID::EN_POLARITY).as_bool() << "u}) {\n"; inc_indent(); } f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; dump_sigspec_rhs(cell->getPort(ID::D)); f << ";\n"; if (cell->hasPort(ID::EN) && cell->type != ID($sdffce)) { dec_indent(); f << indent << "}\n"; } if (cell->hasPort(ID::SRST)) { f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::SRST)); f << " == value<1> {" << cell->getParam(ID::SRST_POLARITY).as_bool() << "u}) {\n"; inc_indent(); f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; dump_const(cell->getParam(ID::SRST_VALUE)); f << ";\n"; dec_indent(); f << indent << "}\n"; } if (cell->hasPort(ID::EN) && cell->type == ID($sdffce)) { dec_indent(); f << indent << "}\n"; } dec_indent(); f << indent << "}\n"; } else if (cell->hasPort(ID::EN)) { // Level-sensitive logic f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1> {" << cell->getParam(ID::EN_POLARITY).as_bool() << "u}) {\n"; inc_indent(); f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; dump_sigspec_rhs(cell->getPort(ID::D)); f << ";\n"; dec_indent(); f << indent << "}\n"; } if (cell->hasPort(ID::ARST)) { // Asynchronous reset (entire coarse cell at once) f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::ARST)); f << " == value<1> {" << cell->getParam(ID::ARST_POLARITY).as_bool() << "u}) {\n"; inc_indent(); f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; dump_const(cell->getParam(ID::ARST_VALUE)); f << ";\n"; dec_indent(); f << indent << "}\n"; } if (cell->hasPort(ID::ALOAD)) { // Asynchronous load f << indent << "if ("; dump_sigspec_rhs(cell->getPort(ID::ALOAD)); f << " == value<1> {" << cell->getParam(ID::ALOAD_POLARITY).as_bool() << "u}) {\n"; inc_indent(); f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; dump_sigspec_rhs(cell->getPort(ID::AD)); f << ";\n"; dec_indent(); f << indent << "}\n"; } if (cell->hasPort(ID::SET)) { // Asynchronous set (for individual bits) f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; dump_sigspec_rhs(cell->getPort(ID::Q)); f << ".update("; dump_const(RTLIL::Const(RTLIL::S1, cell->getParam(ID::WIDTH).as_int())); f << ", "; dump_sigspec_rhs(cell->getPort(ID::SET)); f << (cell->getParam(ID::SET_POLARITY).as_bool() ? "" : ".bit_not()") << ");\n"; } if (cell->hasPort(ID::CLR)) { // Asynchronous clear (for individual bits; priority over set) f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; dump_sigspec_rhs(cell->getPort(ID::Q)); f << ".update("; dump_const(RTLIL::Const(RTLIL::S0, cell->getParam(ID::WIDTH).as_int())); f << ", "; dump_sigspec_rhs(cell->getPort(ID::CLR)); f << (cell->getParam(ID::CLR_POLARITY).as_bool() ? "" : ".bit_not()") << ");\n"; } // Internal cells } else if (is_internal_cell(cell->type)) { 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. } else { log_assert(cell->known()); bool buffered_inputs = false; const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; for (auto conn : cell->connections()) if (cell->input(conn.first)) { RTLIL::Module *cell_module = cell->module->design->module(cell->type); log_assert(cell_module != nullptr && cell_module->wire(conn.first)); RTLIL::Wire *cell_module_wire = cell_module->wire(conn.first); f << indent << mangle(cell) << access << mangle_wire_name(conn.first); if (!is_cxxrtl_blackbox_cell(cell) && wire_types[cell_module_wire].is_buffered()) { buffered_inputs = true; f << ".next"; } f << " = "; dump_sigspec_rhs(conn.second); f << ";\n"; if (getenv("CXXRTL_VOID_MY_WARRANTY") && conn.second.is_wire()) { // Until we have proper clock tree detection, this really awful hack that opportunistically // propagates prev_* values for clocks can be used to estimate how much faster a design could // be if only one clock edge was simulated by replacing: // top.p_clk = value<1>{0u}; top.step(); // top.p_clk = value<1>{1u}; top.step(); // with: // top.prev_p_clk = value<1>{0u}; top.p_clk = value<1>{1u}; top.step(); // Don't rely on this; it will be removed without warning. if (edge_wires[conn.second.as_wire()] && edge_wires[cell_module_wire]) { f << indent << mangle(cell) << access << "prev_" << mangle(cell_module_wire) << " = "; f << "prev_" << mangle(conn.second.as_wire()) << ";\n"; } } } auto assign_from_outputs = [&](bool cell_converged) { for (auto conn : cell->connections()) { if (cell->output(conn.first)) { if (conn.second.empty()) continue; // ignore disconnected ports if (is_cxxrtl_sync_port(cell, conn.first)) continue; // fully sync ports are handled in CELL_SYNC nodes f << indent; dump_sigspec_lhs(conn.second); f << " = " << mangle(cell) << access << mangle_wire_name(conn.first); // Similarly to how there is no purpose to buffering cell inputs, there is also no purpose to buffering // combinatorial cell outputs in case the cell converges within one cycle. (To convince yourself that // this optimization is valid, consider that, since the cell converged within one cycle, it would not // have any buffered wires if they were not output ports. Imagine inlining the cell's eval() function, // and consider the fate of the localized wires that used to be output ports.) // // It is not possible to know apriori whether the cell (which may be late bound) will converge immediately. // Because of this, the choice between using .curr (appropriate for buffered outputs) and .next (appropriate // for unbuffered outputs) is made at runtime. if (cell_converged && is_cxxrtl_comb_port(cell, conn.first)) f << ".next;\n"; else f << ".curr;\n"; } } }; if (buffered_inputs) { // If we have any buffered inputs, there's no chance of converging immediately. f << indent << mangle(cell) << access << "eval(performer);\n"; f << indent << "converged = false;\n"; assign_from_outputs(/*cell_converged=*/false); } else { f << indent << "if (" << mangle(cell) << access << "eval(performer)) {\n"; inc_indent(); assign_from_outputs(/*cell_converged=*/true); dec_indent(); f << indent << "} else {\n"; inc_indent(); f << indent << "converged = false;\n"; assign_from_outputs(/*cell_converged=*/false); dec_indent(); f << indent << "}\n"; } } } void collect_cell_eval(const RTLIL::Cell *cell, bool for_debug, std::vector &cells) { cells.push_back(cell); for (auto port : cell->connections()) if (cell->input(port.first)) collect_sigspec_rhs(port.second, for_debug, cells); } void dump_assign(const RTLIL::SigSig &sigsig, bool for_debug = false) { f << indent; dump_sigspec_lhs(sigsig.first, for_debug); f << " = "; dump_sigspec_rhs(sigsig.second, for_debug); f << ";\n"; } void dump_case_rule(const RTLIL::CaseRule *rule, bool for_debug = false) { for (auto action : rule->actions) dump_assign(action, for_debug); for (auto switch_ : rule->switches) dump_switch_rule(switch_, for_debug); } void dump_switch_rule(const RTLIL::SwitchRule *rule, bool for_debug = false) { // The switch attributes are printed before the switch condition is captured. dump_attrs(rule); std::string signal_temp = fresh_temporary(); f << indent << "const value<" << rule->signal.size() << "> &" << signal_temp << " = "; dump_sigspec(rule->signal, /*is_lhs=*/false, for_debug); f << ";\n"; bool first = true; for (auto case_ : rule->cases) { // The case attributes (for nested cases) are printed before the if/else if/else statement. dump_attrs(rule); f << indent; if (!first) f << "} else "; first = false; if (!case_->compare.empty()) { f << "if ("; bool first = true; for (auto &compare : case_->compare) { if (!first) f << " || "; first = false; if (compare.is_fully_def()) { f << signal_temp << " == "; dump_sigspec(compare, /*is_lhs=*/false, for_debug); } else if (compare.is_fully_const()) { 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_builder.push_back(RTLIL::S1); compare_value_builder.push_back(bit); break; case RTLIL::Sx: case RTLIL::Sz: case RTLIL::Sa: 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 << ") == "; dump_const(compare_value); } else { log_assert(false); } } f << ") "; } f << "{\n"; inc_indent(); dump_case_rule(case_, for_debug); dec_indent(); } f << indent << "}\n"; } void dump_process_case(const RTLIL::Process *proc, bool for_debug = false) { dump_attrs(proc); f << indent << "// process " << proc->name.str() << " case\n"; // The case attributes (for root case) are always empty. log_assert(proc->root_case.attributes.empty()); dump_case_rule(&proc->root_case, for_debug); } void dump_process_syncs(const RTLIL::Process *proc, bool for_debug = false) { dump_attrs(proc); f << indent << "// process " << proc->name.str() << " syncs\n"; for (auto sync : proc->syncs) { log_assert(!for_debug || sync->type == RTLIL::STa); RTLIL::SigBit sync_bit; if (!sync->signal.empty()) { sync_bit = sync->signal[0]; sync_bit = sigmaps[sync_bit.wire->module](sync_bit); if (!sync_bit.is_wire()) continue; // a clock, or more commonly a reset, can be tied to a constant driver } pool events; switch (sync->type) { case RTLIL::STp: log_assert(sync_bit.wire != nullptr); events.insert("posedge_" + mangle(sync_bit)); break; case RTLIL::STn: log_assert(sync_bit.wire != nullptr); events.insert("negedge_" + mangle(sync_bit)); break; case RTLIL::STe: log_assert(sync_bit.wire != nullptr); events.insert("posedge_" + mangle(sync_bit)); events.insert("negedge_" + mangle(sync_bit)); break; case RTLIL::STa: events.insert("true"); break; case RTLIL::ST0: case RTLIL::ST1: case RTLIL::STg: case RTLIL::STi: log_assert(false); } if (!events.empty()) { f << indent << "if ("; bool first = true; for (auto &event : events) { if (!first) f << " || "; first = false; f << event; } f << ") {\n"; inc_indent(); for (auto &action : sync->actions) dump_assign(action, for_debug); for (auto &memwr : sync->mem_write_actions) { RTLIL::Memory *memory = proc->module->memories.at(memwr.memid); std::string valid_index_temp = fresh_temporary(); f << indent << "auto " << valid_index_temp << " = memory_index("; dump_sigspec_rhs(memwr.address); f << ", " << memory->start_offset << ", " << memory->size << ");\n"; // See below for rationale of having both the assert and the condition. // // If assertions are disabled, out of bounds writes are defined to do nothing. f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds write\");\n"; f << indent << "if (" << valid_index_temp << ".valid) {\n"; inc_indent(); f << indent << mangle(memory) << ".update(" << valid_index_temp << ".index, "; dump_sigspec_rhs(memwr.data); f << ", "; dump_sigspec_rhs(memwr.enable); f << ");\n"; dec_indent(); f << indent << "}\n"; } dec_indent(); f << indent << "}\n"; } } } void dump_cell_effect_sync(std::vector &cells) { log_assert(!cells.empty()); const auto &trg = cells[0]->getPort(ID::TRG); const auto &trg_polarity = cells[0]->getParam(ID::TRG_POLARITY); f << indent << "if ("; for (int i = 0; i < trg.size(); i++) { RTLIL::SigBit trg_bit = trg[i]; trg_bit = sigmaps[trg_bit.wire->module](trg_bit); log_assert(trg_bit.wire); if (i != 0) f << " || "; if (trg_polarity[i] == State::S1) f << "posedge_"; else f << "negedge_"; f << mangle(trg_bit); } f << ") {\n"; inc_indent(); std::sort(cells.begin(), cells.end(), [](const RTLIL::Cell *a, const RTLIL::Cell *b) { return a->getParam(ID::PRIORITY).as_int() > b->getParam(ID::PRIORITY).as_int(); }); for (auto cell : cells) { log_assert(cell->getParam(ID::TRG_ENABLE).as_bool()); log_assert(cell->getPort(ID::TRG) == trg); log_assert(cell->getParam(ID::TRG_POLARITY) == trg_polarity); std::vector inlined_cells; collect_cell_eval(cell, /*for_debug=*/false, inlined_cells); dump_inlined_cells(inlined_cells); dump_effect(cell); } dec_indent(); f << indent << "}\n"; } void dump_mem_rdport(const Mem *mem, int portidx, bool for_debug = false) { auto &port = mem->rd_ports[portidx]; dump_attrs(&port); f << indent << "// memory " << mem->memid.str() << " read port " << portidx << "\n"; if (port.clk_enable) { log_assert(!for_debug); RTLIL::SigBit clk_bit = port.clk[0]; clk_bit = sigmaps[clk_bit.wire->module](clk_bit); if (clk_bit.wire) { f << indent << "if (" << (port.clk_polarity ? "posedge_" : "negedge_") << mangle(clk_bit) << ") {\n"; } else { f << indent << "if (false) {\n"; } inc_indent(); } std::vector inlined_cells_addr; collect_sigspec_rhs(port.addr, for_debug, inlined_cells_addr); if (!inlined_cells_addr.empty()) dump_inlined_cells(inlined_cells_addr); std::string valid_index_temp = fresh_temporary(); f << indent << "auto " << valid_index_temp << " = memory_index("; // Almost all non-elidable cells cannot appear in debug_eval(), but $memrd is an exception; asynchronous // memory read ports can. dump_sigspec_rhs(port.addr, for_debug); f << ", " << mem->start_offset << ", " << mem->size << ");\n"; bool has_enable = port.clk_enable && !port.en.is_fully_ones(); if (has_enable) { std::vector inlined_cells_en; collect_sigspec_rhs(port.en, for_debug, inlined_cells_en); if (!inlined_cells_en.empty()) dump_inlined_cells(inlined_cells_en); f << indent << "if ("; dump_sigspec_rhs(port.en); f << ") {\n"; inc_indent(); } // The generated code has two bounds checks; one in an assertion, and another that guards the read. // This is done so that the code does not invoke undefined behavior under any conditions, but nevertheless // loudly crashes if an illegal condition is encountered. The assert may be turned off with -DCXXRTL_NDEBUG // not only for release builds, but also to make sure the simulator (which is presumably embedded in some // larger program) will never crash the code that calls into it. // // If assertions are disabled, out of bounds reads are defined to return zero. f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds read\");\n"; f << indent << "if(" << valid_index_temp << ".valid) {\n"; inc_indent(); if (!mem->wr_ports.empty()) { std::string lhs_temp = fresh_temporary(); f << indent << "value<" << mem->width << "> " << lhs_temp << " = " << mangle(mem) << "[" << valid_index_temp << ".index];\n"; bool transparent = false; for (auto bit : port.transparency_mask) if (bit) transparent = true; if (transparent) { std::string addr_temp = fresh_temporary(); f << indent << "const value<" << port.addr.size() << "> &" << addr_temp << " = "; dump_sigspec_rhs(port.addr); f << ";\n"; for (int i = 0; i < GetSize(mem->wr_ports); i++) { auto &wrport = mem->wr_ports[i]; if (!port.transparency_mask[i]) continue; f << indent << "if (" << addr_temp << " == "; dump_sigspec_rhs(wrport.addr); f << ") {\n"; inc_indent(); f << indent << lhs_temp << " = " << lhs_temp; f << ".update("; dump_sigspec_rhs(wrport.data); f << ", "; dump_sigspec_rhs(wrport.en); f << ");\n"; dec_indent(); f << indent << "}\n"; } } f << indent; dump_sigspec_lhs(port.data); f << " = " << lhs_temp << ";\n"; } else { f << indent; dump_sigspec_lhs(port.data); f << " = " << mangle(mem) << "[" << valid_index_temp << ".index];\n"; } dec_indent(); f << indent << "} else {\n"; inc_indent(); f << indent; dump_sigspec_lhs(port.data); f << " = value<" << mem->width << "> {};\n"; dec_indent(); f << indent << "}\n"; if (has_enable && !port.ce_over_srst) { dec_indent(); f << indent << "}\n"; } if (port.srst != State::S0) { // Synchronous reset std::vector inlined_cells_srst; collect_sigspec_rhs(port.srst, for_debug, inlined_cells_srst); if (!inlined_cells_srst.empty()) dump_inlined_cells(inlined_cells_srst); f << indent << "if ("; dump_sigspec_rhs(port.srst); f << " == value<1> {1u}) {\n"; inc_indent(); f << indent; dump_sigspec_lhs(port.data); f << " = "; dump_const(port.srst_value); f << ";\n"; dec_indent(); f << indent << "}\n"; } if (has_enable && port.ce_over_srst) { dec_indent(); f << indent << "}\n"; } if (port.clk_enable) { dec_indent(); f << indent << "}\n"; } if (port.arst != State::S0) { // Asynchronous reset std::vector inlined_cells_arst; collect_sigspec_rhs(port.arst, for_debug, inlined_cells_arst); if (!inlined_cells_arst.empty()) dump_inlined_cells(inlined_cells_arst); f << indent << "if ("; dump_sigspec_rhs(port.arst); f << " == value<1> {1u}) {\n"; inc_indent(); f << indent; dump_sigspec_lhs(port.data); f << " = "; dump_const(port.arst_value); f << ";\n"; dec_indent(); f << indent << "}\n"; } } void dump_mem_wrports(const Mem *mem, bool for_debug = false) { log_assert(!for_debug); for (int portidx = 0; portidx < GetSize(mem->wr_ports); portidx++) { auto &port = mem->wr_ports[portidx]; dump_attrs(&port); f << indent << "// memory " << mem->memid.str() << " write port " << portidx << "\n"; if (port.clk_enable) { RTLIL::SigBit clk_bit = port.clk[0]; clk_bit = sigmaps[clk_bit.wire->module](clk_bit); if (clk_bit.wire) { f << indent << "if (" << (port.clk_polarity ? "posedge_" : "negedge_") << mangle(clk_bit) << ") {\n"; } else { f << indent << "if (false) {\n"; } inc_indent(); } std::vector inlined_cells_addr; collect_sigspec_rhs(port.addr, for_debug, inlined_cells_addr); if (!inlined_cells_addr.empty()) dump_inlined_cells(inlined_cells_addr); std::string valid_index_temp = fresh_temporary(); f << indent << "auto " << valid_index_temp << " = memory_index("; dump_sigspec_rhs(port.addr); f << ", " << mem->start_offset << ", " << mem->size << ");\n"; // See above for rationale of having both the assert and the condition. // // If assertions are disabled, out of bounds writes are defined to do nothing. f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds write\");\n"; f << indent << "if (" << valid_index_temp << ".valid) {\n"; inc_indent(); std::vector inlined_cells; collect_sigspec_rhs(port.data, for_debug, inlined_cells); collect_sigspec_rhs(port.en, for_debug, inlined_cells); if (!inlined_cells.empty()) dump_inlined_cells(inlined_cells); f << indent << mangle(mem) << ".update(" << valid_index_temp << ".index, "; dump_sigspec_rhs(port.data); f << ", "; dump_sigspec_rhs(port.en); f << ", " << portidx << ");\n"; dec_indent(); f << indent << "}\n"; if (port.clk_enable) { dec_indent(); f << indent << "}\n"; } } } void dump_wire(const RTLIL::Wire *wire, bool is_local) { const auto &wire_type = wire_types[wire]; if (!wire_type.is_named() || wire_type.is_local() != is_local) return; dump_attrs(wire); f << indent; if (wire->port_input && wire->port_output) f << "/*inout*/ "; else if (wire->port_input) f << "/*input*/ "; else if (wire->port_output) f << "/*output*/ "; f << (wire_type.is_buffered() ? "wire" : "value"); if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) { f << "<" << wire->get_string_attribute(ID(cxxrtl_width)) << ">"; } else { f << "<" << wire->width << ">"; } f << " " << mangle(wire) << ";\n"; if (edge_wires[wire]) { if (!wire_type.is_buffered()) { f << indent << "value<" << wire->width << "> prev_" << mangle(wire) << ";\n"; } for (auto edge_type : edge_types) { if (edge_type.first.wire == wire) { std::string prev, next; if (!wire_type.is_buffered()) { prev = "prev_" + mangle(edge_type.first.wire); next = mangle(edge_type.first.wire); } else { prev = mangle(edge_type.first.wire) + ".curr"; next = mangle(edge_type.first.wire) + ".next"; } prev += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()"; next += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()"; if (edge_type.second != RTLIL::STn) { f << indent << "bool posedge_" << mangle(edge_type.first) << "() const {\n"; inc_indent(); f << indent << "return !" << prev << " && " << next << ";\n"; dec_indent(); f << indent << "}\n"; } if (edge_type.second != RTLIL::STp) { f << indent << "bool negedge_" << mangle(edge_type.first) << "() const {\n"; inc_indent(); f << indent << "return " << prev << " && !" << next << ";\n"; dec_indent(); f << indent << "}\n"; } } } } } void dump_debug_wire(const RTLIL::Wire *wire, bool is_local) { const auto &wire_type = wire_types[wire]; if (wire_type.is_member()) return; const auto &debug_wire_type = debug_wire_types[wire]; if (!debug_wire_type.is_named() || debug_wire_type.is_local() != is_local) return; dump_attrs(wire); f << indent; if (debug_wire_type.is_outline()) f << "/*outline*/ "; f << "value<" << wire->width << "> " << mangle(wire) << ";\n"; } void dump_reset_method(RTLIL::Module *module) { int mem_init_idx = 0; inc_indent(); for (auto wire : module->wires()) { const auto &wire_type = wire_types[wire]; if (!wire_type.is_named() || wire_type.is_local()) continue; if (!wire_init.count(wire)) continue; f << indent << mangle(wire) << " = "; if (wire_types[wire].is_buffered()) { f << "wire<" << wire->width << ">"; } else { f << "value<" << wire->width << ">"; } dump_const_init(wire_init.at(wire), wire->width); f << ";\n"; if (edge_wires[wire] && !wire_types[wire].is_buffered()) { f << indent << "prev_" << mangle(wire) << " = "; dump_const(wire_init.at(wire), wire->width); f << ";\n"; } } for (auto &mem : mod_memories[module]) { for (auto &init : mem.inits) { if (init.removed) continue; dump_attrs(&init); int words = GetSize(init.data) / mem.width; f << indent << "static const value<" << mem.width << "> "; f << "mem_init_" << ++mem_init_idx << "[" << words << "] {"; inc_indent(); for (int n = 0; n < words; n++) { if (n % 4 == 0) f << "\n" << indent; else f << " "; dump_const(init.data, mem.width, n * mem.width, /*fixed_width=*/true); f << ","; } dec_indent(); f << "\n"; f << indent << "};\n"; f << indent << "std::copy(std::begin(mem_init_" << mem_init_idx << "), "; f << "std::end(mem_init_" << mem_init_idx << "), "; f << "&" << mangle(&mem) << ".data[" << stringf("%#x", init.addr.as_int()) << "]);\n"; } } for (auto cell : module->cells()) { // Async and initial effectful cells have additional state, which must be reset as well. if (is_effectful_cell(cell->type)) if (!cell->getParam(ID::TRG_ENABLE).as_bool() || cell->getParam(ID::TRG_WIDTH).as_int() == 0) f << indent << mangle(cell) << " = {};\n"; if (is_internal_cell(cell->type)) continue; f << indent << mangle(cell); RTLIL::Module *cell_module = module->design->module(cell->type); if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) { f << "->reset();\n"; } else { f << ".reset();\n"; } } dec_indent(); } void dump_eval_method(RTLIL::Module *module) { inc_indent(); f << indent << "bool converged = " << (eval_converges.at(module) ? "true" : "false") << ";\n"; if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto wire : module->wires()) { if (edge_wires[wire]) { for (auto edge_type : edge_types) { if (edge_type.first.wire == wire) { if (edge_type.second != RTLIL::STn) { f << indent << "bool posedge_" << mangle(edge_type.first) << " = "; f << "this->posedge_" << mangle(edge_type.first) << "();\n"; } if (edge_type.second != RTLIL::STp) { f << indent << "bool negedge_" << mangle(edge_type.first) << " = "; f << "this->negedge_" << mangle(edge_type.first) << "();\n"; } } } } } for (auto wire : module->wires()) dump_wire(wire, /*is_local=*/true); for (auto node : schedule[module]) { switch (node.type) { case FlowGraph::Node::Type::CONNECT: dump_connect(node.connect); break; case FlowGraph::Node::Type::CELL_SYNC: dump_cell_sync(node.cell); break; case FlowGraph::Node::Type::CELL_EVAL: dump_cell_eval(node.cell); break; case FlowGraph::Node::Type::EFFECT_SYNC: dump_cell_effect_sync(node.cells); break; case FlowGraph::Node::Type::PROCESS_CASE: dump_process_case(node.process); break; case FlowGraph::Node::Type::PROCESS_SYNC: dump_process_syncs(node.process); break; case FlowGraph::Node::Type::MEM_RDPORT: dump_mem_rdport(node.mem, node.portidx); break; case FlowGraph::Node::Type::MEM_WRPORTS: dump_mem_wrports(node.mem); break; } } } f << indent << "return converged;\n"; dec_indent(); } void dump_debug_eval_method(RTLIL::Module *module) { inc_indent(); for (auto wire : module->wires()) dump_debug_wire(wire, /*is_local=*/true); for (auto node : debug_schedule[module]) { switch (node.type) { case FlowGraph::Node::Type::CONNECT: dump_connect(node.connect, /*for_debug=*/true); break; case FlowGraph::Node::Type::CELL_SYNC: dump_cell_sync(node.cell, /*for_debug=*/true); break; case FlowGraph::Node::Type::CELL_EVAL: dump_cell_eval(node.cell, /*for_debug=*/true); break; case FlowGraph::Node::Type::PROCESS_CASE: dump_process_case(node.process, /*for_debug=*/true); break; case FlowGraph::Node::Type::PROCESS_SYNC: dump_process_syncs(node.process, /*for_debug=*/true); break; case FlowGraph::Node::Type::MEM_RDPORT: dump_mem_rdport(node.mem, node.portidx, /*for_debug=*/true); break; case FlowGraph::Node::Type::MEM_WRPORTS: dump_mem_wrports(node.mem, /*for_debug=*/true); break; default: log_abort(); } } dec_indent(); } void dump_commit_method(RTLIL::Module *module) { inc_indent(); f << indent << "bool changed = false;\n"; for (auto wire : module->wires()) { const auto &wire_type = wire_types[wire]; if (wire_type.type == WireType::MEMBER && edge_wires[wire]) f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n"; if (wire_type.is_buffered()) f << indent << "if (" << mangle(wire) << ".commit(observer)) changed = true;\n"; } if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto &mem : mod_memories[module]) { if (!writable_memories.count({module, mem.memid})) continue; f << indent << "if (" << mangle(&mem) << ".commit(observer)) changed = true;\n"; } for (auto cell : module->cells()) { if (is_internal_cell(cell->type)) continue; const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; f << indent << "if (" << mangle(cell) << access << "commit(observer)) changed = true;\n"; } } f << indent << "return changed;\n"; dec_indent(); } 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; } }; 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_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); if (serialize) { dump_serialized_metadata(attributes); } else { dump_metadata_map(attributes); } } void dump_debug_info_method(RTLIL::Module *module) { size_t count_scopes = 0; size_t count_public_wires = 0; size_t count_member_wires = 0; size_t count_undriven = 0; size_t count_driven_sync = 0; size_t count_driven_comb = 0; size_t count_mixed_driver = 0; size_t count_alias_wires = 0; size_t count_const_wires = 0; size_t count_inline_wires = 0; size_t count_skipped_wires = 0; inc_indent(); f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n"; f << indent << "if (scopes) {\n"; inc_indent(); // 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, /*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. for (auto cell : module->cells()) { if (cell->type != ID($scopeinfo)) continue; if (cell->getParam(ID::TYPE).decode_string() == "module") { 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)) << ", "; 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_serialized_metadata(module_attrs); f << ", "; dump_serialized_metadata(cell_attrs); f << ");\n"; } else log_assert(false && "Unknown $scopeinfo type"); count_scopes++; } dec_indent(); f << indent << "}\n"; f << indent << "if (items) {\n"; inc_indent(); for (auto wire : module->wires()) { const auto &debug_wire_type = debug_wire_types[wire]; count_public_wires++; switch (debug_wire_type.type) { case WireType::BUFFERED: case WireType::MEMBER: { // 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) flags.push_back("OUTPUT"); else if (wire->port_input) flags.push_back("INPUT"); bool has_driven_sync = false; bool has_driven_comb = false; bool has_undriven = false; if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto bit : SigSpec(wire)) if (!bit_has_state.count(bit)) has_undriven = true; else if (bit_has_state[bit]) has_driven_sync = true; else has_driven_comb = true; } else if (wire->port_output) { switch (cxxrtl_port_type(module, wire->name)) { case CxxrtlPortType::SYNC: has_driven_sync = true; break; case CxxrtlPortType::COMB: has_driven_comb = true; break; case CxxrtlPortType::UNKNOWN: has_driven_sync = has_driven_comb = true; break; } } else { has_undriven = true; } if (has_undriven) flags.push_back("UNDRIVEN"); if (!has_driven_sync && !has_driven_comb && has_undriven) count_undriven++; if (has_driven_sync) flags.push_back("DRIVEN_SYNC"); if (has_driven_sync && !has_driven_comb && !has_undriven) count_driven_sync++; if (has_driven_comb) flags.push_back("DRIVEN_COMB"); if (!has_driven_sync && has_driven_comb && !has_undriven) count_driven_comb++; 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)) << ", "; 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; } 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)) << ", "; 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); if (wire->start_offset != 0) f << ", " << wire->start_offset; f << ");\n"; count_alias_wires++; break; } case WireType::CONST: { // Wire tied to a constant 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)) << ", "; 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)) << ", "; 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; } default: { // Localized or inlined wire with no debug information count_skipped_wires++; break; } } } if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { 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)) << ", "; if (mem.packed) { dump_debug_attrs(mem.cell); } else { dump_debug_attrs(mem.mem); } f << ", " << mangle(&mem) << ", "; f << mem.start_offset << ");\n"; } } dec_indent(); f << indent << "}\n"; if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto cell : module->cells()) { if (is_internal_cell(cell->type)) continue; 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, /*serialize=*/false); f << ");\n"; } } dec_indent(); log_debug("Debug information statistics for module `%s':\n", log_id(module)); log_debug(" Scopes: %zu", count_scopes); log_debug(" Public wires: %zu, of which:\n", count_public_wires); log_debug(" Member wires: %zu, of which:\n", count_member_wires); log_debug(" Undriven: %zu (incl. inputs)\n", count_undriven); log_debug(" Driven sync: %zu\n", count_driven_sync); log_debug(" Driven comb: %zu\n", count_driven_comb); log_debug(" Mixed driver: %zu\n", count_mixed_driver); if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { log_debug(" Inline wires: %zu\n", count_inline_wires); log_debug(" Alias wires: %zu\n", count_alias_wires); log_debug(" Const wires: %zu\n", count_const_wires); log_debug(" Other wires: %zu%s\n", count_skipped_wires, count_skipped_wires > 0 ? " (debug unavailable)" : ""); } } void dump_module_intf(RTLIL::Module *module) { dump_attrs(module); if (module->get_bool_attribute(ID(cxxrtl_blackbox))) { if (module->has_attribute(ID(cxxrtl_template))) f << indent << "template" << template_params(module, /*is_decl=*/true) << "\n"; f << indent << "struct " << mangle(module) << " : public module {\n"; inc_indent(); for (auto wire : module->wires()) { if (wire->port_id != 0) dump_wire(wire, /*is_local=*/false); } f << "\n"; f << indent << "void reset() override {\n"; dump_reset_method(module); f << indent << "}\n"; f << "\n"; // No default argument, to prevent unintentional `return bb_foo::eval();` calls that drop performer. f << indent << "bool eval(performer *performer) override {\n"; dump_eval_method(module); f << indent << "}\n"; f << "\n"; f << indent << "virtual bool commit(observer &observer) {\n"; dump_commit_method(module); f << indent << "}\n"; f << "\n"; f << indent << "bool commit() override {\n"; f << indent << indent << "observer observer;\n"; f << indent << indent << "return commit(observer);\n"; f << indent << "}\n"; if (debug_info) { f << "\n"; f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, " << "std::string path, metadata_map &&cell_attrs = {}) override {\n"; dump_debug_info_method(module); f << indent << "}\n"; } f << "\n"; f << indent << "static std::unique_ptr<" << mangle(module); f << template_params(module, /*is_decl=*/false) << "> "; f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; dec_indent(); f << indent << "}; // struct " << mangle(module) << "\n"; f << "\n"; if (blackbox_specializations.count(module)) { // If templated black boxes are used, the constructor of any module which includes the black box cell // (which calls the declared but not defined in the generated code `create` function) may only be used // if (a) the create function is defined in the same translation unit, or (b) the create function has // a forward-declared explicit specialization. // // Option (b) makes it possible to have the generated code and the black box implementation in different // translation units, which is convenient. Of course, its downside is that black boxes must predefine // a specialization for every combination of parameters the generated code may use; but since the main // purpose of templated black boxes is abstracting over datapath width, it is expected that there would // be very few such combinations anyway. for (auto specialization : blackbox_specializations[module]) { f << indent << "template<>\n"; f << indent << "std::unique_ptr<" << mangle(module) << specialization << "> "; f << mangle(module) << specialization << "::"; f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; f << "\n"; } } } else { f << indent << "struct " << mangle(module) << " : public module {\n"; inc_indent(); for (auto wire : module->wires()) dump_wire(wire, /*is_local=*/false); for (auto wire : module->wires()) dump_debug_wire(wire, /*is_local=*/false); bool has_memories = false; for (auto &mem : mod_memories[module]) { dump_attrs(&mem); f << indent << "memory<" << mem.width << "> " << mangle(&mem) << " { " << mem.size << "u };\n"; has_memories = true; } if (has_memories) f << "\n"; bool has_cells = false; for (auto cell : module->cells()) { // Async and initial effectful cells have additional state, which requires storage. if (is_effectful_cell(cell->type)) { if (cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0) f << indent << "value<1> " << mangle(cell) << ";\n"; // async initial cell if (!cell->getParam(ID::TRG_ENABLE).as_bool() && cell->type == ID($print)) f << indent << "value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << "> " << mangle(cell) << ";\n"; // {EN, ARGS} if (!cell->getParam(ID::TRG_ENABLE).as_bool() && cell->type == ID($check)) f << indent << "value<2> " << mangle(cell) << ";\n"; // {EN, A} } if (is_internal_cell(cell->type)) continue; dump_attrs(cell); RTLIL::Module *cell_module = module->design->module(cell->type); log_assert(cell_module != nullptr); if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) { f << indent << "std::unique_ptr<" << mangle(cell_module) << template_args(cell) << "> "; f << mangle(cell) << " = " << mangle(cell_module) << template_args(cell); f << "::create(" << escape_cxx_string(get_hdl_name(cell)) << ", "; dump_metadata_map(cell->parameters); f << ", "; dump_metadata_map(cell->attributes); f << ");\n"; } else { f << indent << mangle(cell_module) << " " << mangle(cell) << " {interior()};\n"; } has_cells = true; } if (has_cells) f << "\n"; f << indent << mangle(module) << "(interior) {}\n"; f << indent << mangle(module) << "() {\n"; inc_indent(); f << indent << "reset();\n"; dec_indent(); f << indent << "};\n"; f << "\n"; f << indent << "void reset() override;\n"; f << "\n"; f << indent << "bool eval(performer *performer = nullptr) override;\n"; f << "\n"; f << indent << "template\n"; f << indent << "bool commit(ObserverT &observer) {\n"; dump_commit_method(module); f << indent << "}\n"; f << "\n"; f << indent << "bool commit() override {\n"; f << indent << indent << "observer observer;\n"; f << indent << indent << "return commit<>(observer);\n"; f << indent << "}\n"; if (debug_info) { if (debug_eval) { f << "\n"; f << indent << "void debug_eval();\n"; for (auto wire : module->wires()) if (debug_wire_types[wire].is_outline()) { f << indent << "debug_outline debug_eval_outline { std::bind(&" << mangle(module) << "::debug_eval, this) };\n"; break; } } f << "\n"; f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, " << "std::string path, metadata_map &&cell_attrs = {}) override;\n"; } dec_indent(); f << indent << "}; // struct " << mangle(module) << "\n"; f << "\n"; } } void dump_module_impl(RTLIL::Module *module) { if (module->get_bool_attribute(ID(cxxrtl_blackbox))) return; f << indent << "void " << mangle(module) << "::reset() {\n"; dump_reset_method(module); f << indent << "}\n"; f << "\n"; f << indent << "bool " << mangle(module) << "::eval(performer *performer) {\n"; dump_eval_method(module); f << indent << "}\n"; if (debug_info) { if (debug_eval) { f << "\n"; f << indent << "void " << mangle(module) << "::debug_eval() {\n"; dump_debug_eval_method(module); f << indent << "}\n"; } f << "\n"; f << indent << "CXXRTL_EXTREMELY_COLD\n"; f << indent << "void " << mangle(module) << "::debug_info(debug_items *items, debug_scopes *scopes, " << "std::string path, metadata_map &&cell_attrs) {\n"; dump_debug_info_method(module); f << indent << "}\n"; } f << "\n"; } void dump_design(RTLIL::Design *design) { RTLIL::Module *top_module = nullptr; std::vector modules; using Order = IdString::compare_ptr_by_name; TopoSort topo_design; for (auto module : design->modules()) { if (!design->selected_module(module)) continue; if (module->get_bool_attribute(ID(cxxrtl_blackbox))) modules.push_back(module); // cxxrtl blackboxes first if (module->get_blackbox_attribute() || module->get_bool_attribute(ID(cxxrtl_blackbox))) continue; if (module->get_bool_attribute(ID::top)) top_module = module; topo_design.node(module); for (auto cell : module->cells()) { if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell)) continue; RTLIL::Module *cell_module = design->module(cell->type); log_assert(cell_module != nullptr); topo_design.edge(cell_module, module); } } bool no_loops = topo_design.sort(); log_assert(no_loops); modules.insert(modules.end(), topo_design.sorted.begin(), topo_design.sorted.end()); if (split_intf) { // The only thing more depraved than include guards, is mangling filenames to turn them into include guards. std::string include_guard = design_ns + "_header"; std::transform(include_guard.begin(), include_guard.end(), include_guard.begin(), ::toupper); f << "#ifndef " << include_guard << "\n"; f << "#define " << include_guard << "\n"; f << "\n"; if (top_module != nullptr && debug_info) { f << "#include \n"; f << "\n"; f << "#ifdef __cplusplus\n"; f << "extern \"C\" {\n"; f << "#endif\n"; f << "\n"; f << "cxxrtl_toplevel " << design_ns << "_create();\n"; f << "\n"; f << "#ifdef __cplusplus\n"; f << "}\n"; f << "#endif\n"; f << "\n"; } else { f << "// The CXXRTL C API is not available because the design is built without debug information.\n"; f << "\n"; } f << "#ifdef __cplusplus\n"; f << "\n"; f << "#include \n"; f << "\n"; f << "using namespace cxxrtl;\n"; f << "\n"; f << "namespace " << design_ns << " {\n"; f << "\n"; for (auto module : modules) dump_module_intf(module); f << "} // namespace " << design_ns << "\n"; f << "\n"; f << "#endif // __cplusplus\n"; f << "\n"; f << "#endif\n"; *intf_f << f.str(); f.str(""); } if (split_intf) f << "#include \"" << name_from_file_path(intf_filename) << "\"\n"; else f << "#include \n"; f << "\n"; f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n"; f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; f << "#include \n"; f << "#endif\n"; f << "\n"; f << "#if defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; f << "#include \n"; f << "#endif\n"; f << "\n"; f << "using namespace cxxrtl_yosys;\n"; f << "\n"; f << "namespace " << design_ns << " {\n"; f << "\n"; for (auto module : modules) { if (!split_intf) dump_module_intf(module); dump_module_impl(module); } f << "} // namespace " << design_ns << "\n"; f << "\n"; if (top_module != nullptr && debug_info) { f << "extern \"C\"\n"; f << "cxxrtl_toplevel " << design_ns << "_create() {\n"; inc_indent(); std::string top_type = design_ns + "::" + mangle(top_module); f << indent << "return new _cxxrtl_toplevel { "; f << "std::unique_ptr<" << top_type << ">(new " + top_type + ")"; f << " };\n"; dec_indent(); f << "}\n"; } *impl_f << f.str(); f.str(""); } // Edge-type sync rules require us to emit edge detectors, which require coordination between // eval and commit phases. To do this we need to collect them upfront. // // Note that the simulator commit phase operates at wire granularity but edge-type sync rules // operate at wire bit granularity; it is possible to have code similar to: // wire [3:0] clocks; // always @(posedge clocks[0]) ... // To handle this we track edge sensitivity both for wires and wire bits. void register_edge_signal(SigMap &sigmap, RTLIL::SigSpec signal, RTLIL::SyncType type) { signal = sigmap(signal); if (signal.is_fully_const()) return; // a clock, or more commonly a reset, can be tied to a constant driver log_assert(is_valid_clock(signal)); log_assert(type == RTLIL::STp || type == RTLIL::STn || type == RTLIL::STe); RTLIL::SigBit sigbit = signal[0]; if (!edge_types.count(sigbit)) edge_types[sigbit] = type; else if (edge_types[sigbit] != type) edge_types[sigbit] = RTLIL::STe; // Cannot use as_wire because signal might not be a full wire, instead extract the wire from the sigbit edge_wires.insert(sigbit.wire); } void analyze_design(RTLIL::Design *design) { bool has_feedback_arcs = false; bool has_buffered_comb_wires = false; for (auto module : design->modules()) { if (!design->selected_module(module)) continue; SigMap &sigmap = sigmaps[module]; sigmap.set(module); std::vector &memories = mod_memories[module]; memories = Mem::get_all_memories(module); for (auto &mem : memories) { mem.narrow(); mem.coalesce_inits(); } if (module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto port : module->ports) { RTLIL::Wire *wire = module->wire(port); if (wire->port_input && !wire->port_output) { wire_types[wire] = debug_wire_types[wire] = {WireType::MEMBER}; } else if (wire->port_input || wire->port_output) { wire_types[wire] = debug_wire_types[wire] = {WireType::BUFFERED}; } if (wire->has_attribute(ID(cxxrtl_edge))) { RTLIL::Const edge_attr = wire->attributes[ID(cxxrtl_edge)]; if (!(edge_attr.flags & RTLIL::CONST_FLAG_STRING) || (int)edge_attr.decode_string().size() != GetSize(wire)) log_cmd_error("Attribute `cxxrtl_edge' of port `%s.%s' is not a string with one character per bit.\n", log_id(module), log_signal(wire)); std::string edges = wire->get_string_attribute(ID(cxxrtl_edge)); for (int i = 0; i < GetSize(wire); i++) { RTLIL::SigSpec wire_sig = wire; switch (edges[i]) { case '-': break; case 'p': register_edge_signal(sigmap, wire_sig[i], RTLIL::STp); break; case 'n': register_edge_signal(sigmap, wire_sig[i], RTLIL::STn); break; case 'a': register_edge_signal(sigmap, wire_sig[i], RTLIL::STe); break; default: log_cmd_error("Attribute `cxxrtl_edge' of port `%s.%s' contains specifiers " "other than '-', 'p', 'n', or 'a'.\n", log_id(module), log_signal(wire)); } } } } // Black boxes converge by default, since their implementations are quite unlikely to require // internal propagation of comb signals. eval_converges[module] = true; continue; } for (auto wire : module->wires()) if (wire->has_attribute(ID::init)) wire_init[wire] = wire->attributes.at(ID::init); // Construct a flow graph where each node is a basic computational operation generally corresponding // to a fragment of the RTLIL netlist. FlowGraph flow; for (auto conn : module->connections()) flow.add_node(conn); for (auto cell : module->cells()) { if (!cell->known()) log_cmd_error("Unknown cell `%s'.\n", log_id(cell->type)); if (cell->is_mem_cell()) continue; RTLIL::Module *cell_module = design->module(cell->type); if (cell_module && cell_module->get_blackbox_attribute() && !cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) log_cmd_error("External blackbox cell `%s' is not marked as a CXXRTL blackbox.\n", log_id(cell->type)); if (cell_module && cell_module->get_bool_attribute(ID(cxxrtl_blackbox)) && cell_module->get_bool_attribute(ID(cxxrtl_template))) blackbox_specializations[cell_module].insert(template_args(cell)); flow.add_node(cell); // Various DFF cells are treated like posedge/negedge processes, see above for details. if (cell->type.in(ID($dff), ID($dffe), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($dffsr), ID($dffsre), ID($sdff), ID($sdffe), ID($sdffce))) { if (is_valid_clock(cell->getPort(ID::CLK))) register_edge_signal(sigmap, cell->getPort(ID::CLK), cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn); } // Effectful cells may be triggered on posedge/negedge events. if (is_effectful_cell(cell->type) && cell->getParam(ID::TRG_ENABLE).as_bool()) { for (size_t i = 0; i < (size_t)cell->getParam(ID::TRG_WIDTH).as_int(); i++) { RTLIL::SigBit trg = cell->getPort(ID::TRG).extract(i, 1); if (is_valid_clock(trg)) register_edge_signal(sigmap, trg, cell->parameters[ID::TRG_POLARITY][i] == RTLIL::S1 ? RTLIL::STp : RTLIL::STn); } } } for (auto &mem : memories) { flow.add_node(&mem); // Clocked memory cells are treated like posedge/negedge processes as well. for (auto &port : mem.rd_ports) { if (port.clk_enable) if (is_valid_clock(port.clk)) register_edge_signal(sigmap, port.clk, port.clk_polarity ? RTLIL::STp : RTLIL::STn); // For read ports, also move initial value to wire_init (if any). for (int i = 0; i < GetSize(port.data); i++) { if (port.init_value[i] != State::Sx) { SigBit bit = port.data[i]; if (bit.wire) { auto &init = wire_init[bit.wire]; if (init == RTLIL::Const()) { init = RTLIL::Const(State::Sx, GetSize(bit.wire)); } init.set(bit.offset, port.init_value[i]); } } } } for (auto &port : mem.wr_ports) { if (port.clk_enable) if (is_valid_clock(port.clk)) register_edge_signal(sigmap, port.clk, port.clk_polarity ? RTLIL::STp : RTLIL::STn); } if (!mem.wr_ports.empty()) writable_memories.insert({module, mem.memid}); } for (auto proc : module->processes) { flow.add_node(proc.second); for (auto sync : proc.second->syncs) { switch (sync->type) { // Edge-type sync rules require pre-registration. case RTLIL::STp: case RTLIL::STn: case RTLIL::STe: register_edge_signal(sigmap, sync->signal, sync->type); break; // Level-type sync rules require no special handling. case RTLIL::ST0: case RTLIL::ST1: case RTLIL::STa: break; case RTLIL::STg: log_cmd_error("Global clock is not supported.\n"); // Handling of init-type sync rules is delegated to the `proc_init` pass, so we can use the wire // attribute regardless of input. case RTLIL::STi: log_assert(false); } for (auto &memwr : sync->mem_write_actions) { writable_memories.insert({module, memwr.memid}); } } } // Construct a linear order of the flow graph that minimizes the amount of feedback arcs. A flow graph // 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*> node_vertex_map; for (auto node : flow.nodes) node_vertex_map[node] = scheduler.add(node); for (auto node_comb_def : flow.node_comb_defs) { auto vertex = node_vertex_map[node_comb_def.first]; for (auto wire : node_comb_def.second) for (auto succ_node : flow.wire_uses[wire]) { auto succ_vertex = node_vertex_map[succ_node]; vertex->succs.insert(succ_vertex); succ_vertex->preds.insert(vertex); } } // Find out whether the order includes any feedback arcs. std::vector node_order; pool evaluated_nodes; pool feedback_wires; for (auto vertex : scheduler.schedule()) { auto node = vertex->data; node_order.push_back(node); // Any wire that is an output of node vo and input of node vi where vo is scheduled later than vi // is a feedback wire. Feedback wires indicate apparent logic loops in the design, which may be // caused by a true logic loop, but usually are a benign result of dependency tracking that works // on wire, not bit, level. Nevertheless, feedback wires cannot be unbuffered. evaluated_nodes.insert(node); for (auto wire : flow.node_comb_defs[node]) for (auto succ_node : flow.wire_uses[wire]) if (evaluated_nodes[succ_node]) feedback_wires.insert(wire); } if (!feedback_wires.empty()) { has_feedback_arcs = true; log("Module `%s' contains feedback arcs through wires:\n", log_id(module)); for (auto wire : feedback_wires) log(" %s\n", log_id(wire)); } // Conservatively assign wire types. Assignment of types BUFFERED and MEMBER is final, but assignment // of type LOCAL may be further refined to UNUSED or INLINE. for (auto wire : module->wires()) { auto &wire_type = wire_types[wire]; wire_type = {WireType::BUFFERED}; if (feedback_wires[wire]) continue; if (wire->port_output && !module->get_bool_attribute(ID::top)) continue; if (!wire->name.isPublic() && !unbuffer_internal) continue; if (wire->name.isPublic() && !unbuffer_public) continue; if (flow.wire_sync_defs.count(wire) > 0) continue; wire_type = {WireType::MEMBER}; if (edge_wires[wire]) continue; if (wire->get_bool_attribute(ID::keep)) continue; if (wire->port_input || wire->port_output) continue; if (!wire->name.isPublic() && !localize_internal) continue; if (wire->name.isPublic() && !localize_public) continue; wire_type = {WireType::LOCAL}; } // Discover nodes reachable from primary outputs (i.e. members) and collect reachable wire users. 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 else if (node->type == FlowGraph::Node::Type::CELL_EVAL && is_effectful_cell(node->cell->type)) worklist.insert(node); // node has async effects else if (node->type == FlowGraph::Node::Type::EFFECT_SYNC) worklist.insert(node); // node has sync effects else if (node->type == FlowGraph::Node::Type::MEM_WRPORTS) worklist.insert(node); // node is memory write else if (node->type == FlowGraph::Node::Type::PROCESS_SYNC && is_memwr_process(node->process)) worklist.insert(node); // node is memory write else if (flow.node_sync_defs.count(node)) worklist.insert(node); // node is a flip-flop else if (flow.node_comb_defs.count(node)) { for (auto wire : flow.node_comb_defs[node]) if (wire_types[wire].is_member()) worklist.insert(node); // node drives public wires } } dict> live_wires; pool live_nodes; while (!worklist.empty()) { auto node = worklist.pop(); live_nodes.insert(node); for (auto wire : flow.node_uses[node]) { live_wires[wire].insert(node); for (auto pred_node : flow.wire_comb_defs[wire]) if (!live_nodes[pred_node]) worklist.insert(pred_node); } } // Refine wire types taking into account the amount of uses from reachable nodes only. for (auto wire : module->wires()) { auto &wire_type = wire_types[wire]; if (!wire_type.is_local()) continue; if (live_wires[wire].empty()) { wire_type = {WireType::UNUSED}; // wire never used continue; } if (!wire->name.isPublic() && !inline_internal) continue; if (wire->name.isPublic() && !inline_public) continue; if (flow.is_inlinable(wire, live_wires[wire])) { if (flow.wire_comb_defs[wire].size() > 1) log_cmd_error("Wire %s.%s has multiple drivers!\n", log_id(module), log_id(wire)); log_assert(flow.wire_comb_defs[wire].size() == 1); FlowGraph::Node *node = *flow.wire_comb_defs[wire].begin(); switch (node->type) { case FlowGraph::Node::Type::CELL_EVAL: if (!is_inlinable_cell(node->cell->type)) continue; wire_type = {WireType::INLINE, node->cell}; // wire replaced with cell break; case FlowGraph::Node::Type::CONNECT: wire_type = {WireType::INLINE, node->connect.second}; // wire replaced with sig break; default: continue; } live_nodes.erase(node); } } // Emit reachable nodes in eval(). // Accumulate sync effectful cells per trigger condition. dict, std::vector> effect_sync_cells; for (auto node : node_order) if (live_nodes[node]) { if (node->type == FlowGraph::Node::Type::CELL_EVAL && is_effectful_cell(node->cell->type) && node->cell->getParam(ID::TRG_ENABLE).as_bool() && node->cell->getParam(ID::TRG_WIDTH).as_int() != 0) effect_sync_cells[make_pair(node->cell->getPort(ID::TRG), node->cell->getParam(ID::TRG_POLARITY))].push_back(node->cell); else schedule[module].push_back(*node); } for (auto &it : effect_sync_cells) { auto node = flow.add_effect_sync_node(it.second); schedule[module].push_back(*node); } // For maximum performance, the state of the simulation (which is the same as the set of its double buffered // wires, since using a singly buffered wire for any kind of state introduces a race condition) should contain // no wires attached to combinatorial outputs. Feedback wires, by definition, make that impossible. However, // it is possible that a design with no feedback arcs would end up with doubly buffered wires in such cases // as a wire with multiple drivers where one of them is combinatorial and the other is synchronous. Such designs // also require more than one delta cycle to converge. pool buffered_comb_wires; for (auto wire : module->wires()) if (wire_types[wire].is_buffered() && !feedback_wires[wire] && flow.wire_comb_defs[wire].size() > 0) buffered_comb_wires.insert(wire); if (!buffered_comb_wires.empty()) { has_buffered_comb_wires = true; log("Module `%s' contains buffered combinatorial wires:\n", log_id(module)); for (auto wire : buffered_comb_wires) log(" %s\n", log_id(wire)); } // Record whether eval() requires only one delta cycle in this module. eval_converges[module] = feedback_wires.empty() && buffered_comb_wires.empty(); if (debug_info) { // Annotate wire bits with the type of their driver; this is exposed in the debug metadata. for (auto item : flow.bit_has_state) bit_has_state.insert(item); // Assign debug information wire types to public wires according to the chosen debug level. // Unlike with optimized wire types, all assignments here are final. for (auto wire : module->wires()) { const auto &wire_type = wire_types[wire]; auto &debug_wire_type = debug_wire_types[wire]; if (!debug_info) continue; if (wire->port_input || wire_type.is_buffered()) debug_wire_type = wire_type; // wire contains state else if (!wire->name.isPublic()) continue; // internal and stateless if (!debug_member) continue; if (wire_type.is_member()) 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); FlowGraph::Node *node = *flow.wire_comb_defs[it].begin(); if (node->type != FlowGraph::Node::Type::CONNECT) break; // not an alias RTLIL::SigSpec rhs = node->connect.second; if (rhs.is_fully_const()) { debug_wire_type = {WireType::CONST, rhs}; // wire replaced with const } else if (rhs.is_wire()) { if (wire_types[rhs.as_wire()].is_member()) debug_wire_type = {WireType::ALIAS, rhs}; // wire replaced with wire else if (debug_eval && rhs.as_wire()->name.isPublic()) debug_wire_type = {WireType::ALIAS, rhs}; // wire replaced with outline it = rhs.as_wire(); // and keep looking continue; } break; } if (!debug_eval) continue; if (!debug_wire_type.is_exact() && !wire_type.is_member()) debug_wire_type = {WireType::OUTLINE}; // wire is local or inlined } // Discover nodes reachable from primary outputs (i.e. outlines) up until primary inputs (i.e. members) // and collect reachable wire users. 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; while (!worklist.empty()) { auto node = worklist.pop(); debug_live_nodes.insert(node); for (auto wire : flow.node_uses[node]) { if (debug_wire_types[wire].is_member()) continue; // node uses member if (debug_wire_types[wire].is_exact()) continue; // node uses alias or const debug_live_wires[wire].insert(node); for (auto pred_node : flow.wire_comb_defs[wire]) if (!debug_live_nodes[pred_node]) worklist.insert(pred_node); } } // Assign debug information wire types to internal wires used by reachable nodes. This is similar // to refining optimized wire types with the exception that the assignments here are first and final. for (auto wire : module->wires()) { const auto &wire_type = wire_types[wire]; auto &debug_wire_type = debug_wire_types[wire]; if (wire->name.isPublic()) continue; if (debug_live_wires[wire].empty()) { continue; // wire never used } else if (flow.is_inlinable(wire, debug_live_wires[wire])) { log_assert(flow.wire_comb_defs[wire].size() == 1); FlowGraph::Node *node = *flow.wire_comb_defs[wire].begin(); switch (node->type) { case FlowGraph::Node::Type::CELL_EVAL: if (!is_inlinable_cell(node->cell->type)) continue; debug_wire_type = {WireType::INLINE, node->cell}; // wire replaced with cell break; case FlowGraph::Node::Type::CONNECT: debug_wire_type = {WireType::INLINE, node->connect.second}; // wire replaced with sig break; default: continue; } debug_live_nodes.erase(node); } else if (wire_type.is_member() || wire_type.type == WireType::LOCAL) { debug_wire_type = wire_type; // wire not inlinable } else { log_assert(wire_type.type == WireType::INLINE || wire_type.type == WireType::UNUSED); if (flow.wire_comb_defs[wire].size() == 0) { if (wire_init.count(wire)) { // wire never modified debug_wire_type = {WireType::CONST, wire_init.at(wire)}; } else { debug_wire_type = {WireType::CONST, RTLIL::SigSpec(RTLIL::S0, wire->width)}; } } else { debug_wire_type = {WireType::LOCAL}; // wire used only for debug } } } // Emit reachable nodes in debug_eval(). for (auto node : node_order) if (debug_live_nodes[node]) debug_schedule[module].push_back(*node); } auto show_wire_type = [&](const RTLIL::Wire* wire, const WireType &wire_type) { const char *type_str; switch (wire_type.type) { case WireType::UNUSED: type_str = "UNUSED"; break; case WireType::BUFFERED: type_str = "BUFFERED"; break; case WireType::MEMBER: type_str = "MEMBER"; break; case WireType::OUTLINE: type_str = "OUTLINE"; break; case WireType::LOCAL: type_str = "LOCAL"; break; case WireType::INLINE: type_str = "INLINE"; break; case WireType::ALIAS: type_str = "ALIAS"; break; case WireType::CONST: type_str = "CONST"; break; default: type_str = "(invalid)"; } if (wire_type.sig_subst.empty()) log_debug(" %s: %s\n", log_signal((RTLIL::Wire*)wire), type_str); else log_debug(" %s: %s = %s\n", log_signal((RTLIL::Wire*)wire), type_str, log_signal(wire_type.sig_subst)); }; if (print_wire_types && !wire_types.empty()) { log_debug("Wire types:\n"); for (auto wire_type : wire_types) show_wire_type(wire_type.first, wire_type.second); } if (print_debug_wire_types && !debug_wire_types.empty()) { log_debug("Debug wire types:\n"); for (auto debug_wire_type : debug_wire_types) show_wire_type(debug_wire_type.first, debug_wire_type.second); } } if (has_feedback_arcs || has_buffered_comb_wires) { // Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated // by optimizing the design, if after `proc; flatten` there are any feedback wires remaining, it is very // likely that these feedback wires are indicative of a true logic loop, so they get emphasized in the message. const char *why_pessimistic = nullptr; if (has_feedback_arcs) why_pessimistic = "feedback wires"; else if (has_buffered_comb_wires) why_pessimistic = "buffered combinatorial wires"; log_warning("Design contains %s, which require delta cycles during evaluation.\n", why_pessimistic); if (!run_flatten) log("Flattening may eliminate %s from the design.\n", why_pessimistic); if (!run_proc) log("Converting processes to netlists may eliminate %s from the design.\n", why_pessimistic); } } void check_design(RTLIL::Design *design, bool &has_sync_init) { has_sync_init = false; for (auto module : design->modules()) { if (module->get_blackbox_attribute() && !module->has_attribute(ID(cxxrtl_blackbox))) continue; if (!design->selected_whole_module(module)) if (design->selected_module(module)) log_cmd_error("Can't handle partially selected module `%s'!\n", id2cstr(module->name)); if (!design->selected_module(module)) continue; for (auto proc : module->processes) for (auto sync : proc.second->syncs) if (sync->type == RTLIL::STi) has_sync_init = true; } } void prepare_design(RTLIL::Design *design) { bool did_anything = false; bool has_sync_init; log_push(); check_design(design, has_sync_init); if (run_hierarchy) { Pass::call(design, "hierarchy -auto-top"); did_anything = true; } if (run_flatten) { Pass::call(design, "flatten"); did_anything = true; } if (run_proc) { Pass::call(design, "proc"); did_anything = true; } else if (has_sync_init) { // We're only interested in proc_init, but it depends on proc_prune and proc_clean, so call those // in case they weren't already. (This allows `yosys foo.v -o foo.cc` to work.) Pass::call(design, "proc_prune"); Pass::call(design, "proc_clean"); Pass::call(design, "proc_init"); did_anything = true; } // Recheck the design if it was modified. if (did_anything) check_design(design, has_sync_init); log_assert(!has_sync_init); log_pop(); if (did_anything) log_spacer(); analyze_design(design); } }; struct CxxrtlBackend : public Backend { 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 { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_cxxrtl [options] [filename]\n"); log("\n"); log("Write C++ code that simulates the design. The generated code requires a driver\n"); log("that instantiates the design, toggles its clock, and interacts with its ports.\n"); log("\n"); log("The following driver may be used as an example for a design with a single clock\n"); log("driving rising edge triggered flip-flops:\n"); log("\n"); log(" #include \"top.cc\"\n"); log("\n"); log(" int main() {\n"); log(" cxxrtl_design::p_top top;\n"); log(" top.step();\n"); log(" while (1) {\n"); log(" /* user logic */\n"); log(" top.p_clk.set(false);\n"); log(" top.step();\n"); log(" top.p_clk.set(true);\n"); log(" top.step();\n"); log(" }\n"); log(" }\n"); log("\n"); log("Note that CXXRTL simulations, just like the hardware they are simulating, are\n"); log("subject to race conditions. If, in the example above, the user logic would run\n"); log("simultaneously with the rising edge of the clock, the design would malfunction.\n"); log("\n"); log("This backend supports replacing parts of the design with black boxes implemented\n"); log("in C++. If a module marked as a CXXRTL black box, its implementation is ignored,\n"); log("and the generated code consists only of an interface and a factory function.\n"); log("The driver must implement the factory function that creates an implementation of\n"); log("the black box, taking into account the parameters it is instantiated with.\n"); log("\n"); log("For example, the following Verilog code defines a CXXRTL black box interface for\n"); log("a synchronous debug sink:\n"); log("\n"); log(" (* cxxrtl_blackbox *)\n"); log(" module debug(...);\n"); log(" (* cxxrtl_edge = \"p\" *) input clk;\n"); log(" input en;\n"); log(" input [7:0] i_data;\n"); log(" (* cxxrtl_sync *) output [7:0] o_data;\n"); log(" endmodule\n"); log("\n"); log("For this HDL interface, this backend will generate the following C++ interface:\n"); log("\n"); log(" struct bb_p_debug : public module {\n"); log(" value<1> p_clk;\n"); log(" bool posedge_p_clk() const { /* ... */ }\n"); log(" value<1> p_en;\n"); log(" value<8> p_i_data;\n"); log(" wire<8> p_o_data;\n"); log("\n"); log(" bool eval(performer *performer) override;\n"); log(" virtual bool commit(observer &observer);\n"); log(" bool commit() override;\n"); log("\n"); log(" static std::unique_ptr\n"); log(" create(std::string name, metadata_map parameters, metadata_map attributes);\n"); log(" };\n"); log("\n"); log("The `create' function must be implemented by the driver. For example, it could\n"); log("always provide an implementation logging the values to standard error stream:\n"); log("\n"); log(" namespace cxxrtl_design {\n"); log("\n"); log(" struct stderr_debug : public bb_p_debug {\n"); log(" bool eval(performer *performer) override {\n"); log(" if (posedge_p_clk() && p_en)\n"); log(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\n"); log(" p_o_data.next = p_i_data;\n"); log(" return bb_p_debug::eval(performer);\n"); log(" }\n"); log(" };\n"); log("\n"); log(" std::unique_ptr\n"); log(" bb_p_debug::create(std::string name, cxxrtl::metadata_map parameters,\n"); log(" cxxrtl::metadata_map attributes) {\n"); log(" return std::make_unique();\n"); log(" }\n"); log("\n"); log(" }\n"); log("\n"); log("For complex applications of black boxes, it is possible to parameterize their\n"); log("port widths. For example, the following Verilog code defines a CXXRTL black box\n"); log("interface for a configurable width debug sink:\n"); log("\n"); log(" (* cxxrtl_blackbox, cxxrtl_template = \"WIDTH\" *)\n"); log(" module debug(...);\n"); log(" parameter WIDTH = 8;\n"); log(" (* cxxrtl_edge = \"p\" *) input clk;\n"); log(" input en;\n"); log(" (* cxxrtl_width = \"WIDTH\" *) input [WIDTH - 1:0] i_data;\n"); log(" (* cxxrtl_width = \"WIDTH\" *) output [WIDTH - 1:0] o_data;\n"); log(" endmodule\n"); log("\n"); log("For this parametric HDL interface, this backend will generate the following C++\n"); log("interface (only the differences are shown):\n"); log("\n"); log(" template\n"); log(" struct bb_p_debug : public module {\n"); log(" // ...\n"); log(" value p_i_data;\n"); log(" wire p_o_data;\n"); log(" // ...\n"); log(" static std::unique_ptr>\n"); log(" create(std::string name, metadata_map parameters, metadata_map attributes);\n"); log(" };\n"); log("\n"); log("The `create' function must be implemented by the driver, specialized for every\n"); log("possible combination of template parameters. (Specialization is necessary to\n"); log("enable separate compilation of generated code and black box implementations.)\n"); log("\n"); log(" template\n"); log(" struct stderr_debug : public bb_p_debug {\n"); log(" // ...\n"); log(" };\n"); log("\n"); log(" template<>\n"); log(" std::unique_ptr>\n"); log(" bb_p_debug<8>::create(std::string name, cxxrtl::metadata_map parameters,\n"); log(" cxxrtl::metadata_map attributes) {\n"); log(" return std::make_unique>();\n"); log(" }\n"); log("\n"); log("The following attributes are recognized by this backend:\n"); log("\n"); log(" cxxrtl_blackbox\n"); log(" only valid on modules. if specified, the module contents are ignored,\n"); log(" and the generated code includes only the module interface and a factory\n"); log(" function, which will be called to instantiate the module.\n"); log("\n"); log(" cxxrtl_edge\n"); log(" only valid on inputs of black boxes. must be one of \"p\", \"n\", \"a\".\n"); log(" if specified on signal `clk`, the generated code includes edge detectors\n"); log(" `posedge_p_clk()` (if \"p\"), `negedge_p_clk()` (if \"n\"), or both (if\n"); log(" \"a\"), simplifying implementation of clocked black boxes.\n"); log("\n"); log(" cxxrtl_template\n"); log(" only valid on black boxes. must contain a space separated sequence of\n"); log(" identifiers that have a corresponding black box parameters. for each\n"); log(" of them, the generated code includes a `size_t` template parameter.\n"); log("\n"); log(" cxxrtl_width\n"); log(" only valid on ports of black boxes. must be a constant expression, which\n"); log(" is directly inserted into generated code.\n"); log("\n"); log(" cxxrtl_comb, cxxrtl_sync\n"); log(" only valid on outputs of black boxes. if specified, indicates that every\n"); log(" bit of the output port is driven, correspondingly, by combinatorial or\n"); log(" synchronous logic. this knowledge is used for scheduling optimizations.\n"); log(" if neither is specified, the output will be pessimistically treated as\n"); log(" driven by both combinatorial and synchronous logic.\n"); log("\n"); log("The following options are supported by this backend:\n"); log("\n"); log(" -print-wire-types, -print-debug-wire-types\n"); log(" enable additional debug logging, for pass developers.\n"); log("\n"); log(" -header\n"); log(" generate separate interface (.h) and implementation (.cc) files.\n"); log(" if specified, the backend must be called with a filename, and filename\n"); log(" of the interface is derived from filename of the implementation.\n"); log(" otherwise, interface and implementation are generated together.\n"); log("\n"); log(" -namespace \n"); log(" place the generated code into namespace . if not specified,\n"); log(" \"cxxrtl_design\" is used.\n"); log("\n"); log(" -print-output \n"); log(" $print cells in the generated code direct their output to .\n"); log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n"); log(" \"std::cout\" is used. explicitly provided performer overrides this.\n"); log("\n"); log(" -nohierarchy\n"); log(" use design hierarchy as-is. in most designs, a top module should be\n"); log(" present as it is exposed through the C API and has unbuffered outputs\n"); log(" for improved performance; it will be determined automatically if absent.\n"); log("\n"); log(" -noflatten\n"); log(" don't flatten the design. fully flattened designs can evaluate within\n"); log(" one delta cycle if they have no combinatorial feedback.\n"); log(" note that the debug interface and waveform dumps use full hierarchical\n"); log(" names for all wires even in flattened designs.\n"); log("\n"); log(" -noproc\n"); log(" don't convert processes to netlists. in most designs, converting\n"); log(" processes significantly improves evaluation performance at the cost of\n"); log(" slight increase in compilation time.\n"); log("\n"); log(" -O \n"); log(" set the optimization level. the default is -O%d. higher optimization\n", DEFAULT_OPT_LEVEL); log(" levels dramatically decrease compile and run time, and highest level\n"); log(" possible for a design should be used.\n"); log("\n"); log(" -O0\n"); log(" no optimization.\n"); log("\n"); log(" -O1\n"); log(" unbuffer internal wires if possible.\n"); log("\n"); log(" -O2\n"); log(" like -O1, and localize internal wires if possible.\n"); log("\n"); log(" -O3\n"); log(" like -O2, and inline internal wires if possible.\n"); log("\n"); log(" -O4\n"); log(" like -O3, and unbuffer public wires not marked (*keep*) if possible.\n"); log("\n"); log(" -O5\n"); log(" like -O4, and localize public wires not marked (*keep*) if possible.\n"); log("\n"); log(" -O6\n"); log(" like -O5, and inline public wires not marked (*keep*) if possible.\n"); log("\n"); log(" -g \n"); log(" set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL); log(" more visibility and generate more code, but do not pessimize evaluation.\n"); log("\n"); log(" -g0\n"); log(" no debug information. the C API is disabled.\n"); log("\n"); log(" -g1\n"); log(" include bare minimum of debug information necessary to access all design\n"); log(" state. the C API is enabled.\n"); log("\n"); log(" -g2\n"); log(" like -g1, but include debug information for all public wires that are\n"); log(" directly accessible through the C++ interface.\n"); log("\n"); log(" -g3\n"); log(" like -g2, and include debug information for public wires that are tied\n"); log(" to a constant or another public wire.\n"); log("\n"); log(" -g4\n"); log(" like -g3, and compute debug information on demand for all public wires\n"); log(" that were optimized out.\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { bool print_wire_types = false; bool print_debug_wire_types = false; bool nohierarchy = false; bool noflatten = false; bool noproc = false; int opt_level = DEFAULT_OPT_LEVEL; int debug_level = DEFAULT_DEBUG_LEVEL; CxxrtlWorker worker; log_header(design, "Executing CXXRTL backend.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-print-wire-types") { print_wire_types = true; continue; } if (args[argidx] == "-print-debug-wire-types") { print_debug_wire_types = true; continue; } if (args[argidx] == "-nohierarchy") { nohierarchy = true; continue; } if (args[argidx] == "-noflatten") { noflatten = true; continue; } if (args[argidx] == "-noproc") { noproc = true; continue; } if (args[argidx] == "-Og") { log_warning("The `-Og` option has been removed. Use `-g3` instead for complete " "design coverage regardless of optimization level.\n"); continue; } if (args[argidx] == "-O" && argidx+1 < args.size() && args[argidx+1] == "g") { argidx++; log_warning("The `-Og` option has been removed. Use `-g3` instead for complete " "design coverage regardless of optimization level.\n"); continue; } if (args[argidx] == "-O" && argidx+1 < args.size()) { opt_level = std::stoi(args[++argidx]); continue; } if (args[argidx].substr(0, 2) == "-O" && args[argidx].size() == 3 && isdigit(args[argidx][2])) { opt_level = std::stoi(args[argidx].substr(2)); continue; } if (args[argidx] == "-g" && argidx+1 < args.size()) { debug_level = std::stoi(args[++argidx]); continue; } if (args[argidx].substr(0, 2) == "-g" && args[argidx].size() == 3 && isdigit(args[argidx][2])) { debug_level = std::stoi(args[argidx].substr(2)); continue; } if (args[argidx] == "-header") { worker.split_intf = true; continue; } if (args[argidx] == "-namespace" && argidx+1 < args.size()) { worker.design_ns = args[++argidx]; continue; } 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); worker.print_output = "std::cout"; } continue; } break; } extra_args(f, filename, args, argidx); worker.print_wire_types = print_wire_types; worker.print_debug_wire_types = print_debug_wire_types; worker.run_hierarchy = !nohierarchy; worker.run_flatten = !noflatten; worker.run_proc = !noproc; switch (opt_level) { // the highest level here must match DEFAULT_OPT_LEVEL case 6: worker.inline_public = true; YS_FALLTHROUGH case 5: worker.localize_public = true; YS_FALLTHROUGH case 4: worker.unbuffer_public = true; YS_FALLTHROUGH case 3: worker.inline_internal = true; YS_FALLTHROUGH case 2: worker.localize_internal = true; YS_FALLTHROUGH case 1: worker.unbuffer_internal = true; YS_FALLTHROUGH case 0: break; default: log_cmd_error("Invalid optimization level %d.\n", opt_level); } switch (debug_level) { // the highest level here must match DEFAULT_DEBUG_LEVEL case 4: worker.debug_eval = true; YS_FALLTHROUGH case 3: worker.debug_alias = true; YS_FALLTHROUGH case 2: worker.debug_member = true; YS_FALLTHROUGH case 1: worker.debug_info = true; YS_FALLTHROUGH case 0: break; default: log_cmd_error("Invalid debug information level %d.\n", debug_level); } std::ofstream intf_f; if (worker.split_intf) { if (filename == "") log_cmd_error("Option -header must be used with a filename.\n"); worker.intf_filename = filename.substr(0, filename.rfind('.')) + ".h"; intf_f.open(worker.intf_filename, std::ofstream::trunc); if (intf_f.fail()) log_cmd_error("Can't open file `%s' for writing: %s\n", worker.intf_filename.c_str(), strerror(errno)); worker.intf_f = &intf_f; } worker.impl_f = f; worker.prepare_design(design); worker.dump_design(design); } } CxxrtlBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/cxxrtl/runtime/000077500000000000000000000000001520057232300174125ustar00rootroot00000000000000yosys-0.65/backends/cxxrtl/runtime/README.txt000066400000000000000000000022521520057232300211110ustar00rootroot00000000000000This directory contains the runtime components of CXXRTL and should be placed on the include path when building the simulation using the `-I${YOSYS}/backends/cxxrtl/runtime` option. These components are not used in the Yosys binary; they are only built as a part of the simulation binary. The interfaces declared in `cxxrtl_capi*.h` contain the stable C API. These interfaces will not be changed in backward-incompatible ways unless no other option is available, and any breaking changes will be made in a way that causes the downstream code to fail in a visible way. The ABI of these interfaces is considered stable as well, and it will not use features complicating its use via libraries such as libffi or ctypes. The implementations in `cxxrtl_capi*.cc` are considered private; they are still placed in the include path to enable build-system-less builds (where the CXXRTL runtime component is included in the C++ file of the simulation toplevel). The interfaces declared in `cxxrtl*.h` (without `capi`) are unstable and may change without notice. For clarity, all of the files in this directory and its subdirectories have unique names regardless of the directory where they are placed.yosys-0.65/backends/cxxrtl/runtime/cxxrtl/000077500000000000000000000000001520057232300207365ustar00rootroot00000000000000yosys-0.65/backends/cxxrtl/runtime/cxxrtl/capi/000077500000000000000000000000001520057232300216525ustar00rootroot00000000000000yosys-0.65/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc000066400000000000000000000107171520057232300245070ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 file is a part of the CXXRTL C API. It should be used together with `cxxrtl/capi/cxxrtl_capi.h`. #include #include struct _cxxrtl_handle { std::unique_ptr module; cxxrtl::debug_items objects; }; // Private function for use by other units of the C API. const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle) { return handle->objects; } cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design) { return cxxrtl_create_at(design, ""); } cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path_) { std::string top_path = top_path_; if (!top_path.empty()) { // module::debug_info() accepts either an empty path, or a path ending in space to simplify // the logic in generated code. While this is sketchy at best to expose in the C++ API, this // would be a lot worse in the C API, so don't expose it here. assert(top_path.back() != ' '); top_path += ' '; } cxxrtl_handle handle = new _cxxrtl_handle; handle->module = std::move(design->module); handle->module->debug_info(&handle->objects, nullptr, top_path); delete design; return handle; } void cxxrtl_destroy(cxxrtl_handle handle) { delete handle; } void cxxrtl_reset(cxxrtl_handle handle) { handle->module->reset(); } int cxxrtl_eval(cxxrtl_handle handle) { return handle->module->eval(); } int cxxrtl_commit(cxxrtl_handle handle) { return handle->module->commit(); } size_t cxxrtl_step(cxxrtl_handle handle) { return handle->module->step(); } struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts) { auto it = handle->objects.table.find(name); if (it == handle->objects.table.end()) return nullptr; *parts = it->second.size(); return static_cast(&it->second[0]); } void cxxrtl_enum(cxxrtl_handle handle, void *data, void (*callback)(void *data, const char *name, cxxrtl_object *object, size_t parts)) { for (auto &it : handle->objects.table) callback(data, it.first.c_str(), static_cast(&it.second[0]), it.second.size()); } void cxxrtl_outline_eval(cxxrtl_outline outline) { outline->eval(); } int cxxrtl_attr_type(cxxrtl_attr_set attrs_, const char *name) { auto attrs = (cxxrtl::metadata_map*)attrs_; if (!attrs->count(name)) return CXXRTL_ATTR_NONE; switch (attrs->at(name).value_type) { case cxxrtl::metadata::UINT: return CXXRTL_ATTR_UNSIGNED_INT; case cxxrtl::metadata::SINT: return CXXRTL_ATTR_SIGNED_INT; case cxxrtl::metadata::STRING: return CXXRTL_ATTR_STRING; case cxxrtl::metadata::DOUBLE: return CXXRTL_ATTR_DOUBLE; default: // Present unsupported attribute type the same way as no attribute at all. return CXXRTL_ATTR_NONE; } } uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs_, const char *name) { auto &attrs = *(cxxrtl::metadata_map*)attrs_; assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::UINT); return attrs[name].as_uint(); } int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs_, const char *name) { auto &attrs = *(cxxrtl::metadata_map*)attrs_; assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::SINT); return attrs[name].as_sint(); } const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs_, const char *name) { auto &attrs = *(cxxrtl::metadata_map*)attrs_; assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::STRING); return attrs[name].as_string().c_str(); } double cxxrtl_attr_get_double(cxxrtl_attr_set attrs_, const char *name) { auto &attrs = *(cxxrtl::metadata_map*)attrs_; assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::DOUBLE); return attrs[name].as_double(); } yosys-0.65/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h000066400000000000000000000405171520057232300243520ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 CXXRTL_CAPI_H #define CXXRTL_CAPI_H // This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_capi.cc`. // // The CXXRTL C API makes it possible to drive CXXRTL designs using C or any other language that // supports the C ABI, for example, Python. It does not provide a way to implement black boxes. #include #include #include #ifdef __cplusplus extern "C" { #endif // Opaque reference to a design toplevel. // // A design toplevel can only be used to create a design handle. typedef struct _cxxrtl_toplevel *cxxrtl_toplevel; // The constructor for a design toplevel is provided as a part of generated code for that design. // Its prototype matches: // // cxxrtl_toplevel _create(); // Opaque reference to a design handle. // // A design handle is required by all operations in the C API. typedef struct _cxxrtl_handle *cxxrtl_handle; // Create a design handle from a design toplevel. // // The `design` is consumed by this operation and cannot be used afterwards. cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design); // Create a design handle at a given hierarchy position from a design toplevel. // // This operation is similar to `cxxrtl_create`, except the full hierarchical name of every object // is prepended with `top_path`. cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path); // Release all resources used by a design and its handle. void cxxrtl_destroy(cxxrtl_handle handle); // Reinitialize the design, replacing the internal state with the reset values while preserving // black boxes. // // This operation is essentially equivalent to a power-on reset. Values, wires, and memories are // returned to their reset state while preserving the state of black boxes and keeping all of // the interior pointers obtained with e.g. `cxxrtl_get` valid. void cxxrtl_reset(cxxrtl_handle handle); // Evaluate the design, propagating changes on inputs to the `next` value of internal state and // output wires. // // Returns 1 if the design is known to immediately converge, 0 otherwise. int cxxrtl_eval(cxxrtl_handle handle); // Commit the design, replacing the `curr` value of internal state and output wires with the `next` // value. // // Return 1 if any of the `curr` values were updated, 0 otherwise. int cxxrtl_commit(cxxrtl_handle handle); // Simulate the design to a fixed point. // // Returns the number of delta cycles. size_t cxxrtl_step(cxxrtl_handle handle); // Type of a simulated object. // // The type of a simulated object indicates the way it is stored and the operations that are legal // to perform on it (i.e. won't crash the simulation). It says very little about object semantics, // which is specified through flags. enum cxxrtl_type { // Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by // combinatorial cells, or toplevel input nodes. // // Values can be inspected via the `curr` pointer. If the `next` pointer is NULL, the value is // driven by a constant and can never be modified. Otherwise, the value can be modified through // the `next` pointer (which is equal to `curr` if not NULL). Note that changes to the bits // driven by combinatorial cells will be ignored. // // Values always have depth 1. CXXRTL_VALUE = 0, // Wires correspond to doubly buffered netlist nodes, i.e. nodes driven, at least in part, by // storage cells, or by combinatorial cells that are a part of a feedback path. They are also // present in non-optimized builds. // // Wires can be inspected via the `curr` pointer and modified via the `next` pointer (which are // distinct for wires). Note that changes to the bits driven by combinatorial cells will be // ignored. // // Wires always have depth 1. CXXRTL_WIRE = 1, // Memories correspond to memory cells. // // Memories can be inspected and modified via the `curr` pointer. Due to a limitation of this // API, memories cannot yet be modified in a guaranteed race-free way, and the `next` pointer is // always NULL. CXXRTL_MEMORY = 2, // Aliases correspond to netlist nodes driven by another node such that their value is always // exactly equal. // // Aliases can be inspected via the `curr` pointer. They cannot be modified, and the `next` // pointer is always NULL. CXXRTL_ALIAS = 3, // Outlines correspond to netlist nodes that were optimized in a way that makes them inaccessible // outside of a module's `eval()` function. At the highest debug information level, every inlined // node has a corresponding outline object. // // Outlines can be inspected via the `curr` pointer and can never be modified; the `next` pointer // is always NULL. Unlike all other objects, the bits of an outline object are meaningful only // after a call to `cxxrtl_outline_eval` and until any subsequent modification to the netlist. // Observing this requirement is the responsibility of the caller; it is not enforced. // // Outlines always correspond to combinatorial netlist nodes that are not ports. CXXRTL_OUTLINE = 4, // More object types may be added in the future, but the existing ones will never change. }; // Flags of a simulated object. // // The flags of a simulated object indicate its role in the netlist: // * The flags `CXXRTL_INPUT` and `CXXRTL_OUTPUT` designate module ports. // * The flags `CXXRTL_DRIVEN_SYNC`, `CXXRTL_DRIVEN_COMB`, and `CXXRTL_UNDRIVEN` specify // the semantics of node state. An object with several of these flags set has different bits // follow different semantics. enum cxxrtl_flag { // Node is a module input port. // // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined // with `CXXRTL_OUTPUT`, as well as other flags. CXXRTL_INPUT = 1 << 0, // Node is a module output port. // // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with `CXXRTL_INPUT`, // as well as other flags. CXXRTL_OUTPUT = 1 << 1, // Node is a module inout port. // // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with other flags. CXXRTL_INOUT = (CXXRTL_INPUT|CXXRTL_OUTPUT), // Node has bits that are driven by a storage cell. // // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with // `CXXRTL_DRIVEN_COMB` and `CXXRTL_UNDRIVEN`, as well as other flags. // // This flag is set on wires that have bits connected directly to the output of a flip-flop or // a latch, and hold its state. Many `CXXRTL_WIRE` objects may not have the `CXXRTL_DRIVEN_SYNC` // flag set; for example, output ports and feedback wires generally won't. Writing to the `next` // pointer of these wires updates stored state, and for designs without combinatorial loops, // capturing the value from every of these wires through the `curr` pointer creates a complete // snapshot of the design state. CXXRTL_DRIVEN_SYNC = 1 << 2, // Node has bits that are driven by a combinatorial cell or another node. // // This flag can be set on objects of type `CXXRTL_VALUE`, `CXXRTL_WIRE`, and `CXXRTL_OUTLINE`. // It may be combined with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags. // // This flag is set on objects that have bits connected to the output of a combinatorial cell, // or directly to another node. For designs without combinatorial loops, writing to such bits // through the `next` pointer (if it is not NULL) has no effect. CXXRTL_DRIVEN_COMB = 1 << 3, // Node has bits that are not driven. // // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined // with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_DRIVEN_COMB`, as well as other flags. // // This flag is set on objects that have bits not driven by an output of any cell or by another // 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. }; // Description of a simulated object. // // The `curr` and `next` arrays can be accessed directly to inspect and, if applicable, modify // the bits stored in the object. struct cxxrtl_object { // Type of the object. // // All objects have the same memory layout determined by `width` and `depth`, but the type // determines all other properties of the object. uint32_t type; // actually `enum cxxrtl_type` // Flags of the object. uint32_t flags; // actually bit mask of `enum cxxrtl_flags` // Width of the object in bits. size_t width; // Index of the least significant bit. size_t lsb_at; // Depth of the object. Only meaningful for memories; for other objects, always 1. size_t depth; // Index of the first word. Only meaningful for memories; for other objects, always 0; size_t zero_at; // Bits stored in the object, as 32-bit chunks, least significant bits first. // // The width is rounded up to a multiple of 32; the padding bits are always set to 0 by // the simulation code, and must be always written as 0 when modified by user code. // In memories, every element is stored contiguously. Therefore, the total number of chunks // in any object is `((width + 31) / 32) * depth`. // // To allow the simulation to be partitioned into multiple independent units communicating // through wires, the bits are double buffered. To avoid race conditions, user code should // always read from `curr` and write to `next`. The `curr` pointer is always valid; for objects // that cannot be modified, or cannot be modified in a race-free way, `next` is NULL. // // In case where `width == 0`, `curr` is a non-NULL pointer unique for the wire. That is, // there is a 1-to-1 correspondence between simulation objects and `curr` pointers, regardless // of whether they have storage or not. (Aliases' `curr` pointer equals that of some other // simulated object.) uint32_t *curr; uint32_t *next; // Opaque reference to an outline. Only meaningful for outline objects. // // See the documentation of `cxxrtl_outline` for details. When creating a `cxxrtl_object`, set // this field to NULL. struct _cxxrtl_outline *outline; // Opaque reference to an attribute set. // // See the documentation of `cxxrtl_attr_set` for details. When creating a `cxxrtl_object`, set // this field to NULL. // // The lifetime of the pointers returned by `cxxrtl_attr_*` family of functions is the same as // the lifetime of this structure. struct _cxxrtl_attr_set *attrs; // More description fields may be added in the future, but the existing ones will never change. }; // Retrieve description of a simulated object. // // The `name` is the full hierarchical name of the object in the Yosys notation, where public names // have a `\` prefix and hierarchy levels are separated by single spaces. For example, if // the top-level module instantiates a module `foo`, which in turn contains a wire `bar`, the full // hierarchical name is `\foo \bar`. // // The storage of a single abstract object may be split (usually with the `splitnets` pass) into // many physical parts, all of which correspond to the same hierarchical name. To handle such cases, // this function returns an array and writes its length to `parts`. The array is sorted by `lsb_at`. // // Returns the object parts if it was found, NULL otherwise. The returned parts are valid until // the design is destroyed. struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts); // Retrieve description of a single part simulated object. // // This function is a shortcut for the most common use of `cxxrtl_get_parts`. It asserts that, // if the object exists, it consists of a single part. If assertions are disabled, it returns NULL // for multi-part objects. static inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { size_t parts = 0; struct cxxrtl_object *object = cxxrtl_get_parts(handle, name, &parts); assert(object == NULL || parts == 1); if (object == NULL || parts == 1) return object; return NULL; } // Enumerate simulated objects. // // For every object in the simulation, `callback` is called with the provided `data`, the full // hierarchical name of the object (see `cxxrtl_get` for details), and the object parts. // The provided `name` and `object` values are valid until the design is destroyed. void cxxrtl_enum(cxxrtl_handle handle, void *data, void (*callback)(void *data, const char *name, struct cxxrtl_object *object, size_t parts)); // Opaque reference to an outline. // // An outline is a group of outline objects that are evaluated simultaneously. The identity of // an outline can be compared to determine whether any two objects belong to the same outline. typedef struct _cxxrtl_outline *cxxrtl_outline; // Evaluate an outline. // // After evaluating an outline, the bits of every outline object contained in it are consistent // with the current state of the netlist. In general, any further modification to the netlist // causes every outline object to become stale, after which the corresponding outline must be // re-evaluated, otherwise the bits read from that object are meaningless. void cxxrtl_outline_eval(cxxrtl_outline outline); // Opaque reference to an attribute set. // // An attribute set is a map between attribute names (always strings) and values (which may have // several different types). To find out the type of an attribute, use `cxxrtl_attr_type`, and // to retrieve the value of an attribute, use `cxxrtl_attr_as_string`. typedef struct _cxxrtl_attr_set *cxxrtl_attr_set; // Type of an attribute. enum cxxrtl_attr_type { // Attribute is not present. CXXRTL_ATTR_NONE = 0, // Attribute has an unsigned integer value. CXXRTL_ATTR_UNSIGNED_INT = 1, // Attribute has an unsigned integer value. CXXRTL_ATTR_SIGNED_INT = 2, // Attribute has a string value. CXXRTL_ATTR_STRING = 3, // Attribute has a double precision floating point value. CXXRTL_ATTR_DOUBLE = 4, // More attribute types may be defined in the future, but the existing values will never change. }; // Determine the presence and type of an attribute in an attribute set. // // This function returns one of the possible `cxxrtl_attr_type` values. int cxxrtl_attr_type(cxxrtl_attr_set attrs, const char *name); // Retrieve an unsigned integer valued attribute from an attribute set. // // This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_UNSIGNED_INT`. // If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type. uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs, const char *name); // Retrieve a signed integer valued attribute from an attribute set. // // This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_SIGNED_INT`. // If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type. int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs, const char *name); // Retrieve a string valued attribute from an attribute set. The returned string is zero-terminated. // // This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_STRING`. If assertions // are disabled, returns NULL if the attribute is missing or has an incorrect type. const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs, const char *name); // Retrieve a double precision floating point valued attribute from an attribute set. // // This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_DOUBLE`. If assertions // are disabled, returns NULL if the attribute is missing or has an incorrect type. double cxxrtl_attr_get_double(cxxrtl_attr_set attrs, const char *name); #ifdef __cplusplus } #endif #endif yosys-0.65/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.cc000066400000000000000000000053771520057232300253510ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 file is a part of the CXXRTL C API. It should be used together with `cxxrtl/capi/cxxrtl_capi_vcd.h`. #include #include extern const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle); struct _cxxrtl_vcd { cxxrtl::vcd_writer writer; bool flush = false; }; cxxrtl_vcd cxxrtl_vcd_create() { return new _cxxrtl_vcd; } void cxxrtl_vcd_destroy(cxxrtl_vcd vcd) { delete vcd; } void cxxrtl_vcd_timescale(cxxrtl_vcd vcd, int number, const char *unit) { vcd->writer.timescale(number, unit); } void cxxrtl_vcd_add(cxxrtl_vcd vcd, const char *name, cxxrtl_object *object) { // Note the copy. We don't know whether `object` came from a design (in which case it is // an instance of `debug_item`), or from user code (in which case it is an instance of // `cxxrtl_object`), so casting the pointer wouldn't be safe. vcd->writer.add(name, cxxrtl::debug_item(*object)); } void cxxrtl_vcd_add_from(cxxrtl_vcd vcd, cxxrtl_handle handle) { vcd->writer.add(cxxrtl_debug_items_from_handle(handle)); } void cxxrtl_vcd_add_from_if(cxxrtl_vcd vcd, cxxrtl_handle handle, void *data, int (*filter)(void *data, const char *name, const cxxrtl_object *object)) { vcd->writer.add(cxxrtl_debug_items_from_handle(handle), [=](const std::string &name, const cxxrtl::debug_item &item) { return filter(data, name.c_str(), static_cast(&item)); }); } void cxxrtl_vcd_add_from_without_memories(cxxrtl_vcd vcd, cxxrtl_handle handle) { vcd->writer.add_without_memories(cxxrtl_debug_items_from_handle(handle)); } void cxxrtl_vcd_sample(cxxrtl_vcd vcd, uint64_t time) { if (vcd->flush) { vcd->writer.buffer.clear(); vcd->flush = false; } vcd->writer.sample(time); } void cxxrtl_vcd_read(cxxrtl_vcd vcd, const char **data, size_t *size) { if (vcd->flush) { vcd->writer.buffer.clear(); vcd->flush = false; } *data = vcd->writer.buffer.c_str(); *size = vcd->writer.buffer.size(); vcd->flush = true; } yosys-0.65/backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.h000066400000000000000000000102761520057232300252050ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 CXXRTL_CAPI_VCD_H #define CXXRTL_CAPI_VCD_H // This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_vcd_capi.cc`. // // The CXXRTL C API for VCD writing makes it possible to insert virtual probes into designs and // dump waveforms to Value Change Dump files. #include #include #include #ifdef __cplusplus extern "C" { #endif // Opaque reference to a VCD writer. typedef struct _cxxrtl_vcd *cxxrtl_vcd; // Create a VCD writer. cxxrtl_vcd cxxrtl_vcd_create(); // Release all resources used by a VCD writer. void cxxrtl_vcd_destroy(cxxrtl_vcd vcd); // Set VCD timescale. // // The `number` must be 1, 10, or 100, and the `unit` must be one of `"s"`, `"ms"`, `"us"`, `"ns"`, // `"ps"`, or `"fs"`. // // Timescale can only be set before the first call to `cxxrtl_vcd_sample`. void cxxrtl_vcd_timescale(cxxrtl_vcd vcd, int number, const char *unit); // Schedule a specific CXXRTL object to be sampled. // // The `name` is a full hierarchical name as described for `cxxrtl_get`; it does not need to match // the original name of `object`, if any. The `object` must outlive the VCD writer, but there are // no other requirements; if desired, it can be provided by user code, rather than come from // a design. // // Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. void cxxrtl_vcd_add(cxxrtl_vcd vcd, const char *name, struct cxxrtl_object *object); // Schedule all CXXRTL objects in a simulation. // // The design `handle` must outlive the VCD writer. // // Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. void cxxrtl_vcd_add_from(cxxrtl_vcd vcd, cxxrtl_handle handle); // Schedule CXXRTL objects in a simulation that match a given predicate. // // For every object in the simulation, `filter` is called with the provided `data`, the full // hierarchical name of the object (see `cxxrtl_get` for details), and the object description. // The object will be sampled if the predicate returns a non-zero value. // // Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. void cxxrtl_vcd_add_from_if(cxxrtl_vcd vcd, cxxrtl_handle handle, void *data, int (*filter)(void *data, const char *name, const struct cxxrtl_object *object)); // Schedule all CXXRTL objects in a simulation except for memories. // // The design `handle` must outlive the VCD writer. // // Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`. void cxxrtl_vcd_add_from_without_memories(cxxrtl_vcd vcd, cxxrtl_handle handle); // Sample all scheduled objects. // // First, `time` is written to the internal buffer. Second, the values of every signal changed since // the previous call to `cxxrtl_vcd_sample` (all values if this is the first call) are written to // the internal buffer. The contents of the buffer can be retrieved with `cxxrtl_vcd_read`. void cxxrtl_vcd_sample(cxxrtl_vcd vcd, uint64_t time); // Retrieve buffered VCD data. // // The pointer to the start of the next chunk of VCD data is assigned to `*data`, and the length // of that chunk is assigned to `*size`. The pointer to the data is valid until the next call to // `cxxrtl_vcd_sample` or `cxxrtl_vcd_read`. Once all of the buffered data has been retrieved, // this function will always return zero sized chunks. void cxxrtl_vcd_read(cxxrtl_vcd vcd, const char **data, size_t *size); #ifdef __cplusplus } #endif #endif yosys-0.65/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h000066400000000000000000002155331520057232300224440ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2019-2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 file is included by the designs generated with `write_cxxrtl`. It is not used in Yosys itself. // // The CXXRTL support library implements compile time specialized arbitrary width arithmetics, as well as provides // composite lvalues made out of bit slices and concatenations of lvalues. This allows the `write_cxxrtl` pass // to perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler // to unwrap the abstraction and generate efficient code. #ifndef CXXRTL_H #define CXXRTL_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include // `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements. #include #ifndef __has_attribute # define __has_attribute(x) 0 #endif // CXXRTL essentially uses the C++ compiler as a hygienic macro engine that feeds an instruction selector. // It generates a lot of specialized template functions with relatively large bodies that, when inlined // into the caller and (for those with loops) unrolled, often expose many new optimization opportunities. // Because of this, most of the CXXRTL runtime must be always inlined for best performance. #if __has_attribute(always_inline) #define CXXRTL_ALWAYS_INLINE inline __attribute__((__always_inline__)) #else #define CXXRTL_ALWAYS_INLINE inline #endif // Conversely, some functions in the generated code are extremely large yet very cold, with both of these // properties being extreme enough to confuse C++ compilers into spending pathological amounts of time // on a futile (the code becomes worse) attempt to optimize the least important parts of code. #if __has_attribute(optnone) #define CXXRTL_EXTREMELY_COLD __attribute__((__optnone__)) #elif __has_attribute(optimize) #define CXXRTL_EXTREMELY_COLD __attribute__((__optimize__(0))) #else #define CXXRTL_EXTREMELY_COLD #endif // CXXRTL uses assert() to check for C++ contract violations (which may result in e.g. undefined behavior // of the simulation code itself), and CXXRTL_ASSERT to check for RTL contract violations (which may at // most result in undefined simulation results). // // Though by default, CXXRTL_ASSERT() expands to assert(), it may be overridden e.g. when integrating // the simulation into another process that should survive violating RTL contracts. #ifndef CXXRTL_ASSERT #ifndef CXXRTL_NDEBUG #define CXXRTL_ASSERT(x) assert(x) #else #define CXXRTL_ASSERT(x) #endif #endif namespace cxxrtl { // All arbitrary-width values in CXXRTL are backed by arrays of unsigned integers called chunks. The chunk size // is the same regardless of the value width to simplify manipulating values via FFI interfaces, e.g. driving // and introspecting the simulation in Python. // // It is practical to use chunk sizes between 32 bits and platform register size because when arithmetics on // narrower integer types is legalized by the C++ compiler, it inserts code to clear the high bits of the register. // However, (a) most of our operations do not change those bits in the first place because of invariants that are // invisible to the compiler, (b) we often operate on non-power-of-2 values and have to clear the high bits anyway. // Therefore, using relatively wide chunks and clearing the high bits explicitly and only when we know they may be // clobbered results in simpler generated code. typedef uint32_t chunk_t; typedef uint64_t wide_chunk_t; template struct chunk_traits { static_assert(std::is_integral::value && std::is_unsigned::value, "chunk type must be an unsigned integral type"); using type = T; static constexpr size_t bits = std::numeric_limits::digits; static constexpr T mask = std::numeric_limits::max(); }; template struct expr_base; template struct value : public expr_base> { static constexpr size_t bits = Bits; using chunk = chunk_traits; static constexpr chunk::type msb_mask = (Bits % chunk::bits == 0) ? chunk::mask : chunk::mask >> (chunk::bits - (Bits % chunk::bits)); static constexpr size_t chunks = (Bits + chunk::bits - 1) / chunk::bits; chunk::type data[chunks] = {}; value() = default; template explicit constexpr value(Init ...init) : data{init...} {} value(const value &) = default; value &operator=(const value &) = default; value(value &&) = default; value &operator=(value &&) = default; // A (no-op) helper that forces the cast to value<>. CXXRTL_ALWAYS_INLINE const value &val() const { return *this; } std::string str() const { std::stringstream ss; ss << *this; return ss.str(); } // Conversion operations. // // These functions ensure that a conversion is never out of range, and should be always used, if at all // possible, instead of direct manipulation of the `data` member. For very large types, .slice() and // .concat() can be used to split them into more manageable parts. template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE IntegerT get() const { static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, "get() requires T to be an unsigned integral type"); static_assert(std::numeric_limits::digits >= Bits, "get() requires T to be at least as wide as the value is"); IntegerT result = 0; for (size_t n = 0; n < chunks; n++) result |= IntegerT(data[n]) << (n * chunk::bits); return result; } template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE IntegerT get() const { auto unsigned_result = get::type>(); IntegerT result; memcpy(&result, &unsigned_result, sizeof(IntegerT)); return result; } template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE void set(IntegerT value) { static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, "set() requires T to be an unsigned integral type"); static_assert(std::numeric_limits::digits >= Bits, "set() requires the value to be at least as wide as T is"); for (size_t n = 0; n < chunks; n++) data[n] = (value >> (n * chunk::bits)) & chunk::mask; } template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE void set(IntegerT value) { typename std::make_unsigned::type unsigned_value; memcpy(&unsigned_value, &value, sizeof(IntegerT)); set(unsigned_value); } // Operations with compile-time parameters. // // These operations are used to implement slicing, concatenation, and blitting. // The trunc, zext and sext operations add or remove most significant bits (i.e. on the left); // the rtrunc and rzext operations add or remove least significant bits (i.e. on the right). template CXXRTL_ALWAYS_INLINE value trunc() const { static_assert(NewBits <= Bits, "trunc() may not increase width"); value result; for (size_t n = 0; n < result.chunks; n++) result.data[n] = data[n]; result.data[result.chunks - 1] &= result.msb_mask; return result; } template CXXRTL_ALWAYS_INLINE value zext() const { static_assert(NewBits >= Bits, "zext() may not decrease width"); value result; for (size_t n = 0; n < chunks; n++) result.data[n] = data[n]; return result; } template CXXRTL_ALWAYS_INLINE value sext() const { static_assert(NewBits >= Bits, "sext() may not decrease width"); value result; for (size_t n = 0; n < chunks; n++) result.data[n] = data[n]; if (is_neg()) { result.data[chunks - 1] |= ~msb_mask; for (size_t n = chunks; n < result.chunks; n++) result.data[n] = chunk::mask; result.data[result.chunks - 1] &= result.msb_mask; } return result; } template CXXRTL_ALWAYS_INLINE value rtrunc() const { static_assert(NewBits <= Bits, "rtrunc() may not increase width"); value result; constexpr size_t shift_chunks = (Bits - NewBits) / chunk::bits; constexpr size_t shift_bits = (Bits - NewBits) % chunk::bits; chunk::type carry = 0; if (shift_chunks + result.chunks < chunks) { carry = (shift_bits == 0) ? 0 : data[shift_chunks + result.chunks] << (chunk::bits - shift_bits); } for (size_t n = result.chunks; n > 0; n--) { result.data[n - 1] = carry | (data[shift_chunks + n - 1] >> shift_bits); carry = (shift_bits == 0) ? 0 : data[shift_chunks + n - 1] << (chunk::bits - shift_bits); } return result; } template CXXRTL_ALWAYS_INLINE value rzext() const { static_assert(NewBits >= Bits, "rzext() may not decrease width"); value result; constexpr size_t shift_chunks = (NewBits - Bits) / chunk::bits; constexpr size_t shift_bits = (NewBits - Bits) % chunk::bits; chunk::type carry = 0; for (size_t n = 0; n < chunks; n++) { result.data[shift_chunks + n] = (data[n] << shift_bits) | carry; carry = (shift_bits == 0) ? 0 : data[n] >> (chunk::bits - shift_bits); } if (shift_chunks + chunks < result.chunks) result.data[shift_chunks + chunks] = carry; return result; } // Bit blit operation, i.e. a partial read-modify-write. template CXXRTL_ALWAYS_INLINE value blit(const value &source) const { static_assert(Stop >= Start, "blit() may not reverse bit order"); constexpr chunk::type start_mask = ~(chunk::mask << (Start % chunk::bits)); constexpr chunk::type stop_mask = (Stop % chunk::bits + 1 == chunk::bits) ? 0 : (chunk::mask << (Stop % chunk::bits + 1)); value masked = *this; if (Start / chunk::bits == Stop / chunk::bits) { masked.data[Start / chunk::bits] &= stop_mask | start_mask; } else { masked.data[Start / chunk::bits] &= start_mask; for (size_t n = Start / chunk::bits + 1; n < Stop / chunk::bits; n++) masked.data[n] = 0; masked.data[Stop / chunk::bits] &= stop_mask; } value shifted = source .template rzext() .template zext(); return masked.bit_or(shifted); } // Helpers for selecting extending or truncating operation depending on whether the result is wider or narrower // than the operand. In C++17 these can be replaced with `if constexpr`. template struct zext_cast { CXXRTL_ALWAYS_INLINE value operator()(const value &val) { return val.template zext(); } }; template struct zext_cast::type> { CXXRTL_ALWAYS_INLINE value operator()(const value &val) { return val.template trunc(); } }; template struct sext_cast { CXXRTL_ALWAYS_INLINE value operator()(const value &val) { return val.template sext(); } }; template struct sext_cast::type> { CXXRTL_ALWAYS_INLINE value operator()(const value &val) { return val.template trunc(); } }; template CXXRTL_ALWAYS_INLINE value zcast() const { return zext_cast()(*this); } template CXXRTL_ALWAYS_INLINE value scast() const { return sext_cast()(*this); } // Bit replication is far more efficient than the equivalent concatenation. template CXXRTL_ALWAYS_INLINE value repeat() const { static_assert(Bits == 1, "repeat() is implemented only for 1-bit values"); return *this ? value().bit_not() : value(); } // Operations with run-time parameters (offsets, amounts, etc). // // These operations are used for computations. bool bit(size_t offset) const { return data[offset / chunk::bits] & (1 << (offset % chunk::bits)); } void set_bit(size_t offset, bool value = true) { size_t offset_chunks = offset / chunk::bits; size_t offset_bits = offset % chunk::bits; data[offset_chunks] &= ~(1 << offset_bits); data[offset_chunks] |= value ? 1 << offset_bits : 0; } explicit operator bool() const { return !is_zero(); } bool is_zero() const { for (size_t n = 0; n < chunks; n++) if (data[n] != 0) return false; return true; } bool is_neg() const { return data[chunks - 1] & (1 << ((Bits - 1) % chunk::bits)); } bool operator ==(const value &other) const { for (size_t n = 0; n < chunks; n++) if (data[n] != other.data[n]) return false; return true; } bool operator !=(const value &other) const { return !(*this == other); } value bit_not() const { value result; for (size_t n = 0; n < chunks; n++) result.data[n] = ~data[n]; result.data[chunks - 1] &= msb_mask; return result; } value bit_and(const value &other) const { value result; for (size_t n = 0; n < chunks; n++) result.data[n] = data[n] & other.data[n]; return result; } value bit_or(const value &other) const { value result; for (size_t n = 0; n < chunks; n++) result.data[n] = data[n] | other.data[n]; return result; } value bit_xor(const value &other) const { value result; for (size_t n = 0; n < chunks; n++) result.data[n] = data[n] ^ other.data[n]; return result; } value update(const value &val, const value &mask) const { return bit_and(mask.bit_not()).bit_or(val.bit_and(mask)); } template value shl(const value &amount) const { // Ensure our early return is correct by prohibiting values larger than 4 Gbit. static_assert(Bits <= chunk::mask, "shl() of unreasonably large values is not supported"); // Detect shifts definitely large than Bits early. for (size_t n = 1; n < amount.chunks; n++) if (amount.data[n] != 0) return {}; // Past this point we can use the least significant chunk as the shift size. size_t shift_chunks = amount.data[0] / chunk::bits; size_t shift_bits = amount.data[0] % chunk::bits; if (shift_chunks >= chunks) return {}; value result; chunk::type carry = 0; for (size_t n = 0; n < chunks - shift_chunks; n++) { result.data[shift_chunks + n] = (data[n] << shift_bits) | carry; carry = (shift_bits == 0) ? 0 : data[n] >> (chunk::bits - shift_bits); } result.data[result.chunks - 1] &= result.msb_mask; return result; } template value shr(const value &amount) const { // Ensure our early return is correct by prohibiting values larger than 4 Gbit. static_assert(Bits <= chunk::mask, "shr() of unreasonably large values is not supported"); // Detect shifts definitely large than Bits early. for (size_t n = 1; n < amount.chunks; n++) if (amount.data[n] != 0) return (Signed && is_neg()) ? value().bit_not() : value(); // Past this point we can use the least significant chunk as the shift size. size_t shift_chunks = amount.data[0] / chunk::bits; size_t shift_bits = amount.data[0] % chunk::bits; if (shift_chunks >= chunks) return (Signed && is_neg()) ? value().bit_not() : value(); value result; chunk::type carry = 0; for (size_t n = 0; n < chunks - shift_chunks; n++) { result.data[chunks - shift_chunks - 1 - n] = carry | (data[chunks - 1 - n] >> shift_bits); carry = (shift_bits == 0) ? 0 : data[chunks - 1 - n] << (chunk::bits - shift_bits); } if (Signed && is_neg()) { size_t top_chunk_idx = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) / chunk::bits; size_t top_chunk_bits = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) % chunk::bits; for (size_t n = top_chunk_idx + 1; n < chunks; n++) result.data[n] = chunk::mask; if (amount.data[0] != 0) result.data[top_chunk_idx] |= chunk::mask << top_chunk_bits; result.data[result.chunks - 1] &= result.msb_mask; } return result; } template value sshr(const value &amount) const { return shr(amount); } template value bmux(const value &sel) const { static_assert(ResultBits << SelBits == Bits, "invalid sizes used in bmux()"); size_t amount = sel.data[0] * ResultBits; size_t shift_chunks = amount / chunk::bits; size_t shift_bits = amount % chunk::bits; value result; chunk::type carry = 0; if (ResultBits % chunk::bits + shift_bits > chunk::bits) carry = data[result.chunks + shift_chunks] << (chunk::bits - shift_bits); for (size_t n = 0; n < result.chunks; n++) { result.data[result.chunks - 1 - n] = carry | (data[result.chunks + shift_chunks - 1 - n] >> shift_bits); carry = (shift_bits == 0) ? 0 : data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits); } result.data[result.chunks - 1] &= result.msb_mask; 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()"); size_t amount = sel.data[0] * Bits; size_t shift_chunks = amount / chunk::bits; size_t shift_bits = amount % chunk::bits; value result; chunk::type carry = 0; for (size_t n = 0; n < chunks; n++) { result.data[shift_chunks + n] = (data[n] << shift_bits) | carry; carry = (shift_bits == 0) ? 0 : data[n] >> (chunk::bits - shift_bits); } if (Bits % chunk::bits + shift_bits > chunk::bits) result.data[shift_chunks + chunks] = carry; return result; } size_t ctpop() const { size_t count = 0; for (size_t n = 0; n < chunks; n++) { // This loop implements the population count idiom as recognized by LLVM and GCC. for (chunk::type x = data[n]; x != 0; count++) x = x & (x - 1); } return count; } size_t ctlz() const { size_t count = 0; for (size_t n = 0; n < chunks; n++) { chunk::type x = data[chunks - 1 - n]; // First add to `count` as if the chunk is zero constexpr size_t msb_chunk_bits = Bits % chunk::bits != 0 ? Bits % chunk::bits : chunk::bits; count += (n == 0 ? msb_chunk_bits : chunk::bits); // If the chunk isn't zero, correct the `count` value and return if (x != 0) { for (; x != 0; count--) x >>= 1; break; } } return count; } template std::pair, bool /*CarryOut*/> alu(const value &other) const { value result; bool carry = CarryIn; for (size_t n = 0; n < result.chunks; n++) { result.data[n] = data[n] + (Invert ? ~other.data[n] : other.data[n]) + carry; if (result.chunks - 1 == n) result.data[result.chunks - 1] &= result.msb_mask; carry = (result.data[n] < data[n]) || (result.data[n] == data[n] && carry); } return {result, carry}; } value add(const value &other) const { return alu(other).first; } value sub(const value &other) const { return alu(other).first; } value neg() const { return value().sub(*this); } bool ucmp(const value &other) const { bool carry; std::tie(std::ignore, carry) = alu(other); return !carry; // a.ucmp(b) ≡ a u< b } bool scmp(const value &other) const { value result; bool carry; std::tie(result, carry) = alu(other); bool overflow = (is_neg() == !other.is_neg()) && (is_neg() != result.is_neg()); return result.is_neg() ^ overflow; // a.scmp(b) ≡ a s< b } template value mul(const value &other) const { value result; wide_chunk_t wide_result[result.chunks + 1] = {}; for (size_t n = 0; n < chunks; n++) { for (size_t m = 0; m < chunks && n + m < result.chunks; m++) { wide_result[n + m] += wide_chunk_t(data[n]) * wide_chunk_t(other.data[m]); wide_result[n + m + 1] += wide_result[n + m] >> chunk::bits; wide_result[n + m] &= chunk::mask; } } for (size_t n = 0; n < result.chunks; n++) { result.data[n] = wide_result[n]; } result.data[result.chunks - 1] &= result.msb_mask; return result; } std::pair, value> udivmod(value divisor) const { value quotient; value dividend = *this; if (dividend.ucmp(divisor)) return {/*quotient=*/value{0u}, /*remainder=*/dividend}; int64_t divisor_shift = divisor.ctlz() - dividend.ctlz(); assert(divisor_shift >= 0); divisor = divisor.shl(value{(chunk::type) divisor_shift}); for (size_t step = 0; step <= (uint64_t) divisor_shift; step++) { quotient = quotient.shl(value{1u}); if (!dividend.ucmp(divisor)) { dividend = dividend.sub(divisor); quotient.set_bit(0, true); } divisor = divisor.shr(value{1u}); } return {quotient, /*remainder=*/dividend}; } std::pair, value> sdivmod(const value &other) const { value quotient; value remainder; value dividend = sext(); value divisor = other.template sext(); if (is_neg()) dividend = dividend.neg(); if (other.is_neg()) divisor = divisor.neg(); std::tie(quotient, remainder) = dividend.udivmod(divisor); if (is_neg() != other.is_neg()) quotient = quotient.neg(); if (is_neg()) remainder = remainder.neg(); return {quotient.template trunc(), remainder.template trunc()}; } }; // Expression template for a slice, usable as lvalue or rvalue, and composable with other expression templates here. template struct slice_expr : public expr_base> { static_assert(Stop >= Start, "slice_expr() may not reverse bit order"); static_assert(Start < T::bits && Stop < T::bits, "slice_expr() must be within bounds"); static constexpr size_t bits = Stop - Start + 1; T &expr; slice_expr(T &expr) : expr(expr) {} slice_expr(const slice_expr &) = delete; CXXRTL_ALWAYS_INLINE operator value() const { return static_cast &>(expr) .template rtrunc() .template trunc(); } CXXRTL_ALWAYS_INLINE slice_expr &operator=(const value &rhs) { // Generic partial assignment implemented using a read-modify-write operation on the sliced expression. expr = static_cast &>(expr) .template blit(rhs); return *this; } // A helper that forces the cast to value<>, which allows deduction to work. CXXRTL_ALWAYS_INLINE value val() const { return static_cast &>(*this); } }; // Expression template for a concatenation, usable as lvalue or rvalue, and composable with other expression templates here. template struct concat_expr : public expr_base> { static constexpr size_t bits = T::bits + U::bits; T &ms_expr; U &ls_expr; concat_expr(T &ms_expr, U &ls_expr) : ms_expr(ms_expr), ls_expr(ls_expr) {} concat_expr(const concat_expr &) = delete; CXXRTL_ALWAYS_INLINE operator value() const { value ms_shifted = static_cast &>(ms_expr) .template rzext(); value ls_extended = static_cast &>(ls_expr) .template zext(); return ms_shifted.bit_or(ls_extended); } CXXRTL_ALWAYS_INLINE concat_expr &operator=(const value &rhs) { ms_expr = rhs.template rtrunc(); ls_expr = rhs.template trunc(); return *this; } // A helper that forces the cast to value<>, which allows deduction to work. CXXRTL_ALWAYS_INLINE value val() const { return static_cast &>(*this); } }; // Base class for expression templates, providing helper methods for operations that are valid on both rvalues and lvalues. // // Note that expression objects (slices and concatenations) constructed in this way should NEVER be captured because // they refer to temporaries that will, in general, only live until the end of the statement. For example, both of // these snippets perform use-after-free: // // const auto &a = val.slice<7,0>().slice<1>(); // value<1> b = a; // // auto &&c = val.slice<7,0>().slice<1>(); // c = value<1>{1u}; // // An easy way to write code using slices and concatenations safely is to follow two simple rules: // * Never explicitly name any type except `value` or `const value &`. // * Never use a `const auto &` or `auto &&` in any such expression. // Then, any code that compiles will be well-defined. template struct expr_base { template CXXRTL_ALWAYS_INLINE slice_expr slice() const { return {*static_cast(this)}; } template CXXRTL_ALWAYS_INLINE slice_expr slice() { return {*static_cast(this)}; } template CXXRTL_ALWAYS_INLINE concat_expr::type> concat(const U &other) const { return {*static_cast(this), other}; } template CXXRTL_ALWAYS_INLINE concat_expr::type> concat(U &&other) { return {*static_cast(this), other}; } }; template std::ostream &operator<<(std::ostream &os, const value &val) { auto old_flags = os.flags(std::ios::right); auto old_width = os.width(0); auto old_fill = os.fill('0'); os << val.bits << '\'' << std::hex; for (size_t n = val.chunks - 1; n != (size_t)-1; n--) { if (n == val.chunks - 1 && Bits % value::chunk::bits != 0) os.width((Bits % value::chunk::bits + 3) / 4); else os.width((value::chunk::bits + 3) / 4); os << val.data[n]; } os.fill(old_fill); os.width(old_width); os.flags(old_flags); return os; } template struct wire { static constexpr size_t bits = Bits; value curr; value next; wire() = default; explicit constexpr wire(const value &init) : curr(init), next(init) {} template explicit constexpr wire(Init ...init) : curr{init...}, next{init...} {} // Copying and copy-assigning values is natural. If, however, a value is replaced with a wire, // e.g. because a module is built with a different optimization level, then existing code could // unintentionally copy a wire instead, which would create a subtle but serious bug. To make sure // this doesn't happen, prohibit copying and copy-assigning wires. wire(const wire &) = delete; wire &operator=(const wire &) = delete; wire(wire &&) = default; wire &operator=(wire &&) = default; template CXXRTL_ALWAYS_INLINE IntegerT get() const { return curr.template get(); } template CXXRTL_ALWAYS_INLINE void set(IntegerT other) { next.template set(other); } // This method intentionally takes a mandatory argument (to make it more difficult to misuse in // black box implementations, leading to missed observer events). It is generic over its argument // to allow the `on_update` method to be non-virtual. template bool commit(ObserverT &observer) { if (curr != next) { observer.on_update(curr.chunks, curr.data, next.data); curr = next; return true; } return false; } }; template std::ostream &operator<<(std::ostream &os, const wire &val) { os << val.curr; return os; } template struct memory { const size_t depth; std::unique_ptr[]> data; explicit memory(size_t depth) : depth(depth), data(new value[depth]) {} memory(const memory &) = delete; memory &operator=(const memory &) = delete; memory(memory &&) = default; memory &operator=(memory &&other) { assert(depth == other.depth); data = std::move(other.data); write_queue = std::move(other.write_queue); return *this; } // An operator for direct memory reads. May be used at any time during the simulation. const value &operator [](size_t index) const { assert(index < depth); return data[index]; } // An operator for direct memory writes. May only be used before the simulation is started. If used // after the simulation is started, the design may malfunction. value &operator [](size_t index) { assert(index < depth); return data[index]; } // A simple way to make a writable memory would be to use an array of wires instead of an array of values. // However, there are two significant downsides to this approach: first, it has large overhead (2× space // overhead, and O(depth) time overhead during commit); second, it does not simplify handling write port // priorities. Although in principle write ports could be ordered or conditionally enabled in generated // code based on their priorities and selected addresses, the feedback arc set problem is computationally // expensive, and the heuristic based algorithms are not easily modified to guarantee (rather than prefer) // a particular write port evaluation order. // // The approach used here instead is to queue writes into a buffer during the eval phase, then perform // the writes during the commit phase in the priority order. This approach has low overhead, with both space // and time proportional to the amount of write ports. Because virtually every memory in a practical design // has at most two write ports, linear search is used on every write, being the fastest and simplest approach. struct write { size_t index; value val; value mask; int priority; }; std::vector write_queue; void update(size_t index, const value &val, const value &mask, int priority = 0) { assert(index < depth); // Queue up the write while keeping the queue sorted by priority. write_queue.insert( std::upper_bound(write_queue.begin(), write_queue.end(), priority, [](const int a, const write& b) { return a < b.priority; }), write { index, val, mask, priority }); } // See the note for `wire::commit()`. template bool commit(ObserverT &observer) { bool changed = false; for (const write &entry : write_queue) { value elem = data[entry.index]; elem = elem.update(entry.val, entry.mask); if (data[entry.index] != elem) { observer.on_update(value::chunks, data[0].data, elem.data, entry.index); changed |= true; } data[entry.index] = elem; } write_queue.clear(); return changed; } }; struct metadata { const enum { MISSING = 0, UINT = 1, SINT = 2, STRING = 3, DOUBLE = 4, } value_type; // In debug mode, using the wrong .as_*() function will assert. // In release mode, using the wrong .as_*() function will safely return a default value. const uint64_t uint_value = 0; const int64_t sint_value = 0; const std::string string_value = ""; const double double_value = 0.0; metadata() : value_type(MISSING) {} metadata(uint64_t value) : value_type(UINT), uint_value(value) {} metadata(int64_t value) : value_type(SINT), sint_value(value) {} metadata(const std::string &value) : value_type(STRING), string_value(value) {} metadata(const char *value) : value_type(STRING), string_value(value) {} metadata(double value) : value_type(DOUBLE), double_value(value) {} metadata(const metadata &) = default; metadata &operator=(const metadata &) = delete; uint64_t as_uint() const { assert(value_type == UINT); return uint_value; } int64_t as_sint() const { assert(value_type == SINT); return sint_value; } const std::string &as_string() const { assert(value_type == STRING); return string_value; } double as_double() const { 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; struct performer; // An object that allows formatting a string lazily. struct lazy_fmt { virtual std::string operator() () const = 0; }; // Flavor of a `$check` cell. enum class flavor { // Corresponds to a `$assert` cell in other flows, and a Verilog `assert ()` statement. ASSERT, // Corresponds to a `$assume` cell in other flows, and a Verilog `assume ()` statement. ASSUME, // Corresponds to a `$live` cell in other flows, and a Verilog `assert (eventually)` statement. ASSERT_EVENTUALLY, // Corresponds to a `$fair` cell in other flows, and a Verilog `assume (eventually)` statement. ASSUME_EVENTUALLY, // Corresponds to a `$cover` cell in other flows, and a Verilog `cover ()` statement. COVER, }; // An object that can be passed to a `eval()` method in order to act on side effects. The default behavior implemented // below is the same as the behavior of `eval(nullptr)`, except that `-print-output` option of `write_cxxrtl` is not // taken into account. struct performer { // Called by generated formatting code to evaluate a Verilog `$time` expression. virtual int64_t vlog_time() const { return 0; } // Called by generated formatting code to evaluate a Verilog `$realtime` expression. virtual double vlog_realtime() const { return vlog_time(); } // Called when a `$print` cell is triggered. virtual void on_print(const lazy_fmt &formatter, const metadata_map &attributes) { std::cout << formatter(); } // Called when a `$check` cell is triggered. virtual void on_check(flavor type, bool condition, const lazy_fmt &formatter, const metadata_map &attributes) { if (type == flavor::ASSERT || type == flavor::ASSUME) { if (!condition) std::cerr << formatter(); CXXRTL_ASSERT(condition && "Check failed"); } } }; // An object that can be passed to a `commit()` method in order to produce a replay log of every state change in // the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and // a comparatively heavyweight template-based solution is justified. struct observer { // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and // `base` points to the first chunk. void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {} // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to // the memory element chunk count and `base` points to the first chunk of the first element of the memory. void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {} }; // Must be kept in sync with `struct FmtPart` in kernel/fmt.h! // Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. struct fmt_part { enum { LITERAL = 0, INTEGER = 1, STRING = 2, UNICHAR = 3, VLOG_TIME = 4, } type; // LITERAL type std::string str; // INTEGER/STRING/UNICHAR types // + value val; // INTEGER/STRING/VLOG_TIME types enum { RIGHT = 0, LEFT = 1, NUMERIC = 2, } justify; // = RIGHT; char padding; // = '\0'; size_t width; // = 0; // INTEGER type unsigned base; // = 10; bool signed_; // = 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; // + int64_t itime; // + double ftime; // Format the part as a string. // // The values of `vlog_time` and `vlog_realtime` are used for Verilog `$time` and `$realtime`, correspondingly. template std::string render(value val, performer *performer = nullptr) { // 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 LITERAL: return str; case STRING: { buf.reserve(Bits/8); for (size_t i = 0; i < Bits; i += 8) { char ch = 0; for (int j = 0; j < 8 && i + j < int(Bits); j++) if (val.bit(i + j)) ch |= 1 << j; if (ch != 0) buf.append({ch}); } std::reverse(buf.begin(), buf.end()); 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: { 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) { val_width = 1; for (size_t index = 0; index < Bits; index++) if (val.bit(index)) val_width = index + 1; } if (base == 2) { 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 < 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 += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value]; } } else if (base == 10) { 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}); else 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++; } } 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; } case VLOG_TIME: { if (performer) { buf = realtime ? std::to_string(performer->vlog_realtime()) : std::to_string(performer->vlog_time()); } else { buf = realtime ? std::to_string(0.0) : std::to_string(0); } break; } } std::string str; assert(width == 0 || padding != '\0'); 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; } return str; } }; // Tag class to disambiguate values/wires and their aliases. struct debug_alias {}; // Tag declaration to disambiguate values and debug outlines. using debug_outline = ::_cxxrtl_outline; // This structure is intended for consumption via foreign function interfaces, like Python's ctypes. // Because of this it uses a C-style layout that is easy to parse rather than more idiomatic C++. // // To avoid violating strict aliasing rules, this structure has to be a subclass of the one used // in the C API, or it would not be possible to cast between the pointers to these. // // The `attrs` member cannot be owned by this structure because a `cxxrtl_object` can be created // from external C code. struct debug_item : ::cxxrtl_object { // Object types. enum : uint32_t { VALUE = CXXRTL_VALUE, WIRE = CXXRTL_WIRE, MEMORY = CXXRTL_MEMORY, ALIAS = CXXRTL_ALIAS, OUTLINE = CXXRTL_OUTLINE, }; // Object flags. enum : uint32_t { INPUT = CXXRTL_INPUT, OUTPUT = CXXRTL_OUTPUT, INOUT = CXXRTL_INOUT, 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) {} template debug_item(value &item, size_t lsb_offset = 0, uint32_t flags_ = 0) { static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = VALUE; flags = flags_; width = Bits; lsb_at = lsb_offset; depth = 1; zero_at = 0; curr = item.data; next = item.data; outline = nullptr; attrs = nullptr; } template debug_item(const value &item, size_t lsb_offset = 0) { static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = VALUE; flags = DRIVEN_COMB; width = Bits; lsb_at = lsb_offset; depth = 1; zero_at = 0; curr = const_cast(item.data); next = nullptr; outline = nullptr; attrs = nullptr; } template debug_item(wire &item, size_t lsb_offset = 0, uint32_t flags_ = 0) { static_assert(Bits == 0 || (sizeof(item.curr) == value::chunks * sizeof(chunk_t) && sizeof(item.next) == value::chunks * sizeof(chunk_t)), "wire is not compatible with C layout"); type = WIRE; flags = flags_; width = Bits; lsb_at = lsb_offset; depth = 1; zero_at = 0; curr = item.curr.data; next = item.next.data; outline = nullptr; attrs = nullptr; } template debug_item(memory &item, size_t zero_offset = 0) { static_assert(Width == 0 || sizeof(item.data[0]) == value::chunks * sizeof(chunk_t), "memory is not compatible with C layout"); type = MEMORY; flags = 0; width = Width; lsb_at = 0; depth = item.depth; zero_at = zero_offset; curr = item.data ? item.data[0].data : nullptr; next = nullptr; outline = nullptr; attrs = nullptr; } template debug_item(debug_alias, const value &item, size_t lsb_offset = 0) { static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = ALIAS; flags = DRIVEN_COMB; width = Bits; lsb_at = lsb_offset; depth = 1; zero_at = 0; curr = const_cast(item.data); next = nullptr; outline = nullptr; attrs = nullptr; } template debug_item(debug_alias, const wire &item, size_t lsb_offset = 0) { static_assert(Bits == 0 || (sizeof(item.curr) == value::chunks * sizeof(chunk_t) && sizeof(item.next) == value::chunks * sizeof(chunk_t)), "wire is not compatible with C layout"); type = ALIAS; flags = DRIVEN_COMB; width = Bits; lsb_at = lsb_offset; depth = 1; zero_at = 0; curr = const_cast(item.curr.data); next = nullptr; outline = nullptr; attrs = nullptr; } template debug_item(debug_outline &group, const value &item, size_t lsb_offset = 0) { static_assert(Bits == 0 || sizeof(item) == value::chunks * sizeof(chunk_t), "value is not compatible with C layout"); type = OUTLINE; flags = DRIVEN_COMB; width = Bits; lsb_at = lsb_offset; depth = 1; zero_at = 0; curr = const_cast(item.data); next = nullptr; outline = &group; attrs = nullptr; } template IntegerT get() const { assert(width == Bits && depth == 1); value item; std::copy(curr, curr + value::chunks, item.data); return item.template get(); } template void set(IntegerT other) const { assert(width == Bits && depth == 1); value item; item.template set(other); std::copy(item.data, item.data + value::chunks, next); } }; static_assert(std::is_standard_layout::value, "debug_item is not compatible with C layout"); } // namespace cxxrtl typedef struct _cxxrtl_attr_set { cxxrtl::metadata_map map; } *cxxrtl_attr_set; namespace cxxrtl { // Representation of an attribute set in the C++ interface. using debug_attrs = ::_cxxrtl_attr_set; struct debug_items { // Debug items may be composed of multiple parts, but the attributes are shared between all of them. // There are additional invariants, not all of which are not checked by this code: // - Memories and non-memories cannot be mixed together. // - Bit indices (considering `lsb_at` and `width`) must not overlap. // - Row indices (considering `depth` and `zero_at`) must be the same. // - The `INPUT` and `OUTPUT` flags must be the same for all parts. // Other than that, the parts can be quite different, e.g. it is OK to mix a value, a wire, an alias, // and an outline, in the debug information for a single name in four parts. std::map> table; std::map> attrs_table; void add(const std::string &path, debug_item &&item, metadata_map &&item_attrs = {}) { assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos); std::unique_ptr &attrs = attrs_table[path]; if (attrs.get() == nullptr) attrs = std::unique_ptr(new debug_attrs); for (auto attr : item_attrs) attrs->map.insert(attr); item.attrs = attrs.get(); std::vector &parts = table[path]; parts.emplace_back(item); std::sort(parts.begin(), parts.end(), [](const debug_item &a, const debug_item &b) { return a.lsb_at < b.lsb_at; }); } // 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; return table.at(path).size(); } const std::vector &at(const std::string &path) const { return table.at(path); } // Like `at()`, but operates only on single-part debug items. const debug_item &operator [](const std::string &path) const { const std::vector &parts = table.at(path); assert(parts.size() == 1); return parts.at(0); } bool is_memory(const std::string &path) const { return at(path).at(0).type == debug_item::MEMORY; } const metadata_map &attrs(const std::string &path) const { return attrs_table.at(path)->map; } }; // Only `module` scopes are defined. The type is implicit, since Yosys does not currently support // any other scope types. struct debug_scope { std::string module_name; std::unique_ptr module_attrs; std::unique_ptr cell_attrs; }; struct debug_scopes { std::map table; void add(const std::string &path, const std::string &module_name, metadata_map &&module_attrs, metadata_map &&cell_attrs) { assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos); assert(table.count(path) == 0); debug_scope &scope = table[path]; scope.module_name = module_name; scope.module_attrs = std::unique_ptr(new debug_attrs { module_attrs }); 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); } const debug_scope &operator [](const std::string &path) const { return table.at(path); } }; // Tag class to disambiguate the default constructor used by the toplevel module that calls `reset()`, // and the constructor of interior modules that should not call it. struct interior {}; // The core API of the `module` class consists of only four virtual methods: `reset()`, `eval()`, // `commit`, and `debug_info()`. (The virtual destructor is made necessary by C++.) Every other method // is a convenience method, and exists solely to simplify some common pattern for C++ API consumers. // No behavior may be added to such convenience methods that other parts of CXXRTL can rely on, since // there is no guarantee they will be called (and, for example, other CXXRTL libraries will often call // the `eval()` and `commit()` directly instead, as well as being exposed in the C API). struct module { module() {} virtual ~module() {} // Modules with black boxes cannot be copied. Although not all designs include black boxes, // delete the copy constructor and copy assignment operator to make sure that any downstream // code that manipulates modules doesn't accidentally depend on their availability. module(const module &) = delete; module &operator=(const module &) = delete; module(module &&) = default; module &operator=(module &&) = default; virtual void reset() = 0; // The `eval()` callback object, `performer`, is included in the virtual call signature since // the generated code has broadly identical performance properties. virtual bool eval(performer *performer = nullptr) = 0; // The `commit()` callback object, `observer`, is not included in the virtual call signature since // the generated code is severely pessimized by it. To observe commit events, the non-virtual // `commit(observer *)` overload must be called directly on a `module` subclass. virtual bool commit() = 0; size_t step(performer *performer = nullptr) { size_t deltas = 0; bool converged = false; do { converged = eval(performer); deltas++; } while (commit() && !converged); return deltas; } virtual void debug_info(debug_items *items, debug_scopes *scopes, std::string path, metadata_map &&cell_attrs = {}) { (void)items, (void)scopes, (void)path, (void)cell_attrs; } // Compatibility method. #if __has_attribute(deprecated) __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); } }; } // namespace cxxrtl // Internal structures used to communicate with the implementation of the C interface. typedef struct _cxxrtl_toplevel { std::unique_ptr module; } *cxxrtl_toplevel; typedef struct _cxxrtl_outline { std::function eval; } *cxxrtl_outline; // Definitions of internal Yosys cells. Other than the functions in this namespace, CXXRTL is fully generic // and indepenent of Yosys implementation details. // // The `write_cxxrtl` pass translates internal cells (cells with names that start with `$`) to calls of these // functions. All of Yosys arithmetic and logical cells perform sign or zero extension on their operands, // whereas basic operations on arbitrary width values require operands to be of the same width. These functions // bridge the gap by performing the necessary casts. They are named similar to `cell_A[B]`, where A and B are `u` // if the corresponding operand is unsigned, and `s` if it is signed. namespace cxxrtl_yosys { using namespace cxxrtl; // std::max isn't constexpr until C++14 for no particular reason (it's an oversight), so we define our own. template CXXRTL_ALWAYS_INLINE constexpr T max(const T &a, const T &b) { return a > b ? a : b; } // Logic operations template CXXRTL_ALWAYS_INLINE value logic_not(const value &a) { return value { a ? 0u : 1u }; } template CXXRTL_ALWAYS_INLINE value logic_and(const value &a, const value &b) { return value { (bool(a) && bool(b)) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value logic_or(const value &a, const value &b) { return value { (bool(a) || bool(b)) ? 1u : 0u }; } // Reduction operations template CXXRTL_ALWAYS_INLINE value reduce_and(const value &a) { return value { a.bit_not().is_zero() ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value reduce_or(const value &a) { return value { a ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value reduce_xor(const value &a) { return value { (a.ctpop() % 2) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value reduce_xnor(const value &a) { return value { (a.ctpop() % 2) ? 0u : 1u }; } template CXXRTL_ALWAYS_INLINE value reduce_bool(const value &a) { return value { a ? 1u : 0u }; } // Bitwise operations template CXXRTL_ALWAYS_INLINE value not_u(const value &a) { return a.template zcast().bit_not(); } template CXXRTL_ALWAYS_INLINE value not_s(const value &a) { return a.template scast().bit_not(); } template CXXRTL_ALWAYS_INLINE value and_uu(const value &a, const value &b) { return a.template zcast().bit_and(b.template zcast()); } template CXXRTL_ALWAYS_INLINE value and_ss(const value &a, const value &b) { return a.template scast().bit_and(b.template scast()); } template CXXRTL_ALWAYS_INLINE value or_uu(const value &a, const value &b) { return a.template zcast().bit_or(b.template zcast()); } template CXXRTL_ALWAYS_INLINE value or_ss(const value &a, const value &b) { return a.template scast().bit_or(b.template scast()); } template CXXRTL_ALWAYS_INLINE value xor_uu(const value &a, const value &b) { return a.template zcast().bit_xor(b.template zcast()); } template CXXRTL_ALWAYS_INLINE value xor_ss(const value &a, const value &b) { return a.template scast().bit_xor(b.template scast()); } template CXXRTL_ALWAYS_INLINE value xnor_uu(const value &a, const value &b) { return a.template zcast().bit_xor(b.template zcast()).bit_not(); } template CXXRTL_ALWAYS_INLINE value xnor_ss(const value &a, const value &b) { return a.template scast().bit_xor(b.template scast()).bit_not(); } template CXXRTL_ALWAYS_INLINE value shl_uu(const value &a, const value &b) { return a.template zcast().shl(b); } template CXXRTL_ALWAYS_INLINE value shl_su(const value &a, const value &b) { return a.template scast().shl(b); } template CXXRTL_ALWAYS_INLINE value sshl_uu(const value &a, const value &b) { return a.template zcast().shl(b); } template CXXRTL_ALWAYS_INLINE value sshl_su(const value &a, const value &b) { return a.template scast().shl(b); } template CXXRTL_ALWAYS_INLINE value shr_uu(const value &a, const value &b) { return a.shr(b).template zcast(); } template CXXRTL_ALWAYS_INLINE value shr_su(const value &a, const value &b) { return a.template scast().shr(b); } template CXXRTL_ALWAYS_INLINE value sshr_uu(const value &a, const value &b) { return a.shr(b).template zcast(); } template CXXRTL_ALWAYS_INLINE value sshr_su(const value &a, const value &b) { return a.sshr(b).template scast(); } template CXXRTL_ALWAYS_INLINE value shift_uu(const value &a, const value &b) { return shr_uu(a, b); } template CXXRTL_ALWAYS_INLINE value shift_su(const value &a, const value &b) { return shr_su(a, b); } template CXXRTL_ALWAYS_INLINE value shift_us(const value &a, const value &b) { return b.is_neg() ? shl_uu(a, b.template sext().neg()) : shr_uu(a, b); } template CXXRTL_ALWAYS_INLINE value shift_ss(const value &a, const value &b) { return b.is_neg() ? shl_su(a, b.template sext().neg()) : shr_su(a, b); } template CXXRTL_ALWAYS_INLINE value shiftx_uu(const value &a, const value &b) { return shift_uu(a, b); } template CXXRTL_ALWAYS_INLINE value shiftx_su(const value &a, const value &b) { return shift_su(a, b); } template CXXRTL_ALWAYS_INLINE value shiftx_us(const value &a, const value &b) { return shift_us(a, b); } template CXXRTL_ALWAYS_INLINE value shiftx_ss(const value &a, const value &b) { return shift_ss(a, b); } // Comparison operations template CXXRTL_ALWAYS_INLINE value eq_uu(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value{ a.template zext() == b.template zext() ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value eq_ss(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value{ a.template sext() == b.template sext() ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value ne_uu(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value{ a.template zext() != b.template zext() ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value ne_ss(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value{ a.template sext() != b.template sext() ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value eqx_uu(const value &a, const value &b) { return eq_uu(a, b); } template CXXRTL_ALWAYS_INLINE value eqx_ss(const value &a, const value &b) { return eq_ss(a, b); } template CXXRTL_ALWAYS_INLINE value nex_uu(const value &a, const value &b) { return ne_uu(a, b); } template CXXRTL_ALWAYS_INLINE value nex_ss(const value &a, const value &b) { return ne_ss(a, b); } template CXXRTL_ALWAYS_INLINE value gt_uu(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { b.template zext().ucmp(a.template zext()) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value gt_ss(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { b.template sext().scmp(a.template sext()) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value ge_uu(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { !a.template zext().ucmp(b.template zext()) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value ge_ss(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { !a.template sext().scmp(b.template sext()) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value lt_uu(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { a.template zext().ucmp(b.template zext()) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value lt_ss(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { a.template sext().scmp(b.template sext()) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value le_uu(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { !b.template zext().ucmp(a.template zext()) ? 1u : 0u }; } template CXXRTL_ALWAYS_INLINE value le_ss(const value &a, const value &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value { !b.template sext().scmp(a.template sext()) ? 1u : 0u }; } // Arithmetic operations template CXXRTL_ALWAYS_INLINE value pos_u(const value &a) { return a.template zcast(); } template CXXRTL_ALWAYS_INLINE value pos_s(const value &a) { return a.template scast(); } template CXXRTL_ALWAYS_INLINE value neg_u(const value &a) { return a.template zcast().neg(); } template CXXRTL_ALWAYS_INLINE value neg_s(const value &a) { return a.template scast().neg(); } template CXXRTL_ALWAYS_INLINE value add_uu(const value &a, const value &b) { return a.template zcast().add(b.template zcast()); } template CXXRTL_ALWAYS_INLINE value add_ss(const value &a, const value &b) { return a.template scast().add(b.template scast()); } template CXXRTL_ALWAYS_INLINE value sub_uu(const value &a, const value &b) { return a.template zcast().sub(b.template zcast()); } template CXXRTL_ALWAYS_INLINE value sub_ss(const value &a, const value &b) { return a.template scast().sub(b.template scast()); } template CXXRTL_ALWAYS_INLINE value mul_uu(const value &a, const value &b) { constexpr size_t BitsM = BitsA >= BitsB ? BitsA : BitsB; return a.template zcast().template mul(b.template zcast()); } template CXXRTL_ALWAYS_INLINE value mul_ss(const value &a, const value &b) { return a.template scast().template mul(b.template scast()); } template CXXRTL_ALWAYS_INLINE std::pair, value> divmod_uu(const value &a, const value &b) { constexpr size_t Bits = max(BitsY, max(BitsA, BitsB)); value quotient; value remainder; value dividend = a.template zext(); value divisor = b.template trunc().template zext(); std::tie(quotient, remainder) = dividend.udivmod(divisor); return {quotient.template trunc(), remainder.template trunc()}; } template CXXRTL_ALWAYS_INLINE std::pair, value> divmod_ss(const value &a, const value &b) { constexpr size_t Bits = max(BitsY, max(BitsA, BitsB)); value quotient; value remainder; value dividend = a.template sext(); value divisor = b.template sext(); std::tie(quotient, remainder) = dividend.sdivmod(divisor); return {quotient.template trunc(), remainder.template trunc()}; } template CXXRTL_ALWAYS_INLINE value div_uu(const value &a, const value &b) { return divmod_uu(a, b).first; } template CXXRTL_ALWAYS_INLINE value div_ss(const value &a, const value &b) { return divmod_ss(a, b).first; } template CXXRTL_ALWAYS_INLINE value mod_uu(const value &a, const value &b) { return divmod_uu(a, b).second; } template CXXRTL_ALWAYS_INLINE value mod_ss(const value &a, const value &b) { return divmod_ss(a, b).second; } template CXXRTL_ALWAYS_INLINE value modfloor_uu(const value &a, const value &b) { return divmod_uu(a, b).second; } // GHDL Modfloor operator. Returns r=a mod b, such that r has the same sign as b and // a=b*N+r where N is some integer // In practical terms, when a and b have different signs and the remainder returned by divmod_ss is not 0 // then return the remainder + b template CXXRTL_ALWAYS_INLINE value modfloor_ss(const value &a, const value &b) { value r; r = divmod_ss(a, b).second; if((b.is_neg() != a.is_neg()) && !r.is_zero()) return add_ss(b, r); return r; } template CXXRTL_ALWAYS_INLINE value divfloor_uu(const value &a, const value &b) { return divmod_uu(a, b).first; } // Divfloor. Similar to above: returns q=a//b, where q has the sign of a*b and a=b*q+N. // In other words, returns (truncating) a/b, except if a and b have different signs // and there's non-zero remainder, subtract one more towards floor. template CXXRTL_ALWAYS_INLINE value divfloor_ss(const value &a, const value &b) { value q, r; std::tie(q, r) = divmod_ss(a, b); if ((b.is_neg() != a.is_neg()) && !r.is_zero()) return sub_uu(q, value<1> { 1u }); return q; } // Memory helper struct memory_index { bool valid; size_t index; template memory_index(const value &addr, size_t offset, size_t depth) { static_assert(value::chunks <= 1, "memory address is too wide"); size_t offset_index = addr.data[0]; valid = (offset_index >= offset && offset_index < offset + depth); index = offset_index - offset; } }; } // namespace cxxrtl_yosys #endif yosys-0.65/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h000066400000000000000000000724701520057232300240210ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2023 Catherine * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 CXXRTL_REPLAY_H #define CXXRTL_REPLAY_H #if !defined(WIN32) #include #define O_BINARY 0 #else #include #endif #include #include #include #include #include #include #include // Theory of operation // =================== // // Log format // ---------- // // The replay log is a simple data format based on a sequence of 32-bit words. The following BNF-like grammar describes // enough detail to understand the overall structure of the log data and be able to read hex dumps. For a greater // degree of detail see the source code. The format is considered fully internal to CXXRTL and is subject to change // without notice. // // ::= + // ::= 0x52585843 0x00004c54 // ::= * // ::= ( | )* // ::= 0xc0000000 ... // ::= 0xc0000001 ... // ::= 0x0??????? + | 0x1??????? + | 0x2??????? | 0x3??????? // , ::= 0x???????? // ::= | | | // ::= 0xc0000010 // ::= 0xc0000011 // ::= 0xc0000012 // ::= 0xc0000013 // ::= 0xFFFFFFFF // // The replay log contains sample data, however, it does not cover the entire design. Rather, it only contains sample // data for the subset of debug items containing _design state_: inputs and registers/latches. This keeps its size to // a minimum, and recording speed to a maximum. The player samples any missing data by setting the design state items // to the same values they had during recording, and re-evaluating the design. // // Packets for diagnostics (prints, breakpoints, assertions, and assumptions) are used solely for diagnostics emitted // by the C++ testbench driving the simulation, and are not recorded while evaluating the design. (Diagnostics emitted // by the RTL can be reconstructed at replay time, so recording them would be a waste of space.) // // Limits // ------ // // The log may contain: // // * Up to 2**28-1 debug items containing design state. // * Up to 2**32 chunks per debug item. // * Up to 2**32 rows per memory. // * Up to 2**32 samples. // // Of these limits, the last two are most likely to be eventually exceeded by practical recordings. However, other // performance considerations will likely limit the size of such practical recordings first, so the log data format // will undergo a breaking change at that point. // // Operations // ---------- // // As suggested by the name "replay log", this format is designed for recording (writing) once and playing (reading) // many times afterwards, such that reading the format can be done linearly and quickly. The log format is designed to // support three primary read operations: // // 1. Initialization // 2. Rewinding (to time T) // 3. Replaying (for N samples) // // During initialization, the player establishes the mapping between debug item names and their 28-bit identifiers in // the log. It is done once. // // During rewinding, the player begins reading at the latest non-incremental sample that still lies before the requested // sample time. It continues reading incremental samples after that point until it reaches the requested sample time. // This process is very cheap as the design is not evaluated; it is essentially a (convoluted) memory copy operation. // // During replaying, the player evaluates the design at the current time, which causes all debug items to assume // the values they had before recording. This process is expensive. Once done, the player advances to the next state // by reading the next (complete or incremental) sample, as above. Since a range of samples is replayed, this process // is repeated several times in a row. // // In principle, when replaying, the player could only read the state of the inputs and the time delta and use a normal // eval/commit loop to progress the simulation, which is fully deterministic so its calculated design state should be // exactly the same as the recorded design state. In practice, it is both faster and more reliable (in presence of e.g. // user-defined black boxes) to read the recorded values instead of calculating them. // // Note: The operations described above are conceptual and do not correspond exactly to methods on `cxxrtl::player`. // The `cxxrtl::player::replay()` method does not evaluate the design. This is so that delta cycles could be ignored // if they are not of interest while replaying. namespace cxxrtl { // A single diagnostic that can be manipulated as an object (including being written to and read from a file). // This differs from the base CXXRTL interface, where diagnostics can only be emitted via a procedure call, and are // not materialized as objects. struct diagnostic { // The `BREAK` flavor corresponds to a breakpoint, which is a diagnostic type that can currently only be emitted // by the C++ testbench code. enum flavor { BREAK = 0, PRINT = 1, ASSERT = 2, ASSUME = 3, }; flavor type; std::string message; std::string location; // same format as the `src` attribute of `$print` or `$check` cell diagnostic() : type(BREAK) {} diagnostic(flavor type, const std::string &message, const std::string &location) : type(type), message(message), location(location) {} diagnostic(flavor type, const std::string &message, const char *file, unsigned line) : type(type), message(message), location(std::string(file) + ':' + std::to_string(line)) {} }; // A spool stores CXXRTL design state changes in a file. class spool { public: // Unique pointer to a specific sample within a replay log. (Timestamps are not unique.) typedef uint32_t pointer_t; // Numeric identifier assigned to a debug item within a replay log. Range limited to [1, MAXIMUM_IDENT]. typedef uint32_t ident_t; static constexpr uint16_t VERSION = 0x0400; static constexpr uint64_t HEADER_MAGIC = 0x00004c5452585843; static constexpr uint64_t VERSION_MASK = 0xffff000000000000; static constexpr uint32_t PACKET_DEFINE = 0xc0000000; static constexpr uint32_t PACKET_SAMPLE = 0xc0000001; enum sample_flag : uint32_t { EMPTY = 0, INCREMENTAL = 1, }; static constexpr uint32_t MAXIMUM_IDENT = 0x0fffffff; static constexpr uint32_t CHANGE_MASK = 0x30000000; static constexpr uint32_t PACKET_CHANGE = 0x00000000/* | ident */; static constexpr uint32_t PACKET_CHANGEI = 0x10000000/* | ident */; static constexpr uint32_t PACKET_CHANGEL = 0x20000000/* | ident */; static constexpr uint32_t PACKET_CHANGEH = 0x30000000/* | ident */; static constexpr uint32_t PACKET_DIAGNOSTIC = 0xc0000010/* | diagnostic::flavor */; static constexpr uint32_t DIAGNOSTIC_MASK = 0x0000000f; static constexpr uint32_t PACKET_END = 0xffffffff; // Writing spools. class writer { int fd; size_t position; std::vector buffer; // These functions aren't overloaded because of implicit numeric conversions. void emit_word(uint32_t word) { if (position + 1 == buffer.size()) flush(); buffer[position++] = word; } void emit_dword(uint64_t dword) { emit_word(dword >> 0); emit_word(dword >> 32); } void emit_ident(ident_t ident) { assert(ident <= MAXIMUM_IDENT); emit_word(ident); } void emit_size(size_t size) { assert(size <= std::numeric_limits::max()); emit_word(size); } // Same implementation as `emit_size()`, different declared intent. void emit_index(size_t index) { assert(index <= std::numeric_limits::max()); emit_word(index); } void emit_string(std::string str) { // Align to a word boundary, and add at least one terminating \0. str.resize(str.size() + (sizeof(uint32_t) - (str.size() + sizeof(uint32_t)) % sizeof(uint32_t))); for (size_t index = 0; index < str.size(); index += sizeof(uint32_t)) { uint32_t word; memcpy(&word, &str[index], sizeof(uint32_t)); emit_word(word); } } void emit_time(const time ×tamp) { const value &raw_timestamp(timestamp); emit_word(raw_timestamp.data[0]); emit_word(raw_timestamp.data[1]); emit_word(raw_timestamp.data[2]); } public: // Creates a writer, and transfers ownership of `fd`, which must be open for appending. // // The buffer size is currently fixed to a "reasonably large" size, determined empirically by measuring writer // performance on a representative design; large but not so large it would e.g. cause address space exhaustion // on 32-bit platforms. writer(spool &spool) : fd(spool.take_write()), position(0), buffer(32 * 1024 * 1024) { assert(fd != -1); #if !defined(WIN32) int result = ftruncate(fd, 0); #else int result = _chsize_s(fd, 0); #endif assert(result == 0); } writer(writer &&moved) : fd(moved.fd), position(moved.position), buffer(moved.buffer) { moved.fd = -1; moved.position = 0; } writer(const writer &) = delete; writer &operator=(const writer &) = delete; // Both write() calls and fwrite() calls are too expensive to perform implicitly. The API consumer must determine // the optimal time to flush the writer and do that explicitly for best performance. void flush() { assert(fd != -1); size_t data_size = position * sizeof(uint32_t); size_t data_written = write(fd, buffer.data(), data_size); assert(data_size == data_written); position = 0; } ~writer() { if (fd != -1) { flush(); close(fd); } } void write_magic() { // `CXXRTL` followed by version in binary. This header will read backwards on big-endian machines, which allows // detection of this case, both visually and programmatically. emit_dword(((uint64_t)VERSION << 48) | HEADER_MAGIC); } void write_define(ident_t ident, const std::string &name, size_t part_index, size_t chunks, size_t depth) { emit_word(PACKET_DEFINE); emit_ident(ident); emit_string(name); emit_index(part_index); emit_size(chunks); emit_size(depth); } void write_sample(bool incremental, pointer_t pointer, const time ×tamp) { uint32_t flags = (incremental ? sample_flag::INCREMENTAL : 0); emit_word(PACKET_SAMPLE); emit_word(flags); emit_word(pointer); emit_time(timestamp); } void write_change(ident_t ident, size_t chunks, const chunk_t *data) { assert(ident <= MAXIMUM_IDENT); if (chunks == 1 && *data == 0) { emit_word(PACKET_CHANGEL | ident); } else if (chunks == 1 && *data == 1) { emit_word(PACKET_CHANGEH | ident); } else { emit_word(PACKET_CHANGE | ident); for (size_t offset = 0; offset < chunks; offset++) emit_word(data[offset]); } } void write_change(ident_t ident, size_t chunks, const chunk_t *data, size_t index) { assert(ident <= MAXIMUM_IDENT); emit_word(PACKET_CHANGEI | ident); emit_index(index); for (size_t offset = 0; offset < chunks; offset++) emit_word(data[offset]); } void write_diagnostic(const diagnostic &diagnostic) { emit_word(PACKET_DIAGNOSTIC | diagnostic.type); emit_string(diagnostic.message); emit_string(diagnostic.location); } void write_end() { emit_word(PACKET_END); } }; // Reading spools. class reader { FILE *f; uint32_t absorb_word() { // If we're at end of file, `fread` will not write to `word`, and `PACKET_END` will be returned. uint32_t word = PACKET_END; fread(&word, sizeof(word), 1, f); return word; } uint64_t absorb_dword() { uint32_t lo = absorb_word(); uint32_t hi = absorb_word(); return ((uint64_t)hi << 32) | lo; } ident_t absorb_ident() { ident_t ident = absorb_word(); assert(ident <= MAXIMUM_IDENT); return ident; } size_t absorb_size() { return absorb_word(); } size_t absorb_index() { return absorb_word(); } std::string absorb_string() { std::string str; do { size_t end = str.size(); str.resize(end + 4); uint32_t word = absorb_word(); memcpy(&str[end], &word, sizeof(uint32_t)); } while (str.back() != '\0'); // Strings have no embedded zeroes besides the terminating one(s). return str.substr(0, str.find('\0')); } time absorb_time() { value raw_timestamp; raw_timestamp.data[0] = absorb_word(); raw_timestamp.data[1] = absorb_word(); raw_timestamp.data[2] = absorb_word(); return time(raw_timestamp); } public: typedef uint64_t pos_t; // Creates a reader, and transfers ownership of `fd`, which must be open for reading. reader(spool &spool) : f(fdopen(spool.take_read(), "r")) { assert(f != nullptr); } reader(reader &&moved) : f(moved.f) { moved.f = nullptr; } reader(const reader &) = delete; reader &operator=(const reader &) = delete; ~reader() { if (f != nullptr) fclose(f); } pos_t position() { return ftell(f); } void rewind(pos_t position) { fseek(f, position, SEEK_SET); } void read_magic() { uint64_t magic = absorb_dword(); assert((magic & ~VERSION_MASK) == HEADER_MAGIC); assert((magic >> 48) == VERSION); } bool read_define(ident_t &ident, std::string &name, size_t &part_index, size_t &chunks, size_t &depth) { uint32_t header = absorb_word(); if (header == PACKET_END) return false; assert(header == PACKET_DEFINE); ident = absorb_ident(); name = absorb_string(); part_index = absorb_index(); chunks = absorb_size(); depth = absorb_size(); return true; } bool read_sample(bool &incremental, pointer_t &pointer, time ×tamp) { uint32_t header = absorb_word(); if (header == PACKET_END) return false; assert(header == PACKET_SAMPLE); uint32_t flags = absorb_word(); incremental = (flags & sample_flag::INCREMENTAL); pointer = absorb_word(); timestamp = absorb_time(); return true; } bool read_header(uint32_t &header) { header = absorb_word(); return header != PACKET_END; } // This method must be separate from `read_change_data` because `chunks` and `depth` can only be looked up // if `ident` is known. bool read_change_ident(uint32_t header, ident_t &ident) { if ((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) != 0) return false; // some other packet ident = header & MAXIMUM_IDENT; return true; } void read_change_data(uint32_t header, size_t chunks, size_t depth, chunk_t *data) { uint32_t index = 0; switch (header & CHANGE_MASK) { case PACKET_CHANGEL: *data = 0; return; case PACKET_CHANGEH: *data = 1; return; case PACKET_CHANGE: break; case PACKET_CHANGEI: index = absorb_word(); assert(index < depth); break; default: assert(false && "Unrecognized change packet"); } for (size_t offset = 0; offset < chunks; offset++) data[chunks * index + offset] = absorb_word(); } bool read_diagnostic(uint32_t header, diagnostic &diagnostic) { if ((header & ~DIAGNOSTIC_MASK) != PACKET_DIAGNOSTIC) return false; // some other packet uint32_t type = header & DIAGNOSTIC_MASK; assert(type == diagnostic::BREAK || type == diagnostic::PRINT || type == diagnostic::ASSERT || type == diagnostic::ASSUME); diagnostic.type = (diagnostic::flavor)type; diagnostic.message = absorb_string(); diagnostic.location = absorb_string(); return true; } }; // Opening spools. For certain uses of the record/replay mechanism, two distinct open files (two open files, i.e. // two distinct file pointers, and not just file descriptors, which share the file pointer if duplicated) are used, // for a reader and writer thread. This class manages the lifetime of the descriptors for these files. When only // one of them is used, the other is closed harmlessly when the spool is destroyed. private: std::atomic writefd; std::atomic readfd; public: spool(const std::string &filename) : writefd(open(filename.c_str(), O_CREAT|O_BINARY|O_WRONLY|O_APPEND, 0644)), readfd(open(filename.c_str(), O_BINARY|O_RDONLY)) { assert(writefd.load() != -1 && readfd.load() != -1); } spool(spool &&moved) : writefd(moved.writefd.exchange(-1)), readfd(moved.readfd.exchange(-1)) {} spool(const spool &) = delete; spool &operator=(const spool &) = delete; ~spool() { int fd; if ((fd = writefd.exchange(-1)) != -1) close(fd); if ((fd = readfd.exchange(-1)) != -1) close(fd); } // Atomically acquire a write file descriptor for the spool. Can be called once, and will return -1 the next time // it is called. Thread-safe. int take_write() { return writefd.exchange(-1); } // Atomically acquire a read file descriptor for the spool. Can be called once, and will return -1 the next time // it is called. Thread-safe. int take_read() { return readfd.exchange(-1); } }; // A CXXRTL recorder samples design state, producing complete or incremental updates, and writes them to a spool. class recorder { struct variable { spool::ident_t ident; /* <= spool::MAXIMUM_IDENT */ size_t chunks; size_t depth; /* == 1 for wires */ chunk_t *curr; bool memory; }; spool::writer writer; std::vector variables; std::vector inputs; // values of inputs must be recorded explicitly, as their changes are not observed std::unordered_map ident_lookup; bool streaming = false; // whether variable definitions have been written spool::pointer_t pointer = 0; time timestamp; public: template recorder(Args &&...args) : writer(std::forward(args)...) {} void start(module &module, std::string top_path = "") { debug_items items; module.debug_info(&items, /*scopes=*/nullptr, top_path); start(items); } void start(const debug_items &items) { assert(!streaming); writer.write_magic(); for (auto item : items.table) for (size_t part_index = 0; part_index < item.second.size(); part_index++) { auto &part = item.second[part_index]; if ((part.flags & debug_item::INPUT) || (part.flags & debug_item::DRIVEN_SYNC) || (part.type == debug_item::MEMORY)) { variable var; var.ident = variables.size() + 1; var.chunks = (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8); var.depth = part.depth; var.curr = part.curr; var.memory = (part.type == debug_item::MEMORY); ident_lookup[var.curr] = var.ident; assert(variables.size() < spool::MAXIMUM_IDENT); if (part.flags & debug_item::INPUT) inputs.push_back(variables.size()); variables.push_back(var); writer.write_define(var.ident, item.first, part_index, var.chunks, var.depth); } } writer.write_end(); streaming = true; } const time &latest_time() { return timestamp; } const time &advance_time(const time &delta) { assert(!delta.is_negative()); timestamp += delta; return timestamp; } void record_complete() { assert(streaming); writer.write_sample(/*incremental=*/false, pointer++, timestamp); for (auto var : variables) { assert(var.ident != 0); if (!var.memory) writer.write_change(var.ident, var.chunks, var.curr); else for (size_t index = 0; index < var.depth; index++) writer.write_change(var.ident, var.chunks, &var.curr[var.chunks * index], index); } writer.write_end(); } // This function is generic over ModuleT to encourage observer callbacks to be inlined into the commit function. template bool record_incremental(ModuleT &module) { assert(streaming); struct : observer { std::unordered_map *ident_lookup; spool::writer *writer; CXXRTL_ALWAYS_INLINE void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) { writer->write_change(ident_lookup->at(base), chunks, value); } CXXRTL_ALWAYS_INLINE void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) { writer->write_change(ident_lookup->at(base), chunks, value, index); } } record_observer; record_observer.ident_lookup = &ident_lookup; record_observer.writer = &writer; writer.write_sample(/*incremental=*/true, pointer++, timestamp); for (auto input_index : inputs) { variable &var = variables.at(input_index); assert(!var.memory); writer.write_change(var.ident, var.chunks, var.curr); } bool changed = module.commit(record_observer); writer.write_end(); return changed; } void record_diagnostic(const diagnostic &diagnostic) { assert(streaming); // Emit an incremental delta cycle per diagnostic to simplify the logic of the recorder. This is inefficient, but // diagnostics should be rare enough that this inefficiency does not matter. If it turns out to be an issue, this // code should be changed to accumulate diagnostics to a buffer that is flushed in `record_{complete,incremental}` // and also in `advance_time` before the timestamp is changed. (Right now `advance_time` never writes to the spool.) writer.write_sample(/*incremental=*/true, pointer++, timestamp); writer.write_diagnostic(diagnostic); writer.write_end(); } void flush() { writer.flush(); } }; // A CXXRTL player reads samples from a spool, and changes the design state accordingly. To start reading samples, // a spool must have been initialized: the recorder must have been started and an initial complete sample must have // been written. class player { struct variable { size_t chunks; size_t depth; /* == 1 for wires */ chunk_t *curr; }; spool::reader reader; std::unordered_map variables; bool streaming = false; // whether variable definitions have been read bool initialized = false; // whether a sample has ever been read spool::pointer_t pointer = 0; time timestamp; std::map> index_by_pointer; std::map> index_by_timestamp; bool peek_sample(spool::pointer_t &pointer, time ×tamp) { bool incremental; auto position = reader.position(); bool success = reader.read_sample(incremental, pointer, timestamp); reader.rewind(position); return success; } public: template player(Args &&...args) : reader(std::forward(args)...) {} // The `top_path` must match the one given to the recorder. void start(module &module, std::string top_path = "") { debug_items items; module.debug_info(&items, /*scopes=*/nullptr, top_path); start(items); } void start(const debug_items &items) { assert(!streaming); reader.read_magic(); while (true) { spool::ident_t ident; std::string name; size_t part_index; size_t chunks; size_t depth; if (!reader.read_define(ident, name, part_index, chunks, depth)) break; assert(variables.count(ident) == 0); assert(items.count(name) != 0); assert(part_index < items.count(name)); const debug_item &part = items.at(name).at(part_index); assert(chunks == (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8)); assert(depth == part.depth); variable &var = variables[ident]; var.chunks = chunks; var.depth = depth; var.curr = part.curr; } assert(variables.size() > 0); streaming = true; // Establish the initial state of the design. std::vector diagnostics; initialized = replay(&diagnostics); assert(initialized && diagnostics.empty()); } // Returns the pointer of the current sample. spool::pointer_t current_pointer() { assert(initialized); return pointer; } // Returns the time of the current sample. const time ¤t_time() { assert(initialized); return timestamp; } // Returns `true` if there is a next sample to read, and sets `pointer` to its pointer if there is. bool get_next_pointer(spool::pointer_t &pointer) { assert(streaming); time timestamp; return peek_sample(pointer, timestamp); } // Returns `true` if there is a next sample to read, and sets `timestamp` to its time if there is. bool get_next_time(time ×tamp) { assert(streaming); uint32_t pointer; return peek_sample(pointer, timestamp); } // If this function returns `true`, then `current_pointer() == at_pointer`, and the module contains values that // correspond to this pointer in the replay log. To obtain a valid pointer, call `current_pointer()`; while pointers // are monotonically increasing for each consecutive sample, using arithmetic operations to create a new pointer is // not allowed. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample. bool rewind_to(spool::pointer_t at_pointer, std::vector *diagnostics) { assert(initialized); // The pointers in the replay log start from one that is greater than `at_pointer`. In this case the pointer will // never be reached. assert(index_by_pointer.size() > 0); if (at_pointer < index_by_pointer.rbegin()->first) return false; // Find the last complete sample whose pointer is less than or equal to `at_pointer`. Note that the comparison // function used here is `std::greater`, inverting the direction of `lower_bound`. auto position_it = index_by_pointer.lower_bound(at_pointer); assert(position_it != index_by_pointer.end()); reader.rewind(position_it->second); // Replay samples until eventually arriving to `at_pointer` or encountering end of file. while(replay(diagnostics)) { if (pointer == at_pointer) return true; if (diagnostics) diagnostics->clear(); } return false; } // If this function returns `true`, then `current_time() <= at_or_before_timestamp`, and the module contains values // that correspond to `current_time()` in the replay log. If `current_time() == at_or_before_timestamp` and there // are several consecutive samples with the same time, the module contains values that correspond to the first of // these samples. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample. bool rewind_to_or_before(const time &at_or_before_timestamp, std::vector *diagnostics) { assert(initialized); // The timestamps in the replay log start from one that is greater than `at_or_before_timestamp`. In this case // the timestamp will never be reached. Otherwise, this function will always succeed. assert(index_by_timestamp.size() > 0); if (at_or_before_timestamp < index_by_timestamp.rbegin()->first) return false; // Find the last complete sample whose timestamp is less than or equal to `at_or_before_timestamp`. Note that // the comparison function used here is `std::greater`, inverting the direction of `lower_bound`. auto position_it = index_by_timestamp.lower_bound(at_or_before_timestamp); assert(position_it != index_by_timestamp.end()); reader.rewind(position_it->second); // Replay samples until eventually arriving to or past `at_or_before_timestamp` or encountering end of file. while (replay(diagnostics)) { if (timestamp == at_or_before_timestamp) break; time next_timestamp; if (!get_next_time(next_timestamp)) break; if (next_timestamp > at_or_before_timestamp) break; if (diagnostics) diagnostics->clear(); } return true; } // If this function returns `true`, then `current_pointer()` and `current_time()` are updated for the next sample // and the module now contains values that correspond to that sample. If it returns `false`, there was no next sample // to read. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in the next sample. bool replay(std::vector *diagnostics) { assert(streaming); bool incremental; auto position = reader.position(); if (!reader.read_sample(incremental, pointer, timestamp)) return false; // The very first sample that is read must be a complete sample. This is required for the rewind functions to work. assert(initialized || !incremental); // It is possible (though not very useful) to have several complete samples with the same timestamp in a row. // Ensure that we associate the timestamp with the position of the first such complete sample. (This condition // works because the player never jumps over a sample.) if (!incremental && !index_by_pointer.count(pointer)) { assert(!index_by_timestamp.count(timestamp)); index_by_pointer[pointer] = position; index_by_timestamp[timestamp] = position; } uint32_t header; while (reader.read_header(header)) { spool::ident_t ident; diagnostic diag; if (reader.read_change_ident(header, ident)) { variable &var = variables.at(ident); reader.read_change_data(header, var.chunks, var.depth, var.curr); } else if (reader.read_diagnostic(header, diag)) { if (diagnostics) diagnostics->push_back(diag); } else assert(false && "Unrecognized packet header"); } return true; } }; } #endif yosys-0.65/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h000066400000000000000000000140361520057232300234550ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2023 Catherine * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 CXXRTL_TIME_H #define CXXRTL_TIME_H #include #include #include namespace cxxrtl { // A timestamp or a difference in time, stored as a 96-bit number of femtoseconds (10e-15 s). The range and resolution // of this format can represent any VCD timestamp within approx. ±1255321.2 years, without the need for a timescale. class time { public: static constexpr size_t bits = 96; // 3 chunks private: static constexpr size_t resolution_digits = 15; static_assert(sizeof(chunk_t) == 4, "a chunk is expected to be 32-bit"); static constexpr value resolution = value { chunk_t(1000000000000000ull & 0xffffffffull), chunk_t(1000000000000000ull >> 32), 0u }; // Signed number of femtoseconds from the beginning of time. value raw; public: constexpr time() {} explicit constexpr time(const value &raw) : raw(raw) {} explicit operator const value &() const { return raw; } static constexpr time maximum() { return time(value { 0xffffffffu, 0xffffffffu, 0x7fffffffu }); } time(int64_t secs, int64_t femtos) { value<64> secs_val; secs_val.set(secs); value<64> femtos_val; femtos_val.set(femtos); raw = secs_val.sext().mul(resolution).add(femtos_val.sext()); } bool is_zero() const { return raw.is_zero(); } // Extracts the sign of the value. bool is_negative() const { return raw.is_neg(); } // Extracts the number of whole seconds. Negative if the value is negative. int64_t secs() const { return raw.sdivmod(resolution).first.trunc<64>().get(); } // Extracts the number of femtoseconds in the fractional second. Negative if the value is negative. int64_t femtos() const { return raw.sdivmod(resolution).second.trunc<64>().get(); } bool operator==(const time &other) const { return raw == other.raw; } bool operator!=(const time &other) const { return raw != other.raw; } bool operator>(const time &other) const { return other.raw.scmp(raw); } bool operator>=(const time &other) const { return !raw.scmp(other.raw); } bool operator<(const time &other) const { return raw.scmp(other.raw); } bool operator<=(const time &other) const { return !other.raw.scmp(raw); } time operator+(const time &other) const { return time(raw.add(other.raw)); } time &operator+=(const time &other) { *this = *this + other; return *this; } time operator-() const { return time(raw.neg()); } time operator-(const time &other) const { return *this + (-other); } time &operator-=(const time &other) { *this = *this - other; return *this; } operator std::string() const { char buf[48]; // x=2**95; len(f"-{x/1_000_000_000_000_000}.{x^1_000_000_000_000_000}") == 48 int64_t secs = this->secs(); int64_t femtos = this->femtos(); snprintf(buf, sizeof(buf), "%s%" PRIi64 ".%015" PRIi64, is_negative() ? "-" : "", secs >= 0 ? secs : -secs, femtos >= 0 ? femtos : -femtos); return buf; } #if __cplusplus >= 201603L [[nodiscard("ignoring parse errors")]] #endif bool parse(const std::string &str) { enum { parse_sign_opt, parse_integral, parse_fractional, } state = parse_sign_opt; bool negative = false; int64_t integral = 0; int64_t fractional = 0; size_t frac_digits = 0; for (auto chr : str) { switch (state) { case parse_sign_opt: state = parse_integral; if (chr == '+' || chr == '-') { negative = (chr == '-'); break; } /* fallthrough */ case parse_integral: if (chr >= '0' && chr <= '9') { integral *= 10; integral += chr - '0'; } else if (chr == '.') { state = parse_fractional; } else { return false; } break; case parse_fractional: if (chr >= '0' && chr <= '9' && frac_digits < resolution_digits) { fractional *= 10; fractional += chr - '0'; frac_digits++; } else { return false; } break; } } if (frac_digits == 0) return false; while (frac_digits++ < resolution_digits) fractional *= 10; *this = negative ? -time { integral, fractional} : time { integral, fractional }; return true; } }; // Out-of-line definition required until C++17. constexpr value time::resolution; std::ostream &operator<<(std::ostream &os, const time &val) { os << (std::string)val; return os; } // These literals are (confusingly) compatible with the ones from `std::chrono`: the `std::chrono` literals do not // have an underscore (e.g. 1ms) and the `cxxrtl::time` literals do (e.g. 1_ms). This syntactic difference is // a requirement of the C++ standard. Despite being compatible the literals should not be mixed in the same namespace. namespace time_literals { time operator""_s(unsigned long long seconds) { return time { (int64_t)seconds, 0 }; } time operator""_ms(unsigned long long milliseconds) { return time { 0, (int64_t)milliseconds * 1000000000000 }; } time operator""_us(unsigned long long microseconds) { return time { 0, (int64_t)microseconds * 1000000000 }; } time operator""_ns(unsigned long long nanoseconds) { return time { 0, (int64_t)nanoseconds * 1000000 }; } time operator""_ps(unsigned long long picoseconds) { return time { 0, (int64_t)picoseconds * 1000 }; } time operator""_fs(unsigned long long femtoseconds) { return time { 0, (int64_t)femtoseconds }; } }; }; #endif yosys-0.65/backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h000066400000000000000000000206751520057232300233010ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * 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 CXXRTL_VCD_H #define CXXRTL_VCD_H #include namespace cxxrtl { class vcd_writer { struct variable { size_t ident; size_t width; chunk_t *curr; size_t cache_offset; debug_outline *outline; bool *outline_warm; }; std::vector current_scope; std::map outlines; std::vector variables; std::vector cache; std::map aliases; bool streaming = false; void emit_timescale(unsigned number, const std::string &unit) { assert(!streaming); assert(number == 1 || number == 10 || number == 100); assert(unit == "s" || unit == "ms" || unit == "us" || unit == "ns" || unit == "ps" || unit == "fs"); buffer += "$timescale " + std::to_string(number) + " " + unit + " $end\n"; } void emit_scope(const std::vector &scope) { assert(!streaming); 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(); } while (current_scope.size() < scope.size()) { buffer += "$scope module " + scope[current_scope.size()] + " $end\n"; current_scope.push_back(scope[current_scope.size()]); } } void emit_ident(size_t ident) { do { buffer += '!' + ident % 94; // "base94" ident /= 94; } while (ident != 0); } void emit_name(const std::string &name) { for (char c : name) { if (c == ':') { // Due to a bug, GTKWave cannot parse a colon in the variable name, causing the VCD file // to be unreadable. It cannot be escaped either, so replace it with the sideways colon. buffer += ".."; } else { buffer += c; } } } void emit_var(const variable &var, const std::string &type, const std::string &name, size_t lsb_at, bool multipart) { assert(!streaming); buffer += "$var " + type + " " + std::to_string(var.width) + " "; emit_ident(var.ident); buffer += " "; emit_name(name); if (multipart || name.back() == ']' || lsb_at != 0) { if (var.width == 1) buffer += " [" + std::to_string(lsb_at) + "]"; else buffer += " [" + std::to_string(lsb_at + var.width - 1) + ":" + std::to_string(lsb_at) + "]"; } buffer += " $end\n"; } void emit_enddefinitions() { assert(!streaming); buffer += "$enddefinitions $end\n"; streaming = true; } void emit_time(uint64_t timestamp) { assert(streaming); buffer += "#" + std::to_string(timestamp) + "\n"; } void emit_scalar(const variable &var) { assert(streaming); assert(var.width == 1); buffer += (*var.curr ? '1' : '0'); emit_ident(var.ident); buffer += '\n'; } void emit_vector(const variable &var) { assert(streaming); buffer += 'b'; for (size_t bit = var.width - 1; bit != (size_t)-1; bit--) { 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'; } void reset_outlines() { for (auto &outline_it : outlines) outline_it.second = /*warm=*/(outline_it.first == nullptr); } variable ®ister_variable(size_t width, chunk_t *curr, bool constant = false, debug_outline *outline = nullptr) { if (aliases.count(curr)) { return variables[aliases[curr]]; } else { auto outline_it = outlines.emplace(outline, /*warm=*/(outline == nullptr)).first; const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); aliases[curr] = variables.size(); if (constant) { variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1, outline_it->first, &outline_it->second }); } else { variables.emplace_back(variable { variables.size(), width, curr, cache.size(), outline_it->first, &outline_it->second }); cache.insert(cache.end(), &curr[0], &curr[chunks]); } return variables.back(); } } bool test_variable(const variable &var) { if (var.cache_offset == (size_t)-1) return false; // constant if (!*var.outline_warm) { var.outline->eval(); *var.outline_warm = true; } const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset])) { return false; } else { std::copy(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset]); return true; } } static std::vector split_hierarchy(const std::string &hier_name) { std::vector hierarchy; size_t prev = 0; while (true) { size_t curr = hier_name.find_first_of(' ', prev); if (curr == std::string::npos) { hierarchy.push_back(hier_name.substr(prev)); break; } else { hierarchy.push_back(hier_name.substr(prev, curr - prev)); prev = curr + 1; } } return hierarchy; } public: std::string buffer; void timescale(unsigned number, const std::string &unit) { emit_timescale(number, unit); } void add(const std::string &hier_name, const debug_item &item, bool multipart = false) { std::vector scope = split_hierarchy(hier_name); std::string name = scope.back(); scope.pop_back(); emit_scope(scope); switch (item.type) { // Not the best naming but oh well... case debug_item::VALUE: emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), "wire", name, item.lsb_at, multipart); break; case debug_item::WIRE: emit_var(register_variable(item.width, item.curr), "reg", name, item.lsb_at, multipart); break; case debug_item::MEMORY: { const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); for (size_t index = 0; index < item.depth; index++) { chunk_t *nth_curr = &item.curr[stride * index]; std::string nth_name = name + '[' + std::to_string(index) + ']'; emit_var(register_variable(item.width, nth_curr), "reg", nth_name, item.lsb_at, multipart); } break; } case debug_item::ALIAS: // Like VALUE, but, even though `item.next == nullptr` always holds, the underlying value // can actually change, and must be tracked. In most cases the VCD identifier will be // unified with the aliased reg, but we should handle the case where only the alias is // added to the VCD writer, too. emit_var(register_variable(item.width, item.curr), "wire", name, item.lsb_at, multipart); break; case debug_item::OUTLINE: emit_var(register_variable(item.width, item.curr, /*constant=*/false, item.outline), "wire", name, item.lsb_at, multipart); break; } } template void add(const debug_items &items, const Filter &filter) { // `debug_items` is a map, so the items are already sorted in an order optimal for emitting // VCD scope sections. for (auto &it : items.table) for (auto &part : it.second) if (filter(it.first, part)) add(it.first, part, it.second.size() > 1); } void add(const debug_items &items) { this->add(items, [](const std::string &, const debug_item &) { return true; }); } void add_without_memories(const debug_items &items) { this->add(items, [](const std::string &, const debug_item &item) { return item.type != debug_item::MEMORY; }); } void sample(uint64_t timestamp) { bool first_sample = !streaming; if (first_sample) { emit_scope({}); emit_enddefinitions(); } reset_outlines(); emit_time(timestamp); for (auto var : variables) if (test_variable(var) || first_sample) { if (var.width == 1) emit_scalar(var); else emit_vector(var); } } }; } #endif yosys-0.65/backends/edif/000077500000000000000000000000001520057232300153125ustar00rootroot00000000000000yosys-0.65/backends/edif/Makefile.inc000066400000000000000000000000371520057232300175220ustar00rootroot00000000000000 OBJS += backends/edif/edif.o yosys-0.65/backends/edif/edif.cc000066400000000000000000000477721520057232300165510ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ // [[CITE]] EDIF Version 2 0 0 Grammar // http://web.archive.org/web/20050730021644/http://www.edif.org/documentation/BNF_GRAMMAR/index.html #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/newcelltypes.h" #include "kernel/log.h" #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN #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 { int counter; char delim_left, delim_right; std::set generated_names, used_names; std::map name_map; EdifNames() : counter(1), delim_left('['), delim_right(']') { } std::string operator()(std::string id, bool define, bool port_rename = false, int range_left = 0, int range_right = 0) { if (define) { std::string new_id = operator()(id, false); if (port_rename) 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) return name_map.at(id); if (generated_names.count(id) > 0) goto do_rename; if (id == "GND" || id == "VCC") goto do_rename; for (size_t i = 0; i < id.size(); i++) { if ('A' <= id[i] && id[i] <= 'Z') continue; if ('a' <= id[i] && id[i] <= 'z') continue; if ('0' <= id[i] && id[i] <= '9' && i > 0) continue; if (id[i] == '_' && i > 0 && i != id.size()-1) continue; goto do_rename; } used_names.insert(id); return id; do_rename:; std::string gen_name; while (1) { gen_name = stringf("id%05d", counter++); if (generated_names.count(gen_name) == 0 && used_names.count(gen_name) == 0) break; } generated_names.insert(gen_name); name_map[id] = gen_name; return gen_name; } }; struct EdifBackend : public Backend { EdifBackend() : Backend("edif", "write design to EDIF netlist file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_edif [options] [filename]\n"); log("\n"); log("Write the current design to an EDIF netlist file.\n"); log("\n"); log(" -top top_module\n"); log(" set the specified module as design top module\n"); log("\n"); log(" -nogndvcc\n"); log(" do not create \"GND\" and \"VCC\" cells. (this will produce an error\n"); log(" if the design contains constant nets. use \"hilomap\" to map to custom\n"); log(" constant drivers first)\n"); log("\n"); log(" -gndvccy\n"); log(" create \"GND\" and \"VCC\" cells with \"Y\" outputs. (the default is\n"); log(" \"G\" for \"GND\" and \"P\" for \"VCC\".)\n"); log("\n"); log(" -attrprop\n"); log(" create EDIF properties for cell attributes\n"); log("\n"); log(" -keep\n"); log(" create extra KEEP nets by allowing a cell to drive multiple nets.\n"); log("\n"); log(" -pvector {par|bra|ang}\n"); log(" sets the delimiting character for module port rename clauses to\n"); log(" parentheses, square brackets, or angle brackets.\n"); log("\n"); log(" -lsbidx\n"); log(" use index 0 for the LSB bit of a net or port instead of MSB.\n"); log("\n"); log("Unfortunately there are different \"flavors\" of the EDIF file format. This\n"); log("command generates EDIF files for the Xilinx place&route tools. It might be\n"); log("necessary to make small modifications to this command when a different tool\n"); log("is targeted.\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { log_header(design, "Executing EDIF backend.\n"); std::string top_module_name; bool port_rename = false; bool attr_properties = false; bool lsbidx = false; std::map> lib_cell_ports; bool nogndvcc = false, gndvccy = false, keepmode = false; NewCellTypes ct(design); EdifNames edif_names; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-top" && argidx+1 < args.size()) { top_module_name = args[++argidx]; continue; } if (args[argidx] == "-nogndvcc") { nogndvcc = true; continue; } if (args[argidx] == "-gndvccy") { gndvccy = true; continue; } if (args[argidx] == "-attrprop") { attr_properties = true; continue; } if (args[argidx] == "-keep") { keepmode = true; continue; } if (args[argidx] == "-pvector" && argidx+1 < args.size()) { std::string parray; port_rename = true; parray = args[++argidx]; if (parray == "par") { edif_names.delim_left = '(';edif_names.delim_right = ')'; } else if (parray == "ang") { edif_names.delim_left = '<';edif_names.delim_right = '>'; } else { edif_names.delim_left = '[';edif_names.delim_right = ']'; } continue; } if (args[argidx] == "-lsbidx") { lsbidx = true; continue; } break; } extra_args(f, filename, args, argidx); if (top_module_name.empty()) for (auto module : design->modules()) if (module->get_bool_attribute(ID::top)) top_module_name = module->name.str(); for (auto module : design->modules()) { lib_cell_ports[module->name]; for (auto port : module->ports) { Wire *wire = module->wire(port); lib_cell_ports[module->name][port] = std::max(lib_cell_ports[module->name][port], GetSize(wire)); } if (module->get_blackbox_attribute()) continue; if (top_module_name.empty()) top_module_name = module->name.str(); if (module->processes.size() != 0) log_error("Found unmapped processes in module %s: unmapped processes are not supported in EDIF backend!\n", log_id(module->name)); if (module->memories.size() != 0) log_error("Found unmapped memories in module %s: unmapped memories are not supported in EDIF backend!\n", log_id(module->name)); for (auto cell : module->cells()) { if (cell->type == ID($scopeinfo)) continue; if (design->module(cell->type) == nullptr || design->module(cell->type)->get_blackbox_attribute()) { lib_cell_ports[cell->type]; for (auto p : cell->connections()) lib_cell_ports[cell->type][p.first] = std::max(lib_cell_ports[cell->type][p.first], GetSize(p.second)); } } } if (top_module_name.empty()) log_error("No module found in design!\n"); *f << stringf("(edif %s\n", EDIF_DEF(top_module_name)); *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_maybe_version()); *f << stringf(" (external LIB\n"); *f << stringf(" (edifLevel 0)\n"); *f << stringf(" (technology (numberDefinition))\n"); if (!nogndvcc) { *f << stringf(" (cell GND\n"); *f << stringf(" (cellType GENERIC)\n"); *f << stringf(" (view VIEW_NETLIST\n"); *f << stringf(" (viewType NETLIST)\n"); *f << stringf(" (interface (port %c (direction OUTPUT)))\n", gndvccy ? 'Y' : 'G'); *f << stringf(" )\n"); *f << stringf(" )\n"); *f << stringf(" (cell VCC\n"); *f << stringf(" (cellType GENERIC)\n"); *f << stringf(" (view VIEW_NETLIST\n"); *f << stringf(" (viewType NETLIST)\n"); *f << stringf(" (interface (port %c (direction OUTPUT)))\n", gndvccy ? 'Y' : 'P'); *f << stringf(" )\n"); *f << stringf(" )\n"); } for (auto &cell_it : lib_cell_ports) { *f << stringf(" (cell %s\n", EDIF_DEF(cell_it.first)); *f << stringf(" (cellType GENERIC)\n"); *f << stringf(" (view VIEW_NETLIST\n"); *f << stringf(" (viewType NETLIST)\n"); *f << stringf(" (interface\n"); for (auto &port_it : cell_it.second) { const char *dir = "INOUT"; if (ct.cell_known(cell_it.first)) { if (!ct.cell_output(cell_it.first, port_it.first)) dir = "INPUT"; else if (!ct.cell_input(cell_it.first, port_it.first)) dir = "OUTPUT"; } int width = port_it.second; int start = 0; bool upto = false; auto m = design->module(cell_it.first); if (m) { auto w = m->wire(port_it.first); if (w) { width = GetSize(w); start = w->start_offset; upto = w->upto; } } if (width == 1) *f << stringf(" (port %s (direction %s))\n", EDIF_DEF(port_it.first), dir); else { int b[2]; b[upto ? 0 : 1] = start; b[upto ? 1 : 0] = start+width-1; *f << stringf(" (port (array %s %d) (direction %s))\n", EDIF_DEFR(port_it.first, port_rename, b[0], b[1]), width, dir); } } *f << stringf(" )\n"); *f << stringf(" )\n"); *f << stringf(" )\n"); } *f << stringf(" )\n"); std::vector sorted_modules; // extract module dependencies std::map> module_deps; for (auto module : design->modules()) { module_deps[module] = std::set(); for (auto cell : module->cells()) if (design->module(cell->type) != nullptr) module_deps[module].insert(design->module(cell->type)); } // simple good-enough topological sort // (O(n*m) on n elements and depth m) while (module_deps.size() > 0) { size_t sorted_modules_idx = sorted_modules.size(); for (auto &it : module_deps) { for (auto &dep : it.second) if (module_deps.count(dep) > 0) goto not_ready_yet; // log("Next in topological sort: %s\n", log_id(it.first->name)); sorted_modules.push_back(it.first); not_ready_yet:; } if (sorted_modules_idx == sorted_modules.size()) log_error("Cyclic dependency between modules found! Cycle includes module %s.\n", log_id(module_deps.begin()->first->name)); while (sorted_modules_idx < sorted_modules.size()) module_deps.erase(sorted_modules.at(sorted_modules_idx++)); } *f << stringf(" (library DESIGN\n"); *f << stringf(" (edifLevel 0)\n"); *f << stringf(" (technology (numberDefinition))\n"); 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()); 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 (auto i = 0; i < val.size(); i += 4) { int digit_value = 0; 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), hex_string); } }; for (auto module : sorted_modules) { if (module->get_blackbox_attribute()) continue; SigMap sigmap(module); std::map>> net_join_db; *f << stringf(" (cell %s\n", EDIF_DEF(module->name)); *f << stringf(" (cellType GENERIC)\n"); *f << stringf(" (view VIEW_NETLIST\n"); *f << stringf(" (viewType NETLIST)\n"); *f << stringf(" (interface\n"); for (auto cell : module->cells()) { for (auto &conn : cell->connections()) if (cell->output(conn.first)) sigmap.add(conn.second); } for (auto wire : module->wires()) for (auto b1 : SigSpec(wire)) { auto b2 = sigmap(b1); if (b1 == b2 || !b2.wire) continue; log_assert(b1.wire != nullptr); Wire *w1 = b1.wire; Wire *w2 = b2.wire; { int c1 = w1->get_bool_attribute(ID::keep); int c2 = w2->get_bool_attribute(ID::keep); if (c1 > c2) goto promote; if (c1 < c2) goto nopromote; } { int c1 = w1->name.isPublic(); int c2 = w2->name.isPublic(); if (c1 > c2) goto promote; if (c1 < c2) goto nopromote; } { auto count_nontrivial_attr = [](Wire *w) { int count = w->attributes.size(); count -= w->attributes.count(ID::src); count -= w->attributes.count(ID::unused_bits); return count; }; int c1 = count_nontrivial_attr(w1); int c2 = count_nontrivial_attr(w2); if (c1 > c2) goto promote; if (c1 < c2) goto nopromote; } { int c1 = w1->port_id ? INT_MAX - w1->port_id : 0; int c2 = w2->port_id ? INT_MAX - w2->port_id : 0; if (c1 > c2) goto promote; if (c1 < c2) goto nopromote; } nopromote: if (0) promote: sigmap.add(b1); } for (auto wire : module->wires()) { if (wire->port_id == 0) continue; const char *dir = "INOUT"; if (!wire->port_output) dir = "INPUT"; else if (!wire->port_input) dir = "OUTPUT"; if (wire->width == 1) { *f << stringf(" (port %s (direction %s)", EDIF_DEF(wire->name), dir); if (attr_properties) for (auto &p : wire->attributes) add_prop(p.first, p.second); *f << ")\n"; RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire)); net_join_db[sig].insert(make_pair(stringf("(portRef %s)", EDIF_REF(wire->name)), wire->port_input)); } else { int b[2]; b[wire->upto ? 0 : 1] = wire->start_offset; b[wire->upto ? 1 : 0] = wire->start_offset + GetSize(wire) - 1; *f << stringf(" (port (array %s %d) (direction %s)", EDIF_DEFR(wire->name, port_rename, b[0], b[1]), wire->width, dir); if (attr_properties) for (auto &p : wire->attributes) add_prop(p.first, p.second); *f << ")\n"; for (int i = 0; i < wire->width; i++) { RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire, i)); net_join_db[sig].insert(make_pair(stringf("(portRef (member %s %d))", EDIF_REF(wire->name), lsbidx ? i : GetSize(wire)-i-1), wire->port_input)); } } } *f << stringf(" )\n"); *f << stringf(" (contents\n"); if (!nogndvcc) { *f << stringf(" (instance GND (viewRef VIEW_NETLIST (cellRef GND (libraryRef LIB))))\n"); *f << stringf(" (instance VCC (viewRef VIEW_NETLIST (cellRef VCC (libraryRef LIB))))\n"); } for (auto cell : module->cells()) { *f << stringf(" (instance %s\n", EDIF_DEF(cell->name)); *f << stringf(" (viewRef VIEW_NETLIST (cellRef %s%s))", EDIF_REF(cell->type), lib_cell_ports.count(cell->type) > 0 ? " (libraryRef LIB)" : ""); for (auto &p : cell->parameters) add_prop(p.first, p.second); if (attr_properties) for (auto &p : cell->attributes) add_prop(p.first, p.second); *f << stringf(")\n"); for (auto &p : cell->connections()) { RTLIL::SigSpec sig = sigmap(p.second); for (int i = 0; i < GetSize(sig); i++) if (sig[i].wire == NULL && sig[i] != RTLIL::State::S0 && sig[i] != RTLIL::State::S1) log_warning("Bit %d of cell port %s.%s.%s driven by %s will be left unconnected in EDIF output.\n", i, log_id(module), log_id(cell), log_id(p.first), log_signal(sig[i])); else { int member_idx = lsbidx ? i : GetSize(sig)-i-1; auto m = design->module(cell->type); int width = sig.size(); if (m) { auto w = m->wire(p.first); if (w) { member_idx = lsbidx ? i : GetSize(w)-i-1; width = GetSize(w); } } if (width == 1) net_join_db[sig[i]].insert(make_pair(stringf("(portRef %s (instanceRef %s))", EDIF_REF(p.first), EDIF_REF(cell->name)), cell->output(p.first))); else { net_join_db[sig[i]].insert(make_pair(stringf("(portRef (member %s %d) (instanceRef %s))", EDIF_REF(p.first), member_idx, EDIF_REF(cell->name)), cell->output(p.first))); } } } } for (auto &it : net_join_db) { RTLIL::SigBit sig = it.first; 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); 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); log_abort(); } } std::string netname; if (sig == RTLIL::State::S0) netname = "GND_NET"; else if (sig == RTLIL::State::S1) netname = "VCC_NET"; else { netname = log_signal(sig); for (size_t i = 0; i < netname.size(); i++) if (netname[i] == ' ' || netname[i] == '\\') netname.erase(netname.begin() + i--); } *f << stringf(" (net %s (joined\n", EDIF_DEF(netname)); for (auto &ref : it.second) *f << stringf(" %s\n", ref.first); if (sig.wire == NULL) { if (nogndvcc) log_error("Design contains constant nodes (map with \"hilomap\" first).\n"); if (sig == RTLIL::State::S0) *f << stringf(" (portRef %c (instanceRef GND))\n", gndvccy ? 'Y' : 'G'); if (sig == RTLIL::State::S1) *f << stringf(" (portRef %c (instanceRef VCC))\n", gndvccy ? 'Y' : 'P'); } *f << stringf(" )"); if (attr_properties && sig.wire != NULL) for (auto &p : sig.wire->attributes) add_prop(p.first, p.second); *f << stringf("\n )\n"); } for (auto wire : module->wires()) { if (!wire->get_bool_attribute(ID::keep)) continue; for(int i = 0; i < wire->width; i++) { SigBit raw_sig = RTLIL::SigSpec(wire, i); SigBit mapped_sig = sigmap(raw_sig); if (raw_sig == mapped_sig || net_join_db.count(mapped_sig) == 0) continue; std::string netname = log_signal(raw_sig); for (size_t i = 0; i < netname.size(); i++) if (netname[i] == ' ' || netname[i] == '\\') netname.erase(netname.begin() + i--); if (keepmode) { *f << stringf(" (net %s (joined\n", EDIF_DEF(netname)); auto &refs = net_join_db.at(mapped_sig); for (auto &ref : refs) if (ref.second) *f << stringf(" %s\n", ref.first); *f << stringf(" )"); if (attr_properties && raw_sig.wire != NULL) for (auto &p : raw_sig.wire->attributes) add_prop(p.first, p.second); *f << stringf("\n )\n"); } else { log_warning("Ignoring conflicting 'keep' property on net %s. Use -keep to generate the extra net nevertheless.\n", EDIF_DEF(netname)); } } } *f << stringf(" )\n"); *f << stringf(" )\n"); *f << stringf(" )\n"); } *f << stringf(" )\n"); *f << stringf(" (design %s\n", EDIF_DEF(top_module_name)); *f << stringf(" (cellRef %s (libraryRef DESIGN))\n", EDIF_REF(top_module_name)); *f << stringf(" )\n"); *f << stringf(")\n"); } } EdifBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/edif/runtest.py000066400000000000000000000103551520057232300173740ustar00rootroot00000000000000#!/usr/bin/env python3 import os import numpy as np enable_upto = True enable_offset = True enable_hierarchy = True enable_logic = True def make_module(f, modname, width, subs): print("module %s (A, B, C, X, Y, Z);" % modname, file=f) inbits = list() outbits = list() for p in "ABC": offset = np.random.randint(10) if enable_offset else 0 if enable_upto and np.random.randint(2): print(" input [%d:%d] %s;" % (offset, offset+width-1, p), file=f) else: print(" input [%d:%d] %s;" % (offset+width-1, offset, p), file=f) for i in range(offset, offset+width): inbits.append("%s[%d]" % (p, i)) for p in "XYZ": offset = np.random.randint(10) if enable_offset else 0 if enable_upto and np.random.randint(2): print(" output [%d:%d] %s;" % (offset, offset+width-1, p), file=f) else: print(" output [%d:%d] %s;" % (offset+width-1, offset, p), file=f) for i in range(offset, offset+width): outbits.append("%s[%d]" % (p, i)) instidx = 0 subcandidates = list(subs.keys()) while len(outbits) > 0: submod = None if len(subcandidates): submod = np.random.choice(subcandidates) subcandidates.remove(submod) if submod is None or 3*subs[submod] >= len(outbits): for bit in outbits: if enable_logic: print(" assign %s = %s & ~%s;" % (bit, np.random.choice(inbits), np.random.choice(inbits)), file=f) else: print(" assign %s = %s;" % (bit, np.random.choice(inbits)), file=f) break instidx += 1 print(" %s inst%d (" % (submod, instidx), file=f) for p in "ABC": print(" .%s({%s})," % (p, ",".join(np.random.choice(inbits, subs[submod]))), file=f) for p in "XYZ": bits = list(np.random.choice(outbits, subs[submod], False)) for bit in bits: outbits.remove(bit) print(" .%s({%s})%s" % (p, ",".join(bits), "," if p != "Z" else ""), file=f) print(" );", file=f); print("endmodule", file=f) with open("test_top.v", "w") as f: if enable_hierarchy: make_module(f, "sub1", 2, {}) make_module(f, "sub2", 3, {}) make_module(f, "sub3", 4, {}) make_module(f, "sub4", 8, {"sub1": 2, "sub2": 3, "sub3": 4}) make_module(f, "sub5", 8, {"sub1": 2, "sub2": 3, "sub3": 4}) make_module(f, "sub6", 8, {"sub1": 2, "sub2": 3, "sub3": 4}) make_module(f, "top", 32, {"sub4": 8, "sub5": 8, "sub6": 8}) else: make_module(f, "top", 32, {}) os.system("set -x; ../../yosys -p 'synth_xilinx -top top; write_edif -pvector par test_syn.edif' test_top.v") with open("test_syn.tcl", "w") as f: print("read_edif test_syn.edif", file=f) print("link_design", file=f) print("write_verilog -force test_syn.v", file=f) os.system("set -x; vivado -nojournal -nolog -mode batch -source test_syn.tcl") with open("test_tb.v", "w") as f: print("module tb;", file=f) print(" reg [31:0] A, B, C;", file=f) print(" wire [31:0] X, Y, Z;", file=f) print("", file=f) print(" top uut (", file=f) print(" .A(A),", file=f) print(" .B(B),", file=f) print(" .C(C),", file=f) print(" .X(X),", file=f) print(" .Y(Y),", file=f) print(" .Z(Z)", file=f) print(" );", file=f) print("", file=f) print(" initial begin", file=f) for i in range(100): print(" A = 32'h%08x;" % np.random.randint(2**32), file=f) print(" B = 32'h%08x;" % np.random.randint(2**32), file=f) print(" C = 32'h%08x;" % np.random.randint(2**32), file=f) print(" #10;", file=f) print(" $display(\"%x %x %x\", X, Y, Z);", file=f) print(" #10;", file=f) print(" $finish;", file=f) print(" end", file=f) print("endmodule", file=f) os.system("set -x; iverilog -o test_gold test_tb.v test_top.v") os.system("set -x; iverilog -o test_gate test_tb.v test_syn.v ../../techlibs/xilinx/cells_sim.v") os.system("set -x; ./test_gold > test_gold.out") os.system("set -x; ./test_gate > test_gate.out") os.system("set -x; md5sum test_gold.out test_gate.out") yosys-0.65/backends/firrtl/000077500000000000000000000000001520057232300157055ustar00rootroot00000000000000yosys-0.65/backends/firrtl/.gitignore000066400000000000000000000000241520057232300176710ustar00rootroot00000000000000test.fir test_out.v yosys-0.65/backends/firrtl/Makefile.inc000066400000000000000000000000431520057232300201120ustar00rootroot00000000000000 OBJS += backends/firrtl/firrtl.o yosys-0.65/backends/firrtl/firrtl.cc000066400000000000000000001207121520057232300175210ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/log.h" #include "kernel/mem.h" #include #include #include #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN pool used_names; dict namecache; int autoid_counter; typedef unsigned FDirection; static const FDirection FD_NODIRECTION = 0x0; static const FDirection FD_IN = 0x1; static const FDirection FD_OUT = 0x2; static const FDirection FD_INOUT = 0x3; static const int FIRRTL_MAX_DSH_WIDTH_ERROR = 20; // For historic reasons, this is actually one greater than the maximum allowed shift width std::string getFileinfo(const RTLIL::AttrObject *design_entity) { std::string src(design_entity->get_src_attribute()); std::string fileinfo_str = src.empty() ? "" : "@[" + src + "]"; return fileinfo_str; } // Get a port direction with respect to a specific module. FDirection getPortFDirection(IdString id, Module *module) { Wire *wire = module->wires_.at(id); FDirection direction = FD_NODIRECTION; if (wire && wire->port_id) { if (wire->port_input) direction |= FD_IN; if (wire->port_output) direction |= FD_OUT; } return direction; } string next_id() { string new_id; while (1) { new_id = stringf("_%d", autoid_counter++); if (used_names.count(new_id) == 0) break; } used_names.insert(new_id); return new_id; } const char *make_id(IdString id) { if (namecache.count(id) != 0) return namecache.at(id).c_str(); string new_id = log_id(id); for (int i = 0; i < GetSize(new_id); i++) { char &ch = new_id[i]; if ('a' <= ch && ch <= 'z') continue; if ('A' <= ch && ch <= 'Z') continue; if ('0' <= ch && ch <= '9' && i != 0) continue; if ('_' == ch) continue; ch = '_'; } while (used_names.count(new_id) != 0) new_id += '_'; namecache[id] = new_id; used_names.insert(new_id); return namecache.at(id).c_str(); } std::string dump_const_string(const RTLIL::Const &data) { std::string res_str; std::string str = data.decode_string(); for (size_t i = 0; i < str.size(); i++) { if (str[i] == '\n') res_str += "\\n"; else if (str[i] == '\t') res_str += "\\t"; else if (str[i] < 32) res_str += stringf("\\%03o", str[i]); else if (str[i] == '"') res_str += "\\\""; else if (str[i] == '\\') res_str += "\\\\"; else res_str += str[i]; } return res_str; } std::string dump_const(const RTLIL::Const &data) { std::string res_str; // // For debugging purposes to find out how Yosys encodes flags. // res_str += stringf("flags_%x --> ", data.flags); // Real-valued parameter. if (data.flags & RTLIL::CONST_FLAG_REAL) { // Yosys stores real values as strings, so we call the string dumping code. res_str += dump_const_string(data); } // String parameter. else if (data.flags & RTLIL::CONST_FLAG_STRING) { res_str += "\""; res_str += dump_const_string(data); res_str += "\""; } // Numeric (non-real) parameter. else { 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. // // SignedInt // : ( '+' | '-' ) PosInt // ; if (width <= 32) { int32_t int_val = 0; for (int i = 0; i < width; i++) { switch (data[i]) { case State::S0: break; case State::S1: int_val |= (1 << i); break; default: log_error("Unexpected int value\n"); break; } } res_str += stringf("%d", int_val); } else { // If value is larger than 32 bits, then emit a binary representation of // the number as integers are not large enough to contain the result. // There is a caveat to this approach though: // // Note that parameter may be defined as having a fixed width as follows: // // parameter signed [26:0] test_signed; // parameter [26:0] test_unsigned; // parameter signed [40:0] test_signed_large; // // However, if you assign a value on the RHS without specifying the // precision, then yosys considers the value you used as an int and // assigns it a width of 32 bits regardless of the type of the parameter. // // defparam .test_signed = 49; (width = 32, though should be 27 based on definition) // defparam .test_unsigned = 40'd35; (width = 40, though should be 27 based on definition) // defparam .test_signed_large = 40'd12; (width = 40) // // We therefore may lose the precision of the original verilog literal if // it was written without its bitwidth specifier. // Emit binary prefix for string. res_str += "\"b"; // Emit bits. for (int i = width - 1; i >= 0; i--) { log_assert(i < width); switch (data[i]) { case State::S0: res_str += "0"; break; case State::S1: res_str += "1"; break; case State::Sx: res_str += "x"; break; case State::Sz: res_str += "z"; break; case State::Sa: res_str += "-"; break; case State::Sm: res_str += "m"; break; } } res_str += "\""; } } return res_str; } std::string extmodule_name(RTLIL::Cell *cell, RTLIL::Module *mod_instance) { // Since we are creating a custom extmodule for every cell that instantiates // this blackbox, we need to create a custom name for it. We just use the // name of the blackbox itself followed by the name of the cell. const std::string cell_name = std::string(make_id(cell->name)); const std::string blackbox_name = std::string(make_id(mod_instance->name)); const std::string extmodule_name = blackbox_name + "_" + cell_name; return extmodule_name; } /** * Emits a parameterized extmodule. Instance parameters are obtained from * ''cell'' as it represents the instantiation of the blackbox defined by * ''mod_instance'' and therefore contains all its instance parameters. */ void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream &f) { const std::string indent = " "; const std::string blackbox_name = std::string(make_id(mod_instance->name)); const std::string exported_name = extmodule_name(cell, mod_instance); // We use the cell's fileinfo for this extmodule as its parameters come from // the cell and not from the module itself (the module contains default // parameters, not the instance-specific ones we're using to emit the // extmodule). const std::string extmoduleFileinfo = getFileinfo(cell); // Emit extmodule header. f << stringf(" extmodule %s: %s\n", exported_name, extmoduleFileinfo); // Emit extmodule ports. for (auto wire : mod_instance->wires()) { const auto wireName = make_id(wire->name); const std::string wireFileinfo = getFileinfo(wire); if (wire->port_input && wire->port_output) { log_error("Module port %s.%s is inout!\n", log_id(mod_instance), log_id(wire)); } const std::string portDecl = stringf("%s%s %s: UInt<%d> %s\n", indent.c_str(), wire->port_input ? "input" : "output", wireName, wire->width, wireFileinfo.c_str() ); f << portDecl; } // 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, blackbox_name); // Emit extmodule generic parameters. for (const auto &p : cell->parameters) { const RTLIL::IdString p_id = p.first; const RTLIL::Const p_value = p.second; std::string param_name(p_id.c_str()); const std::string param_value = dump_const(p_value); // Remove backslashes from parameters as these come from the internal RTLIL // naming scheme, but should not exist in the emitted firrtl blackboxes. // When firrtl is converted to verilog and given to downstream synthesis // tools, these tools expect to find blackbox names and parameters as they // were originally defined, i.e. without the extra RTLIL naming conventions. param_name.erase( std::remove(param_name.begin(), param_name.end(), '\\'), param_name.end() ); f << stringf("%sparameter %s = %s\n", indent, param_name, param_value); } f << "\n"; } /** * Emits extmodules for every instantiated blackbox in the design. * * RTLIL stores instance parameters at the cell's instantiation location. * However, firrtl does not support module parameterization (everything is * already elaborated). Firrtl instead supports external modules (extmodule), * i.e. blackboxes that are defined by verilog and which have no body in * firrtl itself other than the declaration of the blackboxes ports and * parameters. * * Furthermore, firrtl does not support parameterization (even of extmodules) * at a module's instantiation location and users must instead declare * different extmodules with different instance parameters in the extmodule * definition itself. * * This function goes through the design to identify all RTLIL blackboxes * and emit parameterized extmodules with a unique name for each of them. The * name that's given to the extmodule is * * _ * * Beware that it is therefore necessary for users to replace "parameterized" * instances in the RTLIL sense with these custom extmodules for the firrtl to * be valid. */ void emit_elaborated_extmodules(RTLIL::Design *design, std::ostream &f) { for (auto module : design->modules()) { for (auto cell : module->cells()) { // Is this cell a module instance? bool cellIsModuleInstance = cell->type[0] != '$'; if (cellIsModuleInstance) { // Find the module corresponding to this instance. 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); return; } bool modIsBlackbox = modInstance->get_blackbox_attribute(); if (modIsBlackbox) { emit_extmodule(cell, modInstance, f); } } } } } struct FirrtlWorker { Module *module; std::ostream &f; dict> reverse_wire_map; string unconn_id; RTLIL::Design *design; std::string indent; void register_reverse_wire_map(string id, SigSpec sig) { for (int i = 0; i < GetSize(sig); i++) reverse_wire_map[sig[i]] = make_pair(id, i); } FirrtlWorker(Module *module, std::ostream &f, RTLIL::Design *theDesign) : module(module), f(f), design(theDesign), indent(" ") { } static string make_expr(const SigSpec &sig) { string expr; for (auto chunk : sig.chunks()) { string new_expr; if (chunk.wire == nullptr) { std::vector bits = chunk.data; new_expr = stringf("UInt<%d>(\"h", GetSize(bits)); while (GetSize(bits) % 4 != 0) bits.push_back(State::S0); for (int i = GetSize(bits)-4; i >= 0; i -= 4) { int val = 0; if (bits[i+0] == State::S1) val += 1; if (bits[i+1] == State::S1) val += 2; if (bits[i+2] == State::S1) val += 4; if (bits[i+3] == State::S1) val += 8; new_expr.push_back(val < 10 ? '0' + val : 'a' + val - 10); } new_expr += "\")"; } else if (chunk.offset == 0 && chunk.width == chunk.wire->width) { new_expr = make_id(chunk.wire->name); } else { string wire_id = make_id(chunk.wire->name); new_expr = stringf("bits(%s, %d, %d)", wire_id, chunk.offset + chunk.width - 1, chunk.offset); } if (expr.empty()) expr = new_expr; else expr = "cat(" + new_expr + ", " + expr + ")"; } return expr; } std::string fid(RTLIL::IdString internal_id) { return make_id(internal_id); } std::string cellname(RTLIL::Cell *cell) { return fid(cell->name).c_str(); } void process_instance(RTLIL::Cell *cell, vector &wire_exprs) { std::string cell_type = fid(cell->type); std::string instanceOf; // If this is a parameterized module, its parent module is encoded in the cell type if (cell->type.begins_with("$paramod")) { log_assert(cell->has_attribute(ID::hdlname)); instanceOf = cell->get_string_attribute(ID::hdlname); } else { instanceOf = cell_type; } std::string cell_name = cellname(cell); std::string cell_name_comment; if (cell_name != fid(cell->name)) cell_name_comment = " /* " + fid(cell->name) + " */ "; else cell_name_comment = ""; // Find the module corresponding to this instance. auto instModule = design->module(cell->type); // If there is no instance for this, just return. if (instModule == NULL) { log_warning("No instance for %s.%s\n", cell_type, cell_name); return; } // If the instance is that of a blackbox, use the modified extmodule name // that contains per-instance parameterizations. These instances were // emitted earlier in the firrtl backend. const std::string instanceName = instModule->get_blackbox_attribute() ? extmodule_name(cell, instModule) : instanceOf; std::string cellFileinfo = getFileinfo(cell); 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) { const SigSpec &secondSig = it->second; const std::string firstName = cell_name + "." + make_id(it->first); const std::string secondExpr = make_expr(secondSig); // Find the direction for this port. FDirection dir = getPortFDirection(it->first, instModule); std::string sourceExpr, sinkExpr; const SigSpec *sinkSig = nullptr; switch (dir) { case FD_INOUT: 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; sinkExpr = secondExpr; sinkSig = &secondSig; break; case FD_NODIRECTION: 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, 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, 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, sinkExpr, sourceExpr, cellFileinfo)); } } } wire_exprs.push_back(stringf("\n")); } // Given an expression for a shift amount, and a maximum width, // generate the FIRRTL expression for equivalent dynamic shift taking into account FIRRTL shift semantics. std::string gen_dshl(const string b_expr, const int b_width) { string result = b_expr; if (b_width >= FIRRTL_MAX_DSH_WIDTH_ERROR) { 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); vector port_decls, wire_decls, mem_exprs, cell_exprs, wire_exprs; std::vector memories = Mem::get_all_memories(module); for (auto &mem : memories) mem.narrow(); for (auto wire : module->wires()) { const auto wireName = make_id(wire->name); std::string wireFileinfo = getFileinfo(wire); // If a wire has initial data, issue a warning since FIRRTL doesn't currently support it. if (wire->attributes.count(ID::init)) { log_warning("Initial value (%s) for (%s.%s) not supported\n", wire->attributes.at(ID::init).as_string().c_str(), log_id(module), log_id(wire)); } if (wire->port_id) { 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, wire->port_input ? "input" : "output", wireName, wire->width, wireFileinfo.c_str())); } else { wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, wireName, wire->width, wireFileinfo)); } } for (auto cell : module->cells()) { Const ndef(0, 0); // Is this cell is a module instance? if (module->design->module(cell->type)) { process_instance(cell, wire_exprs); continue; } // Not a module instance. Set up cell properties bool extract_y_bits = false; // Assume no extraction of final bits will be required. int a_width = cell->parameters.at(ID::A_WIDTH, ndef).as_int(); // The width of "A" int b_width = cell->parameters.at(ID::B_WIDTH, ndef).as_int(); // The width of "A" const int y_width = cell->parameters.at(ID::Y_WIDTH, ndef).as_int(); // The width of the result const bool a_signed = cell->parameters.at(ID::A_SIGNED, ndef).as_bool(); const bool b_signed = cell->parameters.at(ID::B_SIGNED, ndef).as_bool(); bool firrtl_is_signed = a_signed; // The result is signed (subsequent code may change this). int firrtl_width = 0; string primop; bool always_uint = false; string y_id = make_id(cell->name); std::string cellFileinfo = getFileinfo(cell); 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, y_id, y_width, cellFileinfo)); if (a_signed) { a_expr = "asSInt(" + a_expr + ")"; } // 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, y_width); } // Assume the FIRRTL width is a single bit. firrtl_width = 1; if (cell->type.in(ID($not), ID($_NOT_))) primop = "not"; else if (cell->type == ID($neg)) { primop = "neg"; firrtl_is_signed = true; // Result of "neg" is signed (an SInt). firrtl_width = a_width; } else if (cell->type == ID($logic_not)) { primop = "eq"; 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); } 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, a_signed ? 'S' : 'U', a_width); } string expr = stringf("%s(%s)", primop, a_expr); if ((firrtl_is_signed && !always_uint)) expr = stringf("asUInt(%s)", expr); 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; } 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_), ID($eq), ID($eqx), ID($gt), ID($ge), ID($lt), ID($le), ID($ne), ID($nex), ID($shr), ID($sshr), ID($sshl), ID($shl), ID($logic_and), ID($logic_or), ID($pow))) { 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, 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, y_width); a_width = y_width; } } // Shift amount is always unsigned, and needn't be padded to result width, // otherwise, we need to cast the b_expr appropriately if (b_signed && !cell->type.in(ID($shr), ID($sshr), ID($shl), ID($sshl), ID($pow))) { 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, y_width); b_width = y_width; } } // For the arithmetic ops, expand operand widths to result widths befor performing the operation. // This corresponds (according to iverilog) to what verilog compilers implement. 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, y_width); a_width = y_width; } if (b_width < y_width) { b_expr = stringf("pad(%s, %d)", b_expr, y_width); b_width = y_width; } } // Assume the FIRRTL width is the width of "A" firrtl_width = a_width; auto a_sig = cell->getPort(ID::A); if (cell->type == ID($add)) { primop = "add"; firrtl_is_signed = a_signed | b_signed; firrtl_width = max(a_width, b_width); } else if (cell->type == ID($sub)) { primop = "sub"; firrtl_is_signed = true; int a_widthInc = (!a_signed && b_signed) ? 2 : (a_signed && !b_signed) ? 1 : 0; int b_widthInc = (a_signed && !b_signed) ? 2 : (!a_signed && b_signed) ? 1 : 0; firrtl_width = max(a_width + a_widthInc, b_width + b_widthInc); } else if (cell->type == ID($mul)) { primop = "mul"; firrtl_is_signed = a_signed | b_signed; firrtl_width = a_width + b_width; } else if (cell->type == ID($div)) { primop = "div"; firrtl_is_signed = a_signed | b_signed; firrtl_width = a_width; } else if (cell->type == ID($mod)) { // "rem" = truncating modulo primop = "rem"; firrtl_width = min(a_width, b_width); } else if (cell->type.in(ID($and), ID($_AND_))) { primop = "and"; always_uint = true; firrtl_width = max(a_width, b_width); } else if (cell->type.in(ID($or), ID($_OR_))) { primop = "or"; always_uint = true; firrtl_width = max(a_width, b_width); } else if (cell->type.in(ID($xor), ID($_XOR_))) { primop = "xor"; always_uint = true; firrtl_width = max(a_width, b_width); } else if (cell->type == ID($xnor)) { primop = "xnor"; always_uint = true; firrtl_width = max(a_width, b_width); } else if ((cell->type == ID($eq)) || (cell->type == ID($eqx))) { primop = "eq"; always_uint = true; firrtl_width = 1; } else if ((cell->type == ID($ne)) || (cell->type == ID($nex))) { primop = "neq"; always_uint = true; firrtl_width = 1; } else if (cell->type == ID($gt)) { primop = "gt"; always_uint = true; firrtl_width = 1; } else if (cell->type == ID($ge)) { primop = "geq"; always_uint = true; firrtl_width = 1; } else if (cell->type == ID($lt)) { primop = "lt"; always_uint = true; firrtl_width = 1; } else if (cell->type == ID($le)) { primop = "leq"; always_uint = true; firrtl_width = 1; } else if ((cell->type == ID($shl)) || (cell->type == ID($sshl))) { // FIRRTL will widen the result (y) by the amount of the shift. // We'll need to offset this by extracting the un-widened portion as Verilog would do. extract_y_bits = true; // Is the shift amount constant? auto b_sig = cell->getPort(ID::B); if (b_sig.is_fully_const()) { primop = "shl"; int shift_amount = b_sig.as_int(); b_expr = std::to_string(shift_amount); firrtl_width = a_width + shift_amount; } else { primop = "dshl"; // Convert from FIRRTL left shift semantics. b_expr = gen_dshl(b_expr, b_width); firrtl_width = a_width + (1 << b_width) - 1; } } else if ((cell->type == ID($shr)) || (cell->type == ID($sshr))) { // We don't need to extract a specific range of bits. extract_y_bits = false; // Is the shift amount constant? auto b_sig = cell->getPort(ID::B); if (b_sig.is_fully_const()) { primop = "shr"; int shift_amount = b_sig.as_int(); b_expr = std::to_string(shift_amount); firrtl_width = max(1, a_width - shift_amount); } else { primop = "dshr"; firrtl_width = a_width; } // We'll need to do some special fixups if the source (and thus result) is signed. if (firrtl_is_signed) { // If this is a "logical" shift right, pretend the source is unsigned. if (cell->type == ID($shr)) { a_expr = "asUInt(" + a_expr + ")"; } } } else if ((cell->type == ID($logic_and))) { primop = "and"; a_expr = "neq(" + a_expr + ", UInt(0))"; b_expr = "neq(" + b_expr + ", UInt(0))"; always_uint = true; firrtl_width = 1; } else if ((cell->type == ID($logic_or))) { primop = "or"; a_expr = "neq(" + a_expr + ", UInt(0))"; b_expr = "neq(" + b_expr + ", UInt(0))"; always_uint = true; firrtl_width = 1; } else if ((cell->type == ID($pow))) { if (a_sig.is_fully_const() && a_sig.as_int() == 2) { // We'll convert this to a shift. To simplify things, change the a_expr to "1" // so we can use b_expr directly as a shift amount. // Only support 2 ** N (i.e., shift left) // FIRRTL will widen the result (y) by the amount of the shift. // We'll need to offset this by extracting the un-widened portion as Verilog would do. a_expr = firrtl_is_signed ? "SInt(1)" : "UInt(1)"; extract_y_bits = true; // Is the shift amount constant? auto b_sig = cell->getPort(ID::B); if (b_sig.is_fully_const()) { primop = "shl"; int shiftAmount = b_sig.as_int(); if (shiftAmount < 0) { log_error("Negative power exponent - %d: %s.%s\n", shiftAmount, log_id(module), log_id(cell)); } b_expr = std::to_string(shiftAmount); firrtl_width = a_width + shiftAmount; } else { primop = "dshl"; // Convert from FIRRTL left shift semantics. b_expr = gen_dshl(b_expr, b_width); firrtl_width = a_width + (1 << b_width) - 1; } } else { log_error("Non power 2: %s.%s\n", log_id(module), log_id(cell)); } } auto it = cell->parameters.find(ID::B_SIGNED); if (it == cell->parameters.end() || !it->second.as_bool()) { b_expr = "asUInt(" + b_expr + ")"; } string expr; // Deal with $xnor == ~^ (not xor) if (primop == "xnor") { expr = stringf("not(xor(%s, %s))", a_expr, b_expr); } else { 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, y_width - 1); } else if (firrtl_is_signed && (firrtl_width + 1) < y_width) { expr = stringf("pad(%s, %d)", expr, y_width); } if ((firrtl_is_signed && !always_uint)) expr = stringf("asUInt(%s)", expr); 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; } if (cell->type.in(ID($mux), ID($_MUX_))) { auto it = cell->parameters.find(ID::WIDTH); int width = it == cell->parameters.end()? 1 : it->second.as_int(); 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, y_id, width, cellFileinfo)); string expr = stringf("mux(%s, %s, %s)", s_expr, b_expr, a_expr); 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; } if (cell->is_mem_cell()) { // Will be handled below, as part of a Mem. continue; } if (cell->type.in(ID($dff))) { bool clkpol = cell->parameters.at(ID::CLK_POLARITY).as_bool(); if (clkpol == false) log_error("Negative edge clock on FF %s.%s.\n", log_id(module), log_id(cell)); int width = cell->parameters.at(ID::WIDTH).as_int(); 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, y_id, width, clk_expr, cellFileinfo)); 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; } if (cell->type == ID($shiftx)) { // assign y = a[b +: y_width]; // We'll extract the correct bits as part of the primop. 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, y_id, y_width)); if (cell->getParam(ID::B_SIGNED).as_bool()) { // Use validif to constrain the selection (test the sign bit) auto b_string = b_expr.c_str(); 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, b_expr); cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } if (cell->type == ID($shift)) { // assign y = a >> b; // where b may be negative string a_expr = make_expr(cell->getPort(ID::A)); 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, 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, 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, b_string); } cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr)); register_reverse_wire_map(y_id, cell->getPort(ID::Y)); continue; } if (cell->type == ID($pos)) { // assign y = a; // printCell(cell); string a_expr = make_expr(cell->getPort(ID::A)); // 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, y_width); } 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; } if (cell->type == ID($scopeinfo)) continue; log_error("Cell type not supported: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell)); } for (auto &mem : memories) { string mem_id = make_id(mem.memid); Const init_data = mem.get_init_data(); if (!init_data.is_fully_undef()) log_error("Memory with initialization data: %s.%s\n", log_id(module), log_id(mem.memid)); if (mem.start_offset != 0) log_error("Memory with nonzero offset: %s.%s\n", log_id(module), log_id(mem.memid)); 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, i)); if (port.clk_enable) log_error("Clocked read port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid)); std::ostringstream rpe; string addr_expr = make_expr(port.addr); string ena_expr = make_expr(State::S1); string clk_expr = make_expr(State::S0); 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), 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, i)); if (!port.clk_enable) log_error("Unclocked write port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid)); if (!port.clk_polarity) log_error("Negedge write port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid)); for (int i = 1; i < GetSize(port.en); i++) if (port.en[0] != port.en[i]) log_error("Complex write enable on port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid)); std::ostringstream wpe; string data_expr = make_expr(port.data); string addr_expr = make_expr(port.addr); 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, 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); 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++) me << stringf(" reader => r%d\n", i); for (int i = 0; i < GetSize(mem.wr_ports); i++) me << stringf(" writer => w%d\n", i); me << stringf(" read-latency => %d\n", 0); me << stringf(" write-latency => %d\n", 1); me << stringf(" read-under-write => undefined\n"); mem_exprs.push_back(me.str()); } for (auto conn : module->connections()) { string y_id = next_id(); int y_width = GetSize(conn.first); string expr = make_expr(conn.second); 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); } for (auto wire : module->wires()) { string expr; std::string wireFileinfo = getFileinfo(wire); if (wire->port_input) continue; int cursor = 0; bool is_valid = false; bool make_unconn_id = false; while (cursor < wire->width) { int chunk_width = 1; string new_expr; SigBit start_bit(wire, cursor); if (reverse_wire_map.count(start_bit)) { pair start_map = reverse_wire_map.at(start_bit); while (cursor+chunk_width < wire->width) { SigBit stop_bit(wire, cursor+chunk_width); if (reverse_wire_map.count(stop_bit) == 0) break; pair stop_map = reverse_wire_map.at(stop_bit); stop_map.second -= chunk_width; if (start_map != stop_map) break; chunk_width++; } new_expr = stringf("bits(%s, %d, %d)", start_map.first, start_map.second + chunk_width - 1, start_map.second); is_valid = true; } else { if (unconn_id.empty()) { unconn_id = next_id(); make_unconn_id = true; } new_expr = unconn_id; } if (expr.empty()) expr = new_expr; else expr = "cat(" + new_expr + ", " + expr + ")"; cursor += chunk_width; } if (is_valid) { if (make_unconn_id) { 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, unconn_id)); } 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(); } // `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, make_id(wire->name))); } } for (auto str : port_decls) f << str; f << stringf("\n"); for (auto str : wire_decls) f << str; f << stringf("\n"); for (auto str : mem_exprs) f << str; f << stringf("\n"); for (auto str : cell_exprs) f << str; f << stringf("\n"); for (auto str : wire_exprs) f << str; f << stringf("\n"); } void run() { emit_module(); } }; struct FirrtlBackend : public Backend { FirrtlBackend() : Backend("firrtl", "write design to a FIRRTL file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_firrtl [options] [filename]\n"); log("\n"); log("Write a FIRRTL netlist of the current design.\n"); log("The following commands are executed by this command:\n"); log(" pmuxtree\n"); log(" bmuxmap\n"); log(" demuxmap\n"); log(" bwmuxmap\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { size_t argidx = args.size(); // We aren't expecting any arguments. // If we weren't explicitly passed a filename, use the last argument (if it isn't a flag). if (filename == "") { if (argidx > 0 && args[argidx - 1][0] != '-') { // extra_args and friends need to see this argument. argidx -= 1; filename = args[argidx]; } } extra_args(f, filename, args, argidx); log_header(design, "Executing FIRRTL backend.\n"); log_push(); Pass::call(design, "pmuxtree"); Pass::call(design, "bmuxmap"); 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 = nullptr; Module *last = nullptr; // Generate module and wire names. for (auto module : design->modules()) { make_id(module->name); last = module; if (top == nullptr && module->get_bool_attribute(ID::top)) { top = module; } for (auto wire : module->wires()) if (wire->port_id) make_id(wire->name); } if (top == nullptr) top = last; if (!top) 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); emit_elaborated_extmodules(design, *f); // Emit non-blackbox modules. for (auto module : design->modules()) { if (!module->get_blackbox_attribute()) { FirrtlWorker worker(module, *f, design); worker.run(); } } used_names.clear(); namecache.clear(); autoid_counter = 0; } } FirrtlBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/firrtl/test.sh000066400000000000000000000006231520057232300172210ustar00rootroot00000000000000#!/usr/bin/env bash set -ex cd ../../ make cd backends/firrtl ../../yosys -q -p 'prep -nordff; write_firrtl test.fir' $1 firrtl -i test.fir -o test_out.v -ll Info ../../yosys -p " read_verilog $1 rename Top gold read_verilog test_out.v rename Top gate prep memory_map miter -equiv -flatten gold gate miter hierarchy -top miter sat -verify -prove trigger 0 -set-init-zero -seq 10 miter " yosys-0.65/backends/firrtl/test.v000066400000000000000000000034021520057232300170520ustar00rootroot00000000000000module test( input clk, wen, input [7:0] uns, input signed [7:0] a, b, input signed [23:0] c, input signed [2:0] sel, output [15:0] s, d, y, z, u, q, p, mul, div, mod, mux, And, Or, Xor, eq, neq, gt, lt, geq, leq, eqx, shr, sshr, shl, sshl, Land, Lor, Lnot, Not, Neg, pos, Andr, Orr, Xorr, Xnorr, Reduce_bool, output [7:0] PMux ); //initial begin //$display("shr = %b", shr); //end assign s = a+{b[6:2], 2'b1}; assign d = a-b; assign y = x; assign z[7:0] = s+d; assign z[15:8] = s-d; assign p = a & b | x; assign mul = a * b; assign div = a / b; assign mod = a % b; assign mux = x[0] ? a : b; assign And = a & b; assign Or = a | b; assign Xor = a ^ b; assign Not = ~a; assign Neg = -a; assign eq = a == b; assign neq = a != b; assign gt = a > b; assign lt = a < b; assign geq = a >= b; assign leq = a <= b; assign eqx = a === b; assign shr = a >> b; //0111111111000000 assign sshr = a >>> b; assign shl = a << b; assign sshl = a <<< b; assign Land = a && b; assign Lor = a || b; assign Lnot = !a; assign pos = $signed(uns); assign Andr = &a; assign Orr = |a; assign Xorr = ^a; assign Xnorr = ~^a; always @* if(!a) begin Reduce_bool = a; end else begin Reduce_bool = b; end //always @(sel or c or a) // begin // case (sel) // 3'b000: PMux = a; // 3'b001: PMux = c[7:0]; // 3'b010: PMux = c[15:8]; // 3'b100: PMux = c[23:16]; // endcase // end endmodule yosys-0.65/backends/functional/000077500000000000000000000000001520057232300165455ustar00rootroot00000000000000yosys-0.65/backends/functional/Makefile.inc000066400000000000000000000002371520057232300207570ustar00rootroot00000000000000OBJS += backends/functional/cxx.o OBJS += backends/functional/smtlib.o OBJS += backends/functional/smtlib_rosette.o OBJS += backends/functional/test_generic.o yosys-0.65/backends/functional/cxx.cc000066400000000000000000000262051520057232300176630ustar00rootroot00000000000000/* * 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 yosys-0.65/backends/functional/cxx_runtime/000077500000000000000000000000001520057232300211125ustar00rootroot00000000000000yosys-0.65/backends/functional/cxx_runtime/sim.h000066400000000000000000000254471520057232300220670ustar00rootroot00000000000000/* * 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 yosys-0.65/backends/functional/smtlib.cc000066400000000000000000000266631520057232300203630ustar00rootroot00000000000000/* * 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 yosys-0.65/backends/functional/smtlib_rosette.cc000066400000000000000000000333261520057232300221220ustar00rootroot00000000000000/* * 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 yosys-0.65/backends/functional/test_generic.cc000066400000000000000000000131131520057232300215260ustar00rootroot00000000000000/* * 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 yosys-0.65/backends/intersynth/000077500000000000000000000000001520057232300166125ustar00rootroot00000000000000yosys-0.65/backends/intersynth/Makefile.inc000066400000000000000000000000531520057232300210200ustar00rootroot00000000000000 OBJS += backends/intersynth/intersynth.o yosys-0.65/backends/intersynth/intersynth.cc000066400000000000000000000176121520057232300213370ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/newcelltypes.h" #include "kernel/log.h" #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN static std::string netname(std::set &conntypes_code, std::set &celltypes_code, std::set &constcells_code, RTLIL::SigSpec sig) { if (!sig.is_fully_const() && !sig.is_wire()) log_error("Can't export composite or non-word-wide signal %s.\n", log_signal(sig)); conntypes_code.insert(stringf("conntype b%d %d 2 %d\n", sig.size(), sig.size(), sig.size())); if (sig.is_fully_const()) { celltypes_code.insert(stringf("celltype CONST_%d b%d *CONST cfg:%d VALUE\n", sig.size(), sig.size(), sig.size())); constcells_code.insert(stringf("node CONST_%d_0x%x CONST_%d CONST CONST_%d_0x%x VALUE 0x%x\n", sig.size(), sig.as_int(), sig.size(), sig.size(), sig.as_int(), sig.as_int())); return stringf("CONST_%d_0x%x", sig.size(), sig.as_int()); } return RTLIL::unescape_id(sig.as_wire()->name); } struct IntersynthBackend : public Backend { IntersynthBackend() : Backend("intersynth", "write design to InterSynth netlist file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_intersynth [options] [filename]\n"); log("\n"); log("Write the current design to an 'intersynth' netlist file. InterSynth is\n"); log("a tool for Coarse-Grain Example-Driven Interconnect Synthesis.\n"); log("\n"); log(" -notypes\n"); log(" do not generate celltypes and conntypes commands. i.e. just output\n"); log(" the netlists. this is used for postsilicon synthesis.\n"); log("\n"); log(" -lib \n"); log(" Use the specified library file for determining whether cell ports are\n"); log(" inputs or outputs. This option can be used multiple times to specify\n"); log(" more than one library.\n"); log("\n"); log(" -selected\n"); log(" only write selected modules. modules must be selected entirely or\n"); log(" not at all.\n"); log("\n"); log("http://bygone.clairexen.net/intersynth/\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { log_header(design, "Executing INTERSYNTH backend.\n"); log_push(); std::vector libfiles; std::vector libs; bool flag_notypes = false; bool selected = false; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-notypes") { flag_notypes = true; continue; } if (args[argidx] == "-lib" && argidx+1 < args.size()) { libfiles.push_back(args[++argidx]); continue; } if (args[argidx] == "-selected") { selected = true; continue; } break; } extra_args(f, filename, args, argidx); 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); 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); } if (libs.size() > 0) log_header(design, "Continuing INTERSYNTH backend.\n"); std::set conntypes_code, celltypes_code; std::string netlists_code; NewCellTypes ct(design); for (auto lib : libs) ct.setup_design(lib); for (auto module : design->modules()) { SigMap sigmap(module); if (module->get_blackbox_attribute()) continue; if (module->memories.size() == 0 && module->processes.size() == 0 && module->cells().size() == 0) continue; if (selected && !design->selected_whole_module(module->name)) { if (design->selected_module(module->name)) log_cmd_error("Can't handle partially selected module %s!\n", log_id(module->name)); continue; } log("Generating netlist %s.\n", log_id(module->name)); if (module->memories.size() != 0 || module->processes.size() != 0) log_error("Can't generate a netlist for a module with unprocessed memories or processes!\n"); std::set constcells_code; netlists_code += stringf("# Netlist of module %s\n", log_id(module->name)); netlists_code += stringf("netlist %s\n", log_id(module->name)); // Module Ports: "std::set celltypes_code" prevents duplicate top level ports for (auto wire : module->wires()) { if (wire->port_input || wire->port_output) { celltypes_code.insert(stringf("celltype !%s b%d %sPORT\n" "%s %s %d %s PORT\n", log_id(wire->name), wire->width, wire->port_input ? "*" : "", wire->port_input ? "input" : "output", log_id(wire->name), wire->width, log_id(wire->name))); netlists_code += stringf("node %s %s PORT %s\n", log_id(wire->name), log_id(wire->name), netname(conntypes_code, celltypes_code, constcells_code, sigmap(wire)).c_str()); } } // Submodules: "std::set celltypes_code" prevents duplicate cell types for (auto cell : module->cells()) { std::string celltype_code, node_code; if (!ct.cell_known(cell->type)) log_error("Found unknown cell type %s in module!\n", log_id(cell->type)); celltype_code = stringf("celltype %s", log_id(cell->type)); node_code = stringf("node %s %s", log_id(cell->name), log_id(cell->type)); for (auto &port : cell->connections()) { RTLIL::SigSpec sig = sigmap(port.second); 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)); } } for (auto ¶m : cell->parameters) { 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.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()); } celltypes_code.insert(celltype_code + "\n"); netlists_code += node_code + "\n"; } if (constcells_code.size() > 0) netlists_code += "# constant cells\n"; for (auto code : constcells_code) netlists_code += code; netlists_code += "\n"; } if (!flag_notypes) { *f << stringf("### Connection Types\n"); for (auto code : conntypes_code) *f << stringf("%s", code); *f << stringf("\n### Cell Types\n"); for (auto code : celltypes_code) *f << stringf("%s", code); } *f << stringf("\n### Netlists\n"); *f << stringf("%s", netlists_code); for (auto lib : libs) delete lib; log_pop(); } } IntersynthBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/jny/000077500000000000000000000000001520057232300152035ustar00rootroot00000000000000yosys-0.65/backends/jny/Makefile.inc000066400000000000000000000000341520057232300174100ustar00rootroot00000000000000 OBJS += backends/jny/jny.o yosys-0.65/backends/jny/jny.cc000066400000000000000000000430301520057232300163120ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Aki "lethalbit" Van Ness * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/log.h" #include #include #include #include #include #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct JnyWriter { private: std::ostream &f; bool _use_selection; // XXX(aki): TODO: this needs to be updated to us // dict and then coalesce_cells needs to be updated // but for now for the PoC this looks to be sufficient std::unordered_map> _cells{}; bool _include_connections; bool _include_attributes; bool _include_properties; string escape_string(string str) { std::string newstr; auto itr = str.begin(); for(; itr != str.end(); ++itr) { switch (*itr) { case '\\': { newstr += "\\\\"; break; } case '\n': { newstr += "\\n"; break; } case '\f': { newstr += "\\f"; break; } case '\t': { newstr += "\\t"; break; } case '\r': { newstr += "\\r"; break; } case '\"': { newstr += "\\\""; break; } case '\b': { newstr += "\\b"; break; } default: { newstr += *itr; } } } return newstr; } // XXX(aki): I know this is far from ideal but i'm out of spoons and cant focus so // it'll have to do for now, void coalesce_cells(Module* mod) { _cells.clear(); for (auto cell : mod->cells()) { const auto cell_type = escape_string(RTLIL::unescape_id(cell->type)); if (_cells.find(cell_type) == _cells.end()) _cells.emplace(cell_type, std::vector()); _cells.at(cell_type).push_back(cell); } } // XXX(aki): this is a lazy way to do this i know,,, std::string gen_indent(const uint16_t level) { std::stringstream s; for (uint16_t i = 0; i <= level; ++i) { s << " "; } return s.str(); } public: JnyWriter(std::ostream &f, bool use_selection, bool connections, bool attributes, bool properties) noexcept: f(f), _use_selection(use_selection), _include_connections(connections), _include_attributes(attributes), _include_properties(properties) { } void write_metadata(Design *design, uint16_t indent_level = 0, std::string invk = "") { log_assert(design != nullptr); design->sort(); f << "{\n"; 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\": ["; size_t fnum{0}; if (_include_connections) { ++fnum; f << "\"connections\""; } if (_include_attributes) { if (fnum > 0) f << ", "; ++fnum; f << "\"attributes\""; } if (_include_properties) { if (fnum > 0) f << ", "; ++fnum; f << "\"properties\""; } f << "],\n"; f << " \"modules\": [\n"; bool first{true}; for (auto mod : _use_selection ? design->selected_modules() : design->modules()) { if (!first) f << ",\n"; write_module(mod, indent_level + 2); first = false; } f << "\n"; f << " ]\n"; f << "}\n"; } void write_sigspec(const RTLIL::SigSpec& sig, uint16_t indent_level = 0) { const auto _indent = gen_indent(indent_level); f << _indent << " {\n"; f << _indent << " \"width\": \"" << sig.size() << "\",\n"; f << _indent << " \"type\": \""; if (sig.is_wire()) { f << "wire"; } else if (sig.is_chunk()) { f << "chunk"; } else if (sig.is_bit()) { f << "bit"; } else { f << "unknown"; } f << "\",\n"; f << _indent << " \"const\": "; if (sig.has_const()) { f << "true"; } else { f << "false"; } f << "\n"; f << _indent << " }"; } void write_mod_conn(const std::pair& conn, uint16_t indent_level = 0) { const auto _indent = gen_indent(indent_level); f << _indent << " {\n"; f << _indent << " \"signals\": [\n"; write_sigspec(conn.first, indent_level + 2); f << ",\n"; write_sigspec(conn.second, indent_level + 2); f << "\n"; f << _indent << " ]\n"; f << _indent << " }"; } void write_cell_conn(const std::pair& sig, uint16_t indent_level = 0) { const auto _indent = gen_indent(indent_level); f << _indent << " {\n"; f << _indent << " \"name\": \"" << escape_string(RTLIL::unescape_id(sig.first)) << "\",\n"; f << _indent << " \"signals\": [\n"; write_sigspec(sig.second, indent_level + 2); f << "\n"; f << _indent << " ]\n"; f << _indent << " }"; } void write_module(Module* mod, uint16_t indent_level = 0) { log_assert(mod != nullptr); coalesce_cells(mod); const auto _indent = gen_indent(indent_level); f << _indent << "{\n"; f << stringf(" %s\"name\": \"%s\",\n", _indent, escape_string(RTLIL::unescape_id(mod->name))); f << _indent << " \"cell_sorts\": [\n"; bool first_sort{true}; for (auto& sort : _cells) { if (!first_sort) f << ",\n"; write_cell_sort(sort, indent_level + 2); first_sort = false; } f << "\n"; f << _indent << " ]"; if (_include_connections) { f << ",\n" << _indent << " \"connections\": [\n"; bool first_conn{true}; for (const auto& conn : mod->connections()) { if (!first_conn) f << ",\n"; write_mod_conn(conn, indent_level + 2); first_conn = false; } f << _indent << " ]"; } if (_include_attributes) { f << ",\n" << _indent << " \"attributes\": {\n"; write_prams(mod->attributes, indent_level + 2); f << "\n"; f << _indent << " }"; } f << "\n" << _indent << "}"; } void write_cell_ports(RTLIL::Cell* port_cell, uint64_t indent_level = 0) { const auto _indent = gen_indent(indent_level); bool first_port{true}; for (auto con : port_cell->connections()) { if (!first_port) f << ",\n"; f << _indent << " {\n"; 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"; if (port_cell->input(con.first)) f << "o"; f << "\",\n"; if (con.second.size() == 1) f << _indent << " \"range\": [0, 0]\n"; else f << stringf(" %s\"range\": [%d, %d]\n", _indent, con.second.size(), 0); f << _indent << " }"; first_port = false; } f << "\n"; } void write_cell_sort(std::pair>& sort, uint16_t indent_level = 0) { const auto port_cell = sort.second.front(); const auto _indent = gen_indent(indent_level); f << _indent << "{\n"; f << stringf(" %s\"type\": \"%s\",\n", _indent, sort.first); f << _indent << " \"ports\": [\n"; write_cell_ports(port_cell, indent_level + 2); f << _indent << " ],\n" << _indent << " \"cells\": [\n"; bool first_cell{true}; for (auto& cell : sort.second) { if (!first_cell) f << ",\n"; write_cell(cell, indent_level + 2); first_cell = false; } f << "\n"; f << _indent << " ]\n"; f << _indent << "}"; } void write_param_val(const Const& v) { if ((v.flags & RTLIL::ConstFlags::CONST_FLAG_STRING) == RTLIL::ConstFlags::CONST_FLAG_STRING) { const auto str = v.decode_string(); // XXX(aki): TODO, uh, yeah f << "\"" << escape_string(str) << "\""; } else if ((v.flags & RTLIL::ConstFlags::CONST_FLAG_SIGNED) == RTLIL::ConstFlags::CONST_FLAG_SIGNED) { f << stringf("\"%dsd %d\"", v.size(), v.as_int(true)); } else if ((v.flags & RTLIL::ConstFlags::CONST_FLAG_REAL) == RTLIL::ConstFlags::CONST_FLAG_REAL) { } else { f << "\"" << escape_string(v.as_string()) << "\""; } } void write_prams(dict& params, uint16_t indent_level = 0) { const auto _indent = gen_indent(indent_level); bool first_param{true}; for (auto& param : params) { if (!first_param) f << stringf(",\n"); const auto param_val = param.second; if (!param_val.empty()) { f << stringf(" %s\"%s\": ", _indent, escape_string(RTLIL::unescape_id(param.first))); write_param_val(param_val); } else { f << stringf(" %s\"%s\": true", _indent, escape_string(RTLIL::unescape_id(param.first))); } first_param = false; } } void write_cell(Cell* cell, uint16_t indent_level = 0) { const auto _indent = gen_indent(indent_level); log_assert(cell != nullptr); f << _indent << " {\n"; f << stringf(" %s\"name\": \"%s\"", _indent, escape_string(RTLIL::unescape_id(cell->name))); if (_include_connections) { f << ",\n" << _indent << " \"connections\": [\n"; bool first_conn{true}; for (const auto& conn : cell->connections()) { if (!first_conn) f << ",\n"; write_cell_conn(conn, indent_level + 2); first_conn = false; } f << "\n"; f << _indent << " ]"; } if (_include_attributes) { f << ",\n" << _indent << " \"attributes\": {\n"; write_prams(cell->attributes, indent_level + 2); f << "\n"; f << _indent << " }"; } if (_include_properties) { f << ",\n" << _indent << " \"parameters\": {\n"; write_prams(cell->parameters, indent_level + 2); f << "\n"; f << _indent << " }"; } f << "\n" << _indent << " }"; } }; struct JnyBackend : public Backend { JnyBackend() : Backend("jny", "generate design metadata") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" jny [options] [selection]\n"); log("\n"); log("Write JSON netlist metadata for the current design\n"); log("\n"); log(" -no-connections\n"); log(" Don't include connection information in the netlist output.\n"); log("\n"); log(" -no-attributes\n"); log(" Don't include attributed information in the netlist output.\n"); log("\n"); log(" -no-properties\n"); 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/main/misc/jny.schema.json\"\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { bool connections{true}; bool attributes{true}; bool properties{true}; size_t argidx{1}; for (; argidx < args.size(); argidx++) { if (args[argidx] == "-no-connections") { connections = false; continue; } if (args[argidx] == "-no-attributes") { attributes = false; continue; } if (args[argidx] == "-no-properties") { properties = false; continue; } break; } // Compose invocation line std::ostringstream invk; if (!args.empty()) { std::copy(args.begin(), args.end(), std::ostream_iterator(invk, " ") ); } invk << filename; extra_args(f, filename, args, argidx); log_header(design, "Executing jny backend.\n"); JnyWriter jny_writer(*f, false, connections, attributes, properties); jny_writer.write_metadata(design, 0, invk.str()); } } JnyBackend; struct JnyPass : public Pass { JnyPass() : Pass("jny", "write design and metadata") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" jny [options] [selection]\n"); log("\n"); log("Write JSON netlist metadata for the current design\n"); log("\n"); log(" -o \n"); log(" write to the specified file.\n"); log("\n"); log(" -no-connections\n"); log(" Don't include connection information in the netlist output.\n"); log("\n"); log(" -no-attributes\n"); log(" Don't include attributed information in the netlist output.\n"); log("\n"); log(" -no-properties\n"); log(" Don't include property information in the netlist output.\n"); log("\n"); log("See 'help write_jny' for a description of the JSON format used.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { std::string filename{}; bool connections{true}; bool attributes{true}; bool properties{true}; size_t argidx{1}; for (; argidx < args.size(); argidx++) { if (args[argidx] == "-o" && argidx+1 < args.size()) { filename = args[++argidx]; continue; } if (args[argidx] == "-no-connections") { connections = false; continue; } if (args[argidx] == "-no-attributes") { attributes = false; continue; } if (args[argidx] == "-no-properties") { properties = false; continue; } break; } // Compose invocation line std::ostringstream invk; if (!args.empty()) { std::copy(args.begin(), args.end(), std::ostream_iterator(invk, " ") ); } extra_args(args, argidx, design); std::ostream *f; std::stringstream buf; bool empty = filename.empty(); if (!empty) { rewrite_filename(filename); std::ofstream *ff = new std::ofstream; 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, strerror(errno)); } f = ff; invk << filename; } else { f = &buf; } JnyWriter jny_writer(*f, false, connections, attributes, properties); jny_writer.write_metadata(design, 0, invk.str()); if (!empty) { delete f; } else { log("%s", buf.str()); } } } JnyPass; PRIVATE_NAMESPACE_END yosys-0.65/backends/json/000077500000000000000000000000001520057232300153545ustar00rootroot00000000000000yosys-0.65/backends/json/Makefile.inc000066400000000000000000000000371520057232300175640ustar00rootroot00000000000000 OBJS += backends/json/json.o yosys-0.65/backends/json/json.cc000066400000000000000000000566551520057232300166550ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/celltypes.h" #include "kernel/cellaigs.h" #include "kernel/log.h" #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct JsonWriter { std::ostream &f; bool use_selection; bool aig_mode; bool compat_int_mode; bool scopeinfo_mode; Design *design; Module *module; SigMap sigmap; int sigidcounter; dict sigids; pool aig_models; 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), scopeinfo_mode(scopeinfo_mode) { } string get_string(string str) { string newstr = "\""; for (char c : str) { if (c == '\\') newstr += "\\\\"; else if (c == '"') newstr += "\\\""; else if (c == '\b') newstr += "\\b"; else if (c == '\f') newstr += "\\f"; else if (c == '\n') newstr += "\\n"; else if (c == '\r') newstr += "\\r"; else if (c == '\t') newstr += "\\t"; else if (c < 0x20) newstr += stringf("\\u%04X", c); else newstr += c; } return newstr + "\""; } string get_name(IdString name) { return get_string(RTLIL::unescape_id(name)); } string get_bits(SigSpec sig) { bool first = true; string str = "["; for (auto bit : sigmap(sig)) { str += first ? " " : ", "; first = false; if (sigids.count(bit) == 0) { string &s = sigids[bit]; if (bit.wire == nullptr) { if (bit == State::S0) s = "\"0\""; else if (bit == State::S1) s = "\"1\""; else if (bit == State::Sz) s = "\"z\""; else s = "\"x\""; } else s = stringf("%d", sigidcounter++); } str += sigids[bit]; } return str + " ]"; } void write_parameter_value(const Const &value) { if ((value.flags & RTLIL::ConstFlags::CONST_FLAG_STRING) != 0) { string str = value.decode_string(); int state = 0; for (char c : str) { if (state == 0) { if (c == '0' || c == '1' || c == 'x' || c == 'z') state = 0; else if (c == ' ') state = 1; else state = 2; } else if (state == 1 && c != ' ') state = 2; } if (state < 2) str += " "; f << get_string(str); } else if (compat_int_mode && GetSize(value) <= 32 && value.is_fully_def()) { if ((value.flags & RTLIL::ConstFlags::CONST_FLAG_SIGNED) != 0) f << stringf("%d", value.as_int()); else f << stringf("%u", value.as_int()); } else { f << get_string(value.as_string()); } } void write_parameters(const dict ¶meters, bool for_module=false) { bool first = true; for (auto ¶m : parameters) { f << stringf("%s\n", first ? "" : ","); f << stringf(" %s%s: ", for_module ? "" : " ", get_name(param.first)); write_parameter_value(param.second); first = false; } } void write_module(Module *module_) { module = module_; log_assert(module->design == design); sigmap.set(module); sigids.clear(); // reserve 0 and 1 to avoid confusion with "0" and "1" sigidcounter = 2; if (module->has_processes()) { 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)); f << stringf(" \"attributes\": {"); write_parameters(module->attributes, /*for_module=*/true); f << stringf("\n },\n"); if (module->parameter_default_values.size()) { f << stringf(" \"parameter_default_values\": {"); write_parameters(module->parameter_default_values, /*for_module=*/true); f << stringf("\n },\n"); } f << stringf(" \"ports\": {"); bool first = true; for (auto n : module->ports) { Wire *w = module->wire(n); if (use_selection && !module->selected(w)) continue; f << stringf("%s\n", first ? "" : ","); 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); if (w->upto) 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)); f << stringf(" }"); first = false; } f << stringf("\n },\n"); f << stringf(" \"cells\": {"); first = true; for (auto c : module->cells()) { if (use_selection && !module->selected(c)) continue; if (!scopeinfo_mode && c->type == ID($scopeinfo)) continue; f << stringf("%s\n", first ? "" : ","); 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)); if (aig_mode) { Aig aig(c); if (!aig.name.empty()) { f << stringf(" \"model\": \"%s\",\n", aig.name); aig_models.insert(aig); } } f << stringf(" \"parameters\": {"); write_parameters(c->parameters); f << stringf("\n },\n"); f << stringf(" \"attributes\": {"); write_parameters(c->attributes); f << stringf("\n },\n"); if (c->known()) { f << stringf(" \"port_directions\": {"); bool first2 = true; for (auto &conn : c->connections()) { string direction = "output"; 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), direction); first2 = false; } f << stringf("\n },\n"); } f << stringf(" \"connections\": {"); bool first2 = true; for (auto &conn : c->connections()) { f << stringf("%s\n", first2 ? "" : ","); f << stringf(" %s: %s", get_name(conn.first), get_bits(conn.second)); first2 = false; } f << stringf("\n }\n"); f << stringf(" }"); first = false; } f << stringf("\n },\n"); if (!module->memories.empty()) { f << stringf(" \"memories\": {"); first = true; for (auto &it : module->memories) { if (use_selection && !module->selected(it.second)) continue; f << stringf("%s\n", first ? "" : ","); 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); f << stringf("\n },\n"); f << stringf(" \"width\": %d,\n", it.second->width); f << stringf(" \"start_offset\": %d,\n", it.second->start_offset); f << stringf(" \"size\": %d\n", it.second->size); f << stringf(" }"); first = false; } f << stringf("\n },\n"); } f << stringf(" \"netnames\": {"); first = true; for (auto w : module->wires()) { if (use_selection && !module->selected(w)) continue; f << stringf("%s\n", first ? "" : ","); 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)); if (w->start_offset) f << stringf(" \"offset\": %d,\n", w->start_offset); if (w->upto) f << stringf(" \"upto\": 1,\n"); if (w->is_signed) f << stringf(" \"signed\": %d,\n", w->is_signed); f << stringf(" \"attributes\": {"); write_parameters(w->attributes); f << stringf("\n }\n"); f << stringf(" }"); first = false; } f << stringf("\n }\n"); f << stringf(" }"); } void write_design(Design *design_) { design = design_; design->sort(); f << stringf("{\n"); 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; for (auto mod : modules) { if (!first_module) f << stringf(",\n"); write_module(mod); first_module = false; } f << stringf("\n }"); if (!aig_models.empty()) { f << stringf(",\n \"models\": {\n"); bool first_model = true; for (auto &aig : aig_models) { if (!first_model) f << stringf(",\n"); f << stringf(" \"%s\": [\n", aig.name); int node_idx = 0; for (auto &node : aig.nodes) { if (node_idx != 0) f << stringf(",\n"); f << stringf(" /* %3d */ [ ", node_idx); if (node.portbit >= 0) f << stringf("\"%sport\", \"%s\", %d", node.inverter ? "n" : "", log_id(node.portname), node.portbit); else if (node.left_parent < 0 && node.right_parent < 0) f << stringf("\"%s\"", node.inverter ? "true" : "false"); else f << stringf("\"%s\", %d, %d", node.inverter ? "nand" : "and", node.left_parent, node.right_parent); for (auto &op : node.outports) f << stringf(", \"%s\", %d", log_id(op.first), op.second); f << stringf(" ]"); node_idx++; } f << stringf("\n ]"); first_model = false; } f << stringf("\n }"); } f << stringf("\n}\n"); } }; struct JsonBackend : public Backend { JsonBackend() : Backend("json", "write design to a JSON file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_json [options] [filename]\n"); log("\n"); log("Write a JSON netlist of the current design.\n"); log("\n"); log(" -aig\n"); log(" include AIG models for the different gate types\n"); log("\n"); log(" -compat-int\n"); 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"); log(" {\n"); log(" \"creator\": \"Yosys \",\n"); log(" \"modules\": {\n"); log(" : {\n"); log(" \"attributes\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"parameter_default_values\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"ports\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"cells\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"memories\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"netnames\": {\n"); log(" : ,\n"); log(" ...\n"); log(" }\n"); log(" }\n"); log(" },\n"); log(" \"models\": {\n"); log(" ...\n"); log(" },\n"); log(" }\n"); log("\n"); log("Where is:\n"); log("\n"); log(" {\n"); log(" \"direction\": <\"input\" | \"output\" | \"inout\">,\n"); log(" \"bits\": \n"); log(" \"offset\": \n"); log(" \"upto\": <1 if the port bit indexing is MSB-first>\n"); log(" \"signed\": <1 if the port is signed>\n"); log(" }\n"); 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.\n"); log("And is:\n"); log("\n"); log(" {\n"); log(" \"hide_name\": <1 | 0>,\n"); log(" \"type\": ,\n"); log(" \"model\": ,\n"); log(" \"parameters\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"attributes\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"port_directions\": {\n"); log(" : <\"input\" | \"output\" | \"inout\">,\n"); log(" ...\n"); log(" },\n"); log(" \"connections\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" }\n"); log("\n"); log("And is:\n"); log("\n"); log(" {\n"); log(" \"hide_name\": <1 | 0>,\n"); log(" \"attributes\": {\n"); log(" : ,\n"); log(" ...\n"); log(" },\n"); log(" \"width\": \n"); log(" \"start_offset\": \n"); log(" \"size\": \n"); log(" }\n"); log("\n"); log("And is:\n"); log("\n"); log(" {\n"); log(" \"hide_name\": <1 | 0>,\n"); log(" \"bits\": \n"); log(" \"offset\": \n"); log(" \"upto\": <1 if the port bit indexing is MSB-first>\n"); log(" \"signed\": <1 if the port is signed>\n"); log(" }\n"); log("\n"); log("The \"hide_name\" fields are set to 1 when the name of this cell or net is\n"); log("automatically created and is likely not of interest for a regular user.\n"); log("\n"); log("The \"port_directions\" section is only included for cells for which the\n"); log("interface is known.\n"); log("\n"); log("Module and cell ports and nets can be single bit wide or vectors of multiple\n"); log("bits. Each individual signal bit is assigned a unique integer. The \n"); log("values referenced above are vectors of this integers. Signal bits that are\n"); log("connected to a constant driver are denoted as string \"0\", \"1\", \"x\", or\n"); log("\"z\" instead of a number.\n"); log("\n"); log("Bit vectors (including integers) are written as string holding the binary\n"); log("representation of the value. Strings are written as strings, with an appended\n"); log("blank in cases of strings of the form /[01xz]* */.\n"); log("\n"); log("For example the following Verilog code:\n"); log("\n"); log(" module test(input x, y);\n"); log(" (* keep *) foo #(.P(42), .Q(1337))\n"); log(" foo_inst (.A({x, y}), .B({y, x}), .C({4'd10, {4{x}}}));\n"); log(" endmodule\n"); log("\n"); log("Translates to the following JSON output:\n"); log("\n"); log(" {\n"); log(" \"creator\": \"Yosys 0.9+2406 (git sha1 fb1168d8, clang 9.0.1 -fPIC -Os)\",\n"); log(" \"modules\": {\n"); log(" \"test\": {\n"); log(" \"attributes\": {\n"); log(" \"cells_not_processed\": \"00000000000000000000000000000001\",\n"); log(" \"src\": \"test.v:1.1-4.10\"\n"); log(" },\n"); log(" \"ports\": {\n"); log(" \"x\": {\n"); log(" \"direction\": \"input\",\n"); log(" \"bits\": [ 2 ]\n"); log(" },\n"); log(" \"y\": {\n"); log(" \"direction\": \"input\",\n"); log(" \"bits\": [ 3 ]\n"); log(" }\n"); log(" },\n"); log(" \"cells\": {\n"); log(" \"foo_inst\": {\n"); log(" \"hide_name\": 0,\n"); log(" \"type\": \"foo\",\n"); log(" \"parameters\": {\n"); log(" \"P\": \"00000000000000000000000000101010\",\n"); log(" \"Q\": \"00000000000000000000010100111001\"\n"); log(" },\n"); log(" \"attributes\": {\n"); log(" \"keep\": \"00000000000000000000000000000001\",\n"); log(" \"module_not_derived\": \"00000000000000000000000000000001\",\n"); log(" \"src\": \"test.v:3.1-3.55\"\n"); log(" },\n"); log(" \"connections\": {\n"); log(" \"A\": [ 3, 2 ],\n"); log(" \"B\": [ 2, 3 ],\n"); log(" \"C\": [ 2, 2, 2, 2, \"0\", \"1\", \"0\", \"1\" ]\n"); log(" }\n"); log(" }\n"); log(" },\n"); log(" \"netnames\": {\n"); log(" \"x\": {\n"); log(" \"hide_name\": 0,\n"); log(" \"bits\": [ 2 ],\n"); log(" \"attributes\": {\n"); log(" \"src\": \"test.v:1.19-1.20\"\n"); log(" }\n"); log(" },\n"); log(" \"y\": {\n"); log(" \"hide_name\": 0,\n"); log(" \"bits\": [ 3 ],\n"); log(" \"attributes\": {\n"); log(" \"src\": \"test.v:1.22-1.23\"\n"); log(" }\n"); log(" }\n"); log(" }\n"); log(" }\n"); log(" }\n"); log(" }\n"); log("\n"); log("The models are given as And-Inverter-Graphs (AIGs) in the following form:\n"); log("\n"); log(" \"models\": {\n"); log(" : [\n"); log(" /* 0 */ [ ],\n"); log(" /* 1 */ [ ],\n"); log(" /* 2 */ [ ],\n"); log(" ...\n"); log(" ],\n"); log(" ...\n"); log(" },\n"); log("\n"); log("The following node-types may be used:\n"); log("\n"); log(" [ \"port\", , , ]\n"); log(" - the value of the specified input port bit\n"); log("\n"); log(" [ \"nport\", , , ]\n"); log(" - the inverted value of the specified input port bit\n"); log("\n"); log(" [ \"and\", , , ]\n"); log(" - the ANDed value of the specified nodes\n"); log("\n"); log(" [ \"nand\", , , ]\n"); log(" - the inverted ANDed value of the specified nodes\n"); log("\n"); log(" [ \"true\", ]\n"); log(" - the constant value 1\n"); log("\n"); log(" [ \"false\", ]\n"); log(" - the constant value 0\n"); log("\n"); log("All nodes appear in topological order. I.e. only nodes with smaller indices\n"); log("are referenced by \"and\" and \"nand\" nodes.\n"); log("\n"); log("The optional at the end of a node specification is a list of\n"); log("output portname and bitindex pairs, specifying the outputs driven by this node.\n"); log("\n"); log("For example, the following is the model for a 3-input 3-output $reduce_and cell\n"); log("inferred by the following code:\n"); log("\n"); log(" module test(input [2:0] in, output [2:0] out);\n"); log(" assign in = &out;\n"); log(" endmodule\n"); log("\n"); log(" \"$reduce_and:3U:3\": [\n"); log(" /* 0 */ [ \"port\", \"A\", 0 ],\n"); log(" /* 1 */ [ \"port\", \"A\", 1 ],\n"); log(" /* 2 */ [ \"and\", 0, 1 ],\n"); log(" /* 3 */ [ \"port\", \"A\", 2 ],\n"); log(" /* 4 */ [ \"and\", 2, 3, \"Y\", 0 ],\n"); log(" /* 5 */ [ \"false\", \"Y\", 1, \"Y\", 2 ]\n"); log(" ]\n"); log("\n"); log("Future version of Yosys might add support for additional fields in the JSON\n"); log("format. A program processing this format must ignore all unknown fields.\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { 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++) { if (args[argidx] == "-aig") { aig_mode = true; continue; } if (args[argidx] == "-compat-int") { 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, use_selection, aig_mode, compat_int_mode, scopeinfo_mode); json_writer.write_design(design); } } JsonBackend; struct JsonPass : public Pass { JsonPass() : Pass("json", "write design in JSON format") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" json [options] [selection]\n"); log("\n"); log("Write a JSON netlist of all selected objects.\n"); log("\n"); log(" -o \n"); log(" write to the specified file.\n"); log("\n"); log(" -aig\n"); log(" also include AIG models for the different gate types\n"); log("\n"); log(" -compat-int\n"); 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"); } void execute(std::vector args, RTLIL::Design *design) override { 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++) { if (args[argidx] == "-o" && argidx+1 < args.size()) { filename = args[++argidx]; continue; } if (args[argidx] == "-aig") { aig_mode = true; continue; } if (args[argidx] == "-compat-int") { compat_int_mode = true; continue; } if (args[argidx] == "-noscopeinfo") { scopeinfo_mode = false; continue; } break; } extra_args(args, argidx, design); std::ostream *f; std::stringstream buf; bool empty = filename.empty(); if (!empty) { rewrite_filename(filename); std::ofstream *ff = new std::ofstream; 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, strerror(errno)); } f = ff; } else { f = &buf; } 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()); } } } JsonPass; PRIVATE_NAMESPACE_END yosys-0.65/backends/rtlil/000077500000000000000000000000001520057232300155315ustar00rootroot00000000000000yosys-0.65/backends/rtlil/Makefile.inc000066400000000000000000000000511520057232300177350ustar00rootroot00000000000000 OBJS += backends/rtlil/rtlil_backend.o yosys-0.65/backends/rtlil/rtlil_backend.cc000066400000000000000000000362241520057232300206440ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * --- * * A very simple and straightforward backend for the RTLIL text * representation. * */ #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.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.size()); switch (data[offset+i]) { case State::S0: break; case State::S1: val |= 1 << i; break; default: val = -1; break; } } if (val >= 0) { f << stringf("%d", val); return; } } 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.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; case RTLIL::Sz: f << stringf("z"); break; case RTLIL::Sa: f << stringf("-"); break; case RTLIL::Sm: f << stringf("m"); break; } } } } else { f << stringf("\""); std::string str = data.decode_string(); for (size_t i = 0; i < str.size(); i++) { if (str[i] == '\n') f << stringf("\\n"); else if (str[i] == '\t') f << stringf("\\t"); else if (str[i] < 32) f << stringf("\\%03o", (unsigned char)str[i]); else if (str[i] == '"') f << stringf("\\\""); else if (str[i] == '\\') f << stringf("\\\\"); else f << str[i]; } f << stringf("\""); } } void RTLIL_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint) { if (chunk.wire == NULL) { 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); else if (chunk.width == 1) f << stringf("%s [%d]", chunk.wire->name, chunk.offset); else f << stringf("%s [%d:%d]", chunk.wire->name, chunk.offset+chunk.width-1, chunk.offset); } } void RTLIL_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint) { if (sig.is_chunk()) { dump_sigchunk(f, sig.as_chunk(), autoint); } else { f << stringf("{ "); auto chunks = sig.chunks(); for (const auto& chunk : reversed(chunks)) { dump_sigchunk(f, chunk, false); f << stringf(" "); } f << stringf("}"); } } void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire) { 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); if (wire->width != 1) f << stringf("width %d ", wire->width); if (wire->upto) f << stringf("upto "); if (wire->start_offset != 0) f << stringf("offset %d ", wire->start_offset); if (wire->port_input && !wire->port_output) f << stringf("input %d ", wire->port_id); if (!wire->port_input && wire->port_output) f << stringf("output %d ", wire->port_id); if (wire->port_input && wire->port_output) f << stringf("inout %d ", wire->port_id); if (wire->is_signed) f << stringf("signed "); f << stringf("%s\n", wire->name); } void RTLIL_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory) { 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); } void RTLIL_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell) { 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"); } for (const auto& [port, sig] : reversed(cell->connections_)) { f << stringf("%s connect %s ", indent, port); dump_sigspec(f, sig); f << stringf("\n"); } f << stringf("%s" "end\n", indent); } void RTLIL_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs) { for (const auto& [lhs, rhs] : cs->actions) { f << stringf("%s" "assign ", indent); dump_sigspec(f, lhs); f << stringf(" "); dump_sigspec(f, rhs); f << stringf("\n"); } 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) { dump_attributes(f, indent, sw); f << stringf("%s" "switch ", indent); dump_sigspec(f, sw->signal); f << stringf("\n"); for (const auto case_ : sw->cases) { 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, case_->compare[i]); } f << stringf("\n"); dump_proc_case_body(f, indent + " ", case_); } 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); switch (sy->type) { case RTLIL::ST0: f << stringf("low "); if (0) case RTLIL::ST1: f << stringf("high "); if (0) case RTLIL::STp: f << stringf("posedge "); if (0) case RTLIL::STn: f << stringf("negedge "); if (0) case RTLIL::STe: f << stringf("edge "); dump_sigspec(f, sy->signal); f << stringf("\n"); break; case RTLIL::STa: f << stringf("always\n"); break; case RTLIL::STg: f << stringf("global\n"); break; case RTLIL::STi: f << stringf("init\n"); break; } for (const auto& [lhs, rhs] : sy->actions) { f << stringf("%s update ", indent); dump_sigspec(f, lhs); f << stringf(" "); dump_sigspec(f, rhs); f << stringf("\n"); } for (auto &it: sy->mem_write_actions) { 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); f << stringf(" "); dump_sigspec(f, it.enable); f << stringf(" "); dump_const(f, it.priority_mask); f << stringf("\n"); } } void RTLIL_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc) { 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* 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); dump_sigspec(f, left); f << stringf(" "); dump_sigspec(f, right); f << stringf("\n"); } 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 || module->is_selected_whole(); bool print_body = !flag_n || !module->is_selected_whole(); if (print_header) { dump_attributes(f, indent, module); f << stringf("%s" "module %s\n", indent, module->name); if (!module->avail_parameters.empty()) { if (only_selected) f << stringf("\n"); 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, p); } else { f << stringf("%s" " parameter %s ", indent, p); dump_const(f, it->second); f << stringf("\n"); } } } } if (print_body) { for (const auto& [_, wire] : reversed(module->wires_)) if (!only_selected || design->selected(module, wire)) { if (only_selected) f << stringf("\n"); dump_wire(f, indent + " ", wire); } for (const auto& [_, mem] : reversed(module->memories)) if (!only_selected || design->selected(module, mem)) { if (only_selected) f << stringf("\n"); dump_memory(f, indent + " ", mem); } for (const auto& [_, cell] : reversed(module->cells_)) if (!only_selected || design->selected(module, cell)) { if (only_selected) f << stringf("\n"); dump_cell(f, indent + " ", cell); } for (const auto& [_, process] : reversed(module->processes)) if (!only_selected || design->selected(module, process)) { if (only_selected) f << stringf("\n"); dump_proc(f, indent + " ", process); } bool first_conn_line = true; for (const auto& [lhs, rhs] : module->connections()) { bool show_conn = !only_selected || design->selected_whole_module(module->name); if (!show_conn) { RTLIL::SigSpec sigs = lhs; sigs.append(rhs); for (auto &c : sigs.chunks()) { if (c.wire == NULL || !design->selected(module, c.wire)) continue; show_conn = true; } } if (show_conn) { if (only_selected && first_conn_line) f << stringf("\n"); dump_conn(f, indent + " ", lhs, rhs); first_conn_line = false; } } } if (print_header) 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) { int init_autoidx = autoidx; if (!flag_m) { int count_selected_mods = 0; for (auto* module : design->modules()) { if (design->selected_whole_module(module->name)) flag_m = true; if (design->selected(module)) count_selected_mods++; } if (count_selected_mods > 1) flag_m = true; } if (!only_selected || flag_m) { if (only_selected) f << stringf("\n"); f << stringf("autoidx %d\n", autoidx); } for (const auto& [_, module] : reversed(design->modules_)) { if (!only_selected || design->selected(module)) { if (only_selected) f << stringf("\n"); dump_module(f, "", module, design, only_selected, flag_m, flag_n); } } log_assert(init_autoidx == autoidx); } YOSYS_NAMESPACE_END PRIVATE_NAMESPACE_BEGIN struct RTLILBackend : public Backend { RTLILBackend() : Backend("rtlil", "write design to RTLIL file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_rtlil [filename]\n"); log("\n"); log("Write the current design to an RTLIL file. (RTLIL is a text representation\n"); log("of a design in yosys's internal format.)\n"); log("\n"); 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"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { std::string arg = args[argidx]; if (arg == "-selected") { selected = true; continue; } if (arg == "-sort") { do_sort = true; continue; } break; } extra_args(f, filename, args, argidx); log("Output filename: %s\n", filename); 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 DumpPass : public Pass { DumpPass() : Pass("dump", "print parts of the design in RTLIL format") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" dump [options] [selection]\n"); log("\n"); log("Write the selected parts of the design to the console or specified file in\n"); log("RTLIL format.\n"); log("\n"); log(" -m\n"); log(" also dump the module headers, even if only parts of a single\n"); log(" module is selected\n"); log("\n"); log(" -n\n"); log(" only dump the module headers if the entire module is selected\n"); log("\n"); log(" -o \n"); log(" write to the specified file.\n"); log("\n"); log(" -a \n"); log(" like -outfile but append instead of overwrite\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { std::string filename; bool flag_m = false, flag_n = false, append = false; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { std::string arg = args[argidx]; if ((arg == "-o" || arg == "-outfile") && argidx+1 < args.size()) { filename = args[++argidx]; append = false; continue; } if ((arg == "-a" || arg == "-append") && argidx+1 < args.size()) { filename = args[++argidx]; append = true; continue; } if (arg == "-m") { flag_m = true; continue; } if (arg == "-n") { flag_n = true; continue; } break; } extra_args(args, argidx, design); std::ostream *f; std::stringstream buf; bool empty = filename.empty(); if (!empty) { rewrite_filename(filename); std::ofstream *ff = new std::ofstream; 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, strerror(errno)); } f = ff; } else { f = &buf; } RTLIL_BACKEND::dump_design(*f, design, true, flag_m, flag_n); if (!empty) { delete f; } else { log("%s", buf.str()); } } } DumpPass; PRIVATE_NAMESPACE_END yosys-0.65/backends/rtlil/rtlil_backend.h000066400000000000000000000047011520057232300205010ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * --- * * A very simple and straightforward backend for the RTLIL text * representation. * */ #ifndef RTLIL_BACKEND_H #define RTLIL_BACKEND_H #include "kernel/yosys.h" #include 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); void dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire); void dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory); void dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell); void dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs); void dump_proc_switch(std::ostream &f, std::string indent, const RTLIL::SwitchRule *sw); void dump_proc_sync(std::ostream &f, std::string indent, const RTLIL::SyncRule *sy); void dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc); void dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right); void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTLIL::Design *design, bool only_selected, bool flag_m = true, bool flag_n = false); void dump_design(std::ostream &f, RTLIL::Design *design, bool only_selected, bool flag_m = true, bool flag_n = false); } YOSYS_NAMESPACE_END #endif yosys-0.65/backends/simplec/000077500000000000000000000000001520057232300160375ustar00rootroot00000000000000yosys-0.65/backends/simplec/.gitignore000066400000000000000000000000271520057232300200260ustar00rootroot00000000000000test00_tb test00_uut.c yosys-0.65/backends/simplec/Makefile.inc000066400000000000000000000000451520057232300202460ustar00rootroot00000000000000 OBJS += backends/simplec/simplec.o yosys-0.65/backends/simplec/simplec.cc000066400000000000000000000621751520057232300200150ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/utils.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct HierDirtyFlags; static pool reserved_cids; static dict id2cid; static string cid(IdString id) { if (id2cid.count(id) == 0) { string s = id.str(); if (GetSize(s) < 2) log_abort(); if (s[0] == '\\') s = s.substr(1); if ('0' <= s[0] && s[0] <= '9') { s = "_" + s; } for (int i = 0; i < GetSize(s); i++) { if ('0' <= s[i] && s[i] <= '9') continue; if ('A' <= s[i] && s[i] <= 'Z') continue; if ('a' <= s[i] && s[i] <= 'z') continue; s[i] = '_'; } while (reserved_cids.count(s)) s += "_"; reserved_cids.insert(s); id2cid[id] = s; } return id2cid.at(id); } struct HierDirtyFlags { int dirty; Module *module; IdString hiername; HierDirtyFlags *parent; pool dirty_bits; pool dirty_cells; pool sticky_dirty_bits; dict children; string prefix, log_prefix; HierDirtyFlags(Module *module, IdString hiername, HierDirtyFlags *parent, const string &prefix, const string &log_prefix) : dirty(0), module(module), hiername(hiername), parent(parent), prefix(prefix), log_prefix(log_prefix) { for (Cell *cell : module->cells()) { Module *mod = module->design->module(cell->type); if (mod) children[cell->name] = new HierDirtyFlags(mod, cell->name, this, prefix + cid(cell->name) + ".", log_prefix + "." + prefix + log_id(cell->name)); } } ~HierDirtyFlags() { for (auto &child : children) delete child.second; } void set_dirty(SigBit bit) { if (dirty_bits.count(bit)) return; dirty_bits.insert(bit); sticky_dirty_bits.insert(bit); HierDirtyFlags *p = this; while (p != nullptr) { p->dirty++; p = p->parent; } } void unset_dirty(SigBit bit) { if (dirty_bits.count(bit) == 0) return; dirty_bits.erase(bit); HierDirtyFlags *p = this; while (p != nullptr) { p->dirty--; log_assert(p->dirty >= 0); p = p->parent; } } void set_dirty(Cell *cell) { if (dirty_cells.count(cell)) return; dirty_cells.insert(cell); HierDirtyFlags *p = this; while (p != nullptr) { p->dirty++; p = p->parent; } } void unset_dirty(Cell *cell) { if (dirty_cells.count(cell) == 0) return; dirty_cells.erase(cell); HierDirtyFlags *p = this; while (p != nullptr) { p->dirty--; log_assert(p->dirty >= 0); p = p->parent; } } }; struct SimplecWorker { bool verbose = false; int max_uintsize = 32; Design *design; dict sigmaps; vector signal_declarations; pool generated_sigtypes; vector util_declarations; pool generated_utils; vector struct_declarations; pool generated_structs; vector funct_declarations; dict>>> bit2cell; dict>> bit2output; dict> driven_bits; dict topoidx; pool activated_cells; pool reactivated_cells; SimplecWorker(Design *design) : design(design) { } string sigtype(int n) { string struct_name = stringf("signal%d_t", n); if (generated_sigtypes.count(n) == 0) { signal_declarations.push_back(""); signal_declarations.push_back(stringf("#ifndef YOSYS_SIMPLEC_SIGNAL%d_T", n)); signal_declarations.push_back(stringf("#define YOSYS_SIMPLEC_SIGNAL%d_T", n)); signal_declarations.push_back(stringf("typedef struct {")); for (int k = 8; k <= max_uintsize; k = 2*k) if (n <= k && k <= max_uintsize) { signal_declarations.push_back(stringf(" uint%d_t value_%d_0 : %d;", k, n-1, n)); goto end_struct; } for (int k = 0; k < n; k += max_uintsize) { int bits = std::min(max_uintsize, n-k); signal_declarations.push_back(stringf(" uint%d_t value_%d_%d : %d;", max_uintsize, k+bits-1, k, bits)); } end_struct: signal_declarations.push_back(stringf("} signal%d_t;", n)); signal_declarations.push_back(stringf("#endif")); generated_sigtypes.insert(n); } return struct_name; } void util_ifdef_guard(string s) { for (int i = 0; i < GetSize(s); i++) if ('a' <= s[i] && s[i] <= 'z') s[i] -= 'a' - 'A'; util_declarations.push_back(""); 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) { if (n == 1 && idx == 0) return signame + ".value_0_0"; string util_name = stringf("yosys_simplec_get_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 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, word_offset)); util_declarations.push_back(stringf("}")); util_declarations.push_back(stringf("#endif")); generated_utils.insert(util_name); } 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, 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, 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); #if 0 util_declarations.push_back(stringf(" if (value)")); 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, 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)); #endif util_declarations.push_back(stringf("}")); util_declarations.push_back(stringf("#endif")); generated_utils.insert(util_name); } return stringf(" %s(&%s, %s);", util_name, signame, expr); } void create_module_struct(Module *mod) { if (generated_structs.count(mod->name)) return; generated_structs.insert(mod->name); sigmaps[mod].set(mod); for (Wire *w : mod->wires()) { if (w->port_output) for (auto bit : SigSpec(w)) bit2output[mod][sigmaps.at(mod)(bit)].insert(bit); } for (Cell *c : mod->cells()) { for (auto &conn : c->connections()) { if (!c->input(conn.first)) { for (auto bit : sigmaps.at(mod)(conn.second)) driven_bits[mod].insert(bit); continue; } int idx = 0; for (auto bit : sigmaps.at(mod)(conn.second)) bit2cell[mod][bit].insert(tuple(c, conn.first, idx++)); } if (design->module(c->type)) create_module_struct(design->module(c->type)); } TopoSort topo; for (Cell *c : mod->cells()) { topo.node(c->name); for (auto &conn : c->connections()) { if (!c->input(conn.first)) continue; for (auto bit : sigmaps.at(mod)(conn.second)) for (auto &it : bit2cell[mod][bit]) topo.edge(c->name, std::get<0>(it)->name); } } topo.analyze_loops = false; topo.sort(); 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)); 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)); 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), 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), 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), 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), cid(c->name), log_id(c))); struct_declarations.push_back(stringf("};")); struct_declarations.push_back("#endif"); } void eval_cell(HierDirtyFlags *work, Cell *cell) { if (cell->type.in(ID($_BUF_), ID($_NOT_))) { SigBit a = sigmaps.at(work->module)(cell->getPort(ID::A)); SigBit y = sigmaps.at(work->module)(cell->getPort(ID::Y)); string a_expr = a.wire ? util_get_bit(work->prefix + cid(a.wire->name), a.wire->width, a.offset) : a.data ? "1" : "0"; string expr; if (cell->type == ID($_BUF_)) expr = a_expr; if (cell->type == ID($_NOT_)) expr = "!" + 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) + stringf(" // %s (%s)", log_id(cell), log_id(cell->type))); work->set_dirty(y); return; } if (cell->type.in(ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_), ID($_ANDNOT_), ID($_ORNOT_))) { SigBit a = sigmaps.at(work->module)(cell->getPort(ID::A)); SigBit b = sigmaps.at(work->module)(cell->getPort(ID::B)); SigBit y = sigmaps.at(work->module)(cell->getPort(ID::Y)); string a_expr = a.wire ? util_get_bit(work->prefix + cid(a.wire->name), a.wire->width, a.offset) : a.data ? "1" : "0"; 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, 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) + stringf(" // %s (%s)", log_id(cell), log_id(cell->type))); work->set_dirty(y); return; } if (cell->type.in(ID($_AOI3_), ID($_OAI3_))) { SigBit a = sigmaps.at(work->module)(cell->getPort(ID::A)); SigBit b = sigmaps.at(work->module)(cell->getPort(ID::B)); SigBit c = sigmaps.at(work->module)(cell->getPort(ID::C)); SigBit y = sigmaps.at(work->module)(cell->getPort(ID::Y)); string a_expr = a.wire ? util_get_bit(work->prefix + cid(a.wire->name), a.wire->width, a.offset) : a.data ? "1" : "0"; string b_expr = b.wire ? util_get_bit(work->prefix + cid(b.wire->name), b.wire->width, b.offset) : b.data ? "1" : "0"; 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, 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) + stringf(" // %s (%s)", log_id(cell), log_id(cell->type))); work->set_dirty(y); return; } if (cell->type.in(ID($_AOI4_), ID($_OAI4_))) { SigBit a = sigmaps.at(work->module)(cell->getPort(ID::A)); SigBit b = sigmaps.at(work->module)(cell->getPort(ID::B)); SigBit c = sigmaps.at(work->module)(cell->getPort(ID::C)); SigBit d = sigmaps.at(work->module)(cell->getPort(ID::D)); SigBit y = sigmaps.at(work->module)(cell->getPort(ID::Y)); string a_expr = a.wire ? util_get_bit(work->prefix + cid(a.wire->name), a.wire->width, a.offset) : a.data ? "1" : "0"; string b_expr = b.wire ? util_get_bit(work->prefix + cid(b.wire->name), b.wire->width, b.offset) : b.data ? "1" : "0"; string c_expr = c.wire ? util_get_bit(work->prefix + cid(c.wire->name), c.wire->width, c.offset) : c.data ? "1" : "0"; 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, 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) + stringf(" // %s (%s)", log_id(cell), log_id(cell->type))); work->set_dirty(y); return; } if (cell->type.in(ID($_MUX_), ID($_NMUX_))) { SigBit a = sigmaps.at(work->module)(cell->getPort(ID::A)); SigBit b = sigmaps.at(work->module)(cell->getPort(ID::B)); SigBit s = sigmaps.at(work->module)(cell->getPort(ID::S)); SigBit y = sigmaps.at(work->module)(cell->getPort(ID::Y)); string a_expr = a.wire ? util_get_bit(work->prefix + cid(a.wire->name), a.wire->width, a.offset) : a.data ? "1" : "0"; string b_expr = b.wire ? util_get_bit(work->prefix + cid(b.wire->name), b.wire->width, b.offset) : b.data ? "1" : "0"; 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, 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) + stringf(" // %s (%s)", log_id(cell), log_id(cell->type))); work->set_dirty(y); return; } log_error("No C model for %s available at the moment (FIXME).\n", log_id(cell->type)); } void eval_dirty(HierDirtyFlags *work) { while (work->dirty) { if (verbose && (!work->dirty_bits.empty() || !work->dirty_cells.empty())) log(" In %s:\n", work->log_prefix); while (!work->dirty_bits.empty() || !work->dirty_cells.empty()) { if (!work->dirty_bits.empty()) { SigSpec dirtysig(work->dirty_bits); dirtysig.sort_and_unify(); for (SigChunk chunk : dirtysig.chunks()) { if (chunk.wire == nullptr) continue; if (verbose) 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) { if (bit2output[work->module].count(bit) && work->parent) for (auto outbit : bit2output[work->module][bit]) { Module *parent_mod = work->parent->module; Cell *parent_cell = parent_mod->cell(work->hiername); IdString port_name = outbit.wire->name; int port_offset = outbit.offset; SigBit parent_bit = sigmaps.at(parent_mod)(parent_cell->getPort(port_name)[port_offset]); log_assert(bit.wire && parent_bit.wire); funct_declarations.push_back(util_set_bit(work->parent->prefix + cid(parent_bit.wire->name), parent_bit.wire->width, parent_bit.offset, util_get_bit(work->prefix + cid(bit.wire->name), bit.wire->width, bit.offset))); work->parent->set_dirty(parent_bit); if (verbose) 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); } for (auto &port : bit2cell[work->module][bit]) { if (work->children.count(std::get<0>(port)->name)) { HierDirtyFlags *child = work->children.at(std::get<0>(port)->name); SigBit child_bit = sigmaps.at(child->module)(SigBit(child->module->wire(std::get<1>(port)), std::get<2>(port))); log_assert(bit.wire && child_bit.wire); funct_declarations.push_back(util_set_bit(work->prefix + cid(child->hiername) + "." + cid(child_bit.wire->name), child_bit.wire->width, child_bit.offset, util_get_bit(work->prefix + cid(bit.wire->name), bit.wire->width, bit.offset))); child->set_dirty(child_bit); if (verbose) 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, log_id(std::get<0>(port)), work->log_prefix.c_str(), log_id(bit.wire), bit.offset); work->set_dirty(std::get<0>(port)); } } work->unset_dirty(bit); } } if (!work->dirty_cells.empty()) { Cell *cell = nullptr; for (auto c : work->dirty_cells) if (cell == nullptr || topoidx.at(cell) < topoidx.at(c)) cell = c; string hiername = work->log_prefix + "." + log_id(cell); if (verbose) 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); activated_cells.insert(hiername); eval_cell(work, cell); work->unset_dirty(cell); } } for (auto &child : work->children) eval_dirty(child.second); } } void eval_sticky_dirty(HierDirtyFlags *work) { Module *mod = work->module; for (Wire *w : mod->wires()) for (SigBit bit : SigSpec(w)) { SigBit canonical_bit = sigmaps.at(mod)(bit); if (canonical_bit == bit) continue; if (work->sticky_dirty_bits.count(canonical_bit) == 0) continue; if (bit.wire == nullptr || canonical_bit.wire == nullptr) continue; funct_declarations.push_back(util_set_bit(work->prefix + cid(bit.wire->name), bit.wire->width, bit.offset, util_get_bit(work->prefix + cid(canonical_bit.wire->name), canonical_bit.wire->width, canonical_bit.offset).c_str())); if (verbose) log(" Propagating alias %s.%s[%d] -> %s.%s[%d].\n", work->log_prefix.c_str(), log_id(canonical_bit.wire), canonical_bit.offset, work->log_prefix.c_str(), log_id(bit.wire), bit.offset); } work->sticky_dirty_bits.clear(); for (auto &child : work->children) eval_sticky_dirty(child.second); } void make_func(HierDirtyFlags *work, const string &func_name, const vector &preamble) { 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, cid(work->module->name))); funct_declarations.push_back("{"); for (auto &line : preamble) funct_declarations.push_back(line); eval_dirty(work); eval_sticky_dirty(work); funct_declarations.push_back("}"); log(" Activated %d cells (%d activated more than once).\n", GetSize(activated_cells), GetSize(reactivated_cells)); } void eval_init(HierDirtyFlags *work, vector &preamble) { Module *module = work->module; for (Wire *w : module->wires()) { if (w->attributes.count(ID::init)) { SigSpec sig = sigmaps.at(module)(w); Const val = w->attributes.at(ID::init); val.resize(GetSize(sig), State::Sx); for (int i = 0; i < GetSize(sig); i++) if (val[i] == State::S0 || val[i] == State::S1) { SigBit bit = sig[i]; preamble.push_back(util_set_bit(work->prefix + cid(bit.wire->name), bit.wire->width, bit.offset, val == State::S1 ? "true" : "false")); work->set_dirty(bit); } } for (SigBit bit : SigSpec(w)) { SigBit val = sigmaps.at(module)(bit); if (val == State::S0 || val == State::S1) preamble.push_back(util_set_bit(work->prefix + cid(bit.wire->name), bit.wire->width, bit.offset, val == State::S1 ? "true" : "false")); if (driven_bits.at(module).count(val) == 0) work->set_dirty(val); } } work->set_dirty(State::S0); work->set_dirty(State::S1); for (auto &child : work->children) eval_init(child.second, preamble); } void make_init_func(HierDirtyFlags *work) { vector preamble; eval_init(work, preamble); make_func(work, cid(work->module->name) + "_init", preamble); } void make_eval_func(HierDirtyFlags *work) { Module *mod = work->module; vector preamble; for (Wire *w : mod->wires()) { if (w->port_input) for (SigBit bit : sigmaps.at(mod)(w)) work->set_dirty(bit); } make_func(work, cid(work->module->name) + "_eval", preamble); } void make_tick_func(HierDirtyFlags* /* work */) { // FIXME } void run(Module *mod) { create_module_struct(mod); HierDirtyFlags work(mod, IdString(), nullptr, "state->", log_id(mod->name)); make_init_func(&work); make_eval_func(&work); make_tick_func(&work); } void write(std::ostream &f) { f << "#include " << std::endl; f << "#include " << std::endl; for (auto &line : signal_declarations) f << line << std::endl; for (auto &line : util_declarations) f << line << std::endl; for (auto &line : struct_declarations) f << line << std::endl; for (auto &line : funct_declarations) f << line << std::endl; } }; struct SimplecBackend : public Backend { SimplecBackend() : Backend("simplec", "convert design to simple C code") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_simplec [options] [filename]\n"); log("\n"); log("Write simple C code for simulating the design. The C code written can be used to\n"); log("simulate the design in a C environment, but the purpose of this command is to\n"); log("generate code that works well with C-based formal verification.\n"); log("\n"); log(" -verbose\n"); log(" this will print the recursive walk used to export the modules.\n"); log("\n"); log(" -i8, -i16, -i32, -i64\n"); log(" set the maximum integer bit width to use in the generated code.\n"); log("\n"); log("THIS COMMAND IS UNDER CONSTRUCTION\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { reserved_cids.clear(); id2cid.clear(); SimplecWorker worker(design); log_header(design, "Executing SIMPLEC backend.\n"); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-verbose") { worker.verbose = true; continue; } if (args[argidx] == "-i8") { worker.max_uintsize = 8; continue; } if (args[argidx] == "-i16") { worker.max_uintsize = 16; continue; } if (args[argidx] == "-i32") { worker.max_uintsize = 32; continue; } if (args[argidx] == "-i64") { worker.max_uintsize = 64; continue; } break; } extra_args(f, filename, args, argidx); Module *topmod = design->top_module(); if (topmod == nullptr) log_error("Current design has no top module.\n"); worker.run(topmod); worker.write(*f); } } SimplecBackend; PRIVATE_NAMESPACE_END yosys-0.65/backends/simplec/test00.sh000066400000000000000000000002361520057232300175130ustar00rootroot00000000000000#!/usr/bin/env bash set -ex ../../yosys -p 'synth -top test; write_simplec -verbose -i8 test00_uut.c' test00_uut.v clang -o test00_tb test00_tb.c ./test00_tb yosys-0.65/backends/simplec/test00_tb.c000066400000000000000000000041251520057232300200110ustar00rootroot00000000000000#include #include #include "test00_uut.c" uint32_t xorshift32() { static uint32_t x32 = 314159265; x32 ^= x32 << 13; x32 ^= x32 >> 17; x32 ^= x32 << 5; return x32; } int main() { struct test_state_t state; uint32_t a, b, c, x, y, z, w; bool first_eval = true; for (int i = 0; i < 10; i++) { a = xorshift32(); b = xorshift32(); c = xorshift32(); x = (a & b) | c; y = a & (b | c); z = a ^ b ^ c; w = z; state.a.value_7_0 = a; state.a.value_15_8 = a >> 8; state.a.value_23_16 = a >> 16; state.a.value_31_24 = a >> 24; state.b.value_7_0 = b; state.b.value_15_8 = b >> 8; state.b.value_23_16 = b >> 16; state.b.value_31_24 = b >> 24; state.c.value_7_0 = c; state.c.value_15_8 = c >> 8; state.c.value_23_16 = c >> 16; state.c.value_31_24 = c >> 24; if (first_eval) { first_eval = false; test_init(&state); } else { test_eval(&state); } uint32_t uut_x = 0; uut_x |= (uint32_t)state.x.value_7_0; uut_x |= (uint32_t)state.x.value_15_8 << 8; uut_x |= (uint32_t)state.x.value_23_16 << 16; uut_x |= (uint32_t)state.x.value_31_24 << 24; uint32_t uut_y = 0; uut_y |= (uint32_t)state.y.value_7_0; uut_y |= (uint32_t)state.y.value_15_8 << 8; uut_y |= (uint32_t)state.y.value_23_16 << 16; uut_y |= (uint32_t)state.y.value_31_24 << 24; uint32_t uut_z = 0; uut_z |= (uint32_t)state.z.value_7_0; uut_z |= (uint32_t)state.z.value_15_8 << 8; uut_z |= (uint32_t)state.z.value_23_16 << 16; uut_z |= (uint32_t)state.z.value_31_24 << 24; uint32_t uut_w = 0; uut_w |= (uint32_t)state.w.value_7_0; uut_w |= (uint32_t)state.w.value_15_8 << 8; uut_w |= (uint32_t)state.w.value_23_16 << 16; uut_w |= (uint32_t)state.w.value_31_24 << 24; printf("---\n"); printf("A: 0x%08x\n", a); printf("B: 0x%08x\n", b); printf("C: 0x%08x\n", c); printf("X: 0x%08x 0x%08x\n", x, uut_x); printf("Y: 0x%08x 0x%08x\n", y, uut_y); printf("Z: 0x%08x 0x%08x\n", z, uut_z); printf("W: 0x%08x 0x%08x\n", w, uut_w); assert(x == uut_x); assert(y == uut_y); assert(z == uut_z); assert(w == uut_w); } return 0; } yosys-0.65/backends/simplec/test00_uut.v000066400000000000000000000006031520057232300202410ustar00rootroot00000000000000module test(input [31:0] a, b, c, output [31:0] x, y, z, w); unit_x unit_x_inst (.a(a), .b(b), .c(c), .x(x)); unit_y unit_y_inst (.a(a), .b(b), .c(c), .y(y)); assign z = a ^ b ^ c, w = z; endmodule module unit_x(input [31:0] a, b, c, output [31:0] x); assign x = (a & b) | c; endmodule module unit_y(input [31:0] a, b, c, output [31:0] y); assign y = a & (b | c); endmodule yosys-0.65/backends/smt2/000077500000000000000000000000001520057232300152705ustar00rootroot00000000000000yosys-0.65/backends/smt2/.gitignore000066400000000000000000000000131520057232300172520ustar00rootroot00000000000000test_cells yosys-0.65/backends/smt2/Makefile.inc000066400000000000000000000042401520057232300175000ustar00rootroot00000000000000 OBJS += backends/smt2/smt2.o ifneq ($(CONFIG),mxe) ifneq ($(CONFIG),emcc) # MSYS targets support yosys-smtbmc, but require a launcher script ifeq ($(CONFIG),$(filter $(CONFIG),msys2 msys2-64)) TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc.exe $(PROGRAM_PREFIX)yosys-smtbmc-script.py TARGETS += $(PROGRAM_PREFIX)yosys-witness.exe $(PROGRAM_PREFIX)yosys-witness-script.py # Needed to find the Python interpreter for yosys-smtbmc scripts. # Override if necessary, it is only used for msys2 targets. PYTHON := $(shell cygpath -w -m $(PREFIX)/bin/python3) $(PROGRAM_PREFIX)yosys-smtbmc-script.py: backends/smt2/smtbmc.py $(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \ -e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@ $(PROGRAM_PREFIX)yosys-witness-script.py: backends/smt2/witness.py $(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \ -e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@ $(PROGRAM_PREFIX)yosys-smtbmc.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-smtbmc-script.py $(P) $(CXX) -DGUI=0 -O -s -o $@ $< $(PROGRAM_PREFIX)yosys-witness.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-witness-script.py $(P) $(CXX) -DGUI=0 -O -s -o $@ $< # Other targets else TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc $(PROGRAM_PREFIX)yosys-witness $(PROGRAM_PREFIX)yosys-smtbmc: backends/smt2/smtbmc.py $(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new $(Q) chmod +x $@.new $(Q) mv $@.new $@ $(PROGRAM_PREFIX)yosys-witness: backends/smt2/witness.py $(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new $(Q) chmod +x $@.new $(Q) mv $@.new $@ endif $(eval $(call add_share_file,share/python3,backends/smt2/smtio.py)) $(eval $(call add_share_file,share/python3,backends/smt2/ywio.py)) endif endif yosys-0.65/backends/smt2/example.v000066400000000000000000000003531520057232300171130ustar00rootroot00000000000000module main(input clk); reg [3:0] counter = 0; always @(posedge clk) begin if (counter == 10) counter <= 0; else counter <= counter + 1; end assert property (counter != 15); // assert property (counter <= 10); endmodule yosys-0.65/backends/smt2/example.ys000066400000000000000000000001761520057232300173040ustar00rootroot00000000000000read_verilog -formal example.v hierarchy; proc; opt; memory -nordff -nomap; opt -fast write_smt2 -bv -mem -wires example.smt2 yosys-0.65/backends/smt2/smt2.cc000066400000000000000000002167341520057232300165010ustar00rootroot00000000000000/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" #include "kernel/newcelltypes.h" #include "kernel/log.h" #include "kernel/mem.h" #include "libs/json11/json11.hpp" #include "kernel/utils.h" #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct Smt2Worker { NewCellTypes ct; SigMap sigmap; RTLIL::Module *module; bool bvmode, memmode, wiresmode, verbose, statebv, statedt, forallmode; dict &mod_stbv_width; int idcounter = 0, statebv_width = 0; std::vector decls, trans, hier, dtmembers; std::map bit_driver; std::set exported_cells, hiercells, hiercells_queue; pool recursive_cells, registers; std::vector memories; dict mem_cells; std::set memory_queue; pool clock_posedge, clock_negedge; vector ex_state_eq, ex_input_eq; std::map> fcache; std::map memarrays; std::map bvsizes; dict ids; bool is_smtlib2_module; const char *get_id(IdString n) { if (ids.count(n) == 0) { std::string str = log_id(n); for (int i = 0; i < GetSize(str); i++) { if (str[i] == '\\') str[i] = '/'; } ids[n] = strdup(str.c_str()); } return ids[n]; } template const char *get_id(T *obj) { return get_id(obj->name); } void makebits(std::string name, int width = 0, std::string comment = std::string()) { std::string decl_str; if (statebv) { if (width == 0) { 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, 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); } else { decl_str = stringf(" (|%s| (_ BitVec %d))", name, width); } } else { if (width == 0) { 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, get_id(module), width); } } if (!comment.empty()) decl_str += " ; " + comment; if (statedt) dtmembers.push_back(decl_str + "\n"); else decls.push_back(decl_str + "\n"); } Smt2Worker(RTLIL::Module *module, bool bvmode, bool memmode, bool wiresmode, bool verbose, bool statebv, bool statedt, bool forallmode, dict &mod_stbv_width, dict>> &mod_clk_cache) : ct(module->design), sigmap(module), module(module), bvmode(bvmode), memmode(memmode), wiresmode(wiresmode), verbose(verbose), statebv(statebv), statedt(statedt), forallmode(forallmode), mod_stbv_width(mod_stbv_width), is_smtlib2_module(module->has_attribute(ID::smtlib2_module)) { pool noclock; makebits(stringf("%s_is", get_id(module))); dict mem_dict; memories = Mem::get_all_memories(module); 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); mem.narrow(); mem_dict[mem.memid] = &mem; for (auto &port : mem.wr_ports) { if (port.clk_enable) { SigSpec clk = sigmap(port.clk); for (int i = 0; i < GetSize(clk); i++) { if (clk[i].wire == nullptr) continue; if (port.clk_polarity) clock_posedge.insert(clk[i]); else clock_negedge.insert(clk[i]); } } for (auto bit : sigmap(port.en)) noclock.insert(bit); for (auto bit : sigmap(port.addr)) noclock.insert(bit); for (auto bit : sigmap(port.data)) noclock.insert(bit); } for (auto &port : mem.rd_ports) { if (port.clk_enable) { SigSpec clk = sigmap(port.clk); for (int i = 0; i < GetSize(clk); i++) { if (clk[i].wire == nullptr) continue; if (port.clk_polarity) clock_posedge.insert(clk[i]); else clock_negedge.insert(clk[i]); } } for (auto bit : sigmap(port.en)) noclock.insert(bit); for (auto bit : sigmap(port.addr)) noclock.insert(bit); for (auto bit : sigmap(port.data)) noclock.insert(bit); Cell *driver = port.cell ? port.cell : mem.cell; for (auto bit : sigmap(port.data)) { if (bit_driver.count(bit)) log_error("Found multiple drivers for %s.\n", log_signal(bit)); bit_driver[bit] = driver; } } } for (auto cell : module->cells()) for (auto &conn : cell->connections()) { if (GetSize(conn.second) == 0) continue; // Handled above. if (cell->is_mem_cell()) { mem_cells[cell] = mem_dict[cell->parameters.at(ID::MEMID).decode_string()]; continue; } bool is_input = ct.cell_input(cell->type, conn.first); bool is_output = ct.cell_output(cell->type, conn.first); if (is_output && !is_input) for (auto bit : sigmap(conn.second)) { if (bit_driver.count(bit)) log_error("Found multiple drivers for %s.\n", log_signal(bit)); bit_driver[bit] = cell; } else if (is_output || !is_input) log_error("Unsupported or unknown directionality on port %s of cell %s.%s (%s).\n", log_id(conn.first), log_id(module), log_id(cell), log_id(cell->type)); if (cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_)) && conn.first.in(ID::CLK, ID::C)) { bool posedge = (cell->type == ID($_DFF_N_)) || (cell->type == ID($dff) && cell->getParam(ID::CLK_POLARITY).as_bool()); for (auto bit : sigmap(conn.second)) { if (posedge) clock_posedge.insert(bit); else clock_negedge.insert(bit); } } else if (mod_clk_cache.count(cell->type) && mod_clk_cache.at(cell->type).count(conn.first)) { for (auto bit : sigmap(conn.second)) { if (mod_clk_cache.at(cell->type).at(conn.first).first) clock_posedge.insert(bit); if (mod_clk_cache.at(cell->type).at(conn.first).second) clock_negedge.insert(bit); } } else { for (auto bit : sigmap(conn.second)) noclock.insert(bit); } } for (auto bit : noclock) { clock_posedge.erase(bit); clock_negedge.erase(bit); } for (auto wire : module->wires()) { auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk); if (gclk_attr != wire->attributes.end()) { if (gclk_attr->second == State::S1) clock_posedge.insert(sigmap(wire)); else if (gclk_attr->second == State::S0) clock_negedge.insert(sigmap(wire)); } } for (auto wire : module->wires()) { if (!wire->port_input || GetSize(wire) != 1) continue; SigBit bit = sigmap(wire); if (clock_posedge.count(bit)) mod_clk_cache[module->name][wire->name].first = true; if (clock_negedge.count(bit)) mod_clk_cache[module->name][wire->name].second = true; } } ~Smt2Worker() { for (auto &it : ids) free(it.second); ids.clear(); } const char *get_id(Module *m) { return get_id(m->name); } const char *get_id(Cell *c) { return get_id(c->name); } const char *get_id(Wire *w) { return get_id(w->name); } void register_bool(RTLIL::SigBit bit, int id) { if (verbose) log("%*s-> register_bool: %s %d\n", 2+2*GetSize(recursive_cells), "", log_signal(bit), id); sigmap.apply(bit); log_assert(fcache.count(bit) == 0); fcache[bit] = std::pair(id, -1); } void register_bv(RTLIL::SigSpec sig, int id) { if (verbose) log("%*s-> register_bv: %s %d\n", 2+2*GetSize(recursive_cells), "", log_signal(sig), id); log_assert(bvmode); sigmap.apply(sig); log_assert(bvsizes.count(id) == 0); bvsizes[id] = GetSize(sig); for (int i = 0; i < GetSize(sig); i++) { log_assert(fcache.count(sig[i]) == 0); fcache[sig[i]] = std::pair(id, i); } } void register_boolvec(RTLIL::SigSpec sig, int id) { if (verbose) log("%*s-> register_boolvec: %s %d\n", 2+2*GetSize(recursive_cells), "", log_signal(sig), id); log_assert(bvmode); sigmap.apply(sig); register_bool(sig[0], id); for (int i = 1; i < GetSize(sig); i++) sigmap.add(sig[i], RTLIL::State::S0); } std::string get_bool(RTLIL::SigBit bit, const char *state_name = "state") { 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 (fcache.count(bit) == 0) { if (verbose) log("%*s-> external bool: %s\n", 2+2*GetSize(recursive_cells), "", log_signal(bit)); makebits(stringf("%s#%d", get_id(module), idcounter), 0, log_signal(bit)); register_bool(bit, idcounter++); } auto f = fcache.at(bit); if (f.second >= 0) return stringf("(= ((_ extract %d %d) (|%s#%d| %s)) #b1)", f.second, f.second, get_id(module), f.first, state_name); return stringf("(|%s#%d| %s)", get_id(module), f.first, state_name); } std::string get_bool(RTLIL::SigSpec sig, const char *state_name = "state") { return get_bool(sig.as_bit(), state_name); } std::string get_bv(RTLIL::SigSpec sig, const char *state_name = "state") { log_assert(bvmode); sigmap.apply(sig); std::vector subexpr; SigSpec orig_sig; while (orig_sig != sig) { for (auto bit : sig) if (bit_driver.count(bit)) export_cell(bit_driver.at(bit)); orig_sig = sig; sigmap.apply(sig); } for (int i = 0, j = 1; i < GetSize(sig); i += j, j = 1) { if (sig[i].wire == nullptr) { while (i+j < GetSize(sig) && sig[i+j].wire == nullptr) j++; subexpr.push_back("#b"); for (int k = i+j-1; k >= i; k--) subexpr.back() += sig[k] == RTLIL::State::S1 ? "1" : "0"; continue; } 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))); continue; } if (fcache.count(sig[i])) { auto t1 = fcache.at(sig[i]); while (i+j < GetSize(sig)) { if (fcache.count(sig[i+j]) == 0) break; auto t2 = fcache.at(sig[i+j]); if (t1.first != t2.first) break; if (t1.second+j != t2.second) break; j++; } if (t1.second == 0 && j == bvsizes.at(t1.first)) subexpr.push_back(stringf("(|%s#%d| %s)", get_id(module), t1.first, state_name)); else subexpr.push_back(stringf("((_ extract %d %d) (|%s#%d| %s))", t1.second + j - 1, t1.second, get_id(module), t1.first, state_name)); continue; } std::set seen_bits = { sig[i] }; while (i+j < GetSize(sig) && sig[i+j].wire && !fcache.count(sig[i+j]) && !seen_bits.count(sig[i+j])) seen_bits.insert(sig[i+j]), j++; if (verbose) log("%*s-> external bv: %s\n", 2+2*GetSize(recursive_cells), "", log_signal(sig.extract(i, j))); for (auto bit : sig.extract(i, j)) log_assert(bit_driver.count(bit) == 0); makebits(stringf("%s#%d", get_id(module), idcounter), j, log_signal(sig.extract(i, j))); subexpr.push_back(stringf("(|%s#%d| %s)", get_id(module), idcounter, state_name)); register_bv(sig.extract(i, j), idcounter++); } if (GetSize(subexpr) > 1) { std::string expr = "", end_str = ""; for (int i = GetSize(subexpr)-1; i >= 0; i--) { if (i > 0) expr += " (concat", end_str += ")"; expr += " " + subexpr[i]; } return expr.substr(1) + end_str; } else { log_assert(GetSize(subexpr) == 1); return subexpr[0]; } } void export_gate(RTLIL::Cell *cell, std::string expr) { RTLIL::SigBit bit = sigmap(cell->getPort(ID::Y).as_bit()); std::string processed_expr; for (char ch : expr) { if (ch == 'A') processed_expr += get_bool(cell->getPort(ID::A)); else if (ch == 'B') processed_expr += get_bool(cell->getPort(ID::B)); else if (ch == 'C') processed_expr += get_bool(cell->getPort(ID::C)); else if (ch == 'D') processed_expr += get_bool(cell->getPort(ID::D)); else if (ch == 'S') processed_expr += get_bool(cell->getPort(ID::S)); else processed_expr += ch; } if (verbose) log("%*s-> import cell: %s\n", 2+2*GetSize(recursive_cells), "", log_id(cell)); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) Bool %s) ; %s\n", get_id(module), idcounter, get_id(module), processed_expr.c_str(), log_signal(bit))); register_bool(bit, idcounter++); recursive_cells.erase(cell); } void export_bvop(RTLIL::Cell *cell, std::string expr, char type = 0) { RTLIL::SigSpec sig_a, sig_b; RTLIL::SigSpec sig_y = sigmap(cell->getPort(ID::Y)); bool is_signed = type == 'U' ? false : cell->getParam(ID::A_SIGNED).as_bool(); int width = GetSize(sig_y); if (type == 's' || type == 'S' || type == 'd' || type == 'b') { if (type == 'b') width = GetSize(cell->getPort(ID::A)); else width = max(width, GetSize(cell->getPort(ID::A))); if (cell->hasPort(ID::B)) width = max(width, GetSize(cell->getPort(ID::B))); } if (cell->hasPort(ID::A)) { sig_a = cell->getPort(ID::A); sig_a.extend_u0(width, is_signed); } if (cell->hasPort(ID::B)) { sig_b = cell->getPort(ID::B); sig_b.extend_u0(width, (type == 'S') || (is_signed && !(type == 's'))); } std::string processed_expr; for (char ch : expr) { if (ch == 'A') processed_expr += get_bv(sig_a); else if (ch == 'B') processed_expr += get_bv(sig_b); else if (ch == 'P') processed_expr += get_bv(cell->getPort(ID::B)); else if (ch == 'S') processed_expr += get_bv(cell->getPort(ID::S)); else if (ch == 'L') processed_expr += is_signed ? "a" : "l"; else if (ch == 'U') processed_expr += is_signed ? "s" : "u"; else processed_expr += ch; } if (width != GetSize(sig_y) && type != 'b') 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)); if (type == 'b') { decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) Bool %s) ; %s\n", get_id(module), idcounter, get_id(module), processed_expr.c_str(), log_signal(sig_y))); register_boolvec(sig_y, idcounter++); } else { decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), idcounter, get_id(module), GetSize(sig_y), processed_expr.c_str(), log_signal(sig_y))); register_bv(sig_y, idcounter++); } recursive_cells.erase(cell); } void export_reduce(RTLIL::Cell *cell, std::string expr, bool identity_val) { RTLIL::SigSpec sig_y = sigmap(cell->getPort(ID::Y)); std::string processed_expr; for (char ch : expr) if (ch == 'A' || ch == 'B') { RTLIL::SigSpec sig = sigmap(cell->getPort(stringf("\\%c", ch))); for (auto bit : sig) processed_expr += " " + get_bool(bit); if (GetSize(sig) == 1) processed_expr += identity_val ? " true" : " false"; } else processed_expr += ch; if (verbose) log("%*s-> import cell: %s\n", 2+2*GetSize(recursive_cells), "", log_id(cell)); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) Bool %s) ; %s\n", get_id(module), idcounter, get_id(module), processed_expr.c_str(), log_signal(sig_y))); register_boolvec(sig_y, idcounter++); recursive_cells.erase(cell); } void export_cell(RTLIL::Cell *cell) { if (verbose) log("%*s=> export_cell %s (%s) [%s]\n", 2+2*GetSize(recursive_cells), "", log_id(cell), log_id(cell->type), exported_cells.count(cell) ? "old" : "new"); if (recursive_cells.count(cell)) log_error("Found logic loop in module %s! See cell %s.\n", get_id(module), get_id(cell)); if (exported_cells.count(cell)) return; exported_cells.insert(cell); recursive_cells.insert(cell); if (cell->type == ID($initstate)) { SigBit bit = sigmap(cell->getPort(ID::Y).as_bit()); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) Bool (|%s_is| state)) ; %s\n", get_id(module), idcounter, get_id(module), get_id(module), log_signal(bit))); register_bool(bit, idcounter++); recursive_cells.erase(cell); return; } if (cell->type.in(ID($_FF_), ID($_DFF_P_), ID($_DFF_N_))) { registers.insert(cell); SigBit q_bit = cell->getPort(ID::Q); if (q_bit.is_wire()) decls.push_back(witness_signal("reg", 1, 0, "", idcounter, q_bit.wire)); makebits(stringf("%s#%d", get_id(module), idcounter), 0, log_signal(cell->getPort(ID::Q))); register_bool(cell->getPort(ID::Q), idcounter++); recursive_cells.erase(cell); return; } if (cell->type == ID($_BUF_)) return export_gate(cell, "A"); if (cell->type == ID($_NOT_)) return export_gate(cell, "(not A)"); if (cell->type == ID($_AND_)) return export_gate(cell, "(and A B)"); if (cell->type == ID($_NAND_)) return export_gate(cell, "(not (and A B))"); if (cell->type == ID($_OR_)) return export_gate(cell, "(or A B)"); if (cell->type == ID($_NOR_)) return export_gate(cell, "(not (or A B))"); if (cell->type == ID($_XOR_)) return export_gate(cell, "(xor A B)"); if (cell->type == ID($_XNOR_)) return export_gate(cell, "(not (xor A B))"); if (cell->type == ID($_ANDNOT_)) return export_gate(cell, "(and A (not B))"); if (cell->type == ID($_ORNOT_)) return export_gate(cell, "(or A (not B))"); if (cell->type == ID($_MUX_)) return export_gate(cell, "(ite S B A)"); if (cell->type == ID($_NMUX_)) return export_gate(cell, "(not (ite S B A))"); if (cell->type == ID($_AOI3_)) return export_gate(cell, "(not (or (and A B) C))"); if (cell->type == ID($_OAI3_)) return export_gate(cell, "(not (and (or A B) C))"); if (cell->type == ID($_AOI4_)) return export_gate(cell, "(not (or (and A B) (and C D)))"); if (cell->type == ID($_OAI4_)) return export_gate(cell, "(not (and (or A B) (or C D)))"); // FIXME: $lut if (bvmode) { if (cell->type.in(ID($ff), ID($dff))) { registers.insert(cell); int smtoffset = 0; for (auto chunk : cell->getPort(ID::Q).chunks()) { if (chunk.is_wire()) decls.push_back(witness_signal("reg", chunk.width, chunk.offset, "", idcounter, chunk.wire, smtoffset)); smtoffset += chunk.width; } makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(ID::Q)), log_signal(cell->getPort(ID::Q))); register_bv(cell->getPort(ID::Q), idcounter++); recursive_cells.erase(cell); return; } if (cell->type.in(ID($anyconst), ID($anyseq), ID($anyinit), ID($allconst), ID($allseq))) { auto QY = cell->type == ID($anyinit) ? ID::Q : ID::Y; registers.insert(cell); 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)); 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()); } 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()); } bool init_only = cell->type.in(ID($anyconst), ID($anyinit), ID($allconst)); bool clk2fflogic = cell->type == ID($anyinit) && cell->get_bool_attribute(ID(clk2fflogic)); int smtoffset = 0; for (auto chunk : cell->getPort(clk2fflogic ? ID::D : QY).chunks()) { if (chunk.is_wire()) decls.push_back(witness_signal(init_only ? "init" : "seq", chunk.width, chunk.offset, "", idcounter, chunk.wire, smtoffset)); smtoffset += chunk.width; } makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(QY)), log_signal(cell->getPort(QY))); if (cell->type == ID($anyseq)) ex_input_eq.push_back(stringf(" (= (|%s#%d| state) (|%s#%d| other_state))", get_id(module), idcounter, get_id(module), idcounter)); register_bv(cell->getPort(QY), idcounter++); recursive_cells.erase(cell); return; } if (cell->type == ID($and)) return export_bvop(cell, "(bvand A B)"); if (cell->type == ID($or)) return export_bvop(cell, "(bvor A B)"); if (cell->type == ID($xor)) return export_bvop(cell, "(bvxor A B)"); if (cell->type == ID($xnor)) return export_bvop(cell, "(bvxnor A B)"); if (cell->type == ID($bweqx)) return export_bvop(cell, "(bvxnor A B)", 'U'); if (cell->type == ID($bwmux)) return export_bvop(cell, "(bvor (bvand A (bvnot S)) (bvand B S))", 'U'); if (cell->type == ID($shl)) return export_bvop(cell, "(bvshl A B)", 's'); if (cell->type == ID($shr)) return export_bvop(cell, "(bvlshr A B)", 's'); if (cell->type == ID($sshl)) return export_bvop(cell, "(bvshl A B)", 's'); if (cell->type == ID($sshr)) return export_bvop(cell, "(bvLshr A B)", 's'); if (cell->type.in(ID($shift), ID($shiftx))) { if (cell->getParam(ID::B_SIGNED).as_bool()) { return export_bvop(cell, stringf("(ite (bvsge P #b%0*d) " "(bvlshr A B) (bvshl A (bvneg B)))", GetSize(cell->getPort(ID::B)), 0), 'S'); // type 'S' sign extends B } else { return export_bvop(cell, "(bvlshr A B)", 's'); } } if (cell->type == ID($lt)) return export_bvop(cell, "(bvUlt A B)", 'b'); if (cell->type == ID($le)) return export_bvop(cell, "(bvUle A B)", 'b'); if (cell->type == ID($ge)) return export_bvop(cell, "(bvUge A B)", 'b'); if (cell->type == ID($gt)) return export_bvop(cell, "(bvUgt A B)", 'b'); if (cell->type == ID($ne)) return export_bvop(cell, "(distinct A B)", 'b'); if (cell->type == ID($nex)) return export_bvop(cell, "(distinct A B)", 'b'); if (cell->type == ID($eq)) return export_bvop(cell, "(= A B)", 'b'); if (cell->type == ID($eqx)) return export_bvop(cell, "(= A B)", 'b'); if (cell->type == ID($not)) return export_bvop(cell, "(bvnot A)"); if (cell->type == ID($pos)) return export_bvop(cell, "A"); if (cell->type == ID($neg)) return export_bvop(cell, "(bvneg A)"); if (cell->type == ID($add)) return export_bvop(cell, "(bvadd A B)"); if (cell->type == ID($sub)) return export_bvop(cell, "(bvsub A B)"); if (cell->type == ID($mul)) return export_bvop(cell, "(bvmul A B)"); if (cell->type == ID($div)) return export_bvop(cell, "(bvUdiv A B)", 'd'); // "rem" = truncating modulo if (cell->type == ID($mod)) return export_bvop(cell, "(bvUrem A B)", 'd'); // "mod" = flooring modulo if (cell->type == ID($modfloor)) { // bvumod doesn't exist because it's the same as bvurem if (cell->getParam(ID::A_SIGNED).as_bool()) { return export_bvop(cell, "(bvsmod A B)", 'd'); } else { return export_bvop(cell, "(bvurem A B)", 'd'); } } // "div" = flooring division if (cell->type == ID($divfloor)) { if (cell->getParam(ID::A_SIGNED).as_bool()) { // bvsdiv is truncating division, so we can't use it here. int width = max(GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::B))); width = max(width, GetSize(cell->getPort(ID::Y))); auto expr = stringf("(let (" "(a_neg (bvslt A #b%0*d)) " "(b_neg (bvslt B #b%0*d))) " "(let ((abs_a (ite a_neg (bvneg A) A)) " "(abs_b (ite b_neg (bvneg B) B))) " "(let ((u (bvudiv abs_a abs_b)) " "(adj (ite (= #b%0*d (bvurem abs_a abs_b)) #b%0*d #b%0*d))) " "(ite (= a_neg b_neg) u " "(bvneg (bvadd u adj))))))", width, 0, width, 0, width, 0, width, 0, width, 1); return export_bvop(cell, expr, 'd'); } else { return export_bvop(cell, "(bvudiv A B)", 'd'); } } if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool)) && 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), 'b'); } if (cell->type == ID($reduce_and)) return export_reduce(cell, "(and A)", true); if (cell->type == ID($reduce_or)) return export_reduce(cell, "(or A)", false); if (cell->type == ID($reduce_xor)) return export_reduce(cell, "(xor A)", false); if (cell->type == ID($reduce_xnor)) return export_reduce(cell, "(not (xor A))", false); if (cell->type == ID($reduce_bool)) return export_reduce(cell, "(or A)", false); if (cell->type == ID($logic_not)) return export_reduce(cell, "(not (or A))", false); if (cell->type == ID($logic_and)) return export_reduce(cell, "(and (or A) (or B))", false); if (cell->type == ID($logic_or)) return export_reduce(cell, "(or A B)", false); if (cell->type.in(ID($mux), ID($pmux))) { int width = GetSize(cell->getPort(ID::Y)); std::string processed_expr = get_bv(cell->getPort(ID::A)); RTLIL::SigSpec sig_b = cell->getPort(ID::B); RTLIL::SigSpec sig_s = cell->getPort(ID::S); get_bv(sig_b); 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]), get_bv(sig_b.extract(i*width, width)).c_str(), processed_expr.c_str()); if (verbose) log("%*s-> import cell: %s\n", 2+2*GetSize(recursive_cells), "", log_id(cell)); RTLIL::SigSpec sig = sigmap(cell->getPort(ID::Y)); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), idcounter, get_id(module), width, processed_expr.c_str(), log_signal(sig))); register_bv(sig, idcounter++); recursive_cells.erase(cell); return; } // FIXME: $slice $concat } if (memmode && cell->is_mem_cell()) { Mem *mem = mem_cells[cell]; if (memarrays.count(mem)) { recursive_cells.erase(cell); return; } int arrayid = idcounter++; memarrays[mem] = arrayid; int abits = max(1, ceil_log2(mem->size)); bool has_sync_wr = false; bool has_async_wr = false; for (auto &port : mem->wr_ports) { if (port.clk_enable) has_sync_wr = true; else has_async_wr = true; } if (has_async_wr && has_sync_wr) log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module)); decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(mem->memid), abits, mem->width, GetSize(mem->rd_ports), GetSize(mem->wr_ports), has_async_wr ? "async" : "sync")); decls.push_back(witness_memory(get_id(mem->memid), cell, mem)); string memstate; if (has_async_wr) { memstate = stringf("%s#%d#final", get_id(module), arrayid); } else { memstate = stringf("%s#%d#0", get_id(module), arrayid); } if (statebv) { makebits(memstate, mem->width*mem->size, get_id(mem->memid)); decls.push_back(stringf("(define-fun |%s_m %s| ((state |%s_s|)) (_ BitVec %d) (|%s| state))\n", get_id(module), get_id(mem->memid), get_id(module), mem->width*mem->size, memstate.c_str())); for (int i = 0; i < GetSize(mem->rd_ports); i++) { auto &port = mem->rd_ports[i]; SigSpec addr_sig = port.addr; addr_sig.extend_u0(abits); std::string addr = get_bv(addr_sig); if (port.clk_enable) log_error("Read port %d (%s) of memory %s.%s is clocked. This is not supported by \"write_smt2\"! " "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(port.data), log_id(mem->memid), log_id(module)); decls.push_back(stringf("(define-fun |%s_m:R%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); std::string read_expr = "#b"; for (int k = 0; k < mem->width; k++) read_expr += "0"; for (int k = 0; k < mem->size; k++) read_expr = stringf("(ite (= (|%s_m:R%dA %s| state) #b%s) ((_ extract %d %d) (|%s| state))\n %s)", get_id(module), i, get_id(mem->memid), Const(k+mem->start_offset, abits).as_string().c_str(), mem->width*(k+1)-1, mem->width*k, memstate.c_str(), read_expr.c_str()); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d)\n %s) ; %s\n", get_id(module), idcounter, get_id(module), mem->width, read_expr.c_str(), log_signal(port.data))); decls.push_back(stringf("(define-fun |%s_m:R%dD %s| ((state |%s_s|)) (_ BitVec %d) (|%s#%d| state))\n", get_id(module), i, get_id(mem->memid), get_id(module), mem->width, get_id(module), idcounter)); register_bv(port.data, idcounter++); } } else { if (statedt) dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", memstate.c_str(), abits, mem->width, get_id(mem->memid))); else decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", memstate.c_str(), get_id(module), abits, mem->width, get_id(mem->memid))); decls.push_back(stringf("(define-fun |%s_m %s| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) (|%s| state))\n", get_id(module), get_id(mem->memid), get_id(module), abits, mem->width, memstate.c_str())); for (int i = 0; i < GetSize(mem->rd_ports); i++) { auto &port = mem->rd_ports[i]; SigSpec addr_sig = port.addr; addr_sig.extend_u0(abits); std::string addr = get_bv(addr_sig); if (port.clk_enable) log_error("Read port %d (%s) of memory %s.%s is clocked. This is not supported by \"write_smt2\"! " "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(port.data), log_id(mem->memid), log_id(module)); decls.push_back(stringf("(define-fun |%s_m:R%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d) (select (|%s| state) (|%s_m:R%dA %s| state))) ; %s\n", get_id(module), idcounter, get_id(module), mem->width, memstate.c_str(), get_id(module), i, get_id(mem->memid), log_signal(port.data))); decls.push_back(stringf("(define-fun |%s_m:R%dD %s| ((state |%s_s|)) (_ BitVec %d) (|%s#%d| state))\n", get_id(module), i, get_id(mem->memid), get_id(module), mem->width, get_id(module), idcounter)); register_bv(port.data, idcounter++); } } memory_queue.insert(mem); recursive_cells.erase(cell); return; } Module *m = module->design->module(cell->type); if (m != nullptr) { decls.push_back(stringf("; yosys-smt2-cell %s %s\n", get_id(cell->type), get_id(cell->name))); decls.push_back(witness_cell(get_id(cell->name), cell)); string cell_state = stringf("(|%s_h %s| state)", get_id(module), get_id(cell->name)); for (auto &conn : cell->connections()) { if (GetSize(conn.second) == 0) continue; Wire *w = m->wire(conn.first); SigSpec sig = sigmap(conn.second); if (w->port_output && !w->port_input) { if (GetSize(w) > 1) { if (bvmode) { makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(w), log_signal(sig)); register_bv(sig, idcounter++); } else { for (int i = 0; i < GetSize(w); i++) { makebits(stringf("%s#%d", get_id(module), idcounter), 0, log_signal(sig[i])); register_bool(sig[i], idcounter++); } } } else { makebits(stringf("%s#%d", get_id(module), idcounter), 0, log_signal(sig)); register_bool(sig, idcounter++); } } } if (statebv) makebits(stringf("%s_h %s", get_id(module), get_id(cell->name)), mod_stbv_width.at(cell->type)); else if (statedt) dtmembers.push_back(stringf(" (|%s_h %s| |%s_s|)\n", get_id(module), get_id(cell->name), get_id(cell->type))); else decls.push_back(stringf("(declare-fun |%s_h %s| (|%s_s|) |%s_s|)\n", get_id(module), get_id(cell->name), get_id(module), get_id(cell->type))); hiercells.insert(cell); hiercells_queue.insert(cell); recursive_cells.erase(cell); return; } if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) { log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_smt2`.\n", log_id(cell->type), log_id(module), log_id(cell)); } if (cell->type.in(ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($dffsr), ID($dffsre)) || cell->type.str().substr(0, 5) == "$_DFF" || cell->type.str().substr(0, 7) == "$_ALDFF") { log_error("Unsupported cell type %s for cell %s.%s -- please run `async2sync; dffunmap` or `clk2fflogic` before `write_smt2`.\n", log_id(cell->type), log_id(module), log_id(cell)); } if (cell->type.in(ID($sr), ID($dlatch), ID($adlatch), ID($dlatchsr)) || cell->type.str().substr(0, 8) == "$_DLATCH" || cell->type.str().substr(0, 5) == "$_SR_") { log_error("Unsupported cell type %s for cell %s.%s -- please run `clk2fflogic` before `write_smt2`.\n", log_id(cell->type), log_id(module), log_id(cell)); } log_error("Unsupported cell type %s for cell %s.%s.\n", log_id(cell->type), log_id(module), log_id(cell)); } void verify_smtlib2_module() { if (!module->get_blackbox_attribute()) log_error("Module %s with smtlib2_module attribute must also have blackbox attribute.\n", log_id(module)); if (module->cells().size() > 0) log_error("Module %s with smtlib2_module attribute must not have any cells inside it.\n", log_id(module)); for (auto wire : module->wires()) if (!wire->port_id) log_error("Wire %s.%s must be input or output since module has smtlib2_module attribute.\n", log_id(module), log_id(wire)); } void run() { if (verbose) log("=> export logic driving outputs\n"); if (is_smtlib2_module) verify_smtlib2_module(); pool reg_bits; for (auto cell : module->cells()) if (cell->type.in(ID($ff), ID($dff), ID($_FF_), ID($_DFF_P_), ID($_DFF_N_), ID($anyinit))) { // not using sigmap -- we want the net directly at the dff output for (auto bit : cell->getPort(ID::Q)) reg_bits.insert(bit); } std::string smtlib2_inputs; std::vector smtlib2_decls; if (is_smtlib2_module) { for (auto wire : module->wires()) { if (!wire->port_input) continue; smtlib2_inputs += stringf("(|%s| (|%s_n %s| state))\n", get_id(wire), get_id(module), get_id(wire)); } } for (auto wire : module->wires()) { bool is_register = false; bool contains_clock = false; for (auto bit : SigSpec(wire)) { if (reg_bits.count(bit)) is_register = true; auto sig_bit = sigmap(bit); if (clock_posedge.count(sig_bit) || clock_negedge.count(sig_bit)) contains_clock = true; } bool is_smtlib2_comb_expr = wire->has_attribute(ID::smtlib2_comb_expr); if (is_smtlib2_comb_expr && !is_smtlib2_module) log_error("smtlib2_comb_expr is only valid in a module with the smtlib2_module attribute: wire %s.%s", log_id(module), log_id(wire)); if (wire->port_id || is_register || contains_clock || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) { RTLIL::SigSpec sig = sigmap(wire); std::vector comments; if (wire->port_input) comments.push_back(stringf("; yosys-smt2-input %s %d\n", get_id(wire), wire->width)); if (wire->port_output) comments.push_back(stringf("; yosys-smt2-output %s %d\n", get_id(wire), wire->width)); if (is_register) comments.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width)); if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) comments.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width)); if (contains_clock && GetSize(wire) == 1 && (clock_posedge.count(sig) || clock_negedge.count(sig))) comments.push_back(stringf("; yosys-smt2-clock %s%s%s\n", get_id(wire), clock_posedge.count(sig) ? " posedge" : "", clock_negedge.count(sig) ? " negedge" : "")); if (wire->port_input && contains_clock) { for (int i = 0; i < GetSize(sig); i++) { bool is_posedge = clock_posedge.count(sig[i]); bool is_negedge = clock_negedge.count(sig[i]); if (is_posedge != is_negedge) comments.push_back(witness_signal( is_posedge ? "posedge" : "negedge", 1, i, get_id(wire), -1, wire)); } } if (wire->port_input) comments.push_back(witness_signal("input", wire->width, 0, get_id(wire), -1, wire)); std::string smtlib2_comb_expr; if (is_smtlib2_comb_expr) { smtlib2_comb_expr = "(let (\n" + smtlib2_inputs + ")\n" + wire->get_string_attribute(ID::smtlib2_comb_expr) + "\n)"; if (wire->port_input || !wire->port_output) log_error("smtlib2_comb_expr is only valid on output: wire %s.%s", log_id(module), log_id(wire)); if (!bvmode && GetSize(sig) > 1) log_error("smtlib2_comb_expr is unsupported on multi-bit wires when -nobv is specified: wire %s.%s", log_id(module), log_id(wire)); comments.push_back(witness_signal("blackbox", wire->width, 0, get_id(wire), -1, wire)); } auto &out_decls = is_smtlib2_comb_expr ? smtlib2_decls : decls; if (bvmode && GetSize(sig) > 1) { std::string sig_bv = is_smtlib2_comb_expr ? smtlib2_comb_expr : get_bv(sig); if (!comments.empty()) out_decls.insert(out_decls.end(), comments.begin(), comments.end()); out_decls.push_back(stringf("(define-fun |%s_n %s| ((state |%s_s|)) (_ BitVec %d) %s)\n", get_id(module), get_id(wire), get_id(module), GetSize(sig), sig_bv.c_str())); if (wire->port_input) ex_input_eq.push_back(stringf(" (= (|%s_n %s| state) (|%s_n %s| other_state))", get_id(module), get_id(wire), get_id(module), get_id(wire))); } else { std::vector sig_bool; for (int i = 0; i < GetSize(sig); i++) { sig_bool.push_back(is_smtlib2_comb_expr ? smtlib2_comb_expr : get_bool(sig[i])); } if (!comments.empty()) out_decls.insert(out_decls.end(), comments.begin(), comments.end()); for (int i = 0; i < GetSize(sig); i++) { if (GetSize(sig) > 1) { out_decls.push_back(stringf("(define-fun |%s_n %s %d| ((state |%s_s|)) Bool %s)\n", get_id(module), get_id(wire), i, get_id(module), sig_bool[i].c_str())); if (wire->port_input) ex_input_eq.push_back(stringf(" (= (|%s_n %s %d| state) (|%s_n %s %d| other_state))", get_id(module), get_id(wire), i, get_id(module), get_id(wire), i)); } else { out_decls.push_back(stringf("(define-fun |%s_n %s| ((state |%s_s|)) Bool %s)\n", get_id(module), get_id(wire), get_id(module), sig_bool[i].c_str())); if (wire->port_input) ex_input_eq.push_back(stringf(" (= (|%s_n %s| state) (|%s_n %s| other_state))", get_id(module), get_id(wire), get_id(module), get_id(wire))); } } } } } decls.insert(decls.end(), smtlib2_decls.begin(), smtlib2_decls.end()); if (verbose) log("=> export logic associated with the initial state\n"); vector init_list; for (auto wire : module->wires()) if (wire->attributes.count(ID::init)) { if (is_smtlib2_module) log_error("init attribute not allowed on wires in module with smtlib2_module attribute: wire %s.%s", log_id(module), log_id(wire)); RTLIL::SigSpec sig = sigmap(wire); Const val = wire->attributes.at(ID::init); 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.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), mask.as_string(), val.as_string(), get_id(wire))); else 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]), val[i] == State::S1 ? "true" : "false", get_id(wire))); } } if (verbose) log("=> export logic driving asserts\n"); int assert_id = 0, assume_id = 0, cover_id = 0; vector assert_list, assume_list, cover_list; for (auto cell : module->cells()) { if (cell->type.in(ID($assert), ID($assume), ID($cover))) { int &id = cell->type == ID($assert) ? assert_id : cell->type == ID($assume) ? assume_id : cell->type == ID($cover) ? cover_id : *(int*)nullptr; char postfix = cell->type == ID($assert) ? 'a' : cell->type == ID($assume) ? 'u' : cell->type == ID($cover) ? 'c' : 0; string name_a = get_bool(cell->getPort(ID::A)); string name_en = get_bool(cell->getPort(ID::EN)); bool private_name = cell->name[0] == '$'; if (!private_name && cell->has_attribute(ID::hdlname)) { for (auto const &part : cell->get_hdlname_attribute()) { if (part == "_witness_") { private_name = true; break; } } } 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())); else decls.push_back(stringf("; yosys-smt2-%s %d %s\n", cell->type.c_str() + 1, id, get_id(cell))); if (cell->type == ID($cover)) decls.push_back(stringf("(define-fun |%s_%c %d| ((state |%s_s|)) Bool (and %s %s)) ; %s\n", get_id(module), postfix, id, get_id(module), name_a.c_str(), name_en.c_str(), get_id(cell))); else decls.push_back(stringf("(define-fun |%s_%c %d| ((state |%s_s|)) Bool (or %s (not %s))) ; %s\n", get_id(module), postfix, id, get_id(module), name_a.c_str(), name_en.c_str(), get_id(cell))); if (cell->type == ID($assert)) assert_list.push_back(stringf("(|%s_a %d| state)", get_id(module), id)); else if (cell->type == ID($assume)) assume_list.push_back(stringf("(|%s_u %d| state)", get_id(module), id)); id++; } } if (verbose) log("=> export logic driving hierarchical cells\n"); for (auto cell : module->cells()) if (module->design->module(cell->type) != nullptr) export_cell(cell); while (!hiercells_queue.empty()) { std::set queue; queue.swap(hiercells_queue); for (auto cell : queue) { string cell_state = stringf("(|%s_h %s| state)", get_id(module), get_id(cell->name)); Module *m = module->design->module(cell->type); log_assert(m != nullptr); hier.push_back(stringf(" (= (|%s_is| state) (|%s_is| %s))\n", get_id(module), get_id(cell->type), cell_state.c_str())); for (auto &conn : cell->connections()) { if (GetSize(conn.second) == 0) continue; Wire *w = m->wire(conn.first); 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)), 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]), get_id(cell->type), get_id(w), i, cell_state.c_str(), get_id(cell->type), get_id(w), i)); } } } } for (int iter = 1; !registers.empty() || !memory_queue.empty(); iter++) { pool this_regs; this_regs.swap(registers); if (verbose) log("=> export logic driving registers [iteration %d]\n", iter); for (auto cell : this_regs) { if (cell->type.in(ID($_FF_), ID($_DFF_P_), ID($_DFF_N_))) { 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, 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, 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, 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)), get_bv(cell->getPort(ID::Y), "other_state"))); } } std::set this_mems; this_mems.swap(memory_queue); for (auto mem : this_mems) { int arrayid = memarrays.at(mem); int abits = max(1, ceil_log2(mem->size)); bool has_sync_wr = false; bool has_async_wr = false; for (auto &port : mem->wr_ports) { if (port.clk_enable) has_sync_wr = true; else has_async_wr = true; } string initial_memstate, final_memstate; if (has_async_wr) { log_assert(!has_sync_wr); initial_memstate = stringf("%s#%d#0", get_id(module), arrayid); final_memstate = stringf("%s#%d#final", get_id(module), arrayid); } if (statebv) { if (has_async_wr) { makebits(final_memstate, mem->width*mem->size, get_id(mem->memid)); } for (int i = 0; i < GetSize(mem->wr_ports); i++) { auto &port = mem->wr_ports[i]; SigSpec addr_sig = port.addr; addr_sig.extend_u0(abits); std::string addr = get_bv(addr_sig); std::string data = get_bv(port.data); std::string mask = get_bv(port.en); decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(mem->memid)); decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), mem->width, data.c_str(), log_signal(port.data))); data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(mem->memid)); decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), mem->width, mask.c_str(), log_signal(port.en))); mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(mem->memid)); std::string data_expr; for (int k = mem->size-1; k >= 0; k--) { std::string new_data = stringf("(bvor (bvand %s %s) (bvand ((_ extract %d %d) (|%s#%d#%d| state)) (bvnot %s)))", data.c_str(), mask.c_str(), mem->width*(k+1)-1, mem->width*k, get_id(module), arrayid, i, mask.c_str()); data_expr += stringf("\n (ite (= %s #b%s) %s ((_ extract %d %d) (|%s#%d#%d| state)))", addr.c_str(), Const(k+mem->start_offset, abits).as_string().c_str(), new_data.c_str(), mem->width*(k+1)-1, mem->width*k, get_id(module), arrayid, i); } decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (_ BitVec %d) (concat%s)) ; %s\n", get_id(module), arrayid, i+1, get_id(module), mem->width*mem->size, data_expr.c_str(), get_id(mem->memid))); } } else { if (has_async_wr) { if (statedt) dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", initial_memstate.c_str(), abits, mem->width, get_id(mem->memid))); else decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n", initial_memstate.c_str(), get_id(module), abits, mem->width, get_id(mem->memid))); } for (int i = 0; i < GetSize(mem->wr_ports); i++) { auto &port = mem->wr_ports[i]; SigSpec addr_sig = port.addr; addr_sig.extend_u0(abits); std::string addr = get_bv(addr_sig); std::string data = get_bv(port.data); std::string mask = get_bv(port.en); decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig))); addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(mem->memid)); decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), mem->width, data.c_str(), log_signal(port.data))); data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(mem->memid)); decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n", get_id(module), i, get_id(mem->memid), get_id(module), mem->width, mask.c_str(), log_signal(port.en))); mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(mem->memid)); data = stringf("(bvor (bvand %s %s) (bvand (select (|%s#%d#%d| state) %s) (bvnot %s)))", data.c_str(), mask.c_str(), get_id(module), arrayid, i, addr.c_str(), mask.c_str()); string empty_mask(mem->width, '0'); decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) " "(ite (= %s #b%s) (|%s#%d#%d| state) (store (|%s#%d#%d| state) %s %s))) ; %s\n", get_id(module), arrayid, i+1, get_id(module), abits, mem->width, mask.c_str(), empty_mask.c_str(), get_id(module), arrayid, i, get_id(module), arrayid, i, addr.c_str(), data.c_str(), get_id(mem->memid))); } } 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, 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, final_memstate, get_id(mem->memid))); Const init_data = mem->get_init_data(); for (int i = 0; i < mem->size; i++) { if (i*mem->width >= GetSize(init_data)) break; Const initword = init_data.extract(i*mem->width, mem->width, State::Sx); Const initmask = initword; bool gen_init_constr = false; for (int k = 0; k < GetSize(initword); k++) { if (initword[k] == State::S0 || initword[k] == State::S1) { gen_init_constr = true; initmask.set(k, State::S1); } else { initmask.set(k, State::S0); initword.set(k, State::S0); } } if (gen_init_constr) { if (statebv) /* FIXME */; else init_list.push_back(stringf("(= (bvand (select (|%s#%d#0| state) #b%s) #b%s) #b%s) ; %s[%d]", get_id(module), arrayid, Const(i, abits).as_string().c_str(), initmask.as_string().c_str(), initword.as_string().c_str(), get_id(mem->memid), i)); } } } } if (verbose) log("=> finalizing SMT2 representation of %s.\n", log_id(module)); for (auto c : hiercells) { assert_list.push_back(stringf("(|%s_a| (|%s_h %s| state))", get_id(c->type), get_id(module), get_id(c->name))); assume_list.push_back(stringf("(|%s_u| (|%s_h %s| state))", get_id(c->type), get_id(module), get_id(c->name))); init_list.push_back(stringf("(|%s_i| (|%s_h %s| state))", get_id(c->type), get_id(module), get_id(c->name))); hier.push_back(stringf(" (|%s_h| (|%s_h %s| state))\n", get_id(c->type), get_id(module), get_id(c->name))); trans.push_back(stringf(" (|%s_t| (|%s_h %s| state) (|%s_h %s| next_state))\n", get_id(c->type), get_id(module), get_id(c->name), get_id(module), get_id(c->name))); ex_state_eq.push_back(stringf("(|%s_ex_state_eq| (|%s_h %s| state) (|%s_h %s| other_state))\n", get_id(c->type), get_id(module), get_id(c->name), get_id(module), get_id(c->name))); } if (forallmode) { string expr = ex_state_eq.empty() ? "true" : "(and"; if (!ex_state_eq.empty()) { if (GetSize(ex_state_eq) == 1) { expr = "\n " + ex_state_eq.front() + "\n"; } else { for (auto &str : ex_state_eq) expr += stringf("\n %s", str); expr += "\n)"; } } decls.push_back(stringf("(define-fun |%s_ex_state_eq| ((state |%s_s|) (other_state |%s_s|)) Bool %s)\n", get_id(module), get_id(module), get_id(module), expr.c_str())); expr = ex_input_eq.empty() ? "true" : "(and"; if (!ex_input_eq.empty()) { if (GetSize(ex_input_eq) == 1) { expr = "\n " + ex_input_eq.front() + "\n"; } else { for (auto &str : ex_input_eq) expr += stringf("\n %s", str); expr += "\n)"; } } decls.push_back(stringf("(define-fun |%s_ex_input_eq| ((state |%s_s|) (other_state |%s_s|)) Bool %s)\n", get_id(module), get_id(module), get_id(module), expr.c_str())); } string assert_expr = assert_list.empty() ? "true" : "(and"; if (!assert_list.empty()) { if (GetSize(assert_list) == 1) { assert_expr = "\n " + assert_list.front() + "\n"; } else { for (auto &str : assert_list) assert_expr += stringf("\n %s", str); assert_expr += "\n)"; } } decls.push_back(stringf("(define-fun |%s_a| ((state |%s_s|)) Bool %s)\n", get_id(module), get_id(module), assert_expr.c_str())); string assume_expr = assume_list.empty() ? "true" : "(and"; if (!assume_list.empty()) { if (GetSize(assume_list) == 1) { assume_expr = "\n " + assume_list.front() + "\n"; } else { for (auto &str : assume_list) assume_expr += stringf("\n %s", str); assume_expr += "\n)"; } } decls.push_back(stringf("(define-fun |%s_u| ((state |%s_s|)) Bool %s)\n", get_id(module), get_id(module), assume_expr.c_str())); string init_expr = init_list.empty() ? "true" : "(and"; if (!init_list.empty()) { if (GetSize(init_list) == 1) { init_expr = "\n " + init_list.front() + "\n"; } else { for (auto &str : init_list) init_expr += stringf("\n %s", str); init_expr += "\n)"; } } decls.push_back(stringf("(define-fun |%s_i| ((state |%s_s|)) Bool %s)\n", get_id(module), get_id(module), init_expr.c_str())); } void write(std::ostream &f) { f << stringf("; yosys-smt2-module %s\n", get_id(module)); if (statebv) { f << stringf("(define-sort |%s_s| () (_ BitVec %d))\n", get_id(module), statebv_width); mod_stbv_width[module->name] = statebv_width; } else if (statedt) { f << stringf("(declare-datatype |%s_s| ((|%s_mk|\n", get_id(module), get_id(module)); for (auto it : dtmembers) f << it; f << stringf(")))\n"); } else f << stringf("(declare-sort |%s_s| 0)\n", get_id(module)); for (auto it : decls) f << it; f << stringf("(define-fun |%s_h| ((state |%s_s|)) Bool ", get_id(module), get_id(module)); if (GetSize(hier) > 1) { f << "(and\n"; for (auto it : hier) f << it; f << "))\n"; } else if (GetSize(hier) == 1) f << "\n" + hier.front() + ")\n"; else f << "true)\n"; f << stringf("(define-fun |%s_t| ((state |%s_s|) (next_state |%s_s|)) Bool ", get_id(module), get_id(module), get_id(module)); if (GetSize(trans) > 1) { f << "(and\n"; for (auto it : trans) f << it; f << "))"; } else if (GetSize(trans) == 1) f << "\n" + trans.front() + ")"; else f << "true)"; f << stringf(" ; end of module %s\n", get_id(module)); } template static std::vector witness_path(T *obj) { std::vector path; if (obj->name.isPublic()) { auto hdlname = obj->get_string_attribute(ID::hdlname); for (auto token : split_tokens(hdlname)) path.push_back("\\" + token); } if (path.empty()) path.push_back(obj->name.str()); return path; } std::string witness_signal(const char *type, int width, int offset, const std::string &smtname, int smtid, RTLIL::Wire *wire, int smtoffset = 0) { std::vector hiername; const char *wire_name = wire->name.c_str(); if (wire_name[0] == '\\') { auto hdlname = wire->get_string_attribute(ID::hdlname); for (auto token : split_tokens(hdlname)) hiername.push_back("\\" + token); } if (hiername.empty()) hiername.push_back(wire->name.str()); std::string line = "; yosys-smt2-witness "; (json11::Json { json11::Json::object { { "type", type }, { "offset", offset }, { "width", width }, { "smtname", smtname.empty() ? json11::Json(smtid) : json11::Json(smtname) }, { "smtoffset", smtoffset }, { "path", witness_path(wire) }, }}).dump(line); line += "\n"; return line; } std::string witness_cell(const char *smtname, RTLIL::Cell *cell) { std::string line = "; yosys-smt2-witness "; (json11::Json {json11::Json::object { { "type", "cell" }, { "smtname", smtname }, { "path", witness_path(cell) }, }}).dump(line); line += "\n"; return line; } std::string witness_memory(const char *smtname, RTLIL::Cell *cell, Mem *mem) { json11::Json::array uninitialized; auto init_data = mem->get_init_data(); int cursor = 0; while (cursor < init_data.size()) { while (cursor < init_data.size() && init_data[cursor] != State::Sx) cursor++; int offset = cursor; while (cursor < init_data.size() && init_data[cursor] == State::Sx) cursor++; int width = cursor - offset; if (width) uninitialized.push_back(json11::Json::object { {"width", width}, {"offset", offset}, }); } std::string line = "; yosys-smt2-witness "; (json11::Json { json11::Json::object { { "type", "mem" }, { "width", mem->width }, { "size", mem->size }, { "rom", mem->wr_ports.empty() }, { "statebv", statebv }, { "smtname", smtname }, { "uninitialized", uninitialized }, { "path", witness_path(cell) }, }}).dump(line); line += "\n"; return line; } }; struct Smt2Backend : public Backend { Smt2Backend() : Backend("smt2", "write design to SMT-LIBv2 file") { } void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_smt2 [options] [filename]\n"); log("\n"); log("Write a SMT-LIBv2 [1] description of the current design. For a module with name\n"); log("'' this will declare the sort '_s' (state of the module) and will\n"); log("define and declare functions operating on that state.\n"); log("\n"); log("The following SMT2 functions are generated for a module with name ''.\n"); log("Some declarations/definitions are printed with a special comment. A prover\n"); log("using the SMT2 files can use those comments to collect all relevant metadata\n"); log("about the design.\n"); log("\n"); log(" ; yosys-smt2-module \n"); log(" (declare-sort |_s| 0)\n"); log(" The sort representing a state of module .\n"); log("\n"); log(" (define-fun |_h| ((state |_s|)) Bool (...))\n"); log(" This function must be asserted for each state to establish the\n"); log(" design hierarchy.\n"); log("\n"); log(" ; yosys-smt2-input \n"); log(" ; yosys-smt2-output \n"); log(" ; yosys-smt2-register \n"); log(" ; yosys-smt2-wire \n"); log(" (define-fun |_n | (|_s|) (_ BitVec ))\n"); log(" (define-fun |_n | (|_s|) Bool)\n"); log(" For each port, register, and wire with the 'keep' attribute set an\n"); log(" accessor function is generated. Single-bit wires are returned as Bool,\n"); log(" multi-bit wires as BitVec.\n"); log("\n"); log(" ; yosys-smt2-cell \n"); log(" (declare-fun |_h | (|_s|) |_s|)\n"); log(" There is a function like that for each hierarchical instance. It\n"); log(" returns the sort that represents the state of the sub-module that\n"); log(" implements the instance.\n"); log("\n"); log(" (declare-fun |_is| (|_s|) Bool)\n"); log(" This function must be asserted 'true' for initial states, and 'false'\n"); log(" otherwise.\n"); log("\n"); log(" (define-fun |_i| ((state |_s|)) Bool (...))\n"); log(" This function must be asserted 'true' for initial states. For\n"); log(" non-initial states it must be left unconstrained.\n"); log("\n"); log(" (define-fun |_t| ((state |_s|) (next_state |_s|)) Bool (...))\n"); log(" This function evaluates to 'true' if the states 'state' and\n"); log(" 'next_state' form a valid state transition.\n"); log("\n"); log(" (define-fun |_a| ((state |_s|)) Bool (...))\n"); log(" This function evaluates to 'true' if all assertions hold in the state.\n"); log("\n"); log(" (define-fun |_u| ((state |_s|)) Bool (...))\n"); log(" This function evaluates to 'true' if all assumptions hold in the state.\n"); log("\n"); log(" ; yosys-smt2-assert \n"); log(" (define-fun |_a | ((state |_s|)) Bool (...))\n"); log(" Each $assert cell is converted into one of this functions. The function\n"); log(" evaluates to 'true' if the assert statement holds in the state.\n"); log("\n"); log(" ; yosys-smt2-assume \n"); log(" (define-fun |_u | ((state |_s|)) Bool (...))\n"); log(" Each $assume cell is converted into one of this functions. The function\n"); log(" evaluates to 'true' if the assume statement holds in the state.\n"); log("\n"); log(" ; yosys-smt2-cover \n"); log(" (define-fun |_c | ((state |_s|)) Bool (...))\n"); log(" Each $cover cell is converted into one of this functions. The function\n"); log(" evaluates to 'true' if the cover statement is activated in the state.\n"); log("\n"); log("Options:\n"); log("\n"); log(" -verbose\n"); log(" this will print the recursive walk used to export the modules.\n"); log("\n"); log(" -stbv\n"); log(" Use a BitVec sort to represent a state instead of an uninterpreted\n"); log(" sort. As a side-effect this will prevent use of arrays to model\n"); log(" memories.\n"); log("\n"); log(" -stdt\n"); log(" Use SMT-LIB 2.6 style datatypes to represent a state instead of an\n"); log(" uninterpreted sort.\n"); log("\n"); log(" -nobv\n"); log(" disable support for BitVec (FixedSizeBitVectors theory). without this\n"); log(" option multi-bit wires are represented using the BitVec sort and\n"); log(" support for coarse grain cells (incl. arithmetic) is enabled.\n"); log("\n"); log(" -nomem\n"); log(" disable support for memories (via ArraysEx theory). this option is\n"); log(" implied by -nobv. only $mem cells without merged registers in\n"); log(" read ports are supported. call \"memory\" with -nordff to make sure\n"); log(" that no registers are merged into $mem read ports. '_m' functions\n"); log(" will be generated for accessing the arrays that are used to represent\n"); log(" memories.\n"); log("\n"); log(" -wires\n"); log(" create '_n' functions for all public wires. by default only ports,\n"); log(" registers, and wires with the 'keep' attribute are exported.\n"); log("\n"); log(" -tpl \n"); log(" use the given template file. the line containing only the token '%%%%'\n"); log(" is replaced with the regular output of this command.\n"); log("\n"); log(" -solver-option PR preview limitations.' html_theme_options["light_css_variables"]["color-announcement-background"] = "var(--color-admonition-title-background--caution)" html_theme_options["light_css_variables"]["color-announcement-text"] = "var(--color-content-foreground)" # Ensure that autosectionlabel will produce unique names autosectionlabel_prefix_document = True # include todos for previews extensions.append('sphinx.ext.todo') # set version if os.getenv("READTHEDOCS"): rtds_version = os.getenv("READTHEDOCS_VERSION") if rtds_version == "latest": release = yosys_ver + "-dev" todo_include_todos = False elif rtds_version.startswith("docs"): release = rtds_version todo_include_todos = True else: release = yosys_ver todo_include_todos = False elif os.getenv("YOSYS_DOCS_RELEASE") is not None: release = yosys_ver todo_include_todos = False else: release = yosys_ver todo_include_todos = True # assign figure numbers numfig = True bibtex_bibfiles = ['literature.bib'] latex_elements = { 'releasename': 'Version', 'preamble': r''' \pdfinfoomitdate 1 \pdfsuppressptexinfo 1 \pdftrailerid{} \usepackage{lmodern} \usepackage{comment} ''' } # custom cmd-ref parsing/linking sys.path += [os.path.dirname(__file__) + "/../"] extensions.append('util.custom_directives') # use autodocs extensions.append('sphinx.ext.autodoc') extensions.append('util.cell_documenter') cells_json = Path(__file__).parent / 'generated' / 'cells.json' extensions.append('util.cmd_documenter') cmds_json = Path(__file__).parent / 'generated' / 'cmds.json' from sphinx.application import Sphinx def setup(app: Sphinx) -> None: from util.RtlilLexer import RtlilLexer app.add_lexer("RTLIL", RtlilLexer) try: from furo_ys.lexers.YoscryptLexer import YoscryptLexer app.add_lexer("yoscrypt", YoscryptLexer) except ModuleNotFoundError: from pygments.lexers.special import TextLexer app.add_lexer("yoscrypt", TextLexer) yosys-0.65/docs/source/getting_started/000077500000000000000000000000001520057232300202505ustar00rootroot00000000000000yosys-0.65/docs/source/getting_started/example_synth.rst000066400000000000000000001005041520057232300236620ustar00rootroot00000000000000Synthesis starter ----------------- This page will be a guided walkthrough of the prepackaged iCE40 FPGA synthesis script - `synth_ice40`. We will take a simple design through each step, looking at the commands being called and what they do to the design. While `synth_ice40` is specific to the iCE40 platform, most of the operations we will be discussing are common across the majority of FPGA synthesis scripts. Thus, this document will provide a good foundational understanding of how synthesis in Yosys is performed, regardless of the actual architecture being used. .. seealso:: Advanced usage docs for :doc:`/using_yosys/synthesis/synth` Demo design ~~~~~~~~~~~ .. role:: yoscrypt(code) :language: yoscrypt First, let's quickly look at the design we'll be synthesizing: .. todo:: reconsider including the whole (~77 line) design like this .. literalinclude:: /code_examples/fifo/fifo.v :language: Verilog :linenos: :caption: :file:`fifo.v` :name: fifo-v .. todo:: fifo.v description While the open source `read_verilog` frontend generally does a pretty good job at processing valid Verilog input, it does not provide very good error handling or reporting. Using an external tool such as `verilator`_ before running Yosys is highly recommended. We can quickly check the Verilog syntax of our design by calling ``verilator --lint-only fifo.v``. .. _verilator: https://www.veripool.org/verilator/ Loading the design ~~~~~~~~~~~~~~~~~~ Let's load the design into Yosys. From the command line, we can call ``yosys fifo.v``. This will open an interactive Yosys shell session and immediately parse the code from :ref:`fifo-v` and convert it into an Abstract Syntax Tree (AST). If you are interested in how this happens, there is more information in the document, :doc:`/yosys_internals/flow/verilog_frontend`. For now, suffice it to say that we do this to simplify further processing of the design. You should see something like the following: .. literalinclude:: /code_examples/fifo/fifo.out :language: console :start-at: $ yosys fifo.v :end-before: echo on .. seealso:: Advanced usage docs for :doc:`/using_yosys/more_scripting/load_design` Elaboration ~~~~~~~~~~~ Now that we are in the interactive shell, we can call Yosys commands directly. Our overall goal is to call :yoscrypt:`synth_ice40 -top fifo`, but for now we can run each of the commands individually for a better sense of how each part contributes to the flow. We will also start with just a single module; ``addr_gen``. At the bottom of the `help` output for `synth_ice40` is the complete list of commands called by this script. Let's start with the section labeled ``begin``: .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: begin: :end-before: flatten: :dedent: :caption: ``begin`` section :name: synth_begin :yoscrypt:`read_verilog -D ICE40_HX -lib -specify +/ice40/cells_sim.v` loads the iCE40 cell models which allows us to include platform specific IP blocks in our design. PLLs are a common example of this, where we might need to reference ``SB_PLL40_CORE`` directly rather than being able to rely on mapping passes later. Since our simple design doesn't use any of these IP blocks, we can skip this command for now. Because these cell models will also be needed once we start mapping to hardware we will still need to load them later. .. note:: ``+/`` is a dynamic reference to the Yosys ``share`` directory. By default, this is ``/usr/local/share/yosys``. If using a locally built version of Yosys from the source directory, this will be the ``share`` folder in the same directory. .. _addr_gen_example: The addr_gen module ^^^^^^^^^^^^^^^^^^^ Since we're just getting started, let's instead begin with :yoscrypt:`hierarchy -top addr_gen`. This command declares that the top level module is ``addr_gen``, and everything else can be discarded. .. literalinclude:: /code_examples/fifo/fifo.v :language: Verilog :start-at: module addr_gen :end-at: endmodule //addr_gen :lineno-match: :caption: ``addr_gen`` module source :name: addr_gen-v .. note:: `hierarchy` should always be the first command after the design has been read. By specifying the top module, `hierarchy` will also set the ``(* top *)`` attribute on it. This is used by other commands that need to know which module is the top. .. use doscon for a console-like display that supports the `yosys> [command]` format. .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: yosys> hierarchy -top addr_gen :end-before: yosys> select :caption: :yoscrypt:`hierarchy -top addr_gen` output :name: hierarchy_output Our ``addr_gen`` circuit now looks like this: .. figure:: /_images/code_examples/fifo/addr_gen_hier.* :class: width-helper invert-helper :name: addr_gen_hier ``addr_gen`` module after `hierarchy` Simple operations like ``addr + 1`` and ``addr == MAX_DATA-1`` can be extracted from our ``always @`` block in :ref:`addr_gen-v`. This gives us the highlighted `$add` and `$eq` cells we see. But control logic (like the ``if .. else``) and memory elements (like the ``addr <= 0``) are not so straightforward. These get put into "processes", shown in the schematic as ``PROC``. Note how the second line refers to the line numbers of the start/end of the corresponding ``always @`` block. In the case of an ``initial`` block, we instead see the ``PROC`` referring to line 0. To handle these, let us now introduce the next command: :cmd:title:`proc`. `proc` is a macro command like `synth_ice40`. Rather than modifying the design directly, it instead calls a series of other commands. In the case of `proc`, these sub-commands work to convert the behavioral logic of processes into multiplexers and registers. Let's see what happens when we run it. For now, we will call :yoscrypt:`proc -noopt` to prevent some automatic optimizations which would normally happen. .. figure:: /_images/code_examples/fifo/addr_gen_proc.* :class: width-helper invert-helper :name: addr_gen_proc ``addr_gen`` module after :yoscrypt:`proc -noopt` There are now a few new cells from our ``always @``, which have been highlighted. The ``if`` statements are now modeled with `$mux` cells, while the register uses an `$adff` cell. If we look at the terminal output we can also see all of the different ``proc_*`` commands being called. We will look at each of these in more detail in :doc:`/using_yosys/synthesis/proc`. Notice how in the top left of :ref:`addr_gen_proc` we have a floating wire, generated from the initial assignment of 0 to the ``addr`` wire. However, this initial assignment is not synthesizable, so this will need to be cleaned up before we can generate the physical hardware. We can do this now by calling `clean`. We're also going to call `opt_expr` now, which would normally be called at the end of `proc`. We can call both commands at the same time by separating them with a colon and space: :yoscrypt:`opt_expr; clean`. .. figure:: /_images/code_examples/fifo/addr_gen_clean.* :class: width-helper invert-helper :name: addr_gen_clean ``addr_gen`` module after :yoscrypt:`opt_expr; clean` You may also notice that the highlighted `$eq` cell input of ``255`` has changed to ``8'11111111``. Constant values are presented in the format ``'``, with 32-bit values instead using the decimal number. This indicates that the constant input has been reduced from 32-bit wide to 8-bit wide. This is a side-effect of running `opt_expr`, which performs constant folding and simple expression rewriting. For more on why this happens, refer to :doc:`/using_yosys/synthesis/opt` and the :ref:`section on opt_expr `. .. note:: :cmd:title:`clean` can also be called with two semicolons after any command, for example we could have called :yoscrypt:`opt_expr;;` instead of :yoscrypt:`opt_expr; clean`. You may notice some scripts will end each line with ``;;``. It is beneficial to run `clean` before inspecting intermediate products to remove disconnected parts of the circuit which have been left over, and in some cases can reduce the processing required in subsequent commands. .. todo:: consider a brief glossary for terms like adff .. seealso:: Advanced usage docs for - :doc:`/using_yosys/synthesis/proc` - :doc:`/using_yosys/synthesis/opt` The full example ^^^^^^^^^^^^^^^^ Let's now go back and check on our full design by using :yoscrypt:`hierarchy -check -top fifo`. By passing the ``-check`` option there we are also telling the `hierarchy` command that if the design includes any non-blackbox modules without an implementation it should return an error. Note that if we tried to run this command now then we would get an error. This is because we already removed all of the modules other than ``addr_gen``. We could restart our shell session, but instead let's use two new commands: - :cmd:title:`design`, and - :cmd:title:`read_verilog`. .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: design -reset :end-before: yosys> proc :caption: reloading :file:`fifo.v` and running :yoscrypt:`hierarchy -check -top fifo` Notice how this time we didn't see any of those ``$abstract`` modules? That's because when we ran ``yosys fifo.v``, the first command Yosys called was :yoscrypt:`read_verilog -defer fifo.v`. The ``-defer`` option there tells `read_verilog` only read the abstract syntax tree and defer actual compilation to a later `hierarchy` command. This is useful in cases where the default parameters of modules yield invalid code which is not synthesizable. This is why Yosys defers compilation automatically and is one of the reasons why hierarchy should always be the first command after loading the design. If we know that our design won't run into this issue, we can skip the ``-defer``. .. todo:: `hierarchy` failure modes .. note:: The number before a command's output increments with each command run. Don't worry if your numbers don't match ours! The output you are seeing comes from the same script that was used to generate the images in this document, included in the source as :file:`fifo.ys`. There are extra commands being run which you don't see, but feel free to try them yourself, or play around with different commands. You can always start over with a clean slate by calling ``exit`` or hitting :kbd:`ctrl+d` (i.e. EOF) and re-launching the Yosys interactive terminal. :kbd:`ctrl+c` (i.e. SIGINT) will also end the terminal session but will return an error code rather than exiting gracefully. We can also run `proc` now to finish off the full :ref:`synth_begin`. Because the design schematic is quite large, we will be showing just the data path for the ``rdata`` output. If you would like to see the entire design for yourself, you can do so with :cmd:title:`show`. Note that the `show` command only works with a single module, so you may need to call it with :yoscrypt:`show fifo`. :ref:`show_intro` section in :doc:`/getting_started/scripting_intro` has more on how to use `show`. .. figure:: /_images/code_examples/fifo/rdata_proc.* :class: width-helper invert-helper :name: rdata_proc ``rdata`` output after `proc` The highlighted ``fifo_reader`` block contains an instance of the :ref:`addr_gen_proc` that we looked at earlier. Notice how the type is shown as ``$paramod\\addr_gen\\MAX_DATA=s32'...``. This is a "parametric module": an instance of the ``addr_gen`` module with the ``MAX_DATA`` parameter set to the given value. The other highlighted block is a `$memrd` cell. At this stage of synthesis we don't yet know what type of memory is going to be implemented, but we *do* know that ``rdata <= data[raddr];`` could be implemented as a read from memory. Note that the `$memrd` cell here is asynchronous, with both the clock and enable signal undefined; shown with the ``1'x`` inputs. .. seealso:: Advanced usage docs for :doc:`/using_yosys/synthesis/proc` Flattening ~~~~~~~~~~ At this stage of a synthesis flow there are a few other commands we could run. In `synth_ice40` we get these: .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: flatten: :end-before: coarse: :dedent: :name: synth_flatten :caption: ``flatten`` section We start by runnning `check`. This doesn't affect the design, but it can identify a few obvious problems which will cause errors later. Calling it here lets us fail faster rather than wasting time on something we know is impossible. Next up is `flatten`. Flattening the design like this can allow for optimizations between modules which would otherwise be missed. Let's run :yoscrypt:`flatten;;` on our design. .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: yosys> flatten :end-before: yosys> select :name: flat_clean :caption: output of :yoscrypt:`flatten;;` .. figure:: /_images/code_examples/fifo/rdata_flat.* :class: width-helper invert-helper :name: rdata_flat ``rdata`` output after :yoscrypt:`flatten;;` .. role:: yoterm(code) :language: doscon The pieces have moved around a bit, but we can see :ref:`addr_gen_proc` from earlier has replaced the ``fifo_reader`` block in :ref:`rdata_proc`. We can also see that the ``addr`` output has been renamed to :file:`fifo_reader.addr` and merged with the ``raddr`` wire feeding into the `$memrd` cell. This wire merging happened during the call to `clean` which we can see in the :ref:`flat_clean`. .. note:: `flatten` and `clean` would normally be combined into a single :yoterm:`yosys> flatten;;` output, but they appear separately here as a side effect of using `echo` for generating the terminal style output. Depending on the target architecture, this stage of synthesis might also see commands such as `tribuf` with the ``-logic`` option and `deminout`. These remove tristate and inout constructs respectively, replacing them with logic suitable for mapping to an FPGA. Since we do not have any such constructs in our example running these commands does not change our design. The coarse-grain representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ At this stage, the design is in coarse-grain representation. It still looks recognizable, and cells are word-level operators with parametrizable width. This is the stage of synthesis where we do things like const propagation, expression rewriting, and trimming unused parts of wires. This is also where we convert our FSMs and hard blocks like DSPs or memories. Such elements have to be inferred from patterns in the design and there are special passes for each. Detection of these patterns can also be affected by optimizations and other transformations done previously. .. note:: While the iCE40 flow had a :ref:`synth_flatten` and put `proc` in the :ref:`synth_begin`, some synthesis scripts will instead include these in this section. Part 1 ^^^^^^ In the iCE40 flow, we start with the following commands: .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: coarse: :end-before: wreduce :dedent: :caption: ``coarse`` section (part 1) :name: synth_coarse1 We've already come across `opt_expr` and `check`, and `opt_clean` is the same as `clean` but with more verbose output. Next up is :yoscrypt:`opt -nodffe -nosdff` performing a set of simple optimizations on the design. This command also ensures that only a specific subset of FF types are included, in preparation for the next command: :cmd:title:`fsm`. Both `opt` and `fsm` are macro commands which are explored in more detail in :doc:`/using_yosys/synthesis/opt` and :doc:`/using_yosys/synthesis/fsm` respectively. Up until now, the data path for ``rdata`` has remained the same since :ref:`rdata_flat`. However the next call to `opt` does cause a change. Specifically, the call to `opt_dff` without the ``-nodffe -nosdff`` options is able to fold one of the `$mux` cells into the `$adff` to form an `$adffe` cell; highlighted below: .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: yosys> opt_dff :end-before: yosys> select :caption: output of `opt_dff` .. figure:: /_images/code_examples/fifo/rdata_adffe.* :class: width-helper invert-helper :name: rdata_adffe ``rdata`` output after `opt_dff` .. seealso:: Advanced usage docs for - :doc:`/using_yosys/synthesis/fsm` - :doc:`/using_yosys/synthesis/opt` Part 2 ^^^^^^ The next group of commands performs a series of optimizations: .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-at: wreduce :end-before: t:$mul :dedent: :caption: ``coarse`` section (part 2) :name: synth_coarse2 First up is :cmd:title:`wreduce`. If we run this we get the following: .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: yosys> wreduce :end-before: yosys> select :caption: output of `wreduce` Looking at the data path for ``rdata``, the most relevant of these width reductions are the ones affecting ``fifo.$flatten\fifo_reader.$add$fifo.v``. That is the `$add` cell incrementing the fifo_reader address. We can look at the schematic and see the output of that cell has now changed. .. todo:: pending bugfix in `wreduce` and/or `opt_clean` .. figure:: /_images/code_examples/fifo/rdata_wreduce.* :class: width-helper invert-helper :name: rdata_wreduce ``rdata`` output after `wreduce` The next two (new) commands are :cmd:title:`peepopt` and :cmd:title:`share`. Neither of these affect our design, and they're explored in more detail in :doc:`/using_yosys/synthesis/opt`, so let's skip over them. :yoscrypt:`techmap -map +/cmp2lut.v -D LUT_WIDTH=4` optimizes certain comparison operators by converting them to LUTs instead. The usage of `techmap` is explored more in :doc:`/using_yosys/synthesis/techmap_synth`. Our next command to run is :cmd:title:`memory_dff`. .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: yosys> memory_dff :end-before: yosys> select :caption: output of `memory_dff` .. figure:: /_images/code_examples/fifo/rdata_memrdv2.* :class: width-helper invert-helper :name: rdata_memrdv2 ``rdata`` output after `memory_dff` As the title suggests, `memory_dff` has merged the output `$dff` into the `$memrd` cell and converted it to a `$memrd_v2` (highlighted). This has also connected the ``CLK`` port to the ``clk`` input as it is now a synchronous memory read with appropriate enable (``EN=1'1``) and reset (``ARST=1'0`` and ``SRST=1'0``) inputs. .. seealso:: Advanced usage docs for - :doc:`/using_yosys/synthesis/opt` - :doc:`/using_yosys/synthesis/techmap_synth` - :doc:`/using_yosys/synthesis/memory` Part 3 ^^^^^^ The third part of the `synth_ice40` flow is a series of commands for mapping to DSPs. By default, the iCE40 flow will not map to the hardware DSP blocks and will only be performed if called with the ``-dsp`` flag: :yoscrypt:`synth_ice40 -dsp`. While our example has nothing that could be mapped to DSPs we can still take a quick look at the commands here and describe what they do. .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-at: t:$mul :end-before: alumacc :dedent: :caption: ``coarse`` section (part 3) :name: synth_coarse3 :yoscrypt:`wreduce t:$mul` performs width reduction again, this time targetting only cells of type `$mul`. :yoscrypt:`techmap -map +/mul2dsp.v -map +/ice40/dsp_map.v ... -D DSP_NAME=$__MUL16X16` uses `techmap` to map `$mul` cells to ``$__MUL16X16`` which are, in turn, mapped to the iCE40 ``SB_MAC16``. Any multipliers which aren't compatible with conversion to ``$__MUL16X16`` are relabelled to ``$__soft_mul`` before `chtype` changes them back to `$mul`. During the mul2dsp conversion, some of the intermediate signals are marked with the attribute ``mul2dsp``. By calling :yoscrypt:`select a:mul2dsp` we restrict the following commands to only operate on the cells and wires used for these signals. `setattr` removes the now unnecessary ``mul2dsp`` attribute. `opt_expr` we've already come across for const folding and simple expression rewriting, the ``-fine`` option just enables more fine-grain optimizations. Then we perform width reduction a final time and clear the selection. .. todo:: ``ice40_dsp`` is pmgen Finally we have `ice40_dsp`: similar to the `memory_dff` command we saw in the previous section, this merges any surrounding registers into the ``SB_MAC16`` cell. This includes not just the input/output registers, but also pipeline registers and even a post-adder where applicable: turning a multiply + add into a single multiply-accumulate. .. seealso:: Advanced usage docs for :doc:`/using_yosys/synthesis/techmap_synth` Part 4 ^^^^^^ That brings us to the fourth and final part for the iCE40 synthesis flow: .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-at: alumacc :end-before: map_ram: :dedent: :caption: ``coarse`` section (part 4) :name: synth_coarse4 Where before each type of arithmetic operation had its own cell, e.g. `$add`, we now want to extract these into `$alu` and `$macc_v2` cells which can help identify opportunities for reusing logic. We do this by running `alumacc`, which we can see produce the following changes in our example design: .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: yosys> alumacc :end-before: yosys> select :caption: output of `alumacc` .. figure:: /_images/code_examples/fifo/rdata_alumacc.* :class: width-helper invert-helper :name: rdata_alumacc ``rdata`` output after `alumacc` Once these cells have been inserted, the call to `opt` can combine cells which are now identical but may have been missed due to e.g. the difference between `$add` and `$sub`. The other new command in this part is :cmd:title:`memory`. `memory` is another macro command which we examine in more detail in :doc:`/using_yosys/synthesis/memory`. For this document, let us focus just on the step most relevant to our example: `memory_collect`. Up until this point, our memory reads and our memory writes have been totally disjoint cells; operating on the same memory only in the abstract. `memory_collect` combines all of the reads and writes for a memory block into a single cell. .. figure:: /_images/code_examples/fifo/rdata_coarse.* :class: width-helper invert-helper :name: rdata_coarse ``rdata`` output after `memory_collect` Looking at the schematic after running `memory_collect` we see that our `$memrd_v2` cell has been replaced with a `$mem_v2` cell named ``data``, the same name that we used in :ref:`fifo-v`. Where before we had a single set of signals for address and enable, we now have one set for reading (``RD_*``) and one for writing (``WR_*``), as well as both ``WR_DATA`` input and ``RD_DATA`` output. .. seealso:: Advanced usage docs for - :doc:`/using_yosys/synthesis/opt` - :doc:`/using_yosys/synthesis/memory` Final note ^^^^^^^^^^ Having now reached the end of the the coarse-grain representation, we could also have gotten here by running :yoscrypt:`synth_ice40 -top fifo -run :map_ram` after loading the design. The :yoscrypt:`-run :` option with an empty ```` starts from the :ref:`synth_begin`, while the ```` runs up to but including the :ref:`map_ram`. Hardware mapping ~~~~~~~~~~~~~~~~ The remaining sections each map a different type of hardware and are much more architecture dependent than the previous sections. As such we will only be looking at each section very briefly. If you skipped calling :yoscrypt:`read_verilog -D ICE40_HX -lib -specify +/ice40/cells_sim.v` earlier, do it now. Memory blocks ^^^^^^^^^^^^^ Mapping to hard memory blocks uses a combination of `memory_libmap` and `techmap`. .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: map_ram: :end-before: map_ffram: :dedent: :name: map_ram :caption: ``map_ram`` section .. figure:: /_images/code_examples/fifo/rdata_map_ram.* :class: width-helper invert-helper :name: rdata_map_ram ``rdata`` output after :ref:`map_ram` The :ref:`map_ram` converts the generic `$mem_v2` into the iCE40 ``SB_RAM40_4K`` (highlighted). We can also see the memory address has been remapped, and the data bits have been reordered (or swizzled). There is also now a `$mux` cell controlling the value of ``rdata``. In :ref:`fifo-v` we wrote our memory as read-before-write, however the ``SB_RAM40_4K`` has undefined behaviour when reading from and writing to the same address in the same cycle. As a result, extra logic is added so that the generated circuit matches the behaviour of the verilog. :ref:`no_rw_check` describes how we could change our verilog to match our hardware instead. If we run `memory_libmap` under the `debug` command we can see candidates which were identified for mapping, along with the costs of each and what logic requires emulation. .. literalinclude:: /code_examples/fifo/fifo.libmap :language: doscon :lines: 2, 6- The ``$__ICE40_RAM4K_`` cell is defined in the file |techlibs/ice40/brams.txt|_, with the mapping to ``SB_RAM40_4K`` done by `techmap` using |techlibs/ice40/brams_map.v|_. Any leftover memory cells are then converted into flip flops (the ``logic fallback``) with `memory_map`. .. |techlibs/ice40/brams.txt| replace:: :file:`techlibs/ice40/brams.txt` .. _techlibs/ice40/brams.txt: https://github.com/YosysHQ/yosys/tree/main/techlibs/ice40/brams.txt .. |techlibs/ice40/brams_map.v| replace:: :file:`techlibs/ice40/brams_map.v` .. _techlibs/ice40/brams_map.v: https://github.com/YosysHQ/yosys/tree/main/techlibs/ice40/brams_map.v .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: map_ffram: :end-before: map_gates: :dedent: :name: map_ffram :caption: ``map_ffram`` section .. figure:: /_images/code_examples/fifo/rdata_map_ffram.* :class: width-helper invert-helper :name: rdata_map_ffram ``rdata`` output after :ref:`map_ffram` .. note:: The visual clutter on the ``RDATA`` output port (highlighted) is an unfortunate side effect of `opt_clean` on the swizzled data bits. In connecting the `$mux` input port directly to ``RDATA`` to reduce the number of wires, the ``$techmap579\data.0.0.RDATA`` wire becomes more visually complex. .. seealso:: Advanced usage docs for - :doc:`/using_yosys/synthesis/techmap_synth` - :doc:`/using_yosys/synthesis/memory` Arithmetic ^^^^^^^^^^ Uses `techmap` to map basic arithmetic logic to hardware. This sees somewhat of an explosion in cells as multi-bit `$mux` and `$adffe` are replaced with single-bit `$_MUX_` and `$_DFFE_PP0P_` cells, while the `$alu` is replaced with primitive `$_OR_` and `$_NOT_` gates and a `$lut` cell. .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: map_gates: :end-before: map_ffs: :dedent: :name: map_gates :caption: ``map_gates`` section .. figure:: /_images/code_examples/fifo/rdata_map_gates.* :class: width-helper invert-helper :name: rdata_map_gates ``rdata`` output after :ref:`map_gates` .. seealso:: Advanced usage docs for :doc:`/using_yosys/synthesis/techmap_synth` Flip-flops ^^^^^^^^^^ Convert FFs to the types supported in hardware with `dfflegalize`, and then use `techmap` to map them. In our example, this converts the `$_DFFE_PP0P_` cells to ``SB_DFFER``. We also run `simplemap` here to convert any remaining cells which could not be mapped to hardware into gate-level primitives. This includes optimizing `$_MUX_` cells where one of the inputs is a constant ``1'0``, replacing it instead with an `$_AND_` cell. .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: map_ffs: :end-before: map_luts: :dedent: :name: map_ffs :caption: ``map_ffs`` section .. figure:: /_images/code_examples/fifo/rdata_map_ffs.* :class: width-helper invert-helper :name: rdata_map_ffs ``rdata`` output after :ref:`map_ffs` .. seealso:: Advanced usage docs for :doc:`/using_yosys/synthesis/techmap_synth` LUTs ^^^^ `abc` and `techmap` are used to map LUTs; converting primitive cell types to use `$lut` and ``SB_CARRY`` cells. Note that the iCE40 flow uses `abc9` rather than `abc`. For more on what these do, and what the difference between these two commands are, refer to :doc:`/using_yosys/synthesis/abc`. .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: map_luts: :end-before: map_cells: :dedent: :name: map_luts :caption: ``map_luts`` section .. figure:: /_images/code_examples/fifo/rdata_map_luts.* :class: width-helper invert-helper :name: rdata_map_luts ``rdata`` output after :ref:`map_luts` Finally we use `techmap` to map the generic `$lut` cells to iCE40 ``SB_LUT4`` cells. .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: map_cells: :end-before: check: :dedent: :name: map_cells :caption: ``map_cells`` section .. figure:: /_images/code_examples/fifo/rdata_map_cells.* :class: width-helper invert-helper :name: rdata_map_cells ``rdata`` output after :ref:`map_cells` .. seealso:: Advanced usage docs for - :doc:`/using_yosys/synthesis/techmap_synth` - :doc:`/using_yosys/synthesis/abc` Other cells ^^^^^^^^^^^ The following commands may also be used for mapping other cells: `hilomap` Some architectures require special driver cells for driving a constant hi or lo value. This command replaces simple constants with instances of such driver cells. `iopadmap` Top-level input/outputs must usually be implemented using special I/O-pad cells. This command inserts such cells to the design. These commands tend to either be in the :ref:`map_cells` or after the :ref:`check` depending on the flow. Final steps ~~~~~~~~~~~~ The next section of the iCE40 synth flow performs some sanity checking and final tidy up: .. literalinclude:: /code_examples/macro_commands/synth_ice40.ys :language: yoscrypt :start-after: check: :dedent: :name: check :caption: ``check`` section The new commands here are: - :cmd:title:`autoname`, - :cmd:title:`stat`, and - :cmd:title:`blackbox`. The output from `stat` is useful for checking resource utilization; providing a list of cells used in the design and the number of each, as well as the number of other resources used such as wires and processes. For this design, the final call to `stat` should look something like the following: .. literalinclude:: /code_examples/fifo/fifo.stat :language: doscon :start-at: yosys> stat -top fifo Note that the :yoscrypt:`-top fifo` here is optional. `stat` will automatically use the module with the ``top`` attribute set, which ``fifo`` was when we called `hierarchy`. If no module is marked ``top``, then stats will be shown for each module selected. The `stat` output is also useful as a kind of sanity-check: Since we have already run `proc`, we wouldn't expect there to be any processes. We also expect ``data`` to use hard memory; if instead of an ``SB_RAM40_4K`` saw a high number of flip-flops being used we might suspect something was wrong. If we instead called `stat` immediately after :yoscrypt:`read_verilog fifo.v` we would see something very different: .. literalinclude:: /code_examples/fifo/fifo.stat :language: doscon :start-at: yosys> stat :end-before: yosys> stat -top fifo Notice how ``fifo`` and ``addr_gen`` are listed separately, and the statistics for ``fifo`` show 2 ``addr_gen`` modules. Because this is before the memory has been mapped, we also see that there is 1 memory with 2048 memory bits; matching our 8-bit wide ``data`` memory with 256 values (:math:`8*256=2048`). Synthesis output ^^^^^^^^^^^^^^^^ The iCE40 synthesis flow has the following output modes available: - `write_blif`, - `write_edif`, and - `write_json`. As an example, if we called :yoscrypt:`synth_ice40 -top fifo -json fifo.json`, our synthesized ``fifo`` design will be output as :file:`fifo.json`. We can then read the design back into Yosys with `read_json`, but make sure you use :yoscrypt:`design -reset` or open a new interactive terminal first. The JSON output we get can also be loaded into `nextpnr`_ to do place and route; but that is beyond the scope of this documentation. .. _nextpnr: https://github.com/YosysHQ/nextpnr .. seealso:: :cmd:title:`synth_ice40` yosys-0.65/docs/source/getting_started/index.rst000066400000000000000000000005251520057232300221130ustar00rootroot00000000000000Getting started with Yosys ========================== This section covers how to get started with Yosys, from installation to a guided walkthrough of synthesizing a design for hardware, and finishing with an introduction to writing re-usable Yosys scripts. .. toctree:: :maxdepth: 3 installation example_synth scripting_intro yosys-0.65/docs/source/getting_started/installation.rst000066400000000000000000000247061520057232300235140ustar00rootroot00000000000000Installation ------------ This document will guide you through the process of installing Yosys. CAD suite(s) ~~~~~~~~~~~~ Yosys is part of the `Tabby CAD Suite `_ and the `OSS CAD Suite `_! The easiest way to use yosys is to install the binary software suite, which contains all required dependencies and related tools. * `Contact YosysHQ `_ for a `Tabby CAD Suite `_ Evaluation License and download link * OR go to https://github.com/YosysHQ/oss-cad-suite-build/releases to download the free OSS CAD Suite * Follow the `Install Instructions on GitHub `_ Make sure to get a Tabby CAD Suite Evaluation License if you need features such as industry-grade SystemVerilog and VHDL parsers! For more information about the difference between Tabby CAD Suite and the OSS CAD Suite, please visit https://www.yosyshq.com/tabby-cad-datasheet Many Linux distributions also provide Yosys binaries, some more up to date than others. Check with your package manager! Targeted architectures ^^^^^^^^^^^^^^^^^^^^^^ The `OSS CAD Suite`_ releases `nightly builds`_ for the following architectures: - **linux-x64** - Most personal Linux based computers - **darwin-x64** - macOS 12 or later with Intel CPU - **darwin-arm64** - macOS 12 or later with M1/M2 CPU - **windows-x64** - Targeted for Windows 10 and 11 - **linux-arm64** - Devices such as Raspberry Pi with 64bit OS For more information about the targeted architectures, and the current build status, check the `OSS CAD Suite`_ git repository. .. _OSS CAD Suite: https://github.com/YosysHQ/oss-cad-suite-build .. _nightly builds: https://github.com/YosysHQ/oss-cad-suite-build/releases/latest Building from source ~~~~~~~~~~~~~~~~~~~~ The Yosys source files can be obtained from the `YosysHQ/Yosys git repository`_. `ABC`_ and some of the other libraries used are included as git submodules. To clone these submodules at the same time, use e.g.: .. code:: console 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 .. _YosysHQ/Yosys git repository: https://github.com/yosyshq/yosys/ .. _ABC: https://github.com/berkeley-abc/abc .. note:: As of Yosys v0.47, releases include a ``yosys.tar.gz`` file which includes all source code and all sub-modules in a single archive. This can be used as an alternative which does not rely on ``git``. Supported platforms ^^^^^^^^^^^^^^^^^^^ The following platforms are supported and regularly tested: - Linux - macOS Other platforms which may work, but instructions may not be up to date and are not regularly tested: - FreeBSD - WSL - Windows with (e.g.) Cygwin Build prerequisites ^^^^^^^^^^^^^^^^^^^ A C++ compiler with C++17 support is required as well as some standard tools such as GNU Flex, GNU Bison (>=3.8), Make, and Python (>=3.11). Some additional tools: readline, libffi, Tcl and zlib; are optional but enabled by default (see :makevar:`ENABLE_*` settings in Makefile). Graphviz and Xdot are used by the `show` command to display schematics. Installing all prerequisites: .. tab:: Ubuntu 22.04 .. code:: console 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 .. tab:: macOS 13 (with Homebrew) .. code:: console brew tap Homebrew/bundle && brew bundle .. tab:: MacPorts .. code:: console sudo port install bison flex readline gawk libffi graphviz \ pkgconfig python311 zlib tcl .. tab:: FreeBSD .. code:: console pkg install bison flex readline gawk libffi graphviz \ pkgconf python311 tcl-wrapper .. note:: On FreeBSD system use gmake instead of make. To run tests use: ``MAKE=gmake CXX=cxx CC=cc gmake test`` .. tab:: Cygwin Use the following command to install all prerequisites, or select these additional packages: .. code:: console setup-x86_64.exe -q --packages=bison,flex,gcc-core,gcc-g++,git,libffi-devel,libreadline-devel,make,pkg-config,python3,tcl-devel,zlib-devel .. warning:: As of this writing, Cygwin only supports up to Python 3.9.16 while the minimum required version of Python is 3.11. This means that Cygwin is not compatible with many of the Python-based frontends. While this does not currently prevent Yosys itself from working, no guarantees are made for continued support. You may also need to specify ``CXXSTD=gnu++17`` to resolve missing ``strdup`` function when using gcc. It is instead recommended to use Windows Subsystem for Linux (WSL) and follow the instructions for Ubuntu. .. tab:: MSYS2 (MINGW64) .. code:: console pacman -S bison flex mingw-w64-x86_64-gcc git libffi-devel libreadline-devel make pkg-config python3 tcl-devel zlib-devel Not that I can get this to work; it's failing during ld with what looks like math library issues: ``multiple definition of `tanh'`` and ``undefined reference to `__imp_acosh'``, as well as issues in `aiger2` with ``seekg`` et al not being available. .. note:: The ``config-msys2-64`` target uses the ``mingw-w64-x86_64-`` prefixed compiler in order to allow compiled exe files to be run without an MSYS2 shell. Build configuration ^^^^^^^^^^^^^^^^^^^ The Yosys build is based solely on Makefiles, and uses a number of variables which influence the build process. The recommended method for configuring builds is with a ``Makefile.conf`` file in the root ``yosys`` directory. The following commands will clean the directory and provide an initial configuration file: .. code:: console make config-clang # ..or.. make config-gcc Check the root Makefile to see what other configuration targets are available. Other variables can then be added to the ``Makefile.conf`` as needed, for example: .. code:: console echo "ENABLE_ZLIB := 0" >> Makefile.conf Using one of these targets will set the ``CONFIG`` variable to something other than ``none``, and will override the environment variable for ``CXX``. To use a different compiler than the default when building, use: .. code:: console make CXX=$CXX # ..or.. make CXX="g++-11" .. note:: Setting the compiler in this way will prevent some other options such as ``ENABLE_CCACHE`` from working as expected. If you have clang, and (a compatible version of) ``ld.lld`` available in PATH, it's recommended to speed up incremental builds with lld by enabling LTO with ``ENABLE_LTO=1``. On macOS, LTO requires using clang from homebrew rather than clang from xcode. For example: .. code:: console make ENABLE_LTO=1 CXX=$(brew --prefix)/opt/llvm/bin/clang++ By default, building (and installing) yosys will build (and install) `ABC`_, using :program:`yosys-abc` as the executable name. To use an existing ABC executable instead, set the ``ABCEXTERNAL`` make variable to point to the desired executable. Running the build system ^^^^^^^^^^^^^^^^^^^^^^^^ From the root ``yosys`` directory, call the following commands: .. code:: console make sudo make install To use a separate (out-of-tree) build directory, provide a path to the Makefile. .. code:: console mkdir build; cd build make -f ../Makefile Out-of-tree builds require a clean source tree. .. seealso:: Refer to :doc:`/yosys_internals/extending_yosys/test_suites` for details on testing Yosys once compiled. Source tree and build system ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Yosys source tree is organized into the following top-level directories: ``backends/`` This directory contains a subdirectory for each of the backend modules. ``docs/`` Contains the source for this documentation, including images and sample code. ``examples/`` Contains example code for using Yosys with some other tools including a demo of the Yosys Python api, and synthesizing for various toolchains such as Intel and Anlogic. ``frontends/`` This directory contains a subdirectory for each of the frontend modules. ``kernel/`` This directory contains all the core functionality of Yosys. This includes the functions and definitions for working with the RTLIL data structures (:file:`rtlil.{h|cc}`), the ``main()`` function (:file:`driver.cc`), the internal framework for generating log messages (:file:`log.{h|cc}`), the internal framework for registering and calling passes (:file:`register.{h|cc}`), some core commands that are not really passes (:file:`select.cc`, :file:`show.cc`, …) and a couple of other small utility libraries. ``libs/`` Libraries packaged with Yosys builds are contained in this folder. See :doc:`/appendix/auxlibs`. ``misc/`` Other miscellany which doesn't fit anywhere else. ``passes/`` This directory contains a subdirectory for each pass or group of passes. For example as of this writing the directory :file:`passes/hierarchy/` contains the code for three passes: `hierarchy`, `submod`, and `uniquify`. ``techlibs/`` This directory contains simulation models and standard implementations for the cells from the internal cell library. ``tests/`` This directory contains the suite of unit tests and regression tests used by Yosys. See :doc:`/yosys_internals/extending_yosys/test_suites`. The top-level Makefile includes :file:`frontends/{*}/Makefile.inc`, :file:`passes/{*}/Makefile.inc` and :file:`backends/{*}/Makefile.inc`. So when extending Yosys it is enough to create a new directory in :file:`frontends/`, :file:`passes/` or :file:`backends/` with your sources and a :file:`Makefile.inc`. The Yosys kernel automatically detects all commands linked with Yosys. So it is not needed to add additional commands to a central list of commands. Good starting points for reading example source code to learn how to write passes are :file:`passes/opt/opt_dff.cc` and :file:`passes/opt/opt_merge.cc`. Users of the Qt Creator IDE can generate a QT Creator project file using make qtcreator. Users of the Eclipse IDE can use the "Makefile Project with Existing Code" project type in the Eclipse "New Project" dialog (only available after the CDT plugin has been installed) to create an Eclipse project in order to programming extensions to Yosys or just browse the Yosys code base. yosys-0.65/docs/source/getting_started/scripting_intro.rst000066400000000000000000000245551520057232300242320ustar00rootroot00000000000000Scripting in Yosys ------------------ On the previous page we went through a synthesis script, running each command in the interactive Yosys shell. On this page, we will be introducing the script file format and how you can make your own synthesis scripts. Yosys script files typically use the :file:`.ys` extension and contain a set of commands for Yosys to run sequentially. These commands are the same ones we were using on the previous page like `read_verilog` and `hierarchy`. Script parsing ~~~~~~~~~~~~~~ As with the interactive shell, each command consists of the command name, and an optional whitespace separated list of arguments. Commands are terminated with the newline character, and anything after a hash sign ``#`` is a comment (i.e. it is ignored). It is also possible to terminate commands with a semicolon ``;``. This is particularly useful in conjunction with the ``-p `` command line option, where ```` can be a string with multiple commands separated by semicolon. In-line comments can also be made with the colon ``:``, where the end of the comment is a semicolon ``;`` or a new line. .. code-block:: :caption: Using the ``-p`` option $ yosys -p 'read_verilog fifo.v; :this is a comment; prep' .. warning:: The space after the semicolon is required for correct parsing. ``log a;log b;`` for example will display ``a;log b`` instead of ``a`` and ``b`` as might be expected. Another special character that can be used in Yosys scripts is the bang ``!``. Anything after the bang will be executed as a shell command. This can only be terminated with a new line. Any semicolons, hashes, or other special characters will be passed to the shell. If an error code is returned from the shell it will be raised by Yosys. `exec` provides a much more flexible way of executing commands, allowing the output to be logged and more control over when to generate errors. .. warning:: Take care when using the ``yosys -p`` option. Some shells such as bash will perform substitution options inside of a double quoted string, such as ``!`` for history substitution and ``$`` for variable substitution; single quotes should be used instead to pass the string to Yosys without substitution. The synthesis starter script ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. role:: yoscrypt(code) :language: yoscrypt All of the images and console output used in :doc:`/getting_started/example_synth` were generated by Yosys, using Yosys script files found in :file:`docs/source/code_examples/fifo`. If you haven't already, let's take a look at some of those script files now. .. literalinclude:: /code_examples/fifo/fifo.ys :language: yoscrypt :lineno-match: :start-at: echo on :end-before: design -reset :caption: A section of :file:`fifo.ys`, generating the images used for :ref:`addr_gen_example` :name: fifo-ys The first command there, :yoscrypt:`echo on`, uses `echo` to enable command echoes on. This is how we generated the code listing for :ref:`hierarchy_output`. Turning command echoes on prints the ``yosys> hierarchy -top addr_gen`` line, making the output look the same as if it were an interactive terminal. :yoscrypt:`hierarchy -top addr_gen` is of course the command we were demonstrating, including the output text and an image of the design schematic after running it. We briefly touched on `select` when it came up in `synth_ice40`, but let's look at it more now. .. _select_intro: Selections intro ^^^^^^^^^^^^^^^^ The `select` command is used to modify and view the list of selected objects: .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: yosys> select :end-before: yosys> show When we call :yoscrypt:`select -module addr_gen` we are changing the currently active selection from the whole design, to just the ``addr_gen`` module. Notice how this changes the ``yosys`` at the start of each command to ``yosys [addr_gen]``? This indicates that any commands we run at this point will *only* operate on the ``addr_gen`` module. When we then call :yoscrypt:`select -list` we get a list of all objects in the ``addr_gen`` module, including the module itself, as well as all of the wires, inputs, outputs, processes, and cells. Next we perform another selection, :yoscrypt:`select t:*`. The ``t:`` part signifies we are matching on the *cell type*, and the ``*`` means to match anything. For this (very simple) selection, we are trying to find all of the cells, regardless of their type. The active selection is now shown as ``[addr_gen]*``, indicating some sub-selection of the ``addr_gen`` module. This gives us the `$add` and `$eq` cells, which we want to highlight for the :ref:`addr_gen_hier` image. .. _select_new_cells: We can assign a name to a selection with :yoscrypt:`select -set`. In our case we are using the name ``new_cells``, and telling it to use the current selection, indicated by the ``%`` symbol. We can then use this named selection by referring to it as ``@new_cells``, which we will see later. Then we clear the selection so that the following commands can operate on the full design. While we split that out for this document, we could have done the same thing in a single line by calling :yoscrypt:`select -set new_cells addr_gen/t:*`. If we know we only have the one module in our design, we can even skip the ``addr_gen/`` part. Looking further down :ref:`the fifo.ys code ` we can see this with :yoscrypt:`select -set new_cells t:$mux t:*dff`. We can also see in that command that selections don't have to be limited to a single statement. Many commands also support an optional ``[selection]`` argument which can be used to override the currently selected objects. We could, for example, call :yoscrypt:`clean addr_gen` to have `clean` operate on *just* the ``addr_gen`` module. Detailed documentation of the select framework can be found under :doc:`/using_yosys/more_scripting/selections` or in the command reference at :cmd:title:`select`. .. _show_intro: Displaying schematics ^^^^^^^^^^^^^^^^^^^^^ While the `select` command is very useful, sometimes nothing beats being able to see a design for yourself. This is where `show` comes in. Note that this document is just an introduction to the `show` command, only covering the basics. For more information, including a guide on what the different symbols represent, see :ref:`interactive_show` and the :doc:`/using_yosys/more_scripting/interactive_investigation` page. .. figure:: /_images/code_examples/fifo/addr_gen_show.* :class: width-helper invert-helper :name: addr_gen_show Calling :yoscrypt:`show addr_gen` after `hierarchy` .. note:: The `show` command requires a working installation of `GraphViz`_ and `xdot`_ for displaying the actual circuit diagrams. .. _GraphViz: http://www.graphviz.org/ .. _xdot: https://github.com/jrfonseca/xdot.py This is the first :yoscrypt:`show` command we called in :file:`fifo.ys`, :ref:`as we saw above `. If we look at the log output for this image we see the following: .. literalinclude:: /code_examples/fifo/fifo.out :language: doscon :start-at: -prefix addr_gen_show :end-before: yosys> show Calling `show` with :yoscrypt:`-format dot` tells it we want to output a :file:`.dot` file rather than opening it for display. The :yoscrypt:`-prefix addr_gen_show` option indicates we want the file to be called :file:`addr_gen_show.{*}`. Remember, we do this in :file:`fifo.ys` because we need to store the image for displaying in the documentation you're reading. But if you just want to display the images locally you can skip these two options. The ``-format`` option internally calls the ``dot`` command line program from GraphViz to convert to formats other than :file:`.dot`. Check `GraphViz output docs`_ for more on available formats. .. _GraphViz output docs: https://graphviz.org/docs/outputs/ .. note:: If you are using a POSIX based version of Yosys (such as for Mac or Linux), xdot will be opened in the background and Yosys can continue to be used. If it it still open, future calls to :yoscrypt:`show` will use the same xdot instance. The ``addr_gen`` at the end tells it we only want the ``addr_gen`` module, just like when we called :yoscrypt:`select -module addr_gen` in :ref:`select_intro`. That last parameter doesn't have to be a module name, it can be any valid selection string. Remember when we :ref:`assigned a name to a selection` and called it ``new_cells``? We saw in the :yoscrypt:`select -list` output that it contained two cells, an `$add` and an `$eq`. We can call `show` on that selection just as easily: .. figure:: /_images/code_examples/fifo/new_cells_show.* :class: width-helper invert-helper :name: new_cells_show Calling :yoscrypt:`show -notitle @new_cells` We could have gotten the same output with :yoscrypt:`show -notitle t:$add t:$eq` if we didn't have the named selection. By adding the :yoscrypt:`-notitle` flag there we can also get rid of the ``addr_gen`` title that would have been automatically added. The last two images were both added for this introduction. The next image is the first one we saw in :doc:`/getting_started/example_synth`: showing the full ``addr_gen`` module while also highlighting ``@new_cells`` and the two ``PROC`` blocks. To achieve this highlight, we make use of the :yoscrypt:`-color` option: .. figure:: /_images/code_examples/fifo/addr_gen_hier.* :class: width-helper invert-helper Calling :yoscrypt:`show -color maroon3 @new_cells -color cornflowerblue p:* -notitle` As described in the the `help` output for `show` (or by clicking on the `show` link), colors are specified as :yoscrypt:`-color `. Color names for the ```` portion can be found on the `GraphViz color docs`_. Unlike the final `show` parameter which can have be any selection string, the ```` part must be a single selection expression or named selection. That means while we can use ``@new_cells``, we couldn't use ``t:$eq t:$add``. In general, if a command lists ``[selection]`` as its final parameter it can be any selection string. Any selections that are not the final parameter, such as those used in options, must be a single expression instead. .. _GraphViz color docs: https://graphviz.org/doc/info/colors For all of the options available to `show`, check the command reference at :cmd:title:`show`. .. seealso:: :ref:`interactive_show` on the :doc:`/using_yosys/more_scripting/interactive_investigation` page. yosys-0.65/docs/source/index.rst000066400000000000000000000021401520057232300167170ustar00rootroot00000000000000================================================================================ Yosys Open SYnthesis Suite ================================================================================ Yosys is an open source framework for RTL synthesis. To learn more about Yosys, see :doc:`/introduction`. For a quick guide on how to get started using Yosys, check out :doc:`/getting_started/index`. For the complete list of commands available, go to :ref:`cmd_ref`. .. todo:: look into command ref improvements - Search bar with live drop down suggestions for matching on title / autocompleting commands - Scroll the left sidebar to the current location on page load - Also the formatting in pdf uses link formatting instead of code formatting .. todolist:: .. toctree:: :maxdepth: 3 :includehidden: Yosys (index) introduction getting_started/index using_yosys/index yosys_internals/index .. toctree:: :caption: Appendix :titlesonly: :includehidden: appendix/primer appendix/rtlil_text appendix/auxlibs appendix/auxprogs bib cell_index cmd_ref yosys-0.65/docs/source/introduction.rst000066400000000000000000000262461520057232300203460ustar00rootroot00000000000000What is Yosys ============= Yosys began as a BSc thesis project by Claire Wolf intended to support synthesis for a CGRA (coarse-grained reconfigurable architecture). It then expanded into more general infrastructure for research on synthesis. Modern Yosys has full support for the synthesizable subset of Verilog-2005 and has been described as "the GCC of hardware synthesis." Freely available and `open source`_, Yosys finds use across hobbyist and commercial applications as well as academic. .. _open source: https://github.com/YosysHQ/yosys .. note:: Yosys is released under the ISC License: A permissive license lets people do anything with your code with proper attribution and without warranty. The ISC license is functionally equivalent to the BSD 2-Clause and MIT licenses, removing some language that is no longer necessary. Together with the place and route tool `nextpnr`_, Yosys can be used to program some FPGAs with a fully end-to-end open source flow (Lattice iCE40 and ECP5). It also does the synthesis portion for the `OpenLane flow`_, targeting the SkyWater 130nm open source PDK for fully open source ASIC design. Yosys can also do formal verification with backends for solver formats like `SMT2`_. .. _nextpnr: https://github.com/YosysHQ/nextpnr .. _OpenLane flow: https://github.com/The-OpenROAD-Project/OpenLane .. _SMT2: https://smtlib.cs.uiowa.edu/ Yosys, and the accompanying Open Source EDA ecosystem, is currently maintained by `Yosys Headquarters`_, with many of the core developers employed by `YosysHQ GmbH`_. A commercial extension, `Tabby CAD Suite`_, includes the Verific frontend for industry-grade SystemVerilog and VHDL support, formal verification with SVA, and formal apps. .. _Yosys Headquarters: https://github.com/YosysHQ .. _YosysHQ GmbH: https://www.yosyshq.com/about .. _Tabby CAD Suite: https://www.yosyshq.com/tabby-cad-datasheet .. figure:: /_static/logo.png :class: width-helper What you can do with Yosys -------------------------- - Read and process (most of) modern Verilog-2005 code - Perform all kinds of operations on netlist (RTL, Logic, Gate) - Perform logic optimizations and gate mapping with ABC Typical applications for Yosys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Synthesis of final production designs - Pre-production synthesis (trial runs before investing in other tools) - Conversion of full-featured Verilog to simple Verilog - Conversion of Verilog to other formats (BLIF, BTOR, etc) - Demonstrating synthesis algorithms (e.g. for educational purposes) - Framework for experimenting with new algorithms - Framework for building custom flows (Not limited to synthesis but also formal verification, reverse engineering, ...) Things you can't do ~~~~~~~~~~~~~~~~~~~ - Process high-level languages such as C/C++/SystemC - Create physical layouts (place&route) - Check out `nextpnr`_ for that - Rely on built-in syntax checking - Use an external tool like `verilator`_ instead .. todo:: nextpnr for FPGAs, consider mentioning openlane, vpr, coriolis .. _nextpnr: https://github.com/YosysHQ/nextpnr .. _verilator: https://www.veripool.org/verilator/ The Yosys family ---------------- As mentioned above, `YosysHQ`_ maintains not just Yosys but an entire family of tools built around it. In no particular order: .. _YosysHQ: https://github.com/YosysHQ SBY for formal verification Yosys provides input parsing and conversion to the formats used by the solver engines. Yosys also provides a unified witness framework for providing cover traces and counter examples for engines which don't natively support this. `SBY source`_ | `SBY docs`_ .. _SBY source: https://github.com/YosysHQ/sby .. _SBY docs: https://yosyshq.readthedocs.io/projects/sby EQY for equivalence checking In addition to input parsing and preparation, Yosys provides the plugin support enabling EQY to operate on designs directly. `EQY source`_ | `EQY docs`_ .. _EQY source: https://github.com/YosysHQ/eqy .. _EQY docs: https://yosyshq.readthedocs.io/projects/eqy MCY for mutation coverage Yosys is used to read the source design, generate a list of possible mutations to maximise design coverage, and then perform selected mutations. `MCY source`_ | `MCY docs`_ .. _MCY source: https://github.com/YosysHQ/mcy .. _MCY docs: https://yosyshq.readthedocs.io/projects/mcy SCY for deep formal traces Since SCY generates and runs SBY, Yosys provides the same utility for SCY as it does for SBY. Yosys additionally provides the trace concatenation needed for outputting the deep traces. `SCY source`_ .. _SCY source: https://github.com/YosysHQ/scy The original thesis abstract ---------------------------- The first version of the Yosys documentation was published as a bachelor thesis at the Vienna University of Technology :cite:p:`BACC`. :Abstract: Most of today's digital design is done in HDL code (mostly Verilog or VHDL) and with the help of HDL synthesis tools. In special cases such as synthesis for coarse-grain cell libraries or when testing new synthesis algorithms it might be necessary to write a custom HDL synthesis tool or add new features to an existing one. In these cases the availability of a Free and Open Source (FOSS) synthesis tool that can be used as basis for custom tools would be helpful. In the absence of such a tool, the Yosys Open SYnthesis Suite (Yosys) was developed. This document covers the design and implementation of this tool. At the moment the main focus of Yosys lies on the high-level aspects of digital synthesis. The pre-existing FOSS logic-synthesis tool ABC is used by Yosys to perform advanced gate-level optimizations. An evaluation of Yosys based on real-world designs is included. It is shown that Yosys can be used as-is to synthesize such designs. The results produced by Yosys in this tests where successfully verified using formal verification and are comparable in quality to the results produced by a commercial synthesis tool. Yosys is a Verilog HDL synthesis tool. This means that it takes a behavioural design description as input and generates an RTL, logical gate or physical gate level description of the design as output. Yosys' main strengths are behavioural and RTL synthesis. A wide range of commands (synthesis passes) exist within Yosys that can be used to perform a wide range of synthesis tasks within the domain of behavioural, rtl and logic synthesis. Yosys is designed to be extensible and therefore is a good basis for implementing custom synthesis tools for specialised tasks. .. figure:: /_images/primer/levels_of_abstraction.* :class: width-helper invert-helper :name: fig:Levels_of_abstraction Where Yosys exists in the layers of abstraction Benefits of open source HDL synthesis ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Cost (also applies to ``free as in free beer`` solutions): Today the cost for a mask set in 180nm technology is far less than the cost for the design tools needed to design the mask layouts. Open Source ASIC flows are an important enabler for ASIC-level Open Source Hardware. - Availability and Reproducibility: If you are a researcher who is publishing, you want to use tools that everyone else can also use. Even if most universities have access to all major commercial tools, you usually do not have easy access to the version that was used in a research project a couple of years ago. With Open Source tools you can even release the source code of the tool you have used alongside your data. - Framework: Yosys is not only a tool. It is a framework that can be used as basis for other developments, so researchers and hackers alike do not need to re-invent the basic functionality. Extensibility was one of Yosys' design goals. - All-in-one: Because of the framework characteristics of Yosys, an increasing number of features become available in one tool. Yosys not only can be used for circuit synthesis but also for formal equivalence checking, SAT solving, and for circuit analysis, to name just a few other application domains. With proprietary software one needs to learn a new tool for each of these applications. - Educational Tool: Proprietary synthesis tools are at times very secretive about their inner workings. They often are ``black boxes``. Yosys is very open about its internals and it is easy to observe the different steps of synthesis. History of Yosys ~~~~~~~~~~~~~~~~ .. todo:: Consider a less academic version of the History of Yosys A Hardware Description Language (HDL) is a computer language used to describe circuits. A HDL synthesis tool is a computer program that takes a formal description of a circuit written in an HDL as input and generates a netlist that implements the given circuit as output. Currently the most widely used and supported HDLs for digital circuits are Verilog :cite:p:`Verilog2005,VerilogSynth` and :abbr:`VHDL (VHSIC HDL, where VHSIC is an acronym for Very-High-Speed Integrated Circuits)` :cite:p:`VHDL,VHDLSynth`. Both HDLs are used for test and verification purposes as well as logic synthesis, resulting in a set of synthesizable and a set of non-synthesizable language features. In this document we only look at the synthesizable subset of the language features. In recent work on heterogeneous coarse-grain reconfigurable logic :cite:p:`intersynth` the need for a custom application-specific HDL synthesis tool emerged. It was soon realised that a synthesis tool that understood Verilog or VHDL would be preferred over a synthesis tool for a custom HDL. Given an existing Verilog or VHDL front end, the work for writing the necessary additional features and integrating them in an existing tool can be estimated to be about the same as writing a new tool with support for a minimalistic custom HDL. The proposed custom HDL synthesis tool should be licensed under a Free and Open Source Software (FOSS) licence. So an existing FOSS Verilog or VHDL synthesis tool would have been needed as basis to build upon. The main advantages of choosing Verilog or VHDL is the ability to synthesize existing HDL code and to mitigate the requirement for circuit-designers to learn a new language. In order to take full advantage of any existing FOSS Verilog or VHDL tool, such a tool would have to provide a feature-complete implementation of the synthesizable HDL subset. Basic RTL synthesis is a well understood field :cite:p:`LogicSynthesis`. Lexing, parsing and processing of computer languages :cite:p:`Dragonbook` is a thoroughly researched field. All the information required to write such tools has been openly available for a long time, and it is therefore likely that a FOSS HDL synthesis tool with a feature-complete Verilog or VHDL front end must exist which can be used as a basis for a custom RTL synthesis tool. Due to the author's preference for Verilog over VHDL it was decided early on to go for Verilog instead of VHDL [#]_. So the existing FOSS Verilog synthesis tools were evaluated. The results of this evaluation are utterly devastating. Therefore a completely new Verilog synthesis tool was implemented and is recommended as basis for custom synthesis tools. This is the tool that is discussed in this document. .. [#] A quick investigation into FOSS VHDL tools yielded similar grim results for FOSS VHDL synthesis tools. yosys-0.65/docs/source/literature.bib000066400000000000000000000172271520057232300177300ustar00rootroot00000000000000 @inproceedings{intersynth, title={Example-driven interconnect synthesis for heterogeneous coarse-grain reconfigurable logic}, author={C. Wolf and Johann Glaser and Florian Schupfer and Jan Haase and Christoph Grimm}, booktitle={FDL Proceeding of the 2012 Forum on Specification and Design Languages}, pages={194--201}, year={2012} } @incollection{intersynthFdlBookChapter, title={Methodology and Example-Driven Interconnect Synthesis for Designing Heterogeneous Coarse-Grain Reconfigurable Architectures}, author={Johann Glaser and C. Wolf}, booktitle={Advances in Models, Methods, and Tools for Complex Chip Design --- Selected contributions from FDL'12}, editor={Jan Haase}, publisher={Springer}, year={2013}, note={to appear} } @unpublished{BACC, author = {C. Wolf}, title = {Design and Implementation of the Yosys Open SYnthesis Suite}, note = {Bachelor Thesis, Vienna University of Technology}, year = {2013} } @unpublished{VerilogFossEval, author = {C. Wolf}, title = {Evaluation of Open Source Verilog Synthesis Tools for Feature-Completeness and Extensibility}, note = {Unpublished Student Research Paper, Vienna University of Technology}, year = {2012} } @article{ABEL, title={A High-Level Design Language for Programmable Logic Devices}, author={Kyu Y. Lee and Michael Holley and Mary Bailey and Walter Bright}, journal={VLSI Design (Manhasset NY: CPM Publications)}, year={June 1985}, pages={50-62} } @MISC{Cheng93vl2mv:a, author = {S-T Cheng and G York and R K Brayton}, title = {VL2MV: A Compiler from Verilog to BLIF-MV}, year = {1993} } @MISC{Odin, author = {Peter Jamieson and Jonathan Rose}, title = {A VERILOG RTL SYNTHESIS TOOL FOR HETEROGENEOUS FPGAS}, year = {2005} } @inproceedings{vtr2012, title={The VTR Project: Architecture and CAD for FPGAs from Verilog to Routing}, author={Jonathan Rose and Jason Luu and Chi Wai Yu and Opal Densmore and Jeff Goeders and Andrew Somerville and Kenneth B. Kent and Peter Jamieson and Jason Anderson}, booktitle={Proceedings of the 20th ACM/SIGDA International Symposium on Field-Programmable Gate Arrays}, pages={77--86}, year={2012}, organization={ACM} } @MISC{LogicSynthesis, author = {G D Hachtel and F Somenzi}, title = {Logic Synthesis and Verification Algorithms}, year = {1996} } @ARTICLE{Verilog2005, journal={IEEE Std 1364-2005 (Revision of IEEE Std 1364-2001)}, title={IEEE Standard for Verilog Hardware Description Language}, author={IEEE Standards Association and others}, year={2006}, doi={10.1109/IEEESTD.2006.99495} } @ARTICLE{VerilogSynth, journal={IEEE Std 1364.1-2002}, title={IEEE Standard for Verilog Register Transfer Level Synthesis}, author={IEEE Standards Association and others}, year={2002}, doi={10.1109/IEEESTD.2002.94220} } @ARTICLE{VHDL, journal={IEEE Std 1076-2008 (Revision of IEEE Std 1076-2002)}, title={IEEE Standard VHDL Language Reference Manual}, author={IEEE Standards Association and others}, year={2009}, month={26}, doi={10.1109/IEEESTD.2009.4772740} } @ARTICLE{VHDLSynth, journal={IEEE Std 1076.6-2004 (Revision of IEEE Std 1076.6-1999)}, title={IEEE Standard for VHDL Register Transfer Level (RTL) Synthesis}, author={IEEE Standards Association and others}, year={2004}, doi={10.1109/IEEESTD.2004.94802} } @ARTICLE{IP-XACT, journal={IEEE Std 1685-2009}, title={IEEE Standard for IP-XACT, Standard Structure for Packaging, Integrating, and Reusing IP within Tools Flows}, author={IEEE Standards Association and others}, year={2010}, pages={C1-360}, keywords={abstraction definitions, address space specification, bus definitions, design environment, EDA, electronic design automation, electronic system level, ESL, implementation constraints, IP-XACT, register transfer level, RTL, SCRs, semantic consistency rules, TGI, tight generator interface, tool and data interoperability, use models, XML design meta-data, XML schema}, doi={10.1109/IEEESTD.2010.5417309} } @book{Dragonbook, author = {Aho, Alfred V. and Sethi, Ravi and Ullman, Jeffrey D.}, title = {Compilers: principles, techniques, and tools}, year = {1986}, isbn = {0-201-10088-6}, publisher = {Addison-Wesley Longman Publishing Co., Inc.}, address = {Boston, MA, USA} } @INPROCEEDINGS{Cummings00, author = {Clifford E. Cummings and Sunburst Design Inc}, title = {Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill}, booktitle = {SNUG (Synopsys Users Group) 2000 User Papers, section-MC1 (1 st paper}, year = {2000} } @ARTICLE{MURPHY, author={D. L. Klipstein}, journal={Cahners Publishing Co., EEE Magazine, Vol. 15, No. 8}, title={The Contributions of Edsel Murphy to the Understanding of the Behavior of Inanimate Objects}, year={August 1967} } @INPROCEEDINGS{fsmextract, author={Yiqiong Shi and Chan Wai Ting and Bah-Hwee Gwee and Ye Ren}, booktitle={Circuits and Systems (ISCAS), Proceedings of 2010 IEEE International Symposium on}, title={A highly efficient method for extracting FSMs from flattened gate-level netlist}, year={2010}, pages={2610-2613}, keywords={circuit CAD;finite state machines;microcontrollers;FSM;control-intensive circuits;finite state machines;flattened gate-level netlist;state register elimination technique;Automata;Circuit synthesis;Continuous wavelet transforms;Design automation;Digital circuits;Hardware design languages;Logic;Microcontrollers;Registers;Signal processing}, doi={10.1109/ISCAS.2010.5537093}, } @ARTICLE{MultiLevelLogicSynth, author={Brayton, R.K. and Hachtel, G.D. and Sangiovanni-Vincentelli, A.L.}, journal={Proceedings of the IEEE}, title={Multilevel logic synthesis}, year={1990}, volume={78}, number={2}, pages={264-300}, keywords={circuit layout CAD;integrated logic circuits;logic CAD;capsule summaries;definitions;detailed analysis;in-depth background;logic decomposition;logic minimisation;logic synthesis;logic synthesis techniques;multilevel combinational logic;multilevel logic synthesis;notation;perspective;survey;synthesis methods;technology mapping;testing;Application specific integrated circuits;Design automation;Integrated circuit synthesis;Logic design;Logic devices;Logic testing;Network synthesis;Programmable logic arrays;Signal synthesis;Silicon}, doi={10.1109/5.52213}, ISSN={0018-9219}, } @article{UllmannSubgraphIsomorphism, author = {Ullmann, J. R.}, title = {An Algorithm for Subgraph Isomorphism}, journal = {J. ACM}, issue_date = {Jan. 1976}, volume = {23}, number = {1}, month = jan, year = {1976}, issn = {0004-5411}, pages = {31--42}, numpages = {12}, doi = {10.1145/321921.321925}, acmid = {321925}, publisher = {ACM}, address = {New York, NY, USA}, } @article{een2003temporal, title={Temporal induction by incremental SAT solving}, author={E{\'e}n, Niklas and S{\"o}rensson, Niklas}, journal={Electronic Notes in Theoretical Computer Science}, volume={89}, number={4}, pages={543--560}, year={2003}, publisher={Elsevier} } @inproceedings{btor, title={BTOR: bit-precise modelling of word-level problems for model checking}, author={Brummayer, Robert and Biere, Armin and Lonsing, Florian}, booktitle={Proceedings of the joint workshops of the 6th international workshop on satisfiability modulo theories and 1st international workshop on bit-precise reasoning}, pages={33--38}, year={2008} } @inproceedings{VIS, title={VIS: A system for verification and synthesis}, author={Brayton, Robert K and Hachtel, Gary D and Sangiovanni-Vincentelli, Alberto and Somenzi, Fabio and Aziz, Adnan and Cheng, Szu-Tsung and Edwards, Stephen and Khatri, Sunil and Kukimoto, Yuji and Pardo, Abelardo and others}, booktitle={Proceedings of the 8th International Conference on Computer Aided Verification}, pages={428--432}, year={1996}, organization={Springer} } yosys-0.65/docs/source/requirements.txt000066400000000000000000000001451520057232300203450ustar00rootroot00000000000000furo-ys @ git+https://github.com/YosysHQ/furo-ys sphinxcontrib-bibtex rtds-action sphinx-inline-tabs yosys-0.65/docs/source/using_yosys/000077500000000000000000000000001520057232300174545ustar00rootroot00000000000000yosys-0.65/docs/source/using_yosys/bugpoint.rst000066400000000000000000000477551520057232300220570ustar00rootroot00000000000000Minimizing failing (or bugged) designs ====================================== .. TODO:: pending merge of https://github.com/YosysHQ/yosys/pull/5068 This document is a how-to guide for reducing problematic designs to the bare minimum needed for reproducing the issue. This is a Yosys specific alternative to the Stack Overflow article: `How to create a Minimal, Reproducible Example`_, and is intended to help when there's something wrong with your design, or with Yosys itself. .. _How to create a Minimal, Reproducible Example: https://stackoverflow.com/help/minimal-reproducible-example .. note:: This guide assumes a moderate degree of familiarity with Yosys and requires some amount of problem solving ability. Before you start ---------------- The first (and often overlooked) step, is to check for and *read* any error messages or warnings. Passing the ``-q`` flag when running Yosys will make it so that only warnings and error messages are written to the console. Don't just read the last message either, there may be warnings that indicate a problem before it happens. While some things may only be regarded as warnings, such as multiple drivers for the same signal or logic loops, these can cause problems in some synthesis flows but not others. A Yosys error (one that starts with ``ERROR:``) may give you a line number from your design, or the name of the object causing issues. If so, you may already have enough information to resolve the problem, or at least understand why it's happening. .. note:: If you're not already, try using the latest version from the `Yosys GitHub`_. You may find that your issue has already been fixed! And even if it isn't, testing with two different versions is a good way to ensure reproducibility. .. _Yosys GitHub: https://github.com/YosysHQ/yosys Another thing to be aware of is that Yosys generally doesn't perform rigorous checking of input designs to ensure they are valid. This is especially true for the `read_verilog` frontend. It is instead recommended that you try load it with `iverilog`_ or `verilator`_ first, as an invalid design can often lead to unexpected issues. .. _iverilog: https://steveicarus.github.io/iverilog/ .. _verilator: https://www.veripool.org/verilator/ If you're using a custom synthesis script, try take a bit of time to figure out which command is failing. Calling ``echo on`` at the start of your script will `echo` each command executed; the last echo before the error should then be where the error has come from. Check the help message for the failing command; does it indicate limited support, or mention some other command that needs to be run first? You can also try to call `check` and/or ``hierarchy -check`` before the failure to see if they report and errors or warnings. Minimizing RTLIL designs with bugpoint -------------------------------------- Yosys provides the `bugpoint` command for reducing a failing design to the smallest portion of that design which still results in failure. While initially developed for Yosys crashes, `bugpoint` can also be used for designs that lead to non-fatal errors, or even failures in other tools that use the output of a Yosys script. .. note:: Make sure to back up your code (design source and yosys script(s)) before making any modifications. Even if the code itself isn't important, this can help avoid "losing" the error while trying to debug it. Can I use bugpoint? ~~~~~~~~~~~~~~~~~~~ The first thing to be aware of is that `bugpoint` is not available in every build of Yosys. Because the command works by invoking external processes, it requires that Yosys can spawn executables. Notably this means `bugpoint` is not able to be used in WebAssembly builds such as that available via YoWASP. The easiest way to check your build of Yosys is by running ``yosys -h bugpoint``. If Yosys displays the help text for `bugpoint` then it is available for use. .. code-block:: console :caption: `bugpoint` is unavailable $ yosys -h bugpoint -- Running command `help bugpoint' -- No such command or cell type: bugpoint Next you need to separate loading the design from the failure point; you should be aiming to reproduce the failure by running ``yosys -s -s ``. If the failure occurs while loading the design, such as during `read_verilog` you will instead have to minimize the input design yourself. Check out the instructions for :ref:`using_yosys/bugpoint:minimizing verilog designs` below. .. note:: You should also be able to run the two scripts separately, calling first ``yosys -s -p 'write_rtlil design.il'`` and then ``yosys -s design.il``. If this doesn't work then it may mean that the failure isn't reproducible from RTLIL and `bugpoint` won't work either. When we talk about failure points here, it doesn't just mean crashes or errors in Yosys. The ```` script can also be a user-defined failure such as the `select` command with one of the ``-assert-*`` options; an example where this might be useful is when a pass is supposed to remove a certain kind of cell, but there is some edge case where the cell is not removed. Another use-case would be minimizing a design which fails with the `equiv_opt` command, suggesting that the optimization in question alters the circuit in some way. It is even possible to use `bugpoint` with failures *external* to Yosys, by making use of the `exec` command in ````. This is especially useful when Yosys is outputting an invalid design, or when some other tool is incompatible with the design. Be sure to use the ``exec -expect-*`` options so that the pass/fail can be detected correctly. Multiple calls to `exec` can be made, or even entire shell scripts: .. code-block:: yoscrypt exec -expect-return 1 --bash Our final failure we can use with `bugpoint` is one returned by a wrapper process, such as ``valgrind`` or ``timeout``. In this case you will be calling something like `` yosys -s design.il``. Here, Yosys is run under a wrapper process which checks for some failure state, like a memory leak or excessive runtime. How do I use bugpoint? ~~~~~~~~~~~~~~~~~~~~~~ At this point you should have: 1. either an RTLIL file containing the design to minimize (referred to here as ``design.il``), or a Yosys script, ````, which loads it; and 2. a Yosys script, ````, which produces the failure and returns a non-zero return status. Now call ``yosys -qq -s design.il`` and take note of the error(s) that get printed. A template script, ````, is provided here which you can use. Make sure to configure it with the correct filenames and use only one of the methods to load the design. Fill in the ``-grep`` option with the error message printed just before. If you are using a wrapper process for your failure state, add the ``-runner ""`` option to the `bugpoint` call. .. code-block:: yoscrypt :caption: ```` template script # Load design read_rtlil design.il ## OR script # Call bugpoint with failure bugpoint -script -grep "" # Save minimized design write_rtlil min.il The ``-grep`` option is used to search the log file generated by the Yosys under test. If the error message is generated by something else, such as a wrapper process or compiler sanitizer, then you should instead use ``-err_grep``. For an OS error, like a SEGFAULT, you can also use ``-expect-return`` to check the error code returned. .. note:: Checking the error message or return status is optional, but highly recommended. `bugpoint` can quite easily introduce bugs by creating malformed designs that commands were not intended to handle. By having some way to check the error, `bugpoint` can ensure that it is the *right* error being reproduced. This is even more important when ```` contains more than one command. By default, `bugpoint` is able to remove any part of the design. In order to keep certain parts, for instance because you already know they are related to the failure, you can use the ``bugpoint_keep`` attribute. This can be done with ``(* bugpoint_keep *)`` in Verilog, ``attribute \bugpoint_keep 1`` in RTLIL, or ``setattr -set bugpoint_keep 1 [selection]`` from a Yosys script. It is also possible to limit `bugpoint` to only removing certain *kinds* of objects, such as only removing entire modules or cells (instances of modules). For more about the options available, check ``help bugpoint`` or :cmd:title:`bugpoint`. In some situations, it may also be helpful to use `setenv` before `bugpoint` to set environment variables for the spawned processes. An example of this is ``setenv UBSAN_OPTIONS halt_on_error=1`` for where you are trying to raise an error on undefined behaviour but only want the child process to halt on error. .. note:: Using `setenv` in this way may or may not affect the current process. For instance the ``UBSAN_OPTIONS halt_on_error`` here only affects child processes, as does the :doc:`Yosys environment variable` ``ABC`` because they are only read on start-up. While others, such as ``YOSYS_NOVERIFIC`` and ``HOME``, are evaluated each time they are used. Once you have finished configuration, you can now run ``yosys ``. The first thing `bugpoint` will do is test the input design fails. If it doesn't, make sure you are using the right ``yosys`` executable; unless the ``-yosys`` option is provided, it will use whatever the shell defaults to, *not* the current ``yosys``. If you are using the ``-runner`` option, try replacing the `bugpoint` command with ``write_rtlil test.il`` and then on a new line, ``! yosys -s test.il`` to check it works as expected and returns a non-zero status. .. seealso:: For more on script parsing and the use of ``!``, check out :ref:`getting_started/scripting_intro:script parsing`. Depending on the size of your design, and the length of your ````, `bugpoint` may take some time; remember, it will run ``yosys -s `` on each iteration of the design. The bigger the design, the more iterations. The longer the ````, the longer each iteration will take. As the design shrinks and `bugpoint` converges, each iteration should take less and less time. Once all simplifications are exhausted and there are no more objects that can be removed, the script will continue and the minimized design can be saved. What do I do with the minimized design? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First off, check the minimized design still fails. This is especially important if you're not using `write_rtlil` to output the minimized design. For example, if you ran :ref:`bugpoint_script` below, then calling ``yosys -s min.v`` should still fail in the same way. .. code-block:: yoscrypt :caption: example `bugpoint` minimizer :name: bugpoint_script read_verilog design.v bugpoint -script write_verilog min.v The `write_rtlil` command is generally more reliable, since `bugpoint` will have run that exact code through the failing script. Other ``write_*`` commands convert from the RTLIL and then back again during the ``read_*`` which can result in differences which mean the design no longer fails. .. note:: Simply calling Yosys with the output of ``write_*``, as in ``yosys -s min.v``, does not guarantee that the corresponding ``read_*`` will be used. For more about this, refer to :doc:`/using_yosys/more_scripting/load_design`, or load the design explicitly with ``yosys -p 'read_verilog min.v' -s ``. Once you've verified the failure still happens, check out :ref:`using_yosys/bugpoint:identifying issues` for more on what to do next. Minimizing Verilog designs -------------------------- .. seealso:: This section is not specific to Yosys, so feel free to use another guide such as Stack Overflow's `How to create a Minimal, Reproducible Example`_. Be sure to check any errors or warnings for messages that might identify source lines or object names that might be causing the failure, and back up your source code before modifying it. If you have multiple source files, you should start by reducing them down to a single file. If a specific file is failing to read, try removing everything else and just focus on that one. If your source uses the ``include`` directive, replace it with the contents of the file referenced. Unlike RTLIL designs where we can use `bugpoint`, Yosys does not provide any tools for minimizing Verilog designs. Instead, you should use an external tool like `C-Reduce`_ (with the ``--not-c`` flag) or `sv-bugpoint`_. .. _C-Reduce: https://github.com/csmith-project/creduce .. _sv-bugpoint: https://github.com/antmicro/sv-bugpoint C-Reduce ~~~~~~~~ As a very brief overview for using C-Reduce, you want your failing source design (``test.v``), and some shell script which checks for the error being investigated (``test.sh``). Below is an :ref:`egtest` which uses `logger` and the ``-expect error "" 1`` option to perform a similar role to ``bugpoint -grep``, along with ``verilator`` to lint the code and make sure it is still valid. .. code-block:: bash :caption: Example test.sh for C-Reduce :name: egtest #!/usr/bin/env bash verilator --lint-only test.v &&/ yosys -p 'logger -expect error "unsupported" 1; read_verilog test.v' .. code-block:: verilog :caption: input test.v module top(input clk, a, b, c, output x, y, z); always @(posedge clk) begin if (a == 1'b1) $stop; end assign x = a; assign y = a ^ b; assign z = c; endmodule In this example ``read_verilog test.v`` is giving an error message that contains the string "unsupported" because the ``$stop`` system task is only supported in ``initial`` blocks. By calling ``creduce ./test.sh test.v --not-c`` we can minimize the design to just the failing code, while still being valid Verilog. .. code-block:: verilog :caption: output test.v module a; always begin $stop; end endmodule sv-bugpoint ~~~~~~~~~~~ sv-bugpoint works quite similarly to C-Reduce, except it requires an output directory to be provided and the check script needs to accept the target file as an input argument: ``sv-bugpoint outDir/ test.sh test.v`` .. code-block:: bash :caption: Example test.sh for sv-bugpoint #!/usr/bin/env bash verilator --lint-only $1 &&/ yosys -p "logger -expect error \"unsupported\" 1; read_verilog $1" Notice that the commands for ``yosys -p`` are now in double quotes (``"``), and the quotes around the error string are escaped (``\"``). This is necessary for the ``$1`` argument subsitution to work correctly. Doing it manually ~~~~~~~~~~~~~~~~~ If for some reason you are unable to use a tool to minimize your code, you can still do it manually. But it can be a time consuming process and requires a lot of iteration. At any point in the process, you can check for anything that is unused or totally disconnected (ports, wires, etc) and remove them. If a specific module is causing the problem, try to set that as the top module instead. Any parameters should have their default values changed to match the failing usage. As a rule of thumb, try to split things roughly in half at each step; similar to a "binary search". If you have 10 cells (instances of modules) in your top module, and have no idea what is causing the issue, split them into two groups of 5 cells. For each group of cells, try remove them and see if the failure still happens. If the error still occurs with the first group removed, but disappears when the second group is removed, then the first group can be safely removed. If a module has no more instances, remove it entirely. Repeat this for each remaining group of cells until each group only has 1 cell in it and no more cells can be removed without making the error disappear. You can also repeat this for each module still in your design. After minimizing the number of cells, do the same for the process blocks in your top module. And again for any generate blocks and combinational blocks. Remember to check for any ports or signals which are no longer used and remove those too. Any signals which are written but never read can also be removed. .. note:: Depending on where the design is failing, there are some commands which may help in identifying unused objects in the design. `hierarchy` will identify which modules are used and which are not, but check for ``$paramod`` modules before removing unused ones. ``debug clean`` will list all unused wires in each module, as well as unused cells which were automatically generated (giving the line number of the source that generated them). Adding the ``-purge`` flag will also include named wires that would normally be ignored by `clean`. Though when there are large numbers of unused wires it is often easier to just delete sections of the code and see what happens. Next, try to remove or reduce assignments (``a = b``) and operations (``a + b``). A good place to start is by checking for any wires/registers which are read but never written. Try removing the signal declaration and replacing references to it with ``'0`` or ``'x``. Do this with any constants too. Try to replace strings with numeric values, and wide signals with smaller ones, then see if the error persists. Check if there are any operations that you can simplify, like replacing ``a & '0`` with ``'0``. If you have enable or reset logic, try removing it and see if the error still occurs. Try reducing ``if .. else`` and ``case`` blocks to a single case. Even if that doesn't work, you may still be able to remove some paths; start with cases that appear to be unreachable and go from there. .. note:: When sharing code on the `Yosys GitHub`_, please try to keep things in English. Declarations and strings should stick to the letters a-z and numbers 0-9, unless the error is arising because of the names/characters used. Identifying issues ------------------ When identifying issues, it is quite useful to understand the conditions under which the issue is occurring. While there are occasionally bugs that affect a significant number of designs, Yosys changes are tested on a variety of designs and operating systems which typically catch any such issues before they make it into the main branch. So what is is it about your situation that makes it unusual? .. note:: If you have access to a different platform you could also check if your issue is reproducible there. Some issues may be specific to the platform or build of Yosys. Try to match the minimized design back to its original context. Could you achieve the same thing a different way, and if so, does this other method have the same issue? Try to change the design in small ways and see what happens; while `bugpoint` can reduce and simplify a design, it doesn't *change* much. What happens if you change operators, for example a left shift (or `$shl`) to a right shift (or `$shr`)? Try to see if the issue is tied to specific parameters, widths, or values. Search `the existing issues`_ and see if someone has already made a bug report. This is where changing the design and finding the limits of what causes the failure really comes in handy. If you're more familiar with how the problem can arise, you may be able to find a related issue more easily. If an issue already exists for one case of the problem but you've found other cases, you can comment on the issue and help get it solved. If there are no existing or related issues already, then check out the steps for :ref:`yosys_internals/extending_yosys/contributing:reporting bugs`. .. _the existing issues: https://github.com/YosysHQ/yosys/issues .. warning:: If you are using a fuzzer to find bugs, follow the instructions for :doc:`/yosys_internals/extending_yosys/advanced_bugpoint`. **Do not** open more than one fuzzer generated issue at a time if you can not identify the root cause. If you are found to be doing this, your issues may be closed without further investigation. yosys-0.65/docs/source/using_yosys/index.rst000066400000000000000000000012221520057232300213120ustar00rootroot00000000000000Using Yosys (advanced) ====================== While much of Yosys is focused around synthesis, there are also a number of other useful things that can be accomplished with Yosys scripts or in an interactive shell. As such this section is broken into two parts: :doc:`/using_yosys/synthesis/index` expands on the :doc:`/getting_started/example_synth` and goes into further detail on the major commands used in synthesis; :doc:`/using_yosys/more_scripting/index` covers the ways Yosys can interact with designs for a deeper investigation. .. toctree:: :maxdepth: 2 :hidden: synthesis/index more_scripting/index bugpoint verilog pyosys yosys-0.65/docs/source/using_yosys/more_scripting/000077500000000000000000000000001520057232300225005ustar00rootroot00000000000000yosys-0.65/docs/source/using_yosys/more_scripting/data_flow_tracking.rst000066400000000000000000000133421520057232300270570ustar00rootroot00000000000000Dataflow tracking ------------------- Yosys can be used to answer questions such as "can this signal affect this other signal?" via its *dataflow tracking* support. For this, four special cells, `$get_tag`, `$set_tag`, `$overwrite_tag` and `$original_tag` are inserted into the design (e.g. by a custom Yosys pass) and then the `dft_tag` is run, which converts these cells into ordinary logic. Typically, one would then use `SBY`_ to prove assertions involving these cells. .. _SBY: https://yosyshq.readthedocs.io/projects/sby Ordinarily in Yosys, the state of a bit is simply ``0`` or ``1`` (or one of the special values, ``z`` and ``x``). During dataflow tracking they are augmented with a set of tags. For example, the state of a bit could be ``0`` and the set of tags ``"KEY"`` and ``"OVERFLOW"``. In addition to their usual operations on the logical bits, Yosys operations must now also process the status of the tags. For this, tags are simply *forwarded* or *propagated* (i.e. copied) from inputs to outputs, according to the following general rule: A tag is forwarded from an input to an output if the input can affect the output, for that particular state of all other inputs. For example, XOR, AND and OR cells propagate tags as follows: #. XOR simply forwards all tags from its inputs to its output, because inputs to XOR can always affect the output. #. AND forwards tags on a given input only if the other input is ``1``. Because if one input is ``0``, the other input can never affect the output. #. Similarly, OR forwards tags only if the other input is ``0``. There are two exceptions to this rule: #. In general, propagation is only determined approximately. For example, unless the ``dft_tag`` code knows about a cell, it simply assumes the worst-case behaviour that all inputs can affect all outputs. Further, the code also does not consider that, when a signal affects multiple inputs of a cell, the resulting simultaneous changes of the inputs can cancel each other out, for example ``A ^ A`` or ``A ^ (B ^ A)`` is independent of ``A``, but its tags would be propagated nonetheless. #. If tag groups are used, the rules are modified (see below). Because of this propagation behaviour, we can answer questions about what signals are affected by a certain signal, by injecting a tag at that point in the circuit, and observing where the tag is visible. Example use cases ~~~~~~~~~~~~~~~~~~ As an example use case, consider a cryptographic processor which is not supposed to expose its secret keys to the outside world. We can tag all key bits with the ``"KEY"`` tag and use `SBY`_ to formally verify that no external signal ever carries the ``"KEY"`` tag, meaning that key information is not visible to the outside. As a caveat, we have to manually clear the ``"KEY"`` tag during cryptographic operations, as proving that the cryptographic operations themselves do not leak key information is beyond the ability of Yosys. However we can still easily detect, if e.g. an engineer forgot to remove debugging code that allows reading back key data. As a different use case, we can modify all adders in the design to set the ``"OVERFLOW"`` tag on their output bits, if the addition overflowed, and then add asserts to all flip-flop inputs and output signals that they do not carry the ``"OVERFLOW"`` tag, i.e. that the results of overflowed additions never affect system state. Note that in this particular example we use the ability of tag insertion to be conditional on logic, in this case the overflow condition of an adder. Semantics of dataflow tracking cells ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``$set_tag`` has inputs ``A``, ``SET``, ``CLR``, an output ``Y`` and a string parameter ``TAG``. The logic value of ``A`` and all tags other than the one named by the ``TAG`` parameter are simply copied to ``Y``. If ``SET`` is ``1``, then the named tag is added to ``Y``. Otherwise, if ``CLR`` is ``1``, then the named tag is removed. Otherwise, the tag is unchanged, i.e. it is present in ``Y`` if it is present in ``A``. ``$get_tag`` has an input ``A`` and an output ``Y`` and a string parameter ``TAG``. ``$get_tag`` inspects ``A`` for the presence or absence of a tag of the given name and sets ``Y`` to ``1`` if the tag is present. The logical value of ``A`` is completely ignored. ``$overwrite_tag`` functions like ``$set_tag``, but lacks the ``Y`` output. Instead of providing a modified version of the input signal, it modifies the signal ``A`` "in-place", i.e. if a signal is input to ``$overwrite_tag``, that is equivalent to interposing a ``$set_tag`` between its driver and all cells it is connected to. The main purpose of ``$overwrite_tag`` is adding tags to signals produced within a module that cannot or should not be modified itself. ``$original_tag`` functions identically to ``$get_tag``, but ignores ``$overwrite_tag``, i.e. when converting the ``$overwrite_tag`` to ``$set_tag`` as described above, it is equivalent to inserting the ``$get_tag`` *before* the ``$set_tag``. Tag groups ~~~~~~~~~~~~~~ Tag groups are an advanced feature that modify the propagation rule discussed above. To use tag groups, simply name tags according to the schema ``"group:name"``. For example, ``"key:0"``, ``"key:a"``, ``"key:b"`` would be three tags in the ``"key"`` group. The propagation rule is then amended by Inputs cannot block the propagation of each other's tags for tags of the same group. For example, an AND gate will propagate a given tag on one input, if the other input is either 1 or carries a tag of the same group. So if one input is ``0, "key:a"`` and the other is ``0, "key:b"`` the result would be ``0, "key:a", "key:b"``, rather than simply ``0``. Note that if we add an unrelated ``"overflow"`` tag to the first input, it would still not be propagated.yosys-0.65/docs/source/using_yosys/more_scripting/index.rst000066400000000000000000000004211520057232300243360ustar00rootroot00000000000000More scripting -------------- .. todo:: brief overview for the more scripting index .. todo:: troubleshooting document(?) .. toctree:: :maxdepth: 3 load_design selections interactive_investigation model_checking data_flow_tracking .. troubleshooting yosys-0.65/docs/source/using_yosys/more_scripting/interactive_investigation.rst000066400000000000000000001027051520057232300305170ustar00rootroot00000000000000Interactive design investigation -------------------------------- .. todo:: interactive design opening text .. role:: yoscrypt(code) :language: yoscrypt .. _interactive_show: A look at the show command ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. TODO:: merge into :doc:`/getting_started/scripting_intro` show section This section explores the `show` command and explains the symbols used in the circuit diagrams generated by it. The code used is included in the Yosys code base under |code_examples/show|_. .. |code_examples/show| replace:: :file:`docs/source/code_examples/show` .. _code_examples/show: https://github.com/YosysHQ/yosys/tree/main/docs/source/code_examples/show A simple circuit ^^^^^^^^^^^^^^^^ :ref:`example_v` below provides the Verilog code for a simple circuit which we will use to demonstrate the usage of `show` in a simple setting. .. literalinclude:: /code_examples/show/example.v :language: Verilog :caption: :file:`example.v` :name: example_v The Yosys synthesis script we will be running is included as :numref:`example_ys`. Note that `show` is called with the ``-pause`` option, that halts execution of the Yosys script until the user presses the Enter key. Using :yoscrypt:`show -pause` also allows the user to enter an interactive shell to further investigate the circuit before continuing synthesis. .. literalinclude:: /code_examples/show/example_show.ys :language: yoscrypt :caption: :file:`example_show.ys` :name: example_ys This script, when executed, will show the design after each of the three synthesis commands. We will now look at each of these diagrams and explain what is shown. .. note:: The images uses in this document are generated from the :file:`example.ys` file, rather than :file:`example_show.ys`. :file:`example.ys` outputs the schematics as :file:`.dot` files rather than displaying them directly. You can view these images yourself by running :file:`yosys example.ys` and then ``xdot example_first.dot`` etc. .. figure:: /_images/code_examples/show/example_first.* :class: width-helper invert-helper Output of the first `show` command in :numref:`example_ys` The first output shows the design directly after being read by the Verilog front-end. Input and output ports are displayed as octagonal shapes. Cells are displayed as rectangles with inputs on the left and outputs on the right side. The cell labels are two lines long: The first line contains a unique identifier for the cell and the second line contains the cell type. Internal cell types are prefixed with a dollar sign. For more details on the internal cell library, see :doc:`/cell_index`. Constants are shown as ellipses with the constant value as label. The syntax ``'`` is used for constants that are not 32-bit wide and/or contain bits that are not 0 or 1 (i.e. ``x`` or ``z``). Ordinary 32-bit constants are written using decimal numbers. Single-bit signals are shown as thin arrows pointing from the driver to the load. Signals that are multiple bits wide are shown as thick arrows. Finally *processes* are shown in boxes with round corners. Processes are Yosys' internal representation of the decision-trees and synchronization events modelled in a Verilog ``always``-block. The label reads ``PROC`` followed by a unique identifier in the first line and contains the source code location of the original ``always``-block in the second line. Note how the multiplexer from the ``?:``-expression is represented as a `$mux` cell but the multiplexer from the ``if``-statement is yet still hidden within the process. The `proc` command transforms the process from the first diagram into a multiplexer and a d-type flip-flop, which brings us to the second diagram: .. figure:: /_images/code_examples/show/example_second.* :class: width-helper invert-helper Output of the second `show` command in :numref:`example_ys` The Rhombus shape to the right is a dangling wire. (Wire nodes are only shown if they are dangling or have "public" names, for example names assigned from the Verilog input.) Also note that the design now contains two instances of a ``BUF``-node. These are artefacts left behind by the `proc` command. It is quite usual to see such artefacts after calling commands that perform changes in the design, as most commands only care about doing the transformation in the least complicated way, not about cleaning up after them. The next call to `clean` (or `opt`, which includes `clean` as one of its operations) will clean up these artefacts. This operation is so common in Yosys scripts that it can simply be abbreviated with the ``;;`` token, which doubles as separator for commands. Unless one wants to specifically analyze this artefacts left behind some operations, it is therefore recommended to always call `clean` before calling `show`. In this script we directly call `opt` as the next step, which finally leads us to the third diagram: .. figure:: /_images/code_examples/show/example_third.* :class: width-helper invert-helper :name: example_out Output of the third `show` command in :ref:`example_ys` Here we see that the `opt` command not only has removed the artifacts left behind by `proc`, but also determined correctly that it can remove the first `$mux` cell without changing the behavior of the circuit. Break-out boxes for signal vectors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The code listing below shows a simple circuit which uses a lot of spliced signal accesses. .. literalinclude:: /code_examples/show/splice.v :caption: :file:`splice.v` :name: splice_src Notice how the output for this circuit from the `show` command (:numref:`splice_dia`) appears quite complex. This is an unfortunate side effect of the way Yosys handles signal vectors (aka. multi-bit wires or buses) as native objects. While this provides great advantages when analyzing circuits that operate on wide integers, it also introduces some additional complexity when the individual bits of of a signal vector are accessed. .. figure:: /_images/code_examples/show/splice.* :class: width-helper invert-helper :name: splice_dia Output of ``yosys -p 'prep -top splice_demo; show' splice.v`` The key elements in understanding this circuit diagram are of course the boxes with round corners and rows labeled ``: - :``. Each of these boxes have one signal per row on one side and a common signal for all rows on the other side. The ``:`` tuples specify which bits of the signals are broken out and connected. So the top row of the box connecting the signals ``a`` and ``x`` indicates that the bit 0 (i.e. the range 0:0) from signal ``a`` is connected to bit 1 (i.e. the range 1:1) of signal ``x``. Lines connecting such boxes together and lines connecting such boxes to cell ports have a slightly different look to emphasise that they are not actual signal wires but a necessity of the graphical representation. This distinction seems like a technicality, until one wants to debug a problem related to the way Yosys internally represents signal vectors, for example when writing custom Yosys commands. Gate level netlists ^^^^^^^^^^^^^^^^^^^ :numref:`first_pitfall` shows two common pitfalls when working with designs mapped to a cell library: .. figure:: /_images/code_examples/show/cmos_00.* :class: width-helper invert-helper :name: first_pitfall A half-adder built from simple CMOS gates, demonstrating common pitfalls when using `show` .. literalinclude:: /code_examples/show/cmos.ys :language: yoscrypt :start-after: pitfall :end-at: cmos_00 :name: pitfall_code :caption: Generating :numref:`first_pitfall` First, Yosys did not have access to the cell library when this diagram was generated, resulting in all cell ports defaulting to being inputs. This is why all ports are drawn on the left side the cells are awkwardly arranged in a large column. Secondly the two-bit vector ``y`` requires breakout-boxes for its individual bits, resulting in an unnecessary complex diagram. .. figure:: /_images/code_examples/show/cmos_01.* :class: width-helper invert-helper :name: second_pitfall Effects of `splitnets` command and of providing a cell library on design in :numref:`first_pitfall` .. literalinclude:: /code_examples/show/cmos.ys :language: yoscrypt :start-after: fixed :end-at: cmos_01 :name: pitfall_avoided :caption: Generating :numref:`second_pitfall` For :numref:`second_pitfall`, Yosys has been given a description of the cell library as Verilog file containing blackbox modules. There are two ways to load cell descriptions into Yosys: First the Verilog file for the cell library can be passed directly to the `show` command using the ``-lib `` option. Secondly it is possible to load cell libraries into the design with the :yoscrypt:`read_verilog -lib ` command. The second method has the great advantage that the library only needs to be loaded once and can then be used in all subsequent calls to the `show` command. In addition to that, :numref:`second_pitfall` was generated after :yoscrypt:`splitnet -ports` was run on the design. This command splits all signal vectors into individual signal bits, which is often desirable when looking at gate-level circuits. The ``-ports`` option is required to also split module ports. Per default the command only operates on interior signals. Miscellaneous notes ^^^^^^^^^^^^^^^^^^^ Per default the `show` command outputs a temporary dot file and launches ``xdot`` to display it. The options ``-format``, ``-viewer`` and ``-prefix`` can be used to change format, viewer and filename prefix. Note that the ``pdf`` and ``ps`` format are the only formats that support plotting multiple modules in one run. The ``dot`` format can be used to output multiple modules, however ``xdot`` will raise an error when trying to read them. In densely connected circuits it is sometimes hard to keep track of the individual signal wires. For these cases it can be useful to call `show` with the ``-colors `` argument, which randomly assigns colors to the nets. The integer (> 0) is used as seed value for the random color assignments. Sometimes it is necessary it try some values to find an assignment of colors that looks good. The command :yoscrypt:`help show` prints a complete listing of all options supported by the `show` command. Navigating the design ~~~~~~~~~~~~~~~~~~~~~ Plotting circuit diagrams for entire modules in the design brings us only helps in simple cases. For complex modules the generated circuit diagrams are just stupidly big and are no help at all. In such cases one first has to select the relevant portions of the circuit. In addition to *what* to display one also needs to carefully decide *when* to display it, with respect to the synthesis flow. In general it is a good idea to troubleshoot a circuit in the earliest state in which a problem can be reproduced. So if, for example, the internal state before calling the `techmap` command already fails to verify, it is better to troubleshoot the coarse-grain version of the circuit before `techmap` than the gate-level circuit after `techmap`. .. Note:: It is generally recommended to verify the internal state of a design by writing it to a Verilog file using :yoscrypt:`write_verilog -noexpr` and using the simulation models from :file:`simlib.v` and :file:`simcells.v` from the Yosys data directory (as printed by ``yosys-config --datdir``). Interactive navigation ^^^^^^^^^^^^^^^^^^^^^^ Once the right state within the synthesis flow for debugging the circuit has been identified, it is recommended to simply add the `shell` command to the matching place in the synthesis script. This command will stop the synthesis at the specified moment and go to shell mode, where the user can interactively enter commands. For most cases, the shell will start with the whole design selected (i.e. when the synthesis script does not already narrow the selection). The command `ls` can now be used to create a list of all modules. The command `cd` can be used to switch to one of the modules (type ``cd ..`` to switch back). Now the `ls` command lists the objects within that module. This is demonstrated below using :file:`example.v` from `A simple circuit`_: .. literalinclude:: /code_examples/show/example.out :language: doscon :start-at: yosys> ls :end-before: yosys [example]> dump :caption: Output of `ls` and `cd` after running :file:`yosys example.v` :name: lscd When a module is selected using the `cd` command, all commands (with a few exceptions, such as the ``read_`` and ``write_`` commands) operate only on the selected module. This can also be useful for synthesis scripts where different synthesis strategies should be applied to different modules in the design. We can see that the cell names from :numref:`example_out` are just abbreviations of the actual cell names, namely the part after the last dollar-sign. Most auto-generated names (the ones starting with a dollar sign) are rather long and contains some additional information on the origin of the named object. But in most cases those names can simply be abbreviated using the last part. Usually all interactive work is done with one module selected using the `cd` command. But it is also possible to work from the design-context (``cd ..``). In this case all object names must be prefixed with ``/``. For example ``a*/b*`` would refer to all objects whose names start with ``b`` from all modules whose names start with ``a``. The `dump` command can be used to print all information about an object. For example, calling :yoscrypt:`dump $2` after the :yoscrypt:`cd example` above: .. literalinclude:: /code_examples/show/example.out :language: RTLIL :start-after: yosys [example]> dump :end-before: yosys [example]> cd :dedent: :caption: Output of :yoscrypt:`dump $2` after :numref:`lscd` :name: dump2 This can for example be useful to determine the names of nets connected to cells, as the net-names are usually suppressed in the circuit diagram if they are auto-generated. Note that the output is in the RTLIL representation, described in :doc:`/yosys_internals/formats/rtlil_rep`. Design Investigation ~~~~~~~~~~~~~~~~~~~~ Yosys can also be used to investigate designs (or netlists created from other tools). - The selection mechanism, especially patterns such as ``%ci`` and ``%co``, can be used to figure out how parts of the design are connected. - Commands such as `submod`, `expose`, and `splice` can be used to transform the design into an equivalent design that is easier to analyse. - Commands such as `eval` and `sat` can be used to investigate the behavior of the circuit. - :cmd:title:`show`. - :cmd:title:`dump`. - :cmd:title:`add` and :cmd:title:`delete` can be used to modify and reorganize a design dynamically. The code used is included in the Yosys code base under |code_examples/scrambler|_. .. |code_examples/scrambler| replace:: :file:`docs/source/code_examples/scrambler` .. _code_examples/scrambler: https://github.com/YosysHQ/yosys/tree/main/docs/source/code_examples/scrambler Changing design hierarchy ^^^^^^^^^^^^^^^^^^^^^^^^^ Commands such as `flatten` and `submod` can be used to change the design hierarchy, i.e. flatten the hierarchy or moving parts of a module to a submodule. This has applications in synthesis scripts as well as in reverse engineering and analysis. An example using `submod` is shown below for reorganizing a module in Yosys and checking the resulting circuit. .. literalinclude:: /code_examples/scrambler/scrambler.v :language: verilog :caption: :file:`scrambler.v` .. literalinclude:: /code_examples/scrambler/scrambler.ys :language: yoscrypt :caption: :file:`scrambler.ys` :end-before: cd .. .. figure:: /_images/code_examples/scrambler/scrambler_p01.* :class: width-helper invert-helper .. figure:: /_images/code_examples/scrambler/scrambler_p02.* :class: width-helper invert-helper Analyzing the resulting circuit with :cmd:title:`eval`: .. todo:: replace inline code .. code:: text > cd xorshift32 > rename n2 in > rename n1 out > eval -set in 1 -show out Eval result: \out = 270369. > eval -set in 270369 -show out Eval result: \out = 67634689. > sat -set out 632435482 Signal Name Dec Hex Bin -------------------- ---------- ---------- ------------------------------------- \in 745495504 2c6f5bd0 00101100011011110101101111010000 \out 632435482 25b2331a 00100101101100100011001100011010 Behavioral changes ^^^^^^^^^^^^^^^^^^ Commands such as `techmap` can be used to make behavioral changes to the design, for example changing asynchronous resets to synchronous resets. This has applications in design space exploration (evaluation of various architectures for one circuit). The following techmap map file replaces all positive-edge async reset flip-flops with positive-edge sync reset flip-flops. The code is taken from the example Yosys script for ASIC synthesis of the Amber ARMv2 CPU. .. todo:: replace inline code .. code:: verilog (* techmap_celltype = "$adff" *) module adff2dff (CLK, ARST, D, Q); parameter WIDTH = 1; parameter CLK_POLARITY = 1; parameter ARST_POLARITY = 1; parameter ARST_VALUE = 0; input CLK, ARST; input [WIDTH-1:0] D; output reg [WIDTH-1:0] Q; wire [1023:0] _TECHMAP_DO_ = "proc"; wire _TECHMAP_FAIL_ = !CLK_POLARITY || !ARST_POLARITY; always @(posedge CLK) if (ARST) Q <= ARST_VALUE; else Q <= D; endmodule For more on the `techmap` command, see the page on :doc:`/yosys_internals/techmap`. Advanced investigation techniques ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When working with very large modules, it is often not enough to just select the interesting part of the module. Instead it can be useful to extract the interesting part of the circuit into a separate module. This can for example be useful if one wants to run a series of synthesis commands on the critical part of the module and wants to carefully read all the debug output created by the commands in order to spot a problem. This kind of troubleshooting is much easier if the circuit under investigation is encapsulated in a separate module. Recall the ``memdemo`` design from :ref:`advanced_logic_cones`: .. figure:: /_images/code_examples/selections/memdemo_00.* :class: width-helper invert-helper ``memdemo`` Because this produces a rather large circuit, it can be useful to split it into smaller parts for viewing and working with. :numref:`submod` does exactly that, utilising the `submod` command to split the circuit into three sections: ``outstage``, ``selstage``, and ``scramble``. .. literalinclude:: /code_examples/selections/submod.ys :language: yoscrypt :caption: Using `submod` to break up the circuit from :file:`memdemo.v` :start-after: cd memdemo :end-before: cd .. :name: submod The ``-name`` option is used to specify the name of the new module and also the name of the new cell in the current module. The resulting circuits are shown below. .. figure:: /_images/code_examples/selections/submod_02.* :class: width-helper invert-helper ``outstage`` .. figure:: /_images/code_examples/selections/submod_03.* :class: width-helper invert-helper :name: selstage ``selstage`` .. figure:: /_images/code_examples/selections/submod_01.* :class: width-helper invert-helper ``scramble`` Evaluation of combinatorial circuits ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The `eval` command can be used to evaluate combinatorial circuits. As an example, we will use the ``selstage`` subnet of ``memdemo`` which we found above and is shown in :numref:`selstage`. .. todo:: replace inline code :: yosys [selstage]> eval -set s2,s1 4'b1001 -set d 4'hc -show n2 -show n1 1. Executing EVAL pass (evaluate the circuit given an input). Full command line: eval -set s2,s1 4'b1001 -set d 4'hc -show n2 -show n1 Eval result: \n2 = 2'10. Eval result: \n1 = 2'10. So the ``-set`` option is used to set input values and the ``-show`` option is used to specify the nets to evaluate. If no ``-show`` option is specified, all selected output ports are used per default. If a necessary input value is not given, an error is produced. The option ``-set-undef`` can be used to instead set all unspecified input nets to undef (``x``). The ``-table`` option can be used to create a truth table. For example: :: yosys [selstage]> eval -set-undef -set d[3:1] 0 -table s1,d[0] 10. Executing EVAL pass (evaluate the circuit given an input). Full command line: eval -set-undef -set d[3:1] 0 -table s1,d[0] \s1 \d [0] | \n1 \n2 ---- ------ | ---- ---- 2'00 1'0 | 2'00 2'00 2'00 1'1 | 2'xx 2'00 2'01 1'0 | 2'00 2'00 2'01 1'1 | 2'xx 2'01 2'10 1'0 | 2'00 2'00 2'10 1'1 | 2'xx 2'10 2'11 1'0 | 2'00 2'00 2'11 1'1 | 2'xx 2'11 Assumed undef (x) value for the following signals: \s2 Note that the `eval` command (as well as the `sat` command discussed in the next sections) does only operate on flattened modules. It can not analyze signals that are passed through design hierarchy levels. So the `flatten` command must be used on modules that instantiate other modules before these commands can be applied. Solving combinatorial SAT problems ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Often the opposite of the `eval` command is needed, i.e. the circuits output is given and we want to find the matching input signals. For small circuits with only a few input bits this can be accomplished by trying all possible input combinations, as it is done by the ``eval -table`` command. For larger circuits however, Yosys provides the `sat` command that uses a `SAT`_ solver, `MiniSAT`_, to solve this kind of problems. .. _SAT: http://en.wikipedia.org/wiki/Circuit_satisfiability .. _MiniSAT: http://minisat.se/ .. note:: While it is possible to perform model checking directly in Yosys, it is highly recommended to use SBY or EQY for formal hardware verification. The `sat` command works very similar to the `eval` command. The main difference is that it is now also possible to set output values and find the corresponding input values. For Example: .. todo:: replace inline code :: yosys [selstage]> sat -show s1,s2,d -set s1 s2 -set n2,n1 4'b1001 11. Executing SAT pass (solving SAT problems in the circuit). Full command line: sat -show s1,s2,d -set s1 s2 -set n2,n1 4'b1001 Setting up SAT problem: Import set-constraint: \s1 = \s2 Import set-constraint: { \n2 \n1 } = 4'1001 Final constraint equation: { \n2 \n1 \s1 } = { 4'1001 \s2 } Imported 3 cells to SAT database. Import show expression: { \s1 \s2 \d } Solving problem with 81 variables and 207 clauses.. SAT solving finished - model found: Signal Name Dec Hex Bin -------------------- ---------- ---------- --------------- \d 9 9 1001 \s1 0 0 00 \s2 0 0 00 Note that the `sat` command supports signal names in both arguments to the ``-set`` option. In the above example we used ``-set s1 s2`` to constraint ``s1`` and ``s2`` to be equal. When more complex constraints are needed, a wrapper circuit must be constructed that checks the constraints and signals if the constraint was met using an extra output port, which then can be forced to a value using the ``-set`` option. (Such a circuit that contains the circuit under test plus additional constraint checking circuitry is called a ``miter`` circuit.) .. literalinclude:: /code_examples/primetest.v :language: verilog :caption: :file:`primetest.v`, a simple miter circuit for testing if a number is prime. But it has a problem. :name: primetest :numref:`primetest` shows a miter circuit that is supposed to be used as a prime number test. If ``ok`` is 1 for all input values ``a`` and ``b`` for a given ``p``, then ``p`` is prime, or at least that is the idea. .. todo:: replace inline code .. code-block:: :caption: Experiments with the miter circuit from :file:`primetest.v`. :name: prime_shell yosys [primetest]> sat -prove ok 1 -set p 31 1. Executing SAT pass (solving SAT problems in the circuit). Full command line: sat -prove ok 1 -set p 31 Setting up SAT problem: Import set-constraint: \p = 16'0000000000011111 Final constraint equation: \p = 16'0000000000011111 Imported 6 cells to SAT database. Import proof-constraint: \ok = 1'1 Final proof equation: \ok = 1'1 Solving problem with 2790 variables and 8241 clauses.. SAT proof finished - model found: FAIL! ______ ___ ___ _ _ _ _ (_____ \ / __) / __) (_) | | | | _____) )___ ___ ___ _| |__ _| |__ _____ _| | _____ __| | | | ____/ ___) _ \ / _ (_ __) (_ __|____ | | || ___ |/ _ |_| | | | | | |_| | |_| || | | | / ___ | | || ____( (_| |_ |_| |_| \___/ \___/ |_| |_| \_____|_|\_)_____)\____|_| Signal Name Dec Hex Bin -------------------- ---------- ---------- --------------------- \a 15029 3ab5 0011101010110101 \b 4099 1003 0001000000000011 \ok 0 0 0 \p 31 1f 0000000000011111 The Yosys shell session shown in :numref:`prime_shell` demonstrates that SAT solvers can even find the unexpected solutions to a problem: Using integer overflow there actually is a way of "factorizing" 31. The clean solution would of course be to perform the test in 32 bits, for example by replacing ``p != a*b`` in the miter with ``p != {16'd0,a}b``, or by using a temporary variable for the 32 bit product ``a*b``. But as 31 fits well into 8 bits (and as the purpose of this document is to show off Yosys features) we can also simply force the upper 8 bits of ``a`` and ``b`` to zero for the `sat` call, as is done below. .. todo:: replace inline code .. code-block:: :caption: Miter circuit from :file:`primetest.v`, with the upper 8 bits of ``a`` and ``b`` constrained to prevent overflow. :name: prime_fixed yosys [primetest]> sat -prove ok 1 -set p 31 -set a[15:8],b[15:8] 0 1. Executing SAT pass (solving SAT problems in the circuit). Full command line: sat -prove ok 1 -set p 31 -set a[15:8],b[15:8] 0 Setting up SAT problem: Import set-constraint: \p = 16'0000000000011111 Import set-constraint: { \a [15:8] \b [15:8] } = 16'0000000000000000 Final constraint equation: { \a [15:8] \b [15:8] \p } = { 16'0000000000000000 16'0000000000011111 } Imported 6 cells to SAT database. Import proof-constraint: \ok = 1'1 Final proof equation: \ok = 1'1 Solving problem with 2790 variables and 8257 clauses.. SAT proof finished - no model found: SUCCESS! /$$$$$$ /$$$$$$$$ /$$$$$$$ /$$__ $$ | $$_____/ | $$__ $$ | $$ \ $$ | $$ | $$ \ $$ | $$ | $$ | $$$$$ | $$ | $$ | $$ | $$ | $$__/ | $$ | $$ | $$/$$ $$ | $$ | $$ | $$ | $$$$$$/ /$$| $$$$$$$$ /$$| $$$$$$$//$$ \____ $$$|__/|________/|__/|_______/|__/ \__/ The ``-prove`` option used in :numref:`prime_fixed` works similar to ``-set``, but tries to find a case in which the two arguments are not equal. If such a case is not found, the property is proven to hold for all inputs that satisfy the other constraints. It might be worth noting, that SAT solvers are not particularly efficient at factorizing large numbers. But if a small factorization problem occurs as part of a larger circuit problem, the Yosys SAT solver is perfectly capable of solving it. Solving sequential SAT problems ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The SAT solver functionality in Yosys can not only be used to solve combinatorial problems, but can also solve sequential problems. Let's consider the ``memdemo`` design from :ref:`advanced_logic_cones` again, and suppose we want to know which sequence of input values for ``d`` will cause the output y to produce the sequence 1, 2, 3 from any initial state. Let's use the following command: .. todo:: replace inline code? .. code-block:: yoscrypt sat -seq 6 -show y -show d -set-init-undef \ -max_undef -set-at 4 y 1 -set-at 5 y 2 -set-at 6 y 3 The ``-seq 6`` option instructs the `sat` command to solve a sequential problem in 6 time steps. (Experiments with lower number of steps have show that at least 3 cycles are necessary to bring the circuit in a state from which the sequence 1, 2, 3 can be produced.) The ``-set-init-undef`` option tells the `sat` command to initialize all registers to the undef (``x``) state. The way the ``x`` state is treated in Verilog will ensure that the solution will work for any initial state. The ``-max_undef`` option instructs the `sat` command to find a solution with a maximum number of undefs. This way we can see clearly which inputs bits are relevant to the solution. Finally the three ``-set-at`` options add constraints for the ``y`` signal to play the 1, 2, 3 sequence, starting with time step 4. This produces the following output: .. todo:: replace inline code .. code-block:: :caption: Solving a sequential SAT problem in the ``memdemo`` module. :name: memdemo_sat yosys [memdemo]> sat -seq 6 -show y -show d -set-init-undef \ -max_undef -set-at 4 y 1 -set-at 5 y 2 -set-at 6 y 3 1. Executing SAT pass (solving SAT problems in the circuit). Full command line: sat -seq 6 -show y -show d -set-init-undef -max_undef -set-at 4 y 1 -set-at 5 y 2 -set-at 6 y 3 Setting up time step 1: Final constraint equation: { } = { } Imported 29 cells to SAT database. Setting up time step 2: Final constraint equation: { } = { } Imported 29 cells to SAT database. Setting up time step 3: Final constraint equation: { } = { } Imported 29 cells to SAT database. Setting up time step 4: Import set-constraint for timestep: \y = 4'0001 Final constraint equation: \y = 4'0001 Imported 29 cells to SAT database. Setting up time step 5: Import set-constraint for timestep: \y = 4'0010 Final constraint equation: \y = 4'0010 Imported 29 cells to SAT database. Setting up time step 6: Import set-constraint for timestep: \y = 4'0011 Final constraint equation: \y = 4'0011 Imported 29 cells to SAT database. Setting up initial state: Final constraint equation: { \y \s2 \s1 \mem[3] \mem[2] \mem[1] \mem[0] } = 24'xxxxxxxxxxxxxxxxxxxxxxxx Import show expression: \y Import show expression: \d Solving problem with 10322 variables and 27881 clauses.. SAT model found. maximizing number of undefs. SAT solving finished - model found: Time Signal Name Dec Hex Bin ---- -------------------- ---------- ---------- --------------- init \mem[0] -- -- xxxx init \mem[1] -- -- xxxx init \mem[2] -- -- xxxx init \mem[3] -- -- xxxx init \s1 -- -- xx init \s2 -- -- xx init \y -- -- xxxx ---- -------------------- ---------- ---------- --------------- 1 \d 0 0 0000 1 \y -- -- xxxx ---- -------------------- ---------- ---------- --------------- 2 \d 1 1 0001 2 \y -- -- xxxx ---- -------------------- ---------- ---------- --------------- 3 \d 2 2 0010 3 \y 0 0 0000 ---- -------------------- ---------- ---------- --------------- 4 \d 3 3 0011 4 \y 1 1 0001 ---- -------------------- ---------- ---------- --------------- 5 \d -- -- 001x 5 \y 2 2 0010 ---- -------------------- ---------- ---------- --------------- 6 \d -- -- xxxx 6 \y 3 3 0011 It is not surprising that the solution sets ``d = 0`` in the first step, as this is the only way of setting the ``s1`` and ``s2`` registers to a known value. The input values for the other steps are a bit harder to work out manually, but the SAT solver finds the correct solution in an instant. There is much more to write about the `sat` command. For example, there is a set of options that can be used to performs sequential proofs using temporal induction :cite:p:`een2003temporal`. The command ``help sat`` can be used to print a list of all options with short descriptions of their functions. yosys-0.65/docs/source/using_yosys/more_scripting/load_design.rst000066400000000000000000000111321520057232300255000ustar00rootroot00000000000000Loading a design ---------------- .. _input files: Input files on the command line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - guesses frontend based on file extension + ``.v`` -> ``read -vlog2k`` + ``.sv`` -> ``read -sv`` + ``.vhd`` and ``.vhdl`` -> ``read -vhdl`` + ``.blif`` and ``.eblif`` -> `read_blif` + ``.json`` -> `read_json` + ``.il`` -> `read_rtlil` (direct textual representation of Yosys internal state) - command line also supports + ``.ys`` -> `script` + ``.tcl`` -> `tcl` + ``-`` -> reads stdin and treats it as a script The `read` command ~~~~~~~~~~~~~~~~~~ - standard method of loading designs - also for defining macros and include directories - uses `verific` command if available + ``-verific`` and ``-noverific`` options to enforce with/without Verific + check ``help read`` for more about the options available and the filetypes supported + elaborate designs with ``verific -import [options] `` (or use `hierarchy`) - fallback to `read_verilog` with ``-defer`` option + does not compile design until `hierarchy` command as discussed in :doc:`/getting_started/example_synth` + more similar to `verific` behaviour - ``read -define`` et al mapped to `verific` or `verilog_defines` - similarly, ``read -incdir`` et al mapped to `verific` or `verilog_defaults` .. note:: The Verific frontend for Yosys, which provides the :cmd:ref:`verific` command, requires Yosys to be built with Verific. For full functionality, custom modifications to the Verific source code from YosysHQ are required, but limited useability can be achieved with some stock Verific builds. Check :doc:`/yosys_internals/extending_yosys/build_verific` for more. .. _Frontend: Yosys frontends ~~~~~~~~~~~~~~~ - :doc:`/cmd/index_frontends` - typically start with ``read_`` - built-in support for heredocs + in-line code with ``</`` prefix. For example: .. code:: yoscrypt cd foo # switch to module foo delete bar # delete object foo/bar cd mycpu # switch to module mycpu dump reg_* # print details on all objects whose names start with reg_ cd .. # switch back to design Note: Most synthesis scripts never switch to module context. But it is a very powerful tool which we explore more in :doc:`/using_yosys/more_scripting/interactive_investigation`. Selecting by object property or type ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Special patterns can be used to select by object property or type. For example: - select all wires whose names start with ``reg_``: :yoscrypt:`select w:reg_*` - select all objects with the attribute ``foobar`` set: :yoscrypt:`select a:foobar` - select all objects with the attribute ``foobar`` set to 42: :yoscrypt:`select a:foobar=42` - select all modules with the attribute ``blabla`` set: :yoscrypt:`select A:blabla` - select all `$add` cells from the module foo: :yoscrypt:`select foo/t:$add` A complete list of pattern expressions can be found in :cmd:title:`select`. Operations on selections ~~~~~~~~~~~~~~~~~~~~~~~~ Combining selections ^^^^^^^^^^^^^^^^^^^^ The `select` command is actually much more powerful than it might seem at first glance. When it is called with multiple arguments, each argument is evaluated and pushed separately on a stack. After all arguments have been processed it simply creates the union of all elements on the stack. So :yoscrypt:`select t:$add a:foo` will select all `$add` cells and all objects with the ``foo`` attribute set: .. literalinclude:: /code_examples/selections/foobaraddsub.v :caption: Test module for operations on selections :name: foobaraddsub :language: verilog .. code-block:: :caption: Output for command ``select t:$add a:foo -list`` on :numref:`foobaraddsub` yosys> select t:$add a:foo -list foobaraddsub/$add$foobaraddsub.v:6$3 foobaraddsub/$sub$foobaraddsub.v:5$2 foobaraddsub/$add$foobaraddsub.v:4$1 In many cases simply adding more and more stuff to the selection is an ineffective way of selecting the interesting part of the design. Special arguments can be used to combine the elements on the stack. For example the ``%i`` arguments pops the last two elements from the stack, intersects them, and pushes the result back on the stack. So :yoscrypt:`select t:$add a:foo %i` will select all `$add` cells that have the ``foo`` attribute set: .. code-block:: :caption: Output for command ``select t:$add a:foo %i -list`` on :numref:`foobaraddsub` yosys> select t:$add a:foo %i -list foobaraddsub/$add$foobaraddsub.v:4$1 Some of the special ``%``-codes: - ``%u``: union of top two elements on stack -- pop 2, push 1 - ``%d``: difference of top two elements on stack -- pop 2, push 1 - ``%i``: intersection of top two elements on stack -- pop 2, push 1 - ``%n``: inverse of top element on stack -- pop 1, push 1 See :cmd:title:`select` for the full list. Expanding selections ^^^^^^^^^^^^^^^^^^^^ :numref:`sumprod` uses the Yosys non-standard ``{... *}`` syntax to set the attribute ``sumstuff`` on all cells generated by the first assign statement. (This works on arbitrary large blocks of Verilog code and can be used to mark portions of code for analysis.) .. literalinclude:: /code_examples/selections/sumprod.v :caption: Another test module for operations on selections :name: sumprod :language: verilog Selecting ``a:sumstuff`` in this module will yield the following circuit diagram: .. figure:: /_images/code_examples/selections/sumprod_00.* :class: width-helper invert-helper :name: sumprod_00 Output of ``show a:sumstuff`` on :numref:`sumprod` As only the cells themselves are selected, but not the temporary wire ``$1_Y``, the two adders are shown as two disjunct parts. This can be very useful for global signals like clock and reset signals: just unselect them using a command such as :yoscrypt:`select -del clk rst` and each cell using them will get its own net label. In this case however we would like to see the cells connected properly. This can be achieved using the ``%x`` action, that broadens the selection, i.e. for each selected wire it selects all cells connected to the wire and vice versa. So :yoscrypt:`show a:sumstuff %x` yields the diagram shown in :numref:`sumprod_01`: .. figure:: /_images/code_examples/selections/sumprod_01.* :class: width-helper invert-helper :name: sumprod_01 Output of ``show a:sumstuff %x`` on :numref:`sumprod` .. _selecting_logic_cones: Selecting logic cones ^^^^^^^^^^^^^^^^^^^^^ :numref:`sumprod_01` shows what is called the ``input cone`` of ``sum``, i.e. all cells and signals that are used to generate the signal ``sum``. The ``%ci`` action can be used to select the input cones of all object in the top selection in the stack maintained by the `select` command. As with the ``%x`` action, these commands broaden the selection by one "step". But this time the operation only works against the direction of data flow. That means, wires only select cells via output ports and cells only select wires via input ports. The following sequence of diagrams demonstrates this step-wise expansion: .. figure:: /_images/code_examples/selections/sumprod_02.* :class: width-helper invert-helper Output of :yoscrypt:`show prod` on :numref:`sumprod` .. figure:: /_images/code_examples/selections/sumprod_03.* :class: width-helper invert-helper Output of :yoscrypt:`show prod %ci` on :numref:`sumprod` .. figure:: /_images/code_examples/selections/sumprod_04.* :class: width-helper invert-helper Output of :yoscrypt:`show prod %ci %ci` on :numref:`sumprod` .. figure:: /_images/code_examples/selections/sumprod_05.* :class: width-helper invert-helper Output of :yoscrypt:`show prod %ci %ci %ci` on :numref:`sumprod` Notice the subtle difference between :yoscrypt:`show prod %ci` and :yoscrypt:`show prod %ci %ci`. Both images show the `$mul` cell driven by some inputs ``$3_Y`` and ``c``. However it is not until the second image, having called ``%ci`` the second time, that `show` is able to distinguish between ``$3_Y`` being a wire and ``c`` being an input. We can see this better with the `dump` command instead: .. literalinclude:: /code_examples/selections/sumprod.out :language: RTLIL :end-at: end :caption: Output of :yoscrypt:`dump prod %ci` .. literalinclude:: /code_examples/selections/sumprod.out :language: RTLIL :start-after: end :caption: Output of :yoscrypt:`dump prod %ci %ci` When selecting many levels of logic, repeating ``%ci`` over and over again can be a bit dull. So there is a shortcut for that: the number of iterations can be appended to the action. So for example the action ``%ci3`` is identical to performing the ``%ci`` action three times. The action ``%ci*`` performs the ``%ci`` action over and over again until it has no effect anymore. .. _advanced_logic_cones: Advanced logic cone selection ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In most cases there are certain cell types and/or ports that should not be considered for the ``%ci`` action, or we only want to follow certain cell types and/or ports. This can be achieved using additional patterns that can be appended to the ``%ci`` action. Lets consider :numref:`memdemo_src`. It serves no purpose other than being a non-trivial circuit for demonstrating some of the advanced Yosys features. This code is available in ``docs/source/code_examples/selections`` of the Yosys source repository. .. literalinclude:: /code_examples/selections/memdemo.v :caption: :file:`memdemo.v` :name: memdemo_src :language: verilog The script :file:`memdemo.ys` is used to generate the images included here. Let's look at the first section: .. literalinclude:: /code_examples/selections/memdemo.ys :caption: Synthesizing :ref:`memdemo_src` :name: memdemo_ys :language: yoscrypt :end-at: opt This loads :numref:`memdemo_src` and synthesizes the included module. Note that this code can be copied and run directly in a Yosys command line session, provided :file:`memdemo.v` is in the same directory. We can now change to the ``memdemo`` module with ``cd memdemo``, and call `show` to see the diagram in :numref:`memdemo_00`. .. figure:: /_images/code_examples/selections/memdemo_00.* :class: width-helper invert-helper :name: memdemo_00 Complete circuit diagram for the design shown in :numref:`memdemo_src` There's a lot going on there, but maybe we are only interested in the tree of multiplexers that select the output value. Let's start by just showing the output signal, ``y``, and its immediate predecessors. Remember `Selecting logic cones`_ from above, we can use :yoscrypt:`show y %ci2`: .. figure:: /_images/code_examples/selections/memdemo_01.* :class: width-helper invert-helper :name: memdemo_01 Output of :yoscrypt:`show y %ci2` From this we would learn that ``y`` is driven by a `$dff` cell, that ``y`` is connected to the output port ``Q``, that the ``clk`` signal goes into the ``CLK`` input port of the cell, and that the data comes from an auto-generated wire into the input ``D`` of the flip-flop cell (indicated by the ``$`` at the start of the name). Let's go a bit further now and try :yoscrypt:`show y %ci5`: .. figure:: /_images/code_examples/selections/memdemo_02.* :class: width-helper invert-helper :name: memdemo_02 Output of :yoscrypt:`show y %ci5` That's starting to get a bit messy, so maybe we want to ignore the mux select inputs. To add a pattern we add a colon followed by the pattern to the ``%ci`` action. The pattern itself starts with ``-`` or ``+``, indicating if it is an include or exclude pattern, followed by an optional comma separated list of cell types, followed by an optional comma separated list of port names in square brackets. In this case, we want to exclude the ``S`` port of the `$mux` cell type with :yoscrypt:`show y %ci5:-$mux[S]`: .. figure:: /_images/code_examples/selections/memdemo_03.* :class: width-helper invert-helper :name: memdemo_03 Output of :yoscrypt:`show y %ci5:-$mux[S]` We could use a command such as :yoscrypt:`show y %ci2:+$dff[Q,D] %ci*:-$mux[S]:-$dff` in which the first ``%ci`` jumps over the initial d-type flip-flop and the 2nd action selects the entire input cone without going over multiplexer select inputs and flip-flop cells: .. figure:: /_images/code_examples/selections/memdemo_05.* :class: width-helper invert-helper :name: memdemo_05 Output of ``show y %ci2:+$dff[Q,D] %ci*:-$mux[S]:-$dff`` Or we could use :yoscrypt:`show y %ci*:-[CLK,S]:+$dff:+$mux` instead, following the input cone all the way but only following `$dff` and `$mux` cells, and ignoring any ports named ``CLK`` or ``S``: .. TODO:: pending discussion on whether rule ordering is a bug or a feature .. figure:: /_images/code_examples/selections/memdemo_04.* :class: width-helper invert-helper :name: memdemo_04 Output of :yoscrypt:`show y %ci*:-[CLK,S]:+$dff,$mux` Similar to ``%ci`` exists an action ``%co`` to select output cones that accepts the same syntax for pattern and repetition. The ``%x`` action mentioned previously also accepts this advanced syntax. These actions for traversing the circuit graph, combined with the actions for boolean operations such as intersection (``%i``) and difference (``%d``) are powerful tools for extracting the relevant portions of the circuit under investigation. Again, see :cmd:title:`select` for full documentation of these expressions. Incremental selection ^^^^^^^^^^^^^^^^^^^^^ Sometimes a selection can most easily be described by a series of add/delete operations. As mentioned previously, the commands :yoscrypt:`select -add` and :yoscrypt:`select -del` respectively add or remove objects from the current selection instead of overwriting it. .. code:: yoscrypt select -none # start with an empty selection select -add reg_* # select a bunch of objects select -del reg_42 # but not this one select -add state %ci # and add more stuff Within a select expression the token ``%`` can be used to push the previous selection on the stack. .. code:: yoscrypt select t:$add t:$sub # select all $add and $sub cells select % %ci % %d # select only the input wires to those cells Storing and recalling selections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. todo:: reflow for not presentation The current selection can be stored in memory with the command ``select -set ``. It can later be recalled using ``select @``. In fact, the ``@`` expression pushes the stored selection on the stack maintained by the `select` command. So for example :yoscrypt:`select @foo @bar %i` will select the intersection between the stored selections ``foo`` and ``bar``. In larger investigation efforts it is highly recommended to maintain a script that sets up relevant selections, so they can easily be recalled, for example when Yosys needs to be re-run after a design or source code change. The `history` command can be used to list all recent interactive commands. This feature can be useful for creating such a script from the commands used in an interactive session. Remember that select expressions can also be used directly as arguments to most commands. Some commands also accept a single select argument to some options. In those cases selection variables must be used to capture more complex selections. Example code from |code_examples/selections|_: .. |code_examples/selections| replace:: :file:`docs/source/code_examples/selections` .. _code_examples/selections: https://github.com/YosysHQ/yosys/tree/main/docs/source/code_examples/selections .. literalinclude:: /code_examples/selections/select.v :language: verilog :caption: :file:`select.v` .. literalinclude:: /code_examples/selections/select.ys :language: yoscrypt :caption: :file:`select.ys` :name: select_ys .. figure:: /_images/code_examples/selections/select.* :class: width-helper invert-helper Circuit diagram produced by :numref:`select_ys` yosys-0.65/docs/source/using_yosys/pyosys.rst000066400000000000000000000167211520057232300215630ustar00rootroot00000000000000Scripting with Pyosys ===================== Pyosys is a limited subset of the Yosys C++ API (aka "libyosys") made available using the Python programming language. Like ``.ys`` and ``.tcl`` scripts, Pyosys provides an interface to write Yosys scripts in the Python programming language, giving you the benefits of a type system, control flow, object-oriented programming, and more; especially that the other options lack a type system and control flow/OOP in Tcl is limited. Though unlike these two, Pyosys goes a bit further, allowing you to use the Yosys API to implement advanced functionality that would otherwise require custom passes written in C++. Getting Pyosys -------------- Pyosys supports CPython 3.8 or higher. You can access Pyosys using one of two methods: 1. Compiling Yosys with the Makefile flag ``ENABLE_PYOSYS=1`` This adds the flag ``-y`` to the Yosys binary, which allows you to execute Python scripts using an interpreter embedded in Yosys itself: ``yosys -y ./my_pyosys_script.py`` Do note this requires some build-time dependencies to be available to Python, namely, ``pybind11`` and ``cxxheaderparser``. By default, the required ``uv`` package will be used to create an ephemeral environment with the correct versions of the tools installed. You can force use of your current Python environment by passing the Makefile flag ``PYOSYS_USE_UV=0``. 2. Installing the Pyosys wheels On macOS and GNU/Linux you can install pre-built wheels of Yosys using ``pip``: ``python3 -m pip install pyosys`` Which then allows you to run your scripts as follows: ``python3 ./my_pyosys_script.py`` Scripting and Database Inspection --------------------------------- To start with, you have to import libyosys as follows: .. code-block:: python from pyosys import libyosys As a reminder, Python allows you to alias imported modules and objects, so this import may be preferable for terseness: .. code-block:: python from pyosys import libyosys as ys Now, scripting is actually quite similar to ``.ys`` and ``.tcl`` script in that you can provide mostly text commands. Albeit, you can construct your scripts to use Python's amenities like conditional execution, loops, and functions: .. code-block:: python do_flatten = True ys.run_pass("read_verilog tests/simple/fiedler-cooley.v") ys.run_pass("hierarchy -check -auto-top") if do_flatten: ys.run_pass("flatten") …but this does not strictly provide anything that Tcl scripts do not provide you with. The real power of using Pyosys comes from the fact you can manually instantiate, manage, and interact with the design database. As an example, here is the same script with a manually instantiated design. .. literalinclude:: /code_examples/pyosys/simple_database.py :start-after: loading design :end-before: top module inspection :language: python What's new here is that you can manually inspect the design's database. This gives you access to a huge chunk of the design database API as declared in the ``kernel/rtlil.h`` header. For example, here's how to list the input and output ports of the top module of your design: .. literalinclude:: /code_examples/pyosys/simple_database.py :start-after: top module inspection :end-before: # synth :language: python .. tip:: C++ data structures in Yosys are bridged to Python such that they have a pretty similar API to Python objects, for example: - ``std::vector`` supports the same methods as `iterables `_ in Python. - ``std::set`` and hashlib ``pool`` support the same methods as ``set``\s in Python. While ``set`` is ordered, ``pool`` is not and modifications may cause a complete reordering of the set. - ``dict`` supports the same methods as ``dict``\s in Python, albeit it is unordered, and modifications may cause a complete reordering of the dictionary. - ``idict`` uses a custom set of methods because it doesn't map very cleanly to an existing Python data structure. See ``pyosys/hashlib.h`` for more info. For most operations, the Python equivalents are also supported as arguments where they will automatically be cast to the right type, so you do not have to manually instantiate the right underlying C++ object(s) yourself. Modifying the Database ---------------------- .. warning:: Any modifications to the database may invalidate previous references held by Python, just as if you were writing C++. Pyosys does not currently attempt to keep deleted objects alive if a reference is held by Python. You are not restricted to inspecting the database either: you have the ability to modify it, and introduce new elements and/or changes to your design. As a demonstrative example, let's assume we want to add an enable line to all flip-flops in our fiedler-cooley design. First of all, we will run :yoscrypt:`synth` to convert all of the logic to Yosys's internal cell structure (see :ref:`sec:celllib_gates`): .. literalinclude:: /code_examples/pyosys/simple_database.py :start-after: # synth :end-before: adding the enable line :language: python Next, we need to add the new port. The method for this is ``Module::addWire``\. .. tip:: IdString is Yosys's internal representation of strings used as identifiers within Verilog designs. They are efficient as only integers are stored and passed around, but they can be translated to and from normal strings at will. Pyosys will automatically cast Python strings to IdStrings for you, but the rules around IdStrings apply, namely that *broadly*: - Identifiers for internal cells must start with ``$``\. - All other identifiers must start with ``\``\. .. literalinclude:: /code_examples/pyosys/simple_database.py :start-after: adding the enable line :end-before: hooking the enable line :language: python Notice how we modified the wire then called a method to make Yosys re-process the ports. Next, we can iterate over all constituent cells, and if they are of the type ``$_DFF_P_``, we do two things: 1. Change their type to ``$_DFFE_PP_`` to enable hooking up an enable signal. 2. Hooking up the enable signal. .. literalinclude:: /code_examples/pyosys/simple_database.py :start-after: hooking the enable line :end-before: run check :language: python To verify that you did everything correctly, it is prudent to call ``.check()`` on the module you're manipulating as follows after you're done with a set of changes: .. literalinclude:: /code_examples/pyosys/simple_database.py :start-after: run check :end-before: write output :language: python And then finally, write your outputs. Here, I choose an intermediate Verilog file and :yoscrypt:`synth_ice40` to map it to the iCE40 architecture. .. literalinclude:: /code_examples/pyosys/simple_database.py :start-after: write output :language: python And voilà, you will note that in the intermediate output, all ``always @`` statements should have an ``if (enable)``\. Encapsulating as Passes ----------------------- Just like when writing C++, you can encapsulate routines in terms of "passes", which adds your Pass to a global registry of commands accessible using ``run_pass``\. .. literalinclude:: /code_examples/pyosys/pass.py :language: python In general, abstract classes and virtual methods are not really supported by Pyosys due to their complexity, but there are two exceptions which are: - ``Pass`` in ``kernel/register.h`` - ``Monitor`` in ``kernel/rtlil.h`` yosys-0.65/docs/source/using_yosys/synthesis/000077500000000000000000000000001520057232300215055ustar00rootroot00000000000000yosys-0.65/docs/source/using_yosys/synthesis/abc.rst000066400000000000000000000205711520057232300227710ustar00rootroot00000000000000The ABC toolbox =============== .. role:: yoscrypt(code) :language: yoscrypt ABC_, from the University of California, Berkeley, is a logic toolbox used for fine-grained optimisation and LUT mapping. Yosys has two different commands, which both use this logic toolbox, but use it in different ways. The `abc` pass can be used for both ASIC (e.g. :yoscrypt:`abc -liberty`) and FPGA (:yoscrypt:`abc -lut`) mapping, but this page will focus on FPGA mapping. The `abc9` pass generally provides superior mapping quality due to being aware of combination boxes and DFF and LUT timings, giving it a more global view of the mapping problem. .. _ABC: https://github.com/berkeley-abc/abc ABC: the unit delay model, simple and efficient ----------------------------------------------- The `abc` pass uses a highly simplified view of an FPGA: - An FPGA is made up of a network of inputs that connect through LUTs to a network of outputs. These inputs may actually be I/O pins, D flip-flops, memory blocks or DSPs, but ABC is unaware of this. - Each LUT has 1 unit of delay between an input and its output, and this applies for all inputs of a LUT, and for all sizes of LUT up to the maximum LUT size allowed; e.g. the delay between the input of a LUT2 and its output is the same as the delay between the input of a LUT6 and its output. - A LUT may take up a variable number of area units. This is constant for each size of LUT; e.g. a LUT4 may take up 1 unit of area, but a LUT5 may take up 2 units of area, but this applies for all LUT4s and LUT5s. This is known as the "unit delay model", because each LUT uses one unit of delay. From this view, the problem ABC has to solve is finding a mapping of the network to LUTs that has the lowest delay, and then optimising the mapping for size while maintaining this delay. This approach has advantages: - It is simple and easy to implement. - Working with unit delays is fast to manipulate. - It reflects *some* FPGA families, for example, the iCE40HX/LP fits the assumptions of the unit delay model quite well (almost all synchronous blocks, except for adders). But this approach has drawbacks, too: - The network of inputs and outputs with only LUTs means that a lot of combinational cells (multipliers and LUTRAM) are invisible to the unit delay model, meaning the critical path it optimises for is not necessarily the actual critical path. - LUTs are implemented as multiplexer trees, so there is a delay caused by the result propagating through the remaining multiplexers. This means the assumption of delay being equal isn't true in physical hardware, and is proportionally larger for larger LUTs. - Even synchronous blocks have arrival times (propagation delay between clock edge to output changing) and setup times (requirement for input to be stable before clock edge) which affect the delay of a path. ABC9: the generalised delay model, realistic and flexible --------------------------------------------------------- ABC9 uses a more detailed and accurate model of an FPGA: - An FPGA is made up of a network of inputs that connect through LUTs and combinational boxes to a network of outputs. These boxes have specified delays between inputs and outputs, and may have an associated network ("white boxes") or not ("black boxes"), but must be treated as a whole. - Each LUT has a specified delay between an input and its output in arbitrary delay units, and this varies for all inputs of a LUT and for all sizes of LUT, but each size of LUT has the same associated delay; e.g. the delay between input A and output is different between a LUT2 and a LUT6, but is constant for all LUT6s. - A LUT may take up a variable number of area units. This is constant for each size of LUT; e.g. a LUT4 may take up 1 unit of area, but a LUT5 may take up 2 units of area, but this applies for all LUT4s and LUT5s. This is known as the "generalised delay model", because it has been generalised to arbitrary delay units. ABC9 doesn't actually care what units you use here, but the Yosys convention is picoseconds. Note the introduction of boxes as a concept. While the generalised delay model does not require boxes, they naturally fit into it to represent combinational delays. Even synchronous delays like arrival and setup can be emulated with combinational boxes that act as a delay. This is further extended to white boxes, where the mapper is able to see inside a box, and remove orphan boxes with no outputs, such as adders. Again, ABC9 finds a mapping of the network to LUTs that has the lowest delay, and then minimises it to find the lowest area, but it has a lot more information to work with about the network. The result here is that ABC9 can remove boxes (like adders) to reduce area, optimise better around those boxes, and also permute inputs to give the critical path the fastest inputs. .. todo:: more about logic minimization & register balancing et al with ABC Setting up a flow for ABC9 -------------------------- Much of the configuration comes from attributes and ``specify`` blocks in Verilog simulation models. ``specify`` syntax ~~~~~~~~~~~~~~~~~~ Since ``specify`` is a relatively obscure part of the Verilog standard, a quick guide to the syntax: .. code-block:: verilog specify // begins a specify block (A => B) = 123; // simple combinational path from A to B with a delay of 123. (A *> B) = 123; // simple combinational path from A to all bits of B with a delay of 123 for all. if (FOO) (A => B) = 123; // paths may apply under specific conditions. (posedge CLK => (Q : D)) = 123; // combinational path triggered on the positive edge of CLK; used for clock-to-Q arrival paths. $setup(A, posedge CLK, 123); // setup constraint for an input relative to a clock. endspecify // ends a specify block By convention, all delays in ``specify`` blocks are in integer picoseconds. Files containing ``specify`` blocks should be read with the ``-specify`` option to `read_verilog` so that they aren't skipped. LUTs ^^^^ LUTs need to be annotated with an ``(* abc9_lut=N *)`` attribute, where ``N`` is the relative area of that LUT model. For example, if an architecture can combine LUTs to produce larger LUTs, then the combined LUTs would have increasingly larger ``N``. Conversely, if an architecture can split larger LUTs into smaller LUTs, then the smaller LUTs would have smaller ``N``. LUTs are generally specified with simple combinational paths from the LUT inputs to the LUT output. DFFs ^^^^ DFFs should be annotated with an ``(* abc9_flop *)`` attribute, however ABC9 has some specific requirements for this to be valid: - the DFF must initialise to zero (consider using `dfflegalize` to ensure this). - the DFF cannot have any asynchronous resets/sets (see the simplification idiom and the Boxes section for what to do here). It is worth noting that in pure ``abc9`` mode, only the setup and arrival times are passed to ABC9 (specifically, they are modelled as buffers with the given delay). In ``abc9 -dff``, the flop itself is passed to ABC9, permitting sequential optimisations. Some vendors have universal DFF models which include async sets/resets even when they're unused. Therefore *the simplification idiom* exists to handle this: by using a ``techmap`` file to discover flops which have a constant driver to those asynchronous controls, they can be mapped into an intermediate, simplified flop which qualifies as an ``(* abc9_flop *)``, ran through `abc9`, and then mapped back to the original flop. This is used in `synth_intel_alm` and `synth_quicklogic` for the PolarPro3. DFFs are usually specified to have setup constraints against the clock on the input signals, and an arrival time for the ``Q`` output. Boxes ^^^^^ A "box" is a purely-combinational piece of hard logic. If the logic is exposed to ABC9, it's a "whitebox", otherwise it's a "blackbox". Carry chains would be best implemented as whiteboxes, but a DSP would be best implemented as a blackbox (multipliers are too complex to easily work with). LUT RAMs can be implemented as whiteboxes too. Boxes are arguably the biggest advantage that ABC9 has over ABC: by being aware of carry chains and DSPs, it avoids optimising for a path that isn't the actual critical path, while the generally-longer paths result in ABC9 being able to reduce design area by mapping other logic to slower cells with greater logic density. yosys-0.65/docs/source/using_yosys/synthesis/cell_libs.rst000066400000000000000000000076671520057232300242070ustar00rootroot00000000000000Mapping to cell libraries ------------------------- .. role:: yoscrypt(code) :language: yoscrypt While much of this documentation focuses on the use of Yosys with FPGAs, it is also possible to map to cell libraries which can be used in designing ASICs. This section will cover a brief `example project`_, available in the Yosys source code under :file:`docs/source/code_examples/intro/`. The project contains a simple ASIC synthesis script (:file:`counter.ys`), a digital design written in Verilog (:file:`counter.v`), and a simple CMOS cell library (:file:`mycells.lib`). Many of the early steps here are already covered in more detail in the :doc:`/getting_started/example_synth` document. .. note:: The :file:`counter.ys` script includes the commands used to generate the images in this document. Code snippets in this document skip these commands; including line numbers to allow the reader to follow along with the source. To learn more about these commands, check out :ref:`interactive_show`. .. _example project: https://github.com/YosysHQ/yosys/tree/main/docs/source/code_examples/intro A simple counter ~~~~~~~~~~~~~~~~ First, let's quickly look at the design: .. literalinclude:: /code_examples/intro/counter.v :language: Verilog :linenos: :name: counter-v :caption: :file:`counter.v` This is a simple counter with reset and enable. If the reset signal, ``rst``, is high then the counter will reset to 0. Otherwise, if the enable signal, ``en``, is high then the ``count`` register will increment by 1 each rising edge of the clock, ``clk``. Loading the design ~~~~~~~~~~~~~~~~~~ .. literalinclude:: /code_examples/intro/counter.ys :language: yoscrypt :lines: 1-3 :lineno-match: :caption: :file:`counter.ys` - read design Our circuit now looks like this: .. figure:: /_images/code_examples/intro/counter_00.* :class: width-helper invert-helper :name: counter-hierarchy ``counter`` after `hierarchy` Coarse-grain representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: /code_examples/intro/counter.ys :language: yoscrypt :lines: 7-10 :lineno-match: :caption: :file:`counter.ys` - the high-level stuff .. figure:: /_images/code_examples/intro/counter_01.* :class: width-helper invert-helper Coarse-grain representation of the ``counter`` module Logic gate mapping ~~~~~~~~~~~~~~~~~~ .. literalinclude:: /code_examples/intro/counter.ys :language: yoscrypt :lines: 14-15 :lineno-match: :caption: :file:`counter.ys` - mapping to internal cell library .. figure:: /_images/code_examples/intro/counter_02.* :class: width-helper invert-helper ``counter`` after `techmap` Mapping to hardware ~~~~~~~~~~~~~~~~~~~ For this example, we are using a Liberty file to describe a cell library which our internal cell library will be mapped to: .. todo:: find a Liberty pygments style? .. literalinclude:: /code_examples/intro/mycells.lib :language: text :linenos: :name: mycells-lib :caption: :file:`mycells.lib` Recall that the Yosys built-in logic gate types are `$_NOT_`, `$_AND_`, `$_OR_`, `$_XOR_`, and `$_MUX_` with an assortment of dff memory types. :ref:`mycells-lib` defines our target cells as ``BUF``, ``NOT``, ``NAND``, ``NOR``, and ``DFF``. Mapping between these is performed with the commands `dfflibmap` and `abc` as follows: .. literalinclude:: /code_examples/intro/counter.ys :language: yoscrypt :lines: 20-27 :lineno-match: :caption: :file:`counter.ys` - mapping to hardware The final version of our ``counter`` module looks like this: .. figure:: /_images/code_examples/intro/counter_03.* :class: width-helper invert-helper ``counter`` after hardware cell mapping Before finally being output as a verilog file with `write_verilog`, which can then be loaded into another tool: .. literalinclude:: /code_examples/intro/counter.ys :language: yoscrypt :lines: 30-31 :lineno-match: :caption: :file:`counter.ys` - write synthesized design yosys-0.65/docs/source/using_yosys/synthesis/extract.rst000066400000000000000000000162331520057232300237160ustar00rootroot00000000000000The extract pass ---------------- - Like the `techmap` pass, the `extract` pass is called with a map file. It compares the circuits inside the modules of the map file with the design and looks for sub-circuits in the design that match any of the modules in the map file. - If a match is found, the `extract` pass will replace the matching subcircuit with an instance of the module from the map file. - In a way the `extract` pass is the inverse of the techmap pass. .. todo:: add/expand supporting text, also mention custom pattern matching and pmgen Example code can be found in |code_examples/macc|_. .. |code_examples/macc| replace:: :file:`docs/source/code_examples/macc` .. _code_examples/macc: https://github.com/YosysHQ/yosys/tree/main/docs/source/code_examples/macc .. literalinclude:: /code_examples/macc/macc_simple_test.ys :language: yoscrypt :lines: 1-2 .. figure:: /_images/code_examples/macc/macc_simple_test_00a.* :class: width-helper invert-helper before `extract` .. literalinclude:: /code_examples/macc/macc_simple_test.ys :language: yoscrypt :lines: 6 .. figure:: /_images/code_examples/macc/macc_simple_test_00b.* :class: width-helper invert-helper after `extract` .. literalinclude:: /code_examples/macc/macc_simple_test.v :language: verilog :caption: :file:`macc_simple_test.v` .. literalinclude:: /code_examples/macc/macc_simple_xmap.v :language: verilog :caption: :file:`macc_simple_xmap.v` .. literalinclude:: /code_examples/macc/macc_simple_test_01.v :language: verilog :caption: :file:`macc_simple_test_01.v` .. figure:: /_images/code_examples/macc/macc_simple_test_01a.* :class: width-helper invert-helper .. figure:: /_images/code_examples/macc/macc_simple_test_01b.* :class: width-helper invert-helper .. literalinclude:: /code_examples/macc/macc_simple_test_02.v :language: verilog :caption: :file:`macc_simple_test_02.v` .. figure:: /_images/code_examples/macc/macc_simple_test_02a.* :class: width-helper invert-helper .. figure:: /_images/code_examples/macc/macc_simple_test_02b.* :class: width-helper invert-helper The wrap-extract-unwrap method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Often a coarse-grain element has a constant bit-width, but can be used to implement operations with a smaller bit-width. For example, a 18x25-bit multiplier can also be used to implement 16x20-bit multiplication. A way of mapping such elements in coarse grain synthesis is the wrap-extract-unwrap method: wrap Identify candidate-cells in the circuit and wrap them in a cell with a constant wider bit-width using `techmap`. The wrappers use the same parameters as the original cell, so the information about the original width of the ports is preserved. Then use the `connwrappers` command to connect up the bit-extended in- and outputs of the wrapper cells. extract Now all operations are encoded using the same bit-width as the coarse grain element. The `extract` command can be used to replace circuits with cells of the target architecture. unwrap The remaining wrapper cell can be unwrapped using `techmap`. Example: DSP48_MACC ~~~~~~~~~~~~~~~~~~~ This section details an example that shows how to map MACC operations of arbitrary size to MACC cells with a 18x25-bit multiplier and a 48-bit adder (such as the Xilinx DSP48 cells). Preconditioning: :file:`macc_xilinx_swap_map.v` Make sure ``A`` is the smaller port on all multipliers .. todo:: add/expand supporting text .. literalinclude:: /code_examples/macc/macc_xilinx_swap_map.v :language: verilog :caption: :file:`macc_xilinx_swap_map.v` Wrapping multipliers: :file:`macc_xilinx_wrap_map.v` .. literalinclude:: /code_examples/macc/macc_xilinx_wrap_map.v :language: verilog :lines: 1-46 :caption: :file:`macc_xilinx_wrap_map.v` Wrapping adders: :file:`macc_xilinx_wrap_map.v` .. literalinclude:: /code_examples/macc/macc_xilinx_wrap_map.v :language: verilog :lines: 48-89 :caption: :file:`macc_xilinx_wrap_map.v` Extract: :file:`macc_xilinx_xmap.v` .. literalinclude:: /code_examples/macc/macc_xilinx_xmap.v :language: verilog :caption: :file:`macc_xilinx_xmap.v` ... simply use the same wrapping commands on this module as on the design to create a template for the `extract` command. Unwrapping multipliers: :file:`macc_xilinx_unwrap_map.v` .. literalinclude:: /code_examples/macc/macc_xilinx_unwrap_map.v :language: verilog :lines: 1-30 :caption: ``$__mul_wrapper`` module in :file:`macc_xilinx_unwrap_map.v` Unwrapping adders: :file:`macc_xilinx_unwrap_map.v` .. literalinclude:: /code_examples/macc/macc_xilinx_unwrap_map.v :language: verilog :lines: 32-61 :caption: ``$__add_wrapper`` module in :file:`macc_xilinx_unwrap_map.v` .. literalinclude:: /code_examples/macc/macc_xilinx_test.v :language: verilog :lines: 1-6 :caption: ``test1`` of :file:`macc_xilinx_test.v` .. figure:: /_images/code_examples/macc/macc_xilinx_test1a.* :class: width-helper invert-helper .. figure:: /_images/code_examples/macc/macc_xilinx_test1b.* :class: width-helper invert-helper .. literalinclude:: /code_examples/macc/macc_xilinx_test.v :language: verilog :lines: 8-13 :caption: ``test2`` of :file:`macc_xilinx_test.v` .. figure:: /_images/code_examples/macc/macc_xilinx_test2a.* :class: width-helper invert-helper .. figure:: /_images/code_examples/macc/macc_xilinx_test2b.* :class: width-helper invert-helper Wrapping in ``test1``: .. figure:: /_images/code_examples/macc/macc_xilinx_test1b.* :class: width-helper invert-helper .. literalinclude:: /code_examples/macc/macc_xilinx_test.ys :language: yoscrypt :start-after: part c :end-before: end part c .. figure:: /_images/code_examples/macc/macc_xilinx_test1c.* :class: width-helper invert-helper Wrapping in ``test2``: .. figure:: /_images/code_examples/macc/macc_xilinx_test2b.* :class: width-helper invert-helper .. literalinclude:: /code_examples/macc/macc_xilinx_test.ys :language: yoscrypt :start-after: part c :end-before: end part c .. figure:: /_images/code_examples/macc/macc_xilinx_test2c.* :class: width-helper invert-helper Extract in ``test1``: .. figure:: /_images/code_examples/macc/macc_xilinx_test1c.* :class: width-helper invert-helper .. literalinclude:: /code_examples/macc/macc_xilinx_test.ys :language: yoscrypt :start-after: part d :end-before: end part d .. figure:: /_images/code_examples/macc/macc_xilinx_test1d.* :class: width-helper invert-helper Extract in ``test2``: .. figure:: /_images/code_examples/macc/macc_xilinx_test2c.* :class: width-helper invert-helper .. literalinclude:: /code_examples/macc/macc_xilinx_test.ys :language: yoscrypt :start-after: part d :end-before: end part d .. figure:: /_images/code_examples/macc/macc_xilinx_test2d.* :class: width-helper invert-helper Unwrap in ``test2``: .. figure:: /_images/code_examples/macc/macc_xilinx_test2d.* :class: width-helper invert-helper .. literalinclude:: /code_examples/macc/macc_xilinx_test.ys :language: yoscrypt :start-after: part e :end-before: end part e .. figure:: /_images/code_examples/macc/macc_xilinx_test2e.* :class: width-helper invert-helperyosys-0.65/docs/source/using_yosys/synthesis/fsm.rst000066400000000000000000000132031520057232300230230ustar00rootroot00000000000000FSM handling ============ The `fsm` command identifies, extracts, optimizes (re-encodes), and re-synthesizes finite state machines. It again is a macro that calls a series of other commands: .. literalinclude:: /code_examples/macro_commands/fsm.ys :language: yoscrypt :start-after: #end: :caption: Passes called by `fsm` See also :doc:`/cmd/index_passes_fsm`. The algorithms used for FSM detection and extraction are influenced by a more general reported technique :cite:p:`fsmextract`. FSM detection ~~~~~~~~~~~~~ The `fsm_detect` pass identifies FSM state registers. It sets the ``fsm_encoding = "auto"`` attribute on any (multi-bit) wire that matches the following description: - Does not already have the ``fsm_encoding`` attribute. - Is not an output of the containing module. - Is driven by single `$dff` or `$adff` cell. - The ``D``-Input of this `$dff` or `$adff` cell is driven by a multiplexer tree that only has constants or the old state value on its leaves. - The state value is only used in the said multiplexer tree or by simple relational cells that compare the state value to a constant (usually `$eq` cells). This heuristic has proven to work very well. It is possible to overwrite it by setting ``fsm_encoding = "auto"`` on registers that should be considered FSM state registers and setting ``fsm_encoding = "none"`` on registers that match the above criteria but should not be considered FSM state registers. Note however that marking state registers with ``fsm_encoding`` that are not suitable for FSM recoding can cause synthesis to fail or produce invalid results. FSM extraction ~~~~~~~~~~~~~~ The `fsm_extract` pass operates on all state signals marked with the (``fsm_encoding != "none"``) attribute. For each state signal the following information is determined: - The state registers - The asynchronous reset state if the state registers use asynchronous reset - All states and the control input signals used in the state transition functions - The control output signals calculated from the state signals and control inputs - A table of all state transitions and corresponding control inputs- and outputs The state registers (and asynchronous reset state, if applicable) is simply determined by identifying the driver for the state signal. From there the `$mux`\ -tree driving the state register inputs is recursively traversed. All select inputs are control signals and the leaves of the `$mux`\ -tree are the states. The algorithm fails if a non-constant leaf that is not the state signal itself is found. The list of control outputs is initialized with the bits from the state signal. It is then extended by adding all values that are calculated by cells that compare the state signal with a constant value. In most cases this will cover all uses of the state register, thus rendering the state encoding arbitrary. If however a design uses e.g. a single bit of the state value to drive a control output directly, this bit of the state signal will be transformed to a control output of the same value. Finally, a transition table for the FSM is generated. This is done by using the ConstEval C++ helper class (defined in kernel/consteval.h) that can be used to evaluate parts of the design. The ConstEval class can be asked to calculate a given set of result signals using a set of signal-value assignments. It can also be passed a list of stop-signals that abort the ConstEval algorithm if the value of a stop-signal is needed in order to calculate the result signals. The `fsm_extract` pass uses the ConstEval class in the following way to create a transition table. For each state: 1. Create a ConstEval object for the module containing the FSM 2. Add all control inputs to the list of stop signals 3. Set the state signal to the current state 4. Try to evaluate the next state and control output 5. If step 4 was not successful: - Recursively goto step 4 with the offending stop-signal set to 0. - Recursively goto step 4 with the offending stop-signal set to 1. 6. If step 4 was successful: Emit transition Finally a `$fsm` cell is created with the generated transition table and added to the module. This new cell is connected to the control signals and the old drivers for the control outputs are disconnected. FSM optimization ~~~~~~~~~~~~~~~~ The `fsm_opt` pass performs basic optimizations on `$fsm` cells (not including state recoding). The following optimizations are performed (in this order): - Unused control outputs are removed from the `$fsm` cell. The attribute ``unused_bits`` (that is usually set by the `opt_clean` pass) is used to determine which control outputs are unused. - Control inputs that are connected to the same driver are merged. - When a control input is driven by a control output, the control input is removed and the transition table altered to give the same performance without the external feedback path. - Entries in the transition table that yield the same output and only differ in the value of a single control input bit are merged and the different bit is removed from the sensitivity list (turned into a don't-care bit). - Constant inputs are removed and the transition table is altered to give an unchanged behaviour. - Unused inputs are removed. FSM recoding ~~~~~~~~~~~~ The `fsm_recode` pass assigns new bit pattern to the states. Usually this also implies a change in the width of the state signal. At the moment of this writing only one-hot encoding with all-zero for the reset state is supported. The `fsm_recode` pass can also write a text file with the changes performed by it that can be used when verifying designs synthesized by Yosys using Synopsys Formality. yosys-0.65/docs/source/using_yosys/synthesis/index.rst000066400000000000000000000023021520057232300233430ustar00rootroot00000000000000Synthesis in detail ------------------- Synthesis can generally be broken down into coarse-grain synthesis, and fine-grain synthesis. We saw this in :doc:`/getting_started/example_synth` where a design was loaded and elaborated and then went through a series of coarse-grain optimizations before being mapped to hard blocks and fine-grain cells. Most commands in Yosys will target either coarse-grain representation or fine-grain representation, with only a select few compatible with both states. Commands such as `proc`, `fsm`, and `memory` rely on the additional information in the coarse-grain representation, along with a number of optimizations such as `wreduce`, `share`, and `alumacc`. `opt` provides optimizations which are useful in both states, while `techmap` is used to convert coarse-grain cells to the corresponding fine-grain representation. Single-bit cells (logic gates, FFs) as well as LUTs, half-adders, and full-adders make up the bulk of the fine-grain representation and are necessary for commands such as `abc`\ /`abc9`, `simplemap`, `dfflegalize`, and `memory_map`. .. toctree:: :maxdepth: 3 synth proc fsm memory opt techmap_synth extract abc cell_libs yosys-0.65/docs/source/using_yosys/synthesis/memory.rst000066400000000000000000000546251520057232300235630ustar00rootroot00000000000000Memory handling =============== The `memory` command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the RTL netlist, memory reads and writes are individual cells. This makes consolidating the number of ports for a memory easier. The `memory` pass transforms memories to an implementation. Per default that is logic for address decoders and registers. It also is a macro command that calls the other common ``memory_*`` passes in a sensible order: .. literalinclude:: /code_examples/macro_commands/memory.ys :language: yoscrypt :start-after: #end: :caption: Passes called by `memory` .. todo:: Make ``memory_*`` notes less quick Some quick notes: - `memory_dff` merges registers into the memory read- and write cells. - `memory_collect` collects all read and write cells for a memory and transforms them into one multi-port memory cell. - `memory_map` takes the multi-port memory cell and transforms it to address decoder logic and registers. For more information about `memory`, such as disabling certain sub commands, see :doc:`/cmd/index_passes_memory`. Example ------- .. todo:: describe ``memory`` images |code_examples/synth_flow|_. .. |code_examples/synth_flow| replace:: :file:`docs/source/code_examples/synth_flow` .. _code_examples/synth_flow: https://github.com/YosysHQ/yosys/tree/main/docs/source/code_examples/synth_flow .. figure:: /_images/code_examples/synth_flow/memory_01.* :class: width-helper invert-helper .. literalinclude:: /code_examples/synth_flow/memory_01.ys :language: yoscrypt :caption: :file:`memory_01.ys` .. literalinclude:: /code_examples/synth_flow/memory_01.v :language: verilog :caption: :file:`memory_01.v` .. figure:: /_images/code_examples/synth_flow/memory_02.* :class: width-helper invert-helper .. literalinclude:: /code_examples/synth_flow/memory_02.v :language: verilog :caption: :file:`memory_02.v` .. literalinclude:: /code_examples/synth_flow/memory_02.ys :language: yoscrypt :caption: :file:`memory_02.ys` .. _memory_map: Memory mapping ^^^^^^^^^^^^^^ Usually it is preferred to use architecture-specific RAM resources for memory. For example: .. code-block:: yoscrypt memory -nomap memory_libmap -lib my_memory_map.txt techmap -map my_memory_map.v memory_map `memory_libmap` attempts to convert memory cells (`$mem_v2` etc) into hardware supported memory using a provided library (:file:`my_memory_map.txt` in the example above). Where necessary, emulation logic is added to ensure functional equivalence before and after this conversion. :yoscrypt:`techmap -map my_memory_map.v` then uses `techmap` to map to hardware primitives. Any leftover memory cells unable to be converted are then picked up by `memory_map` and mapped to DFFs and address decoders. .. note:: More information about what mapping options are available and associated costs of each can be found by enabling debug outputs. This can be done with the `debug` command, or by using the ``-g`` flag when calling Yosys to globally enable debug messages. For more on the lib format for `memory_libmap`, see `passes/memory/memlib.md `_ Supported memory patterns ^^^^^^^^^^^^^^^^^^^^^^^^^ Note that not all supported patterns are included in this document, of particular note is that combinations of multiple patterns should generally work. For example, `wbe`_ could be used in conjunction with any of the simple dual port (SDP) models. In general if a hardware memory definition does not support a given configuration, additional logic will be instantiated to guarantee behaviour is consistent with simulation. Notes ----- Memory kind selection ~~~~~~~~~~~~~~~~~~~~~ The memory inference code will automatically pick target memory primitive based on memory geometry and features used. Depending on the target, there can be up to four memory primitive classes available for selection: - FF RAM (aka logic): no hardware primitive used, memory lowered to a bunch of FFs and multiplexers - Can handle arbitrary number of write ports, as long as all write ports are in the same clock domain - Can handle arbitrary number and kind of read ports - LUT RAM (aka distributed RAM): uses LUT storage as RAM - Supported on most FPGAs (with notable exception of ice40) - Usually has one synchronous write port, one or more asynchronous read ports - Small - Will never be used for ROMs (lowering to plain LUTs is always better) - Block RAM: dedicated memory tiles - Supported on basically all FPGAs - Supports only synchronous reads - Two ports with separate clocks - Usually supports true dual port (with notable exception of ice40 that only supports SDP) - Usually supports asymmetric memories and per-byte write enables - Several kilobits in size - Huge RAM: - Only supported on several targets: - Some Xilinx UltraScale devices (UltraRAM) - Two ports, both with mutually exclusive synchronous read and write - Single clock - Initial data must be all-0 - Some ice40 devices (SPRAM) - Single port with mutually exclusive synchronous read and write - Does not support initial data - Nexus (large RAM) - Two ports, both with mutually exclusive synchronous read and write - Single clock - Will not be automatically selected by memory inference code, needs explicit opt-in via ram_style attribute In general, you can expect the automatic selection process to work roughly like this: - If any read port is asynchronous, only LUT RAM (or FF RAM) can be used. - If there is more than one write port, only block RAM can be used, and this needs to be a hardware-supported true dual port pattern - … unless all write ports are in the same clock domain, in which case FF RAM can also be used, but this is generally not what you want for anything but really small memories - Otherwise, either FF RAM, LUT RAM, or block RAM will be used, depending on memory size This process can be overridden by attaching a ram_style attribute to the memory: - ``(* ram_style = "logic" *)`` selects FF RAM - ``(* ram_style = "distributed" *)`` selects LUT RAM - ``(* ram_style = "block" *)`` selects block RAM - ``(* ram_style = "huge" *)`` selects huge RAM It is an error if this override cannot be realized for the given target. Many alternate spellings of the attribute are also accepted, for compatibility with other software. Initial data ~~~~~~~~~~~~ Most FPGA targets support initializing all kinds of memory to user-provided values. If explicit initialization is not used the initial memory value is undefined. Initial data can be provided by either initial statements writing memory cells one by one of ``$readmemh`` or ``$readmemb`` system tasks. For an example pattern, see `sr_init`_. .. _wbe: Write port with byte enables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Byte enables can be used with any supported pattern - To ensure that multiple writes will be merged into one port, they need to have disjoint bit ranges, have the same address, and the same clock - Any write enable granularity will be accepted (down to per-bit write enables), but using smaller granularity than natively supported by the target is very likely to be inefficient (eg. using 4-bit bytes on ECP5 will result in either padding the bytes with 5 dummy bits to native 9-bit units or splitting the RAM into two block RAMs) .. code:: verilog reg [31 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable[0]) mem[write_addr][7:0] <= write_data[7:0]; if (write_enable[1]) mem[write_addr][15:8] <= write_data[15:8]; if (write_enable[2]) mem[write_addr][23:16] <= write_data[23:16]; if (write_enable[3]) mem[write_addr][31:24] <= write_data[31:24]; if (read_enable) read_data <= mem[read_addr]; end Simple dual port (SDP) memory patterns -------------------------------------- .. todo:: assorted enables, e.g. cen, wen+ren Asynchronous-read SDP ~~~~~~~~~~~~~~~~~~~~~ - This will result in LUT RAM on supported targets .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) if (write_enable) mem[write_addr] <= write_data; assign read_data = mem[read_addr]; Synchronous SDP with clock domain crossing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Will result in block RAM or LUT RAM depending on size - No behavior guarantees in case of simultaneous read and write to the same address .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge write_clk) begin if (write_enable) mem[write_addr] <= write_data; end always @(posedge read_clk) begin if (read_enable) read_data <= mem[read_addr]; end Synchronous SDP read first ~~~~~~~~~~~~~~~~~~~~~~~~~~ - The read and write parts can be in the same or different processes. - Will result in block RAM or LUT RAM depending on size - As long as the same clock is used for both, yosys will ensure read-first behavior. This may require extra circuitry on some targets for block RAM. If this is not necessary, use one of the patterns below. .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_enable) read_data <= mem[read_addr]; end .. _no_rw_check: Synchronous SDP with undefined collision behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Like above, but the read value is undefined when read and write ports target the same address in the same cycle .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_enable) begin read_data <= mem[read_addr]; if (write_enable && read_addr == write_addr) // this if block read_data <= 'x; end end - Or below, using the no_rw_check attribute .. code:: verilog (* no_rw_check *) reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_enable) read_data <= mem[read_addr]; end .. _sdp_wf: Synchronous SDP with write-first behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Will result in block RAM or LUT RAM depending on size - May use additional circuitry for block RAM if write-first is not natively supported. Will always use additional circuitry for LUT RAM. .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_enable) begin read_data <= mem[read_addr]; if (write_enable && read_addr == write_addr) read_data <= write_data; end end Synchronous SDP with write-first behavior (alternate pattern) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - This pattern is supported for compatibility, but is much less flexible than the above .. code:: verilog reg [ADDR_WIDTH - 1 : 0] read_addr_reg; reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; read_addr_reg <= read_addr; end assign read_data = mem[read_addr_reg]; Single-port RAM memory patterns ------------------------------- Asynchronous-read single-port RAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Will result in single-port LUT RAM on supported targets .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) if (write_enable) mem[addr] <= write_data; assign read_data = mem[addr]; Synchronous single-port RAM with mutually exclusive read/write ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Will result in single-port block RAM or LUT RAM depending on size - This is the correct pattern to infer ice40 SPRAM (with manual ram_style selection) - On targets that don't support read/write block RAM ports (eg. ice40), will result in SDP block RAM instead - For block RAM, will use "NO_CHANGE" mode if available .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[addr] <= write_data; else if (read_enable) read_data <= mem[addr]; end Synchronous single-port RAM with read-first behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Will only result in single-port block RAM when read-first behavior is natively supported; otherwise, SDP RAM with additional circuitry will be used - Many targets (Xilinx, ECP5, …) can only natively support read-first/write-first single-port RAM (or TDP RAM) where the write_enable signal implies the read_enable signal (ie. can never write without reading). The memory inference code will run a simple SAT solver on the control signals to determine if this is the case, and insert emulation circuitry if it cannot be easily proven. .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[addr] <= write_data; if (read_enable) read_data <= mem[addr]; end Synchronous single-port RAM with write-first behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Will result in single-port block RAM or LUT RAM when supported - Block RAMs will require extra circuitry if write-first behavior not natively supported .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[addr] <= write_data; if (read_enable) if (write_enable) read_data <= write_data; else read_data <= mem[addr]; end .. _sr_init: Synchronous read port with initial value ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Initial read port values can be combined with any other supported pattern - If block RAM is used and initial read port values are not natively supported by the target, small emulation circuit will be inserted .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; reg [DATA_WIDTH - 1 : 0] read_data; initial read_data = 'h1234; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_enable) read_data <= mem[read_addr]; end Read register reset patterns ---------------------------- Resets can be combined with any other supported pattern (except that synchronous reset and asynchronous reset cannot both be used on a single read port). If block RAM is used and the selected reset (synchronous or asynchronous) is used but not natively supported by the target, small emulation circuitry will be inserted. Synchronous reset, reset priority over enable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_reset) read_data <= 'h1234; else if (read_enable) read_data <= mem[read_addr]; end Synchronous reset, enable priority over reset ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_enable) if (read_reset) read_data <= 'h1234; else read_data <= mem[read_addr]; end Synchronous read port with asynchronous reset ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; end always @(posedge clk, posedge read_reset) begin if (read_reset) read_data <= 'h1234; else if (read_enable) read_data <= mem[read_addr]; end Asymmetric memory patterns -------------------------- To construct an asymmetric memory (memory with read/write ports of differing widths): - Declare the memory with the width of the narrowest intended port - Split all wide ports into multiple narrow ports - To ensure the wide ports will be correctly merged: - For the address, use a concatenation of actual address in the high bits and a constant in the low bits - Ensure the actual address is identical for all ports belonging to the wide port - Ensure that clock is identical - For read ports, ensure that enable/reset signals are identical (for write ports, the enable signal may vary — this will result in using the byte enable functionality) Asymmetric memory is supported on all targets, but may require emulation circuitry where not natively supported. Note that when the memory is larger than the underlying block RAM primitive, hardware asymmetric memory support is likely not to be used even if present as it is more expensive. .. _wide_sr: Wide synchronous read port ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: verilog reg [7:0] mem [0:255]; wire [7:0] write_addr; wire [5:0] read_addr; wire [7:0] write_data; reg [31:0] read_data; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; if (read_enable) begin read_data[7:0] <= mem[{read_addr, 2'b00}]; read_data[15:8] <= mem[{read_addr, 2'b01}]; read_data[23:16] <= mem[{read_addr, 2'b10}]; read_data[31:24] <= mem[{read_addr, 2'b11}]; end end Wide asynchronous read port ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Note: the only target natively supporting this pattern is Xilinx UltraScale .. code:: verilog reg [7:0] mem [0:511]; wire [8:0] write_addr; wire [5:0] read_addr; wire [7:0] write_data; wire [63:0] read_data; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; end assign read_data[7:0] = mem[{read_addr, 3'b000}]; assign read_data[15:8] = mem[{read_addr, 3'b001}]; assign read_data[23:16] = mem[{read_addr, 3'b010}]; assign read_data[31:24] = mem[{read_addr, 3'b011}]; assign read_data[39:32] = mem[{read_addr, 3'b100}]; assign read_data[47:40] = mem[{read_addr, 3'b101}]; assign read_data[55:48] = mem[{read_addr, 3'b110}]; assign read_data[63:56] = mem[{read_addr, 3'b111}]; Wide write port ~~~~~~~~~~~~~~~ .. code:: verilog reg [7:0] mem [0:255]; wire [5:0] write_addr; wire [7:0] read_addr; wire [31:0] write_data; reg [7:0] read_data; always @(posedge clk) begin if (write_enable[0]) mem[{write_addr, 2'b00}] <= write_data[7:0]; if (write_enable[1]) mem[{write_addr, 2'b01}] <= write_data[15:8]; if (write_enable[2]) mem[{write_addr, 2'b10}] <= write_data[23:16]; if (write_enable[3]) mem[{write_addr, 2'b11}] <= write_data[31:24]; if (read_enable) read_data <= mem[read_addr]; end True dual port (TDP) patterns ----------------------------- - Many different variations of true dual port memory can be created by combining two single-port RAM patterns on the same memory - When TDP memory is used, memory inference code has much less maneuver room to create requested semantics compared to individual single-port patterns (which can end up lowered to SDP memory where necessary) — supported patterns depend strongly on the target - In particular, when both ports have the same clock, it's likely that "undefined collision" mode needs to be manually selected to enable TDP memory inference - The examples below are non-exhaustive — many more combinations of port types are possible - Note: if two write ports are in the same process, this defines a priority relation between them (if both ports are active in the same clock, the later one wins). On almost all targets, this will result in a bit of extra circuitry to ensure the priority semantics. If this is not what you want, put them in separate processes. - Priority is not supported when using the verific front end and any priority semantics are ignored. TDP with different clocks, exclusive read/write ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk_a) begin if (write_enable_a) mem[addr_a] <= write_data_a; else if (read_enable_a) read_data_a <= mem[addr_a]; end always @(posedge clk_b) begin if (write_enable_b) mem[addr_b] <= write_data_b; else if (read_enable_b) read_data_b <= mem[addr_b]; end TDP with same clock, read-first behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - This requires hardware inter-port read-first behavior, and will only work on some targets (Xilinx, Nexus) .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable_a) mem[addr_a] <= write_data_a; if (read_enable_a) read_data_a <= mem[addr_a]; end always @(posedge clk) begin if (write_enable_b) mem[addr_b] <= write_data_b; if (read_enable_b) read_data_b <= mem[addr_b]; end TDP with multiple read ports ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The combination of a single write port with an arbitrary amount of read ports is supported on all targets — if a multi-read port primitive is available (like Xilinx RAM64M), it'll be used as appropriate. Otherwise, the memory will be automatically split into multiple primitives. .. code:: verilog reg [31:0] mem [0:31]; always @(posedge clk) begin if (write_enable) mem[write_addr] <= write_data; end assign read_data_a = mem[read_addr_a]; assign read_data_b = mem[read_addr_b]; assign read_data_c = mem[read_addr_c]; Patterns only supported with Verific ------------------------------------ The following patterns are only supported when the design is read in using the Verific front-end. Synchronous SDP with write-first behavior via blocking assignments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Use `sdp_wf`_ for compatibility with Yosys Verilog frontend. .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @(posedge clk) begin if (write_enable) mem[write_addr] = write_data; if (read_enable) read_data <= mem[read_addr]; end Asymmetric memories via part selection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Build wide ports out of narrow ports instead (see `wide_sr`_) for compatibility with Yosys Verilog frontend. .. code:: verilog reg [31:0] mem [2**ADDR_WIDTH - 1 : 0]; wire [1:0] byte_lane; wire [7:0] write_data; always @(posedge clk) begin if (write_enable) mem[write_addr][byte_lane * 8 +: 8] <= write_data; if (read_enable) read_data <= mem[read_addr]; end Undesired patterns ------------------ Asynchronous writes ~~~~~~~~~~~~~~~~~~~ - Not supported in modern FPGAs - Not supported in yosys code anyhow .. code:: verilog reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0]; always @* begin if (write_enable) mem[write_addr] = write_data; end assign read_data = mem[read_addr]; yosys-0.65/docs/source/using_yosys/synthesis/opt.rst000066400000000000000000000222711520057232300230450ustar00rootroot00000000000000Optimization passes =================== Yosys employs a number of optimizations to generate better and cleaner results. This chapter outlines these optimizations. .. todo:: "outlines these optimizations" or "outlines *some*.."? The `opt` macro command -------------------------------- The Yosys pass `opt` runs a number of simple optimizations. This includes removing unused signals and cells and const folding. It is recommended to run this pass after each major step in the synthesis script. This macro command calls the following ``opt_*`` commands: .. literalinclude:: /code_examples/macro_commands/opt.ys :language: yoscrypt :start-after: #end: :caption: Passes called by `opt` .. _adv_opt_expr: Constant folding and simple expression rewriting - `opt_expr` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. todo:: unsure if this is too much detail and should be in :doc:`/yosys_internals/index` This pass performs constant folding on the internal combinational cell types described in :doc:`/cell_index`. This means a cell with all constant inputs is replaced with the constant value this cell drives. In some cases this pass can also optimize cells with some constant inputs. .. table:: Const folding rules for `$_AND_` cells as used in `opt_expr`. :name: tab:opt_expr_and :align: center ========= ========= =========== A-Input B-Input Replacement ========= ========= =========== any 0 0 0 any 0 1 1 1 --------- --------- ----------- X/Z X/Z X 1 X/Z X X/Z 1 X --------- --------- ----------- any X/Z 0 X/Z any 0 --------- --------- ----------- :math:`a` 1 :math:`a` 1 :math:`b` :math:`b` ========= ========= =========== :numref:`Table %s ` shows the replacement rules used for optimizing an `$_AND_` gate. The first three rules implement the obvious const folding rules. Note that 'any' might include dynamic values calculated by other parts of the circuit. The following three lines propagate undef (X) states. These are the only three cases in which it is allowed to propagate an undef according to Sec. 5.1.10 of IEEE Std. 1364-2005 :cite:p:`Verilog2005`. The next two lines assume the value 0 for undef states. These two rules are only used if no other substitutions are possible in the current module. If other substitutions are possible they are performed first, in the hope that the 'any' will change to an undef value or a 1 and therefore the output can be set to undef. The last two lines simply replace an `$_AND_` gate with one constant-1 input with a buffer. Besides this basic const folding the `opt_expr` pass can replace 1-bit wide `$eq` and `$ne` cells with buffers or not-gates if one input is constant. Equality checks may also be reduced in size if there are redundant bits in the arguments (i.e. bits which are constant on both inputs). This can, for example, result in a 32-bit wide constant like ``255`` being reduced to the 8-bit value of ``8'11111111`` if the signal being compared is only 8-bit as in :ref:`addr_gen_clean` of :doc:`/getting_started/example_synth`. The `opt_expr` pass is very conservative regarding optimizing `$mux` cells, as these cells are often used to model decision-trees and breaking these trees can interfere with other optimizations. .. literalinclude:: /code_examples/opt/opt_expr.ys :language: Verilog :start-after: read_verilog <